@@ -113,6 +113,7 @@ vi.mock('@/lib/execution/event-buffer', () => ({
113113} ) )
114114
115115import { cancelWorkflowExecutionContract } from '@/lib/api/contracts/workflows'
116+ import { cancelWorkflowExecutionPostAuth } from '@/lib/execution/cancel-workflow-execution-post-auth'
116117import { POST as cancelExecution } from './route'
117118
118119/**
@@ -984,6 +985,71 @@ describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => {
984985 expect ( response . status ) . toBe ( 403 )
985986 } )
986987
988+ it ( 'conceals a workflow outside the asserted Copilot workspace' , async ( ) => {
989+ const response = await cancelWorkflowExecutionPostAuth ( {
990+ workflowId : 'wf-1' ,
991+ executionId : 'ex-1' ,
992+ userId : 'user-1' ,
993+ assertedWorkspaceId : 'workspace-other' ,
994+ } )
995+
996+ expect ( response . status ) . toBe ( 404 )
997+ await expect ( response . json ( ) ) . resolves . toEqual ( { error : 'Execution not found' } )
998+ expect ( databaseMock . db . select ) . not . toHaveBeenCalled ( )
999+ expect ( mockMarkExecutionCancelled ) . not . toHaveBeenCalled ( )
1000+ } )
1001+
1002+ it ( 'stops before lookup when cancellation is aborted during authorization' , async ( ) => {
1003+ const controller = new AbortController ( )
1004+ workflowAuthzMockFns . mockAuthorizeWorkflowByWorkspacePermission . mockImplementationOnce (
1005+ async ( ) => {
1006+ controller . abort ( )
1007+ return { allowed : true , workflow : { workspaceId : 'workspace-1' } }
1008+ }
1009+ )
1010+
1011+ const response = await cancelWorkflowExecutionPostAuth ( {
1012+ workflowId : 'wf-1' ,
1013+ executionId : 'ex-1' ,
1014+ userId : 'user-1' ,
1015+ abortSignal : controller . signal ,
1016+ } )
1017+
1018+ expect ( response . status ) . toBe ( 409 )
1019+ await expect ( response . json ( ) ) . resolves . toEqual ( {
1020+ error : 'Request aborted before workflow run cancellation could be applied.' ,
1021+ } )
1022+ expect ( databaseMock . db . select ) . not . toHaveBeenCalled ( )
1023+ expect ( mockMarkExecutionCancelled ) . not . toHaveBeenCalled ( )
1024+ } )
1025+
1026+ it ( 'stops before mutation when cancellation is aborted during execution lookup' , async ( ) => {
1027+ const controller = new AbortController ( )
1028+ dbChainMockFns . limit . mockImplementationOnce ( async ( ) => {
1029+ controller . abort ( )
1030+ return [
1031+ {
1032+ executionDeadlineAt : null ,
1033+ executionOrigin : null ,
1034+ status : 'running' ,
1035+ workspaceId : 'workspace-1' ,
1036+ } ,
1037+ ]
1038+ } )
1039+
1040+ const response = await cancelWorkflowExecutionPostAuth ( {
1041+ workflowId : 'wf-1' ,
1042+ executionId : 'ex-1' ,
1043+ userId : 'user-1' ,
1044+ abortSignal : controller . signal ,
1045+ } )
1046+
1047+ expect ( response . status ) . toBe ( 409 )
1048+ expect ( mockMarkExecutionCancelled ) . not . toHaveBeenCalled ( )
1049+ expect ( mockAbortManualExecution ) . not . toHaveBeenCalled ( )
1050+ expect ( mockCancelByExecution ) . not . toHaveBeenCalled ( )
1051+ } )
1052+
9871053 it ( 'returns 404 when the execution does not belong to the workflow' , async ( ) => {
9881054 dbChainMockFns . limit . mockResolvedValueOnce ( [ ] )
9891055
0 commit comments