Skip to content

Commit 42d17d6

Browse files
committed
test(hitl): update cancel route tests for paused execution cancellation
- Mock PauseResumeManager.cancelPausedExecution to prevent DB calls - Add pausedCancelled to all expected response objects - Add test for HITL paused execution cancellation path - Add missing auth/authz tests - Switch to vi.hoisted pattern for all mocks
1 parent 394be59 commit 42d17d6

File tree

1 file changed

+80
-39
lines changed
  • apps/sim/app/api/workflows/[id]/executions/[executionId]/cancel

1 file changed

+80
-39
lines changed

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

Lines changed: 80 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@
55
import { NextRequest } from 'next/server'
66
import { beforeEach, describe, expect, it, vi } from 'vitest'
77

8-
const mockCheckHybridAuth = vi.fn()
9-
const mockAuthorizeWorkflowByWorkspacePermission = vi.fn()
10-
const mockMarkExecutionCancelled = vi.fn()
11-
const mockAbortManualExecution = vi.fn()
12-
13-
vi.mock('@sim/logger', () => ({
14-
createLogger: () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn() }),
8+
const {
9+
mockCheckHybridAuth,
10+
mockAuthorizeWorkflowByWorkspacePermission,
11+
mockMarkExecutionCancelled,
12+
mockAbortManualExecution,
13+
mockCancelPausedExecution,
14+
} = vi.hoisted(() => ({
15+
mockCheckHybridAuth: vi.fn(),
16+
mockAuthorizeWorkflowByWorkspacePermission: vi.fn(),
17+
mockMarkExecutionCancelled: vi.fn(),
18+
mockAbortManualExecution: vi.fn(),
19+
mockCancelPausedExecution: vi.fn(),
1520
}))
1621

1722
vi.mock('@/lib/auth/hybrid', () => ({
@@ -26,19 +31,37 @@ vi.mock('@/lib/execution/manual-cancellation', () => ({
2631
abortManualExecution: (...args: unknown[]) => mockAbortManualExecution(...args),
2732
}))
2833

34+
vi.mock('@/lib/workflows/executor/human-in-the-loop-manager', () => ({
35+
PauseResumeManager: {
36+
cancelPausedExecution: (...args: unknown[]) => mockCancelPausedExecution(...args),
37+
},
38+
}))
39+
2940
vi.mock('@/lib/workflows/utils', () => ({
3041
authorizeWorkflowByWorkspacePermission: (params: unknown) =>
3142
mockAuthorizeWorkflowByWorkspacePermission(params),
3243
}))
3344

45+
vi.mock('@/lib/posthog/server', () => ({
46+
captureServerEvent: vi.fn(),
47+
}))
48+
3449
import { POST } from './route'
3550

51+
const makeRequest = () =>
52+
new NextRequest('http://localhost/api/workflows/wf-1/executions/ex-1/cancel', {
53+
method: 'POST',
54+
})
55+
56+
const makeParams = () => ({ params: Promise.resolve({ id: 'wf-1', executionId: 'ex-1' }) })
57+
3658
describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => {
3759
beforeEach(() => {
3860
vi.clearAllMocks()
3961
mockCheckHybridAuth.mockResolvedValue({ success: true, userId: 'user-1' })
4062
mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: true })
4163
mockAbortManualExecution.mockReturnValue(false)
64+
mockCancelPausedExecution.mockResolvedValue(false)
4265
})
4366

4467
it('returns success when cancellation was durably recorded', async () => {
@@ -47,14 +70,7 @@ describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => {
4770
reason: 'recorded',
4871
})
4972

50-
const response = await POST(
51-
new NextRequest('http://localhost/api/workflows/wf-1/executions/ex-1/cancel', {
52-
method: 'POST',
53-
}),
54-
{
55-
params: Promise.resolve({ id: 'wf-1', executionId: 'ex-1' }),
56-
}
57-
)
73+
const response = await POST(makeRequest(), makeParams())
5874

5975
expect(response.status).toBe(200)
6076
await expect(response.json()).resolves.toEqual({
@@ -63,6 +79,7 @@ describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => {
6379
redisAvailable: true,
6480
durablyRecorded: true,
6581
locallyAborted: false,
82+
pausedCancelled: false,
6683
reason: 'recorded',
6784
})
6885
})
@@ -73,14 +90,7 @@ describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => {
7390
reason: 'redis_unavailable',
7491
})
7592

76-
const response = await POST(
77-
new NextRequest('http://localhost/api/workflows/wf-1/executions/ex-1/cancel', {
78-
method: 'POST',
79-
}),
80-
{
81-
params: Promise.resolve({ id: 'wf-1', executionId: 'ex-1' }),
82-
}
83-
)
93+
const response = await POST(makeRequest(), makeParams())
8494

8595
expect(response.status).toBe(200)
8696
await expect(response.json()).resolves.toEqual({
@@ -89,6 +99,7 @@ describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => {
8999
redisAvailable: false,
90100
durablyRecorded: false,
91101
locallyAborted: false,
102+
pausedCancelled: false,
92103
reason: 'redis_unavailable',
93104
})
94105
})
@@ -99,14 +110,7 @@ describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => {
99110
reason: 'redis_write_failed',
100111
})
101112

