@@ -555,4 +555,40 @@ describe("SqliteQueue", () => {
555555 failed : 0 ,
556556 } ) ;
557557 } ) ;
558+
559+ test ( "expired running job with no retries should be marked as failed" , async ( ) => {
560+ const queue = new SqliteQueue < Work > (
561+ "expired-job-queue" ,
562+ buildDBClient ( ":memory:" , { runMigrations : true } ) ,
563+ {
564+ defaultJobArgs : {
565+ numRetries : 0 , // No retries
566+ } ,
567+ keepFailedJobs : true ,
568+ } ,
569+ ) ;
570+
571+ // Enqueue a job
572+ await queue . enqueue ( { increment : 1 } ) ;
573+
574+ // Dequeue the job (makes it "running")
575+ const dequeuedJob = await queue . attemptDequeue ( { timeoutSecs : 1 } ) ; // Short timeout
576+ expect ( dequeuedJob ) . not . toBeNull ( ) ;
577+ expect ( dequeuedJob ! . status ) . toBe ( "running" ) ;
578+ expect ( dequeuedJob ! . numRunsLeft ) . toBe ( 0 ) ; // No retries left
579+
580+ // Wait for the job to expire
581+ await new Promise ( ( resolve ) => setTimeout ( resolve , 2000 ) ) ; // Wait 1.5 seconds
582+
583+ // Try to dequeue again - should pick up the expired job for cleanup
584+ const expiredJob = await queue . attemptDequeue ( { timeoutSecs : 5 } ) ;
585+ expect ( expiredJob ) . toBeNull ( ) ;
586+
587+ // Check stats - job should now be failed, not stuck in running
588+ const stats = await queue . stats ( ) ;
589+ expect ( stats . running ) . toBe ( 0 ) ;
590+ expect ( stats . failed ) . toBe ( 1 ) ;
591+ expect ( stats . pending ) . toBe ( 0 ) ;
592+ expect ( stats . pending_retry ) . toBe ( 0 ) ;
593+ } ) ;
558594} ) ;
0 commit comments