@@ -569,4 +569,93 @@ describe("BatchQueue", () => {
569569 }
570570 } ) ;
571571 } ) ;
572+
573+ describe ( "completion callback error handling" , ( ) => {
574+ redisTest (
575+ "should preserve Redis data when completion callback throws an error" ,
576+ async ( { redisContainer } ) => {
577+ const queue = createBatchQueue ( redisContainer , { startConsumers : true } ) ;
578+ let callbackCallCount = 0 ;
579+ let lastCompletionResult : CompleteBatchResult | null = null ;
580+
581+ try {
582+ queue . onProcessItem ( async ( { itemIndex } ) => {
583+ return { success : true , runId : `run_${ itemIndex } ` } ;
584+ } ) ;
585+
586+ queue . onBatchComplete ( async ( result ) => {
587+ callbackCallCount ++ ;
588+ lastCompletionResult = result ;
589+ // Simulate database failure on first attempt
590+ if ( callbackCallCount === 1 ) {
591+ throw new Error ( "Database temporarily unavailable" ) ;
592+ }
593+ } ) ;
594+
595+ await queue . initializeBatch ( createInitOptions ( "batch1" , "env1" , 3 ) ) ;
596+ await enqueueItems ( queue , "batch1" , "env1" , createBatchItems ( 3 ) ) ;
597+
598+ // Wait for completion callback to be called (and fail)
599+ await vi . waitFor (
600+ ( ) => {
601+ expect ( callbackCallCount ) . toBeGreaterThanOrEqual ( 1 ) ;
602+ } ,
603+ { timeout : 5000 }
604+ ) ;
605+
606+ // Redis data should still exist after callback failure
607+ const meta = await queue . getBatchMeta ( "batch1" ) ;
608+ expect ( meta ) . not . toBeNull ( ) ;
609+ expect ( meta ?. batchId ) . toBe ( "batch1" ) ;
610+
611+ // Verify the completion result was correct
612+ expect ( lastCompletionResult ) . not . toBeNull ( ) ;
613+ expect ( lastCompletionResult ! . batchId ) . toBe ( "batch1" ) ;
614+ expect ( lastCompletionResult ! . successfulRunCount ) . toBe ( 3 ) ;
615+ expect ( lastCompletionResult ! . runIds ) . toHaveLength ( 3 ) ;
616+ } finally {
617+ await queue . close ( ) ;
618+ }
619+ }
620+ ) ;
621+
622+ redisTest (
623+ "should cleanup Redis data when completion callback succeeds" ,
624+ async ( { redisContainer } ) => {
625+ const queue = createBatchQueue ( redisContainer , { startConsumers : true } ) ;
626+ let completionCalled = false ;
627+
628+ try {
629+ queue . onProcessItem ( async ( { itemIndex } ) => {
630+ return { success : true , runId : `run_${ itemIndex } ` } ;
631+ } ) ;
632+
633+ queue . onBatchComplete ( async ( ) => {
634+ completionCalled = true ;
635+ // Callback succeeds - no error thrown
636+ } ) ;
637+
638+ await queue . initializeBatch ( createInitOptions ( "batch1" , "env1" , 3 ) ) ;
639+ await enqueueItems ( queue , "batch1" , "env1" , createBatchItems ( 3 ) ) ;
640+
641+ // Wait for completion
642+ await vi . waitFor (
643+ ( ) => {
644+ expect ( completionCalled ) . toBe ( true ) ;
645+ } ,
646+ { timeout : 5000 }
647+ ) ;
648+
649+ // Small delay to ensure cleanup has occurred
650+ await new Promise ( ( resolve ) => setTimeout ( resolve , 100 ) ) ;
651+
652+ // Redis data should be cleaned up after successful callback
653+ const meta = await queue . getBatchMeta ( "batch1" ) ;
654+ expect ( meta ) . toBeNull ( ) ;
655+ } finally {
656+ await queue . close ( ) ;
657+ }
658+ }
659+ ) ;
660+ } ) ;
572661} ) ;
0 commit comments