@@ -12,136 +12,197 @@ import (
1212 "github.com/stretchr/testify/mock"
1313 "github.com/stretchr/testify/require"
1414
15+ "github.com/rollkit/rollkit/pkg/store"
1516 mocksStore "github.com/rollkit/rollkit/test/mocks"
1617 "github.com/rollkit/rollkit/types"
1718)
1819
19- // TestPendingBase_InitAndGetLastSubmittedDataHeight verifies that pendingBase initializes correctly
20- // and returns the correct last submitted height when no metadata is present.
21- func TestPendingBase_InitAndGetLastSubmittedDataHeight (t * testing.T ) {
22- mockStore := mocksStore .NewStore (t )
23- logger := log .NewNopLogger ()
24- mockStore .On ("GetMetadata" , mock .Anything , LastSubmittedDataHeightKey ).Return (nil , ds .ErrNotFound ).Once ()
25- pb , err := newPendingBase (mockStore , logger , LastSubmittedDataHeightKey , fetchData )
26- assert .NoError (t , err )
27- assert .NotNil (t , pb )
28- assert .Equal (t , uint64 (0 ), pb .lastHeight .Load ())
20+ // --- Generic test case struct and helpers for pendingBase tests ---
21+ type pendingBaseTestCase [T any ] struct {
22+ name string
23+ key string
24+ fetch func (ctx context.Context , store store.Store , height uint64 ) (T , error )
25+ makeItem func (height uint64 ) T
26+ // For GetBlockData, returns (header, data, error) for a given height
27+ mockGetBlockData func (height uint64 ) (any , any , error )
2928}
3029
31- // TestPendingBase_GetPending_AllCases checks pendingBase.getPending for no data, all data submitted,
32- // some pending data, and error cases.
33- func TestPendingBase_GetPending_AllCases (t * testing.T ) {
34- mockStore := mocksStore .NewStore (t )
35- logger := log .NewNopLogger ()
36- mockStore .On ("GetMetadata" , mock .Anything , LastSubmittedDataHeightKey ).Return (nil , ds .ErrNotFound ).Once ()
37- pb , err := newPendingBase (mockStore , logger , LastSubmittedDataHeightKey , fetchData )
38- require .NoError (t , err )
39-
40- ctx := context .Background ()
41-
42- // Case: no data
43- mockStore .On ("Height" , ctx ).Return (uint64 (0 ), nil ).Once ()
44- pending , err := pb .getPending (ctx )
45- assert .NoError (t , err )
46- assert .Nil (t , pending )
47-
48- // Case: all data submitted
49- pb .lastHeight .Store (5 )
50- mockStore .On ("Height" , ctx ).Return (uint64 (5 ), nil ).Once ()
51- pending , err = pb .getPending (ctx )
52- assert .NoError (t , err )
53- assert .Nil (t , pending )
54-
55- // Case: some pending data
56- pb .lastHeight .Store (2 )
57- mockStore .On ("Height" , ctx ).Return (uint64 (4 ), nil ).Once ()
58- for i := uint64 (3 ); i <= 4 ; i ++ {
59- data := & types.Data {Metadata : & types.Metadata {Height : i }}
60- mockStore .On ("GetBlockData" , ctx , i ).Return (nil , data , nil ).Once ()
61- }
62- pending , err = pb .getPending (ctx )
63- assert .NoError (t , err )
64- assert .Len (t , pending , 2 )
65- assert .Equal (t , uint64 (3 ), pending [0 ].Height ())
66- assert .Equal (t , uint64 (4 ), pending [1 ].Height ())
67-
68- // Case: error in store
69- pb .lastHeight .Store (4 )
70- mockStore .On ("Height" , ctx ).Return (uint64 (5 ), nil ).Once ()
71- mockStore .On ("GetBlockData" , ctx , uint64 (5 )).Return (nil , nil , errors .New ("err" )).Once ()
72- pending , err = pb .getPending (ctx )
73- assert .Error (t , err )
74- assert .Empty (t , pending )
30+ func runPendingBase_InitAndGetLastSubmittedHeight [T any ](t * testing.T , tc pendingBaseTestCase [T ]) {
31+ t .Run (tc .name + "/InitAndGetLastSubmittedHeight" , func (t * testing.T ) {
32+ mockStore := mocksStore .NewStore (t )
33+ logger := log .NewNopLogger ()
34+ mockStore .On ("GetMetadata" , mock .Anything , tc .key ).Return (nil , ds .ErrNotFound ).Once ()
35+ pb , err := newPendingBase (mockStore , logger , tc .key , tc .fetch )
36+ assert .NoError (t , err )
37+ assert .NotNil (t , pb )
38+ assert .Equal (t , uint64 (0 ), pb .lastHeight .Load ())
39+ })
40+ }
41+
42+ func runPendingBase_GetPending_AllCases [T any ](t * testing.T , tc pendingBaseTestCase [T ]) {
43+ t .Run (tc .name + "/GetPending_AllCases" , func (t * testing.T ) {
44+ mockStore := mocksStore .NewStore (t )
45+ logger := log .NewNopLogger ()
46+ mockStore .On ("GetMetadata" , mock .Anything , tc .key ).Return (nil , ds .ErrNotFound ).Once ()
47+ pb , err := newPendingBase (mockStore , logger , tc .key , tc .fetch )
48+ require .NoError (t , err )
49+ ctx := context .Background ()
50+
51+ // Case: no items
52+ mockStore .On ("Height" , ctx ).Return (uint64 (0 ), nil ).Once ()
53+ pending , err := pb .getPending (ctx )
54+ assert .NoError (t , err )
55+ assert .Nil (t , pending )
56+
57+ // Case: all items submitted
58+ pb .lastHeight .Store (5 )
59+ mockStore .On ("Height" , ctx ).Return (uint64 (5 ), nil ).Once ()
60+ pending , err = pb .getPending (ctx )
61+ assert .NoError (t , err )
62+ assert .Nil (t , pending )
63+
64+ // Case: some pending items
65+ pb .lastHeight .Store (2 )
66+ mockStore .On ("Height" , ctx ).Return (uint64 (4 ), nil ).Once ()
67+ for i := uint64 (3 ); i <= 4 ; i ++ {
68+ ret0 , ret1 , retErr := tc .mockGetBlockData (i )
69+ mockStore .On ("GetBlockData" , ctx , i ).Return (ret0 , ret1 , retErr ).Once ()
70+ }
71+ pending , err = pb .getPending (ctx )
72+ assert .NoError (t , err )
73+ assert .Len (t , pending , 2 )
74+ // Use reflection to call Height() for both types
75+ getHeight := func (item any ) uint64 {
76+ return item .(interface { Height () uint64 }).Height ()
77+ }
78+ assert .Equal (t , uint64 (3 ), getHeight (pending [0 ]))
79+ assert .Equal (t , uint64 (4 ), getHeight (pending [1 ]))
80+
81+ // Case: error in store
82+ pb .lastHeight .Store (4 )
83+ mockStore .On ("Height" , ctx ).Return (uint64 (5 ), nil ).Once ()
84+ // For error case, always return error for height 5
85+ mockStore .On ("GetBlockData" , ctx , uint64 (5 )).Return (nil , nil , errors .New ("err" )).Once ()
86+ pending , err = pb .getPending (ctx )
87+ assert .Error (t , err )
88+ assert .Empty (t , pending )
89+ })
90+ }
91+
92+ func runPendingBase_isEmpty_numPending [T any ](t * testing.T , tc pendingBaseTestCase [T ]) {
93+ t .Run (tc .name + "/isEmpty_numPending" , func (t * testing.T ) {
94+ mockStore := mocksStore .NewStore (t )
95+ logger := log .NewNopLogger ()
96+ mockStore .On ("GetMetadata" , mock .Anything , tc .key ).Return (nil , ds .ErrNotFound ).Once ()
97+ pb , err := newPendingBase (mockStore , logger , tc .key , tc .fetch )
98+ require .NoError (t , err )
99+
100+ // isEmpty true
101+ pb .lastHeight .Store (10 )
102+ mockStore .On ("Height" , mock .Anything ).Return (uint64 (10 ), nil ).Once ()
103+ assert .True (t , pb .isEmpty ())
104+
105+ // isEmpty false
106+ pb .lastHeight .Store (5 )
107+ mockStore .On ("Height" , mock .Anything ).Return (uint64 (10 ), nil ).Once ()
108+ assert .False (t , pb .isEmpty ())
109+
110+ // numPending
111+ pb .lastHeight .Store (3 )
112+ mockStore .On ("Height" , mock .Anything ).Return (uint64 (7 ), nil ).Once ()
113+ assert .Equal (t , uint64 (4 ), pb .numPending ())
114+ })
75115}
76116
77- // TestPendingBase_isEmpty_numPending verifies isEmpty and numPending methods for correct behavior
78- // when there are no pending items, some pending items, and all items submitted.
79- func TestPendingBase_isEmpty_numPending (t * testing.T ) {
80- mockStore := mocksStore .NewStore (t )
81- logger := log .NewNopLogger ()
82- mockStore .On ("GetMetadata" , mock .Anything , LastSubmittedDataHeightKey ).Return (nil , ds .ErrNotFound ).Once ()
83- pb , err := newPendingBase (mockStore , logger , LastSubmittedDataHeightKey , fetchData )
84- require .NoError (t , err )
85-
86- // isEmpty true
87- pb .lastHeight .Store (10 )
88- mockStore .On ("Height" , mock .Anything ).Return (uint64 (10 ), nil ).Once ()
89- assert .True (t , pb .isEmpty ())
90-
91- // isEmpty false
92- pb .lastHeight .Store (5 )
93- mockStore .On ("Height" , mock .Anything ).Return (uint64 (10 ), nil ).Once ()
94- assert .False (t , pb .isEmpty ())
95-
96- // numPending
97- pb .lastHeight .Store (3 )
98- mockStore .On ("Height" , mock .Anything ).Return (uint64 (7 ), nil ).Once ()
99- assert .Equal (t , uint64 (4 ), pb .numPending ())
117+ func runPendingBase_setLastSubmittedHeight [T any ](t * testing.T , tc pendingBaseTestCase [T ]) {
118+ t .Run (tc .name + "/setLastSubmittedHeight" , func (t * testing.T ) {
119+ mockStore := mocksStore .NewStore (t )
120+ logger := log .NewNopLogger ()
121+ mockStore .On ("GetMetadata" , mock .Anything , tc .key ).Return (nil , ds .ErrNotFound ).Once ()
122+ pb , err := newPendingBase (mockStore , logger , tc .key , tc .fetch )
123+ require .NoError (t , err )
124+
125+ ctx := context .Background ()
126+ pb .lastHeight .Store (2 )
127+ // Should update
128+ mockStore .On ("SetMetadata" , ctx , tc .key , mock .Anything ).Return (nil ).Once ()
129+ pb .setLastSubmittedHeight (ctx , 5 )
130+ assert .Equal (t , uint64 (5 ), pb .lastHeight .Load ())
131+
132+ // Should not update (new <= old)
133+ pb .lastHeight .Store (5 )
134+ pb .setLastSubmittedHeight (ctx , 4 )
135+ assert .Equal (t , uint64 (5 ), pb .lastHeight .Load ())
136+ })
100137}
101138
102- // TestPendingBase_setLastSubmittedHeight checks that setLastSubmittedHeight updates the last height
103- // and persists it only when the new height is greater than the current one.
104- func TestPendingBase_setLastSubmittedHeight (t * testing.T ) {
105- mockStore := mocksStore .NewStore (t )
106- logger := log .NewNopLogger ()
107- mockStore .On ("GetMetadata" , mock .Anything , LastSubmittedDataHeightKey ).Return (nil , ds .ErrNotFound ).Once ()
108- pb , err := newPendingBase (mockStore , logger , LastSubmittedDataHeightKey , fetchData )
109- require .NoError (t , err )
110-
111- ctx := context .Background ()
112- pb .lastHeight .Store (2 )
113- // Should update
114- mockStore .On ("SetMetadata" , ctx , LastSubmittedDataHeightKey , mock .Anything ).Return (nil ).Once ()
115- pb .setLastSubmittedHeight (ctx , 5 )
116- assert .Equal (t , uint64 (5 ), pb .lastHeight .Load ())
117-
118- // Should not update (new <= old)
119- pb .lastHeight .Store (5 )
120- pb .setLastSubmittedHeight (ctx , 4 )
121- assert .Equal (t , uint64 (5 ), pb .lastHeight .Load ())
139+ func runPendingBase_init_with_existing_metadata [T any ](t * testing.T , tc pendingBaseTestCase [T ]) {
140+ t .Run (tc .name + "/init_with_existing_metadata" , func (t * testing.T ) {
141+ mockStore := mocksStore .NewStore (t )
142+ logger := log .NewNopLogger ()
143+ val := make ([]byte , 8 )
144+ binary .LittleEndian .PutUint64 (val , 7 )
145+ mockStore .On ("GetMetadata" , mock .Anything , tc .key ).Return (val , nil ).Once ()
146+ pb := & pendingBase [T ]{store : mockStore , logger : logger , metaKey : tc .key , fetch : tc .fetch }
147+ err := pb .init ()
148+ assert .NoError (t , err )
149+ assert .Equal (t , uint64 (7 ), pb .lastHeight .Load ())
150+ })
122151}
123152
124- // TestPendingBase_init_with_existing_metadata ensures that pendingBase initializes its last height
125- // from existing metadata in the store.
126- func TestPendingBase_init_with_existing_metadata (t * testing.T ) {
127- mockStore := mocksStore .NewStore (t )
128- logger := log .NewNopLogger ()
129- val := make ([]byte , 8 )
130- binary .LittleEndian .PutUint64 (val , 7 )
131- mockStore .On ("GetMetadata" , mock .Anything , LastSubmittedDataHeightKey ).Return (val , nil ).Once ()
132- pb := & pendingBase [* types.Data ]{store : mockStore , logger : logger , metaKey : LastSubmittedDataHeightKey , fetch : fetchData }
133- err := pb .init ()
134- assert .NoError (t , err )
135- assert .Equal (t , uint64 (7 ), pb .lastHeight .Load ())
153+ func runPendingBase_init_invalid_length [T any ](t * testing.T , tc pendingBaseTestCase [T ]) {
154+ t .Run (tc .name + "/init_invalid_length" , func (t * testing.T ) {
155+ mockStore := mocksStore .NewStore (t )
156+ logger := log .NewNopLogger ()
157+ mockStore .On ("GetMetadata" , mock .Anything , tc .key ).Return ([]byte {1 , 2 }, nil ).Once ()
158+ pb := & pendingBase [T ]{store : mockStore , logger : logger , metaKey : tc .key , fetch : tc .fetch }
159+ err := pb .init ()
160+ assert .Error (t , err )
161+ })
136162}
137163
138- // TestPendingBase_init_invalid_length checks that pendingBase.init returns an error
139- // if the stored metadata for last height is not the expected length.
140- func TestPendingBase_init_invalid_length (t * testing.T ) {
141- mockStore := mocksStore .NewStore (t )
142- logger := log .NewNopLogger ()
143- mockStore .On ("GetMetadata" , mock .Anything , LastSubmittedDataHeightKey ).Return ([]byte {1 , 2 }, nil ).Once ()
144- pb := & pendingBase [* types.Data ]{store : mockStore , logger : logger , metaKey : LastSubmittedDataHeightKey , fetch : fetchData }
145- err := pb .init ()
146- assert .Error (t , err )
164+ func TestPendingBase_Generic (t * testing.T ) {
165+ dataCase := pendingBaseTestCase [* types.Data ]{
166+ name : "Data" ,
167+ key : LastSubmittedDataHeightKey ,
168+ fetch : fetchData ,
169+ makeItem : func (height uint64 ) * types.Data {
170+ return & types.Data {Metadata : & types.Metadata {Height : height }}
171+ },
172+ mockGetBlockData : func (height uint64 ) (any , any , error ) {
173+ return nil , & types.Data {Metadata : & types.Metadata {Height : height }}, nil
174+ },
175+ }
176+ headerCase := pendingBaseTestCase [* types.SignedHeader ]{
177+ name : "Header" ,
178+ key : LastSubmittedHeaderHeightKey ,
179+ fetch : fetchSignedHeader ,
180+ makeItem : func (height uint64 ) * types.SignedHeader {
181+ return & types.SignedHeader {Header : types.Header {BaseHeader : types.BaseHeader {Height : height }}}
182+ },
183+ mockGetBlockData : func (height uint64 ) (any , any , error ) {
184+ return & types.SignedHeader {Header : types.Header {BaseHeader : types.BaseHeader {Height : height }}}, nil , nil
185+ },
186+ }
187+
188+ cases := []any {dataCase , headerCase }
189+
190+ for _ , c := range cases {
191+ switch tc := c .(type ) {
192+ case pendingBaseTestCase [* types.Data ]:
193+ runPendingBase_InitAndGetLastSubmittedHeight (t , tc )
194+ runPendingBase_GetPending_AllCases (t , tc )
195+ runPendingBase_isEmpty_numPending (t , tc )
196+ runPendingBase_setLastSubmittedHeight (t , tc )
197+ runPendingBase_init_with_existing_metadata (t , tc )
198+ runPendingBase_init_invalid_length (t , tc )
199+ case pendingBaseTestCase [* types.SignedHeader ]:
200+ runPendingBase_InitAndGetLastSubmittedHeight (t , tc )
201+ runPendingBase_GetPending_AllCases (t , tc )
202+ runPendingBase_isEmpty_numPending (t , tc )
203+ runPendingBase_setLastSubmittedHeight (t , tc )
204+ runPendingBase_init_with_existing_metadata (t , tc )
205+ runPendingBase_init_invalid_length (t , tc )
206+ }
207+ }
147208}
0 commit comments