@@ -90,6 +90,8 @@ vi.mock('../../src/services/indexerService.js', () => ({
9090 getIndexerStatus : vi . fn ( ) . mockResolvedValue ( { } ) ,
9191 resetIndexer : vi . fn ( ) . mockResolvedValue ( undefined ) ,
9292 replayFromLedger : vi . fn ( ) . mockResolvedValue ( undefined ) ,
93+ previewReset : vi . fn ( ) . mockResolvedValue ( { } ) ,
94+ previewReplay : vi . fn ( ) . mockResolvedValue ( { } ) ,
9395} ) ) ;
9496
9597vi . mock ( '../../src/workers/soroban-event-worker.js' , ( ) => ( {
@@ -113,6 +115,8 @@ import {
113115 getIndexerStatus ,
114116 resetIndexer ,
115117 replayFromLedger ,
118+ previewReset ,
119+ previewReplay ,
116120} from '../../src/services/indexerService.js' ;
117121
118122// ─── Helpers ──────────────────────────────────────────────────────────────────
@@ -520,6 +524,50 @@ describe('POST /v1/admin/indexer/reset', () => {
520524 expect ( res . status ) . toBe ( 500 ) ;
521525 expect ( res . body ) . toEqual ( { error : 'Reset failed' } ) ;
522526 } ) ;
527+
528+ it ( 'returns a dry-run preview without calling resetIndexer when dryRun=true' , async ( ) => {
529+ const preview = {
530+ currentLastLedger : 1000 ,
531+ currentLastCursor : 'cursor_xyz' ,
532+ targetLastLedger : 500 ,
533+ } ;
534+ vi . mocked ( previewReset ) . mockResolvedValueOnce ( preview ) ;
535+
536+ const res = await request ( app )
537+ . post ( '/v1/admin/indexer/reset?dryRun=true' )
538+ . set ( 'Authorization' , `Bearer ${ createToken ( ) } ` )
539+ . send ( { ledger : 500 } ) ;
540+
541+ expect ( res . status ) . toBe ( 200 ) ;
542+ expect ( res . body ) . toEqual ( { dryRun : true , preview } ) ;
543+ expect ( previewReset ) . toHaveBeenCalledWith ( 500 ) ;
544+ expect ( resetIndexer ) . not . toHaveBeenCalled ( ) ;
545+ } ) ;
546+
547+ it ( 'treats non-true dryRun values as a real reset' , async ( ) => {
548+ const res = await request ( app )
549+ . post ( '/v1/admin/indexer/reset?dryRun=false' )
550+ . set ( 'Authorization' , `Bearer ${ createToken ( ) } ` )
551+ . send ( { ledger : 300 } ) ;
552+
553+ expect ( res . status ) . toBe ( 200 ) ;
554+ expect ( res . body ) . toEqual ( { ok : true , lastLedger : 300 } ) ;
555+ expect ( resetIndexer ) . toHaveBeenCalledWith ( 300 ) ;
556+ expect ( previewReset ) . not . toHaveBeenCalled ( ) ;
557+ } ) ;
558+
559+ it ( 'returns 500 when previewReset throws for a dry-run request' , async ( ) => {
560+ vi . mocked ( previewReset ) . mockRejectedValueOnce ( new Error ( 'Preview failed' ) ) ;
561+
562+ const res = await request ( app )
563+ . post ( '/v1/admin/indexer/reset?dryRun=true' )
564+ . set ( 'Authorization' , `Bearer ${ createToken ( ) } ` )
565+ . send ( { ledger : 500 } ) ;
566+
567+ expect ( res . status ) . toBe ( 500 ) ;
568+ expect ( res . body ) . toEqual ( { error : 'Reset failed' } ) ;
569+ expect ( resetIndexer ) . not . toHaveBeenCalled ( ) ;
570+ } ) ;
523571} ) ;
524572
525573describe ( 'POST /v1/admin/indexer/replay' , ( ) => {
@@ -587,5 +635,49 @@ describe('POST /v1/admin/indexer/replay', () => {
587635 expect ( res . status ) . toBe ( 500 ) ;
588636 expect ( res . body ) . toEqual ( { error : 'Replay failed' } ) ;
589637 } ) ;
638+
639+ it ( 'returns a dry-run preview without triggering a replay when dryRun=true' , async ( ) => {
640+ const preview = {
641+ fromLedger : 200 ,
642+ currentLastLedger : 1000 ,
643+ currentLastCursor : 'cursor_xyz' ,
644+ eventCount : 42 ,
645+ minLedgerInReplayRange : 210 ,
646+ maxLedgerInReplayRange : 999 ,
647+ } ;
648+ vi . mocked ( previewReplay ) . mockResolvedValueOnce ( preview ) ;
649+
650+ const res = await request ( app )
651+ . post ( '/v1/admin/indexer/replay?from_ledger=200&dryRun=true' )
652+ . set ( 'Authorization' , `Bearer ${ createToken ( ) } ` ) ;
653+
654+ expect ( res . status ) . toBe ( 200 ) ;
655+ expect ( res . body ) . toEqual ( { dryRun : true , preview } ) ;
656+ expect ( previewReplay ) . toHaveBeenCalledWith ( 200 ) ;
657+ expect ( replayFromLedger ) . not . toHaveBeenCalled ( ) ;
658+ } ) ;
659+
660+ it ( 'treats non-true dryRun values as a real replay' , async ( ) => {
661+ const res = await request ( app )
662+ . post ( '/v1/admin/indexer/replay?from_ledger=200&dryRun=false' )
663+ . set ( 'Authorization' , `Bearer ${ createToken ( ) } ` ) ;
664+
665+ expect ( res . status ) . toBe ( 202 ) ;
666+ expect ( res . body ) . toMatchObject ( { ok : true , replayingFrom : 200 } ) ;
667+ expect ( replayFromLedger ) . toHaveBeenCalledWith ( 200 ) ;
668+ expect ( previewReplay ) . not . toHaveBeenCalled ( ) ;
669+ } ) ;
670+
671+ it ( 'returns 500 when previewReplay throws for a dry-run request' , async ( ) => {
672+ vi . mocked ( previewReplay ) . mockRejectedValueOnce ( new Error ( 'Preview failed' ) ) ;
673+
674+ const res = await request ( app )
675+ . post ( '/v1/admin/indexer/replay?from_ledger=200&dryRun=true' )
676+ . set ( 'Authorization' , `Bearer ${ createToken ( ) } ` ) ;
677+
678+ expect ( res . status ) . toBe ( 500 ) ;
679+ expect ( res . body ) . toEqual ( { error : 'Replay failed' } ) ;
680+ expect ( replayFromLedger ) . not . toHaveBeenCalled ( ) ;
681+ } ) ;
590682} ) ;
591683
0 commit comments