Skip to content

Commit 6382f51

Browse files
committed
fix(copilot): share workflow cancellation boundary
1 parent d8bd378 commit 6382f51

5 files changed

Lines changed: 1114 additions & 1022 deletions

File tree

apps/sim/app/api/workflows/[id]/executions/[executionId]/cancel/route.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ vi.mock('@/lib/execution/event-buffer', () => ({
113113
}))
114114

115115
import { cancelWorkflowExecutionContract } from '@/lib/api/contracts/workflows'
116+
import { cancelWorkflowExecutionPostAuth } from '@/lib/execution/cancel-workflow-execution-post-auth'
116117
import { 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

Comments
 (0)