@@ -1347,3 +1347,58 @@ func TestClient_GetAllChainConfigs_Paginates(t *testing.T) {
13471347 assert .Nil (t , configs )
13481348 })
13491349}
1350+
1351+ // The page budget bounds one poll's work, so it must cost latency rather than
1352+ // coverage: a set larger than the budget has to be walked in full across
1353+ // successive polls instead of re-reading the same prefix forever.
1354+ func TestClient_GetAllPendingOutbounds_CarriesCursorAcrossPolls (t * testing.T ) {
1355+ ctx := context .Background ()
1356+
1357+ total := pendingOutboundMaxPages + 3
1358+ pages := make ([]* uexecutortypes.QueryAllPendingOutboundsResponse , total )
1359+ for i := range pages {
1360+ var next []byte
1361+ if i < total - 1 {
1362+ next = []byte (fmt .Sprintf ("k%d" , i + 1 ))
1363+ }
1364+ pages [i ] = pendingPage (fmt .Sprintf ("ob-%d" , i ), next )
1365+ }
1366+ mockClient := & mockUExecutorQueryClient {pages : pages }
1367+ client := & Client {logger : zerolog .Nop (), uexecutorClients : []uexecutortypes.QueryClient {mockClient }}
1368+
1369+ // First poll spends the budget and parks mid-set.
1370+ first , _ , err := client .GetAllPendingOutbounds (ctx )
1371+ require .NoError (t , err )
1372+ require .Len (t , first , pendingOutboundMaxPages )
1373+ require .NotNil (t , client .pendingCursor , "must remember where it stopped" )
1374+
1375+ // Second poll resumes from there rather than restarting at the head.
1376+ resumeFrom := client .pendingCursor
1377+ second , _ , err := client .GetAllPendingOutbounds (ctx )
1378+ require .NoError (t , err )
1379+ require .Len (t , second , 3 , "the remainder of the set" )
1380+ assert .Equal (t , "ob-" + fmt .Sprint (pendingOutboundMaxPages ), second [0 ].OutboundId ,
1381+ "must continue after the parked cursor, not re-read the prefix" )
1382+ assert .Equal (t , resumeFrom , mockClient .requestedKeys [pendingOutboundMaxPages ],
1383+ "the parked cursor is what gets sent" )
1384+
1385+ // Reaching the end resets, so tail additions are seen on the next poll.
1386+ assert .Nil (t , client .pendingCursor )
1387+ }
1388+
1389+ // A failed page must not lose the ground already covered either.
1390+ func TestClient_GetAllPendingOutbounds_ResumesAfterPageFailure (t * testing.T ) {
1391+ mockClient := & mockUExecutorQueryClient {
1392+ pages : []* uexecutortypes.QueryAllPendingOutboundsResponse {
1393+ pendingPage ("ob-0" , []byte ("k1" )),
1394+ pendingPage ("ob-1" , []byte ("k2" )),
1395+ },
1396+ failAfterPage : 1 ,
1397+ }
1398+ client := & Client {logger : zerolog .Nop (), uexecutorClients : []uexecutortypes.QueryClient {mockClient }}
1399+
1400+ entries , _ , err := client .GetAllPendingOutbounds (context .Background ())
1401+ require .NoError (t , err )
1402+ require .Len (t , entries , 1 )
1403+ assert .Equal (t , []byte ("k1" ), client .pendingCursor , "resume at the page that failed" )
1404+ }
0 commit comments