@@ -11,12 +11,13 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
1111import kotlinx.coroutines.cancelAndJoin
1212import kotlinx.coroutines.delay
1313import kotlinx.coroutines.launch
14+ import kotlinx.coroutines.test.currentTime
1415import kotlinx.coroutines.test.runTest
15- import org.junit.jupiter.api.Disabled
1616import org.junit.jupiter.api.Nested
1717import org.junit.jupiter.api.Test
1818import org.junit.jupiter.api.assertThrows
1919import org.vechain.indexer.BlockTestBuilder.Companion.buildBlock
20+ import org.vechain.indexer.exception.RateLimitException
2021import org.vechain.indexer.exception.ReorgException
2122import org.vechain.indexer.thor.client.ThorClient
2223import org.vechain.indexer.thor.model.Block
@@ -174,6 +175,50 @@ internal class IndexerRunnerTest {
174175 coVerify(exactly = 1 ) { indexer.initialise() }
175176 }
176177
178+ @Test
179+ fun `should use exponential backoff on repeated failures` () = runTest {
180+ var initAttempts = 0
181+ val indexer =
182+ createMockIndexer(
183+ name = " indexer1" ,
184+ initializeBlock = {
185+ initAttempts++
186+ if (initAttempts < 4 ) {
187+ throw RuntimeException (" Init failed" )
188+ }
189+ }
190+ )
191+
192+ val runner = IndexerRunner ()
193+ runner.initialiseAll(listOf (indexer))
194+
195+ expectThat(initAttempts).isEqualTo(4 )
196+ // Delays: 1s + 2s + 4s = 7s total virtual time
197+ expectThat(currentTime).isEqualTo(7_000L )
198+ }
199+
200+ @Test
201+ fun `should use longer delay for RateLimitException` () = runTest {
202+ var initAttempts = 0
203+ val indexer =
204+ createMockIndexer(
205+ name = " indexer1" ,
206+ initializeBlock = {
207+ initAttempts++
208+ if (initAttempts < 2 ) {
209+ throw RateLimitException (" Rate limited" )
210+ }
211+ }
212+ )
213+
214+ val runner = IndexerRunner ()
215+ runner.initialiseAll(listOf (indexer))
216+
217+ expectThat(initAttempts).isEqualTo(2 )
218+ // Rate limit delay is 30s
219+ expectThat(currentTime).isEqualTo(30_000L )
220+ }
221+
177222 @Test
178223 fun `should complete even if one indexer is slow` () = runTest {
179224 val fastIndexer = createMockIndexer(" fast" )
@@ -280,7 +325,6 @@ internal class IndexerRunnerTest {
280325 }
281326
282327 @Test
283- @Disabled(" Causes JVM instrumentation crash with byte-buddy agent" )
284328 fun `BlockIndexer starts processing while LogsIndexer fast syncs` () = runTest {
285329 val processingStarted = mutableListOf<String >()
286330 var fastSyncCompleted = false
@@ -307,7 +351,14 @@ internal class IndexerRunnerTest {
307351 )
308352
309353 val thorClient = mockk<ThorClient >()
310- coEvery { thorClient.waitForBlock(any<BlockRevision >()) } returns buildBlock(num = 0L )
354+ // Register catch-all first (with delay), then specific stub (MockK LIFO)
355+ coEvery { thorClient.waitForBlock(any<BlockRevision >()) } coAnswers
356+ {
357+ delay(5000 )
358+ buildBlock(num = (firstArg<BlockRevision >() as BlockRevision .Number ).number)
359+ }
360+ coEvery { thorClient.waitForBlock(BlockRevision .Number (0L )) } returns
361+ buildBlock(num = 0L )
311362
312363 val runner = IndexerRunner ()
313364 runner.fastSyncWithEarlyProcessing(
@@ -326,7 +377,6 @@ internal class IndexerRunnerTest {
326377 inner class RunWithDynamicGroups {
327378
328379 @Test
329- @Disabled(" Causes JVM instrumentation crash with byte-buddy agent" )
330380 fun `single group delegates to runAllIndexers` () = runTest {
331381 val thorClient = mockk<ThorClient >()
332382 val block0 = buildBlock(num = 0L )
@@ -335,7 +385,13 @@ internal class IndexerRunnerTest {
335385 val indexer1 = createMockIndexer(" indexer1" , currentBlock = 0L )
336386 val indexer2 = createMockIndexer(" indexer2" , currentBlock = 0L )
337387
338- coEvery { thorClient.waitForBlock(any<BlockRevision >()) } returns block0
388+ // Register catch-all with delay first, then specific (MockK LIFO)
389+ coEvery { thorClient.waitForBlock(any<BlockRevision >()) } coAnswers
390+ {
391+ delay(5000 )
392+ buildBlock(num = (firstArg<BlockRevision >() as BlockRevision .Number ).number)
393+ }
394+ coEvery { thorClient.waitForBlock(BlockRevision .Number (0L )) } returns block0
339395
340396 val runner = IndexerRunner ()
341397 val job = launch {
@@ -350,23 +406,23 @@ internal class IndexerRunnerTest {
350406 }
351407
352408 @Test
353- @Disabled(" Causes JVM instrumentation crash with byte-buddy agent" )
354409 fun `multiple groups process independently` () = runTest {
355410 val thorClient = mockk<ThorClient >()
356411
357412 // Two indexers far apart -> two groups
358413 val indexer1 = createMockIndexer(" indexer1" , currentBlock = 0L )
359414 val indexer2 = createMockIndexer(" indexer2" , currentBlock = 200_000L )
360415
361- coEvery { thorClient.waitForBlock(BlockRevision .Number (0L )) } returns
362- buildBlock(num = 0L )
363- coEvery { thorClient.waitForBlock(BlockRevision .Number (200_000L )) } returns
364- buildBlock(num = 200_000L )
416+ // Register catch-all with delay first, then specific stubs (MockK LIFO)
365417 coEvery { thorClient.waitForBlock(any<BlockRevision >()) } coAnswers
366418 {
367419 delay(5000 )
368420 buildBlock(num = (firstArg<BlockRevision >() as BlockRevision .Number ).number)
369421 }
422+ coEvery { thorClient.waitForBlock(BlockRevision .Number (0L )) } returns
423+ buildBlock(num = 0L )
424+ coEvery { thorClient.waitForBlock(BlockRevision .Number (200_000L )) } returns
425+ buildBlock(num = 200_000L )
370426
371427 val runner = IndexerRunner ()
372428 val job = launch {
@@ -420,7 +476,6 @@ internal class IndexerRunnerTest {
420476 }
421477
422478 @Test
423- @Disabled(" Test timing issue - blocks not processed before cancellation" )
424479 fun `should process blocks through all indexers in same group concurrently` () = runTest {
425480 val thorClient = mockk<ThorClient >()
426481 val block0 = buildBlock(num = 0L )
@@ -456,12 +511,13 @@ internal class IndexerRunnerTest {
456511 }
457512 }
458513
459- coEvery { thorClient.waitForBlock( BlockRevision . Number ( 0L )) } returns block0
514+ // Register catch-all with delay first, then specific (MockK LIFO)
460515 coEvery { thorClient.waitForBlock(any<BlockRevision >()) } coAnswers
461516 {
462517 delay(5000 )
463518 buildBlock(num = (firstArg<BlockRevision >() as BlockRevision .Number ).number)
464519 }
520+ coEvery { thorClient.waitForBlock(BlockRevision .Number (0L )) } returns block0
465521
466522 val runner = IndexerRunner ()
467523 val job = launch { runner.runAllIndexers(listOf (indexer1, indexer2), thorClient, 1 ) }
@@ -542,7 +598,6 @@ internal class IndexerRunnerTest {
542598 }
543599
544600 @Test
545- @Disabled(" Causes OutOfMemoryError during test execution" )
546601 fun `should retry block processing on failure` () = runTest {
547602 val thorClient = mockk<ThorClient >()
548603 val block0 = buildBlock(num = 0L )
@@ -566,7 +621,14 @@ internal class IndexerRunnerTest {
566621 }
567622 }
568623
569- coEvery { thorClient.waitForBlock(any<BlockRevision >()) } returns block0
624+ // Return correct block numbers; delay after block 0 to prevent infinite loop
625+ coEvery { thorClient.waitForBlock(any<BlockRevision >()) } coAnswers
626+ {
627+ val blockNum = (firstArg<BlockRevision >() as BlockRevision .Number ).number
628+ if (blockNum > 0L ) delay(5000 )
629+ buildBlock(num = blockNum)
630+ }
631+ coEvery { thorClient.waitForBlock(BlockRevision .Number (0L )) } returns block0
570632
571633 val runner = IndexerRunner ()
572634 val job = launch { runner.runAllIndexers(listOf (indexer), thorClient, 1 ) }
@@ -974,21 +1036,21 @@ internal class IndexerRunnerTest {
9741036 }
9751037
9761038 @Test
977- @Disabled(" Test timing issue - processBlock not called before cancellation" )
9781039 fun `should handle single indexer in multiple groups scenario` () = runTest {
9791040 val thorClient = mockk<ThorClient >()
9801041 val block0 = buildBlock(num = 0L )
9811042 val block1 = buildBlock(num = 1L )
9821043
9831044 val indexer = createMockIndexer(" indexer1" , currentBlock = 0L )
9841045
985- coEvery { thorClient.waitForBlock(BlockRevision .Number (0L )) } returns block0
986- coEvery { thorClient.waitForBlock(BlockRevision .Number (1L )) } returns block1
1046+ // Register catch-all with delay first, then specific stubs (MockK LIFO)
9871047 coEvery { thorClient.waitForBlock(any<BlockRevision >()) } coAnswers
9881048 {
9891049 delay(5000 ) // Block future fetches to prevent OOM
9901050 buildBlock(num = (firstArg<BlockRevision >() as BlockRevision .Number ).number)
9911051 }
1052+ coEvery { thorClient.waitForBlock(BlockRevision .Number (0L )) } returns block0
1053+ coEvery { thorClient.waitForBlock(BlockRevision .Number (1L )) } returns block1
9921054
9931055 val runner = IndexerRunner ()
9941056 val job = launch { runner.runAllIndexers(listOf (indexer), thorClient, 1 ) }
0 commit comments