@@ -938,6 +938,115 @@ describe('Queues - Payload', () => {
938938 expect ( allSimples . docs [ 0 ] ?. title ) . toBe ( 'from single task' )
939939 } )
940940
941+ describe ( 'when a queued task slug is no longer registered in config' , ( ) => {
942+ let originalTasks : typeof payload . config . jobs . tasks
943+
944+ beforeEach ( ( ) => {
945+ originalTasks = payload . config . jobs . tasks
946+ } )
947+
948+ afterEach ( ( ) => {
949+ payload . config . jobs . tasks = originalTasks
950+ } )
951+
952+ it ( 'should permanently fail the job after one attempt instead of retrying forever' , async ( ) => {
953+ payload . config . jobs . deleteJobOnComplete = false
954+
955+ const job = await payload . jobs . queue ( {
956+ task : 'CreateSimple' ,
957+ input : {
958+ message : 'queued before task removal' ,
959+ } ,
960+ } )
961+
962+ // Simulate a deploy that removed the 'CreateSimple' task from config
963+ payload . config . jobs . tasks = originalTasks ! . filter ( ( t ) => t . slug !== 'CreateSimple' )
964+
965+ await payload . jobs . run ( { silent : true } )
966+
967+ const jobAfterRun = await payload . findByID ( {
968+ collection : 'payload-jobs' ,
969+ id : job . id ,
970+ } )
971+
972+ expect ( jobAfterRun . hasError ) . toBe ( true )
973+ expect ( jobAfterRun . processing ) . toBe ( false )
974+ expect ( jobAfterRun . totalTried ) . toBe ( 1 )
975+ } )
976+
977+ it ( 'should permanently fail when a workflow handler calls a task that has been removed' , async ( ) => {
978+ payload . config . jobs . deleteJobOnComplete = false
979+
980+ const job = await payload . jobs . queue ( {
981+ workflow : 'workflowNoRetriesSet' ,
982+ input : {
983+ message : 'queued before referenced task removal' ,
984+ } ,
985+ } )
986+
987+ payload . config . jobs . tasks = originalTasks ! . filter ( ( t ) => t . slug !== 'CreateSimple' )
988+
989+ await payload . jobs . run ( { silent : true } )
990+
991+ const jobAfterRun = await payload . findByID ( {
992+ collection : 'payload-jobs' ,
993+ id : job . id ,
994+ } )
995+
996+ expect ( jobAfterRun . hasError ) . toBe ( true )
997+ expect ( jobAfterRun . processing ) . toBe ( false )
998+ expect ( jobAfterRun . totalTried ) . toBe ( 1 )
999+ } )
1000+ } )
1001+
1002+ it ( 'should not retry a workflow with no retries configured when its handler throws' , async ( ) => {
1003+ payload . config . jobs . deleteJobOnComplete = false
1004+
1005+ const job = await payload . jobs . queue ( {
1006+ workflow : 'throwsInHandlerNoRetries' ,
1007+ input : { } ,
1008+ } )
1009+
1010+ await payload . jobs . run ( { silent : true } )
1011+
1012+ const jobAfterRun = await payload . findByID ( {
1013+ collection : 'payload-jobs' ,
1014+ id : job . id ,
1015+ } )
1016+
1017+ expect ( jobAfterRun . hasError ) . toBe ( true )
1018+ expect ( jobAfterRun . processing ) . toBe ( false )
1019+ expect ( jobAfterRun . totalTried ) . toBe ( 1 )
1020+ } )
1021+
1022+ it ( 'should retry a workflow with retries=1 exactly once when its handler throws' , async ( ) => {
1023+ payload . config . jobs . deleteJobOnComplete = false
1024+
1025+ const job = await payload . jobs . queue ( {
1026+ workflow : 'throwsInHandlerRetries1' ,
1027+ input : { } ,
1028+ } )
1029+
1030+ let hasJobsRemaining = true
1031+ while ( hasJobsRemaining ) {
1032+ const response = await payload . jobs . run ( { silent : true } )
1033+ if ( response . noJobsRemaining ) {
1034+ hasJobsRemaining = false
1035+ }
1036+ }
1037+
1038+ const jobAfterRun = await payload . findByID ( {
1039+ collection : 'payload-jobs' ,
1040+ id : job . id ,
1041+ } )
1042+
1043+ // Initial attempt + 1 retry = 2. Once hasError is true the queue stops picking it up,
1044+ // so the loop naturally bounds at exactly 2 attempts.
1045+ expect ( jobAfterRun . totalTried ) . toBe ( 2 )
1046+ expect ( jobAfterRun . hasError ) . toBe ( true )
1047+ expect ( jobAfterRun . processing ) . toBe ( false )
1048+ } )
1049+
9411050 it ( 'can queue and run via the endpoint single tasks without workflows' , async ( ) => {
9421051 const workflowsRef = payload . config . jobs . workflows
9431052 delete payload . config . jobs . workflows
0 commit comments