@@ -150,6 +150,12 @@ const ACTIVE_RESUME_TARGET = {
150150 resumeExecutionId : 'resume-ex-1' ,
151151}
152152
153+ const REPLACEMENT_ACTIVE_RESUME_TARGET = {
154+ ...ACTIVE_RESUME_TARGET ,
155+ resumeEntryId : 'resume-entry-2' ,
156+ resumeExecutionId : 'resume-ex-2' ,
157+ }
158+
153159describe ( 'cancelWorkflowExecution' , ( ) => {
154160 beforeEach ( ( ) => {
155161 vi . clearAllMocks ( )
@@ -785,6 +791,85 @@ describe('cancelWorkflowExecution', () => {
785791 expect ( mockClearExecutionCancellation ) . not . toHaveBeenCalled ( )
786792 } )
787793
794+ it ( 'finishes cancellation when a failed active-resume rollback detects a replacement' , async ( ) => {
795+ mockStagePausedCancellation
796+ . mockResolvedValueOnce ( { kind : 'active_resume' , target : ACTIVE_RESUME_TARGET } )
797+ . mockResolvedValueOnce ( {
798+ kind : 'active_resume' ,
799+ target : REPLACEMENT_ACTIVE_RESUME_TARGET ,
800+ } )
801+ mockGetActiveResumeCancellationTarget . mockResolvedValueOnce ( REPLACEMENT_ACTIVE_RESUME_TARGET )
802+ mockRollbackActiveResumeCancellation . mockResolvedValueOnce ( false )
803+ mockMarkExecutionCancelled
804+ . mockResolvedValueOnce ( { durablyRecorded : false , reason : 'redis_unavailable' } )
805+ . mockResolvedValueOnce ( { durablyRecorded : true , reason : 'recorded' } )
806+ mockCompletePausedCancellation . mockResolvedValueOnce ( true )
807+
808+ const response = await POST ( makeRequest ( ) , makeParams ( ) )
809+
810+ expect ( response . status ) . toBe ( 200 )
811+ await expect ( response . json ( ) ) . resolves . toMatchObject ( {
812+ success : true ,
813+ durablyRecorded : true ,
814+ pausedCancelled : true ,
815+ reason : 'recorded' ,
816+ } )
817+ expect ( mockRollbackActiveResumeCancellation ) . toHaveBeenCalledWith (
818+ 'ex-1' ,
819+ 'wf-1' ,
820+ 'resume-entry-1'
821+ )
822+ expect ( mockMarkExecutionCancelled ) . toHaveBeenNthCalledWith ( 1 , 'resume-ex-1' , {
823+ executionDeadlineAt : null ,
824+ } )
825+ expect ( mockMarkExecutionCancelled ) . toHaveBeenNthCalledWith ( 2 , 'resume-ex-2' , {
826+ executionDeadlineAt : null ,
827+ } )
828+ expect ( mockCompletePausedCancellation ) . toHaveBeenCalledWith ( 'ex-1' , 'wf-1' )
829+ } )
830+
831+ it ( 'finishes late pause cancellation when rollback detects a replacement resume' , async ( ) => {
832+ mockStagePausedCancellation
833+ . mockResolvedValueOnce ( { kind : 'not_paused' } )
834+ . mockResolvedValueOnce ( { kind : 'active_resume' , target : ACTIVE_RESUME_TARGET } )
835+ . mockResolvedValueOnce ( {
836+ kind : 'active_resume' ,
837+ target : REPLACEMENT_ACTIVE_RESUME_TARGET ,
838+ } )
839+ mockGetActiveResumeCancellationTarget . mockResolvedValueOnce ( REPLACEMENT_ACTIVE_RESUME_TARGET )
840+ mockRollbackActiveResumeCancellation . mockResolvedValueOnce ( false )
841+ mockMarkExecutionCancelled
842+ . mockResolvedValueOnce ( { durablyRecorded : false , reason : 'redis_unavailable' } )
843+ . mockResolvedValueOnce ( { durablyRecorded : false , reason : 'redis_unavailable' } )
844+ . mockResolvedValueOnce ( { durablyRecorded : true , reason : 'recorded' } )
845+ mockCompletePausedCancellation . mockResolvedValueOnce ( true )
846+
847+ const response = await POST ( makeRequest ( ) , makeParams ( ) )
848+
849+ expect ( response . status ) . toBe ( 200 )
850+ await expect ( response . json ( ) ) . resolves . toMatchObject ( {
851+ success : true ,
852+ durablyRecorded : true ,
853+ pausedCancelled : true ,
854+ reason : 'recorded' ,
855+ } )
856+ expect ( mockRollbackActiveResumeCancellation ) . toHaveBeenCalledWith (
857+ 'ex-1' ,
858+ 'wf-1' ,
859+ 'resume-entry-1'
860+ )
861+ expect ( mockMarkExecutionCancelled ) . toHaveBeenNthCalledWith ( 1 , 'ex-1' , {
862+ executionDeadlineAt : null ,
863+ } )
864+ expect ( mockMarkExecutionCancelled ) . toHaveBeenNthCalledWith ( 2 , 'resume-ex-1' , {
865+ executionDeadlineAt : null ,
866+ } )
867+ expect ( mockMarkExecutionCancelled ) . toHaveBeenNthCalledWith ( 3 , 'resume-ex-2' , {
868+ executionDeadlineAt : null ,
869+ } )
870+ expect ( mockCompletePausedCancellation ) . toHaveBeenCalledWith ( 'ex-1' , 'wf-1' )
871+ } )
872+
788873 it ( 'returns success when a paused HITL execution is cancelled directly in the database' , async ( ) => {
789874 mockStagePausedCancellation . mockResolvedValue ( { kind : 'idle' } )
790875 mockCompletePausedCancellation . mockResolvedValue ( true )
0 commit comments