102-
const response = await POST(
103-
new NextRequest('http://localhost/api/workflows/wf-1/executions/ex-1/cancel', {
104-
method: 'POST',
105-
}),
106-
{
107-
params: Promise.resolve({ id: 'wf-1', executionId: 'ex-1' }),
108-
}
109-
)
113+
const response = await POST(makeRequest(), makeParams())
110114

111115
expect(response.status).toBe(200)
112116
await expect(response.json()).resolves.toEqual({
@@ -115,6 +119,7 @@ describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => {
115119
redisAvailable: true,
116120
durablyRecorded: false,
117121
locallyAborted: false,
122+
pausedCancelled: false,
118123
reason: 'redis_write_failed',
119124
})
120125
})
@@ -126,14 +131,7 @@ describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => {
126131
})
127132
mockAbortManualExecution.mockReturnValue(true)
128133

129-
const response = await POST(
130-
new NextRequest('http://localhost/api/workflows/wf-1/executions/ex-1/cancel', {
131-
method: 'POST',
132-
}),
133-
{
134-
params: Promise.resolve({ id: 'wf-1', executionId: 'ex-1' }),
135-
}
136-
)
134+
const response = await POST(makeRequest(), makeParams())
137135

138136
expect(response.status).toBe(200)
139137
await expect(response.json()).resolves.toEqual({
@@ -142,7 +140,50 @@ describe('POST /api/workflows/[id]/executions/[executionId]/cancel', () => {
142140
redisAvailable: false,
143141
durablyRecorded: false,
144142
locallyAborted: true,
143+
pausedCancelled: false,
144+
reason: 'redis_unavailable',
145+
})
146+
})
147+
148+
it('returns success when a paused HITL execution is cancelled directly in the database', async () => {
149+
mockMarkExecutionCancelled.mockResolvedValue({
150+
durablyRecorded: false,
151+
reason: 'redis_unavailable',
152+
})
153+
mockCancelPausedExecution.mockResolvedValue(true)
154+
155+
const response = await POST(makeRequest(), makeParams())
156+
157+
expect(response.status).toBe(200)
158+
await expect(response.json()).resolves.toEqual({
159+
success: true,
160+
executionId: 'ex-1',
161+
redisAvailable: false,
162+
durablyRecorded: false,
163+
locallyAborted: false,
164+
pausedCancelled: true,
145165
reason: 'redis_unavailable',
146166
})
147167
})
168+
169+
it('returns 401 when auth fails', async () => {
170+
mockCheckHybridAuth.mockResolvedValue({ success: false, error: 'Unauthorized' })
171+
172+
const response = await POST(makeRequest(), makeParams())
173+
174+
expect(response.status).toBe(401)
175+
})
176+
177+
it('returns 403 when workflow access is denied', async () => {
178+
mockMarkExecutionCancelled.mockResolvedValue({ durablyRecorded: true, reason: 'recorded' })
179+
mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({
180+
allowed: false,
181+
message: 'Access denied',
182+
status: 403,
183+
})
184+
185+
const response = await POST(makeRequest(), makeParams())
186+
187+
expect(response.status).toBe(403)
188+
})
148189
})

0 commit comments

Comments
 (0)