@@ -12,13 +12,21 @@ const {
1212 mockGetJobQueue,
1313 mockShouldExecuteInline,
1414 mockEnqueue,
15+ mockLoggerInfo,
16+ mockLoggerWarn,
17+ mockLoggerError,
18+ mockLoggerDebug,
1519} = vi . hoisted ( ( ) => ( {
1620 mockCheckHybridAuth : vi . fn ( ) ,
1721 mockAuthorizeWorkflowByWorkspacePermission : vi . fn ( ) ,
1822 mockPreprocessExecution : vi . fn ( ) ,
1923 mockGetJobQueue : vi . fn ( ) ,
2024 mockShouldExecuteInline : vi . fn ( ) ,
2125 mockEnqueue : vi . fn ( ) . mockResolvedValue ( 'job-123' ) ,
26+ mockLoggerInfo : vi . fn ( ) ,
27+ mockLoggerWarn : vi . fn ( ) ,
28+ mockLoggerError : vi . fn ( ) ,
29+ mockLoggerDebug : vi . fn ( ) ,
2230} ) )
2331
2432vi . mock ( '@/lib/auth/hybrid' , ( ) => ( {
@@ -70,10 +78,10 @@ vi.mock('@/background/workflow-execution', () => ({
7078
7179vi . mock ( '@sim/logger' , ( ) => ( {
7280 createLogger : vi . fn ( ) . mockReturnValue ( {
73- info : vi . fn ( ) ,
74- warn : vi . fn ( ) ,
75- error : vi . fn ( ) ,
76- debug : vi . fn ( ) ,
81+ info : mockLoggerInfo ,
82+ warn : mockLoggerWarn ,
83+ error : mockLoggerError ,
84+ debug : mockLoggerDebug ,
7785 } ) ,
7886} ) )
7987
@@ -84,10 +92,27 @@ vi.mock('uuid', () => ({
8492
8593import { POST } from './route'
8694
95+ async function readJsonBody ( response : Response ) {
96+ const text = await response . text ( )
97+
98+ return {
99+ text,
100+ json : text ? JSON . parse ( text ) : null ,
101+ }
102+ }
103+
104+ function expectCheckpointOrder ( checkpoints : string [ ] , expected : string [ ] ) {
105+ expect ( checkpoints ) . toEqual ( expected )
106+ }
107+
87108describe ( 'workflow execute async route' , ( ) => {
109+ const asyncCheckpoints : string [ ] = [ ]
110+
88111 beforeEach ( ( ) => {
89112 vi . clearAllMocks ( )
90113
114+ asyncCheckpoints . length = 0
115+
91116 mockCheckHybridAuth . mockReset ( )
92117 mockAuthorizeWorkflowByWorkspacePermission . mockReset ( )
93118 mockPreprocessExecution . mockReset ( )
@@ -102,31 +127,58 @@ describe('workflow execute async route', () => {
102127 completeJob : vi . fn ( ) ,
103128 markJobFailed : vi . fn ( ) ,
104129 } )
105- mockShouldExecuteInline . mockReturnValue ( false )
130+ mockShouldExecuteInline . mockImplementation ( ( ) => {
131+ asyncCheckpoints . push ( 'shouldExecuteInline' )
132+ return false
133+ } )
134+
135+ mockCheckHybridAuth . mockImplementation ( async ( ) => {
136+ asyncCheckpoints . push ( 'authorization' )
137+ return {
138+ success : true ,
139+ userId : 'session-user-1' ,
140+ authType : 'session' ,
141+ }
142+ } )
143+
144+ mockAuthorizeWorkflowByWorkspacePermission . mockImplementation ( async ( ) => {
145+ asyncCheckpoints . push ( 'authorizeWorkflowByWorkspacePermission' )
146+ return {
147+ allowed : true ,
148+ workflow : {
149+ id : 'workflow-1' ,
150+ userId : 'owner-1' ,
151+ workspaceId : 'workspace-1' ,
152+ } ,
153+ }
154+ } )
106155
107- mockCheckHybridAuth . mockResolvedValue ( {
108- success : true ,
109- userId : 'session-user-1' ,
110- authType : 'session' ,
156+ mockPreprocessExecution . mockImplementation ( async ( ) => {
157+ asyncCheckpoints . push ( 'preprocessing' )
158+ return {
159+ success : true ,
160+ actorUserId : 'actor-1' ,
161+ workflowRecord : {
162+ id : 'workflow-1' ,
163+ userId : 'owner-1' ,
164+ workspaceId : 'workspace-1' ,
165+ } ,
166+ }
111167 } )
112168
113- mockAuthorizeWorkflowByWorkspacePermission . mockResolvedValue ( {
114- allowed : true ,
115- workflow : {
116- id : 'workflow-1' ,
117- userId : 'owner-1' ,
118- workspaceId : 'workspace-1' ,
119- } ,
169+ mockGetJobQueue . mockImplementation ( async ( ) => {
170+ asyncCheckpoints . push ( 'getJobQueue' )
171+ return {
172+ enqueue : mockEnqueue ,
173+ startJob : vi . fn ( ) ,
174+ completeJob : vi . fn ( ) ,
175+ markJobFailed : vi . fn ( ) ,
176+ }
120177 } )
121178
122- mockPreprocessExecution . mockResolvedValue ( {
123- success : true ,
124- actorUserId : 'actor-1' ,
125- workflowRecord : {
126- id : 'workflow-1' ,
127- userId : 'owner-1' ,
128- workspaceId : 'workspace-1' ,
129- } ,
179+ mockEnqueue . mockImplementation ( async ( ) => {
180+ asyncCheckpoints . push ( 'enqueue' )
181+ return 'job-123'
130182 } )
131183 } )
132184
@@ -142,11 +194,23 @@ describe('workflow execute async route', () => {
142194 const params = Promise . resolve ( { id : 'workflow-1' } )
143195
144196 const response = await POST ( req as any , { params } )
145- const body = await response . json ( )
197+ const { json : body , text : bodyText } = await readJsonBody ( response )
146198
147- expect ( response . status ) . toBe ( 202 )
199+ expect (
200+ response . status ,
201+ `Expected async execute route to return 202, got ${ response . status } with body: ${ bodyText } `
202+ ) . toBe ( 202 )
203+ expectCheckpointOrder ( asyncCheckpoints , [
204+ 'authorization' ,
205+ 'authorizeWorkflowByWorkspacePermission' ,
206+ 'preprocessing' ,
207+ 'getJobQueue' ,
208+ 'enqueue' ,
209+ 'shouldExecuteInline' ,
210+ ] )
148211 expect ( body . executionId ) . toBe ( 'execution-123' )
149212 expect ( body . jobId ) . toBe ( 'job-123' )
213+ expect ( mockLoggerError ) . not . toHaveBeenCalled ( )
150214 expect ( mockEnqueue ) . toHaveBeenCalledWith (
151215 'workflow-execution' ,
152216 expect . objectContaining ( {
@@ -177,4 +241,42 @@ describe('workflow execute async route', () => {
177241 }
178242 )
179243 } )
244+
245+ it ( 'returns queue failure payload with checkpoint trail and logs the queue error' , async ( ) => {
246+ const queueError = new Error ( 'queue unavailable' )
247+ mockEnqueue . mockImplementationOnce ( async ( ) => {
248+ asyncCheckpoints . push ( 'enqueue' )
249+ throw queueError
250+ } )
251+
252+ const req = createMockRequest (
253+ 'POST' ,
254+ { input : { hello : 'world' } } ,
255+ {
256+ 'Content-Type' : 'application/json' ,
257+ 'X-Execution-Mode' : 'async' ,
258+ }
259+ )
260+
261+ const response = await POST ( req as any , { params : Promise . resolve ( { id : 'workflow-1' } ) } )
262+ const { json : body , text : bodyText } = await readJsonBody ( response )
263+
264+ expectCheckpointOrder ( asyncCheckpoints , [
265+ 'authorization' ,
266+ 'authorizeWorkflowByWorkspacePermission' ,
267+ 'preprocessing' ,
268+ 'getJobQueue' ,
269+ 'enqueue' ,
270+ ] )
271+ expect (
272+ response . status ,
273+ `Expected async execute route to return 500, got ${ response . status } with body: ${ bodyText } `
274+ ) . toBe ( 500 )
275+ expect ( body ) . toEqual ( { error : 'Failed to queue async execution: queue unavailable' } )
276+ expect ( mockShouldExecuteInline ) . not . toHaveBeenCalled ( )
277+ expect ( mockLoggerError ) . toHaveBeenCalledWith (
278+ '[req-12345678] Failed to queue async execution' ,
279+ queueError
280+ )
281+ } )
180282} )
0 commit comments