@@ -524,4 +524,199 @@ describe("TaskExecutor", () => {
524524 } ,
525525 } ) ;
526526 } ) ;
527+
528+ test ( "should call onFailure hooks with error when task fails" , async ( ) => {
529+ const globalFailureOrder : string [ ] = [ ] ;
530+ const failurePayloads : any [ ] = [ ] ;
531+ const failureErrors : any [ ] = [ ] ;
532+ const failureInits : any [ ] = [ ] ;
533+
534+ // Register global init hook to provide init data
535+ lifecycleHooks . registerGlobalInitHook ( {
536+ id : "test-init" ,
537+ fn : async ( ) => {
538+ return {
539+ foo : "bar" ,
540+ } ;
541+ } ,
542+ } ) ;
543+
544+ // Register two global failure hooks
545+ lifecycleHooks . registerGlobalFailureHook ( {
546+ id : "global-failure-1" ,
547+ fn : async ( { payload, error, init } ) => {
548+ console . log ( "Executing global failure hook 1" ) ;
549+ globalFailureOrder . push ( "global-1" ) ;
550+ failurePayloads . push ( payload ) ;
551+ failureErrors . push ( error ) ;
552+ failureInits . push ( init ) ;
553+ } ,
554+ } ) ;
555+
556+ lifecycleHooks . registerGlobalFailureHook ( {
557+ id : "global-failure-2" ,
558+ fn : async ( { payload, error, init } ) => {
559+ console . log ( "Executing global failure hook 2" ) ;
560+ globalFailureOrder . push ( "global-2" ) ;
561+ failurePayloads . push ( payload ) ;
562+ failureErrors . push ( error ) ;
563+ failureInits . push ( init ) ;
564+ } ,
565+ } ) ;
566+
567+ // Register task-specific failure hook
568+ lifecycleHooks . registerTaskFailureHook ( "test-task" , {
569+ id : "task-failure" ,
570+ fn : async ( { payload, error, init } ) => {
571+ console . log ( "Executing task failure hook" ) ;
572+ globalFailureOrder . push ( "task" ) ;
573+ failurePayloads . push ( payload ) ;
574+ failureErrors . push ( error ) ;
575+ failureInits . push ( init ) ;
576+ } ,
577+ } ) ;
578+
579+ // Verify hooks are registered
580+ const globalHooks = lifecycleHooks . getGlobalFailureHooks ( ) ;
581+ console . log (
582+ "Registered global hooks:" ,
583+ globalHooks . map ( ( h ) => h . id )
584+ ) ;
585+ const taskHook = lifecycleHooks . getTaskFailureHook ( "test-task" ) ;
586+ console . log ( "Registered task hook:" , taskHook ? "yes" : "no" ) ;
587+
588+ const expectedError = new Error ( "Task failed intentionally" ) ;
589+
590+ const task = {
591+ id : "test-task" ,
592+ fns : {
593+ run : async ( payload : any , params : RunFnParams < any > ) => {
594+ throw expectedError ;
595+ } ,
596+ } ,
597+ } ;
598+
599+ const tracingSDK = new TracingSDK ( {
600+ url : "http://localhost:4318" ,
601+ } ) ;
602+
603+ const tracer = new TriggerTracer ( {
604+ name : "test-task" ,
605+ version : "1.0.0" ,
606+ tracer : tracingSDK . getTracer ( "test-task" ) ,
607+ logger : tracingSDK . getLogger ( "test-task" ) ,
608+ } ) ;
609+
610+ const consoleInterceptor = new ConsoleInterceptor ( tracingSDK . getLogger ( "test-task" ) , false ) ;
611+
612+ const executor = new TaskExecutor ( task , {
613+ tracingSDK,
614+ tracer,
615+ consoleInterceptor,
616+ retries : {
617+ enabledInDev : false ,
618+ default : {
619+ maxAttempts : 1 ,
620+ } ,
621+ } ,
622+ handleErrorFn : undefined ,
623+ } ) ;
624+
625+ const execution : TaskRunExecution = {
626+ task : {
627+ id : "test-task" ,
628+ filePath : "test-task.ts" ,
629+ } ,
630+ attempt : {
631+ number : 1 ,
632+ startedAt : new Date ( ) ,
633+ id : "test-attempt-id" ,
634+ status : "success" ,
635+ backgroundWorkerId : "test-background-worker-id" ,
636+ backgroundWorkerTaskId : "test-background-worker-task-id" ,
637+ } ,
638+ run : {
639+ id : "test-run-id" ,
640+ payload : '{"test":"data"}' ,
641+ payloadType : "application/json" ,
642+ metadata : { } ,
643+ startedAt : new Date ( ) ,
644+ tags : [ ] ,
645+ isTest : false ,
646+ createdAt : new Date ( ) ,
647+ durationMs : 0 ,
648+ costInCents : 0 ,
649+ baseCostInCents : 0 ,
650+ priority : 0 ,
651+ } ,
652+ machine : {
653+ name : "micro" ,
654+ cpu : 1 ,
655+ memory : 1 ,
656+ centsPerMs : 0 ,
657+ } ,
658+ queue : {
659+ name : "test-queue" ,
660+ id : "test-queue-id" ,
661+ } ,
662+ environment : {
663+ type : "PRODUCTION" ,
664+ id : "test-environment-id" ,
665+ slug : "test-environment-slug" ,
666+ } ,
667+ organization : {
668+ id : "test-organization-id" ,
669+ name : "test-organization-name" ,
670+ slug : "test-organization-slug" ,
671+ } ,
672+ project : {
673+ id : "test-project-id" ,
674+ name : "test-project-name" ,
675+ slug : "test-project-slug" ,
676+ ref : "test-project-ref" ,
677+ } ,
678+ } ;
679+
680+ const worker : ServerBackgroundWorker = {
681+ id : "test-background-worker-id" ,
682+ version : "1.0.0" ,
683+ contentHash : "test-content-hash" ,
684+ engine : "V2" ,
685+ } ;
686+
687+ const result = await executor . execute ( execution , worker , { } ) ;
688+
689+ // Verify hooks were called in correct order
690+ expect ( globalFailureOrder ) . toEqual ( [ "global-1" , "global-2" , "task" ] ) ;
691+
692+ // Verify each hook received the correct payload
693+ failurePayloads . forEach ( ( payload ) => {
694+ expect ( payload ) . toEqual ( { test : "data" } ) ;
695+ } ) ;
696+
697+ // Verify each hook received the correct error
698+ failureErrors . forEach ( ( error ) => {
699+ expect ( error ) . toBe ( expectedError ) ;
700+ } ) ;
701+
702+ // Verify each hook received the correct init data
703+ failureInits . forEach ( ( init ) => {
704+ expect ( init ) . toEqual ( { foo : "bar" } ) ;
705+ } ) ;
706+
707+ // Verify the final result contains the error
708+ expect ( result ) . toEqual ( {
709+ result : {
710+ ok : false ,
711+ id : "test-run-id" ,
712+ error : {
713+ type : "BUILT_IN_ERROR" ,
714+ message : "Task failed intentionally" ,
715+ name : "Error" ,
716+ stackTrace : expect . any ( String ) ,
717+ } ,
718+ skippedRetrying : false ,
719+ } ,
720+ } ) ;
721+ } ) ;
527722} ) ;
0 commit comments