@@ -7,20 +7,28 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
77import { createMockRequest } from '@/app/api/__test-utils__/utils'
88
99describe ( 'Chat Subdomain API Route' , ( ) => {
10- const mockWorkflowSingleOutput = {
11- id : 'response-id' ,
12- content : 'Test response' ,
13- timestamp : new Date ( ) . toISOString ( ) ,
14- type : 'workflow' ,
10+ const createMockStream = ( ) => {
11+ return new ReadableStream ( {
12+ start ( controller ) {
13+ controller . enqueue (
14+ new TextEncoder ( ) . encode ( 'data: {"blockId":"agent-1","chunk":"Hello"}\n\n' )
15+ )
16+ controller . enqueue (
17+ new TextEncoder ( ) . encode ( 'data: {"blockId":"agent-1","chunk":" world"}\n\n' )
18+ )
19+ controller . enqueue (
20+ new TextEncoder ( ) . encode ( 'data: {"event":"final","data":{"success":true}}\n\n' )
21+ )
22+ controller . close ( )
23+ } ,
24+ } )
1525 }
1626
17- // Mock functions
1827 const mockAddCorsHeaders = vi . fn ( ) . mockImplementation ( ( response ) => response )
1928 const mockValidateChatAuth = vi . fn ( ) . mockResolvedValue ( { authorized : true } )
2029 const mockSetChatAuthCookie = vi . fn ( )
21- const mockExecuteWorkflowForChat = vi . fn ( ) . mockResolvedValue ( mockWorkflowSingleOutput )
30+ const mockExecuteWorkflowForChat = vi . fn ( ) . mockResolvedValue ( createMockStream ( ) )
2231
23- // Mock database return values
2432 const mockChatResult = [
2533 {
2634 id : 'chat-id' ,
@@ -41,13 +49,24 @@ describe('Chat Subdomain API Route', () => {
4149 const mockWorkflowResult = [
4250 {
4351 isDeployed : true ,
52+ state : {
53+ blocks : { } ,
54+ edges : [ ] ,
55+ loops : { } ,
56+ parallels : { } ,
57+ } ,
58+ deployedState : {
59+ blocks : { } ,
60+ edges : [ ] ,
61+ loops : { } ,
62+ parallels : { } ,
63+ } ,
4464 } ,
4565 ]
4666
4767 beforeEach ( ( ) => {
4868 vi . resetModules ( )
4969
50- // Mock chat API utils
5170 vi . doMock ( '../utils' , ( ) => ( {
5271 addCorsHeaders : mockAddCorsHeaders ,
5372 validateChatAuth : mockValidateChatAuth ,
@@ -56,7 +75,6 @@ describe('Chat Subdomain API Route', () => {
5675 executeWorkflowForChat : mockExecuteWorkflowForChat ,
5776 } ) )
5877
59- // Mock logger
6078 vi . doMock ( '@/lib/logs/console-logger' , ( ) => ( {
6179 createLogger : vi . fn ( ) . mockReturnValue ( {
6280 debug : vi . fn ( ) ,
@@ -66,32 +84,35 @@ describe('Chat Subdomain API Route', () => {
6684 } ) ,
6785 } ) )
6886
69- // Mock database
7087 vi . doMock ( '@/db' , ( ) => {
71- const mockLimitChat = vi . fn ( ) . mockReturnValue ( mockChatResult )
72- const mockWhereChat = vi . fn ( ) . mockReturnValue ( { limit : mockLimitChat } )
73-
74- const mockLimitWorkflow = vi . fn ( ) . mockReturnValue ( mockWorkflowResult )
75- const mockWhereWorkflow = vi . fn ( ) . mockReturnValue ( { limit : mockLimitWorkflow } )
76-
77- const mockFrom = vi . fn ( ) . mockImplementation ( ( table ) => {
78- // Check which table is being queried
79- if ( table === 'workflow' ) {
80- return { where : mockWhereWorkflow }
88+ const mockSelect = vi . fn ( ) . mockImplementation ( ( fields ) => {
89+ if ( fields && fields . isDeployed !== undefined ) {
90+ return {
91+ from : vi . fn ( ) . mockReturnValue ( {
92+ where : vi . fn ( ) . mockReturnValue ( {
93+ limit : vi . fn ( ) . mockReturnValue ( mockWorkflowResult ) ,
94+ } ) ,
95+ } ) ,
96+ }
97+ }
98+ return {
99+ from : vi . fn ( ) . mockReturnValue ( {
100+ where : vi . fn ( ) . mockReturnValue ( {
101+ limit : vi . fn ( ) . mockReturnValue ( mockChatResult ) ,
102+ } ) ,
103+ } ) ,
81104 }
82- return { where : mockWhereChat }
83105 } )
84106
85- const mockSelect = vi . fn ( ) . mockReturnValue ( { from : mockFrom } )
86-
87107 return {
88108 db : {
89109 select : mockSelect ,
90110 } ,
111+ chat : { } ,
112+ workflow : { } ,
91113 }
92114 } )
93115
94- // Mock API response helpers
95116 vi . doMock ( '@/app/api/workflows/utils' , ( ) => ( {
96117 createErrorResponse : vi . fn ( ) . mockImplementation ( ( message , status , code ) => {
97118 return new Response (
@@ -277,37 +298,47 @@ describe('Chat Subdomain API Route', () => {
277298 } )
278299
279300 it ( 'should return 503 when workflow is not available' , async ( ) => {
301+ // Override the default workflow result to return non-deployed
280302 vi . doMock ( '@/db' , ( ) => {
281- const mockLimitChat = vi . fn ( ) . mockReturnValue ( [
282- {
283- id : 'chat-id' ,
284- workflowId : 'unavailable-workflow' ,
285- isActive : true ,
286- authType : 'public' ,
287- } ,
288- ] )
289- const mockWhereChat = vi . fn ( ) . mockReturnValue ( { limit : mockLimitChat } )
290-
291- // Second call returns non-deployed workflow
292- const mockLimitWorkflow = vi . fn ( ) . mockReturnValue ( [
293- {
294- isDeployed : false ,
295- } ,
296- ] )
297- const mockWhereWorkflow = vi . fn ( ) . mockReturnValue ( { limit : mockLimitWorkflow } )
298-
299- // Mock from function to return different where implementations
300- const mockFrom = vi
301- . fn ( )
302- . mockImplementationOnce ( ( ) => ( { where : mockWhereChat } ) ) // First call (chat)
303- . mockImplementationOnce ( ( ) => ( { where : mockWhereWorkflow } ) ) // Second call (workflow)
303+ // Track call count to return different results
304+ let callCount = 0
305+
306+ const mockLimit = vi . fn ( ) . mockImplementation ( ( ) => {
307+ callCount ++
308+ if ( callCount === 1 ) {
309+ // First call - chat query
310+ return [
311+ {
312+ id : 'chat-id' ,
313+ workflowId : 'unavailable-workflow' ,
314+ userId : 'user-id' ,
315+ isActive : true ,
316+ authType : 'public' ,
317+ outputConfigs : [ { blockId : 'block-1' , path : 'output' } ] ,
318+ } ,
319+ ]
320+ }
321+ if ( callCount === 2 ) {
322+ // Second call - workflow query
323+ return [
324+ {
325+ isDeployed : false ,
326+ } ,
327+ ]
328+ }
329+ return [ ]
330+ } )
304331
332+ const mockWhere = vi . fn ( ) . mockReturnValue ( { limit : mockLimit } )
333+ const mockFrom = vi . fn ( ) . mockReturnValue ( { where : mockWhere } )
305334 const mockSelect = vi . fn ( ) . mockReturnValue ( { from : mockFrom } )
306335
307336 return {
308337 db : {
309338 select : mockSelect ,
310339 } ,
340+ chat : { } ,
341+ workflow : { } ,
311342 }
312343 } )
313344
@@ -325,6 +356,48 @@ describe('Chat Subdomain API Route', () => {
325356 expect ( data ) . toHaveProperty ( 'message' , 'Chat workflow is not available' )
326357 } )
327358
359+ it ( 'should return streaming response for valid chat messages' , async ( ) => {
360+ const req = createMockRequest ( 'POST' , { message : 'Hello world' , conversationId : 'conv-123' } )
361+ const params = Promise . resolve ( { subdomain : 'test-chat' } )
362+
363+ const { POST } = await import ( './route' )
364+
365+ const response = await POST ( req , { params } )
366+
367+ expect ( response . status ) . toBe ( 200 )
368+ expect ( response . headers . get ( 'Content-Type' ) ) . toBe ( 'text/event-stream' )
369+ expect ( response . headers . get ( 'Cache-Control' ) ) . toBe ( 'no-cache' )
370+ expect ( response . headers . get ( 'Connection' ) ) . toBe ( 'keep-alive' )
371+
372+ // Verify executeWorkflowForChat was called with correct parameters
373+ expect ( mockExecuteWorkflowForChat ) . toHaveBeenCalledWith ( 'chat-id' , 'Hello world' , 'conv-123' )
374+ } )
375+
376+ it ( 'should handle streaming response body correctly' , async ( ) => {
377+ const req = createMockRequest ( 'POST' , { message : 'Hello world' } )
378+ const params = Promise . resolve ( { subdomain : 'test-chat' } )
379+
380+ const { POST } = await import ( './route' )
381+
382+ const response = await POST ( req , { params } )
383+
384+ expect ( response . status ) . toBe ( 200 )
385+ expect ( response . body ) . toBeInstanceOf ( ReadableStream )
386+
387+ // Test that we can read from the response stream
388+ if ( response . body ) {
389+ const reader = response . body . getReader ( )
390+ const { value, done } = await reader . read ( )
391+
392+ if ( ! done && value ) {
393+ const chunk = new TextDecoder ( ) . decode ( value )
394+ expect ( chunk ) . toMatch ( / ^ d a t a : / )
395+ }
396+
397+ reader . releaseLock ( )
398+ }
399+ } )
400+
328401 it ( 'should handle workflow execution errors gracefully' , async ( ) => {
329402 const originalExecuteWorkflow = mockExecuteWorkflowForChat . getMockImplementation ( )
330403 mockExecuteWorkflowForChat . mockImplementationOnce ( async ( ) => {
@@ -338,15 +411,64 @@ describe('Chat Subdomain API Route', () => {
338411
339412 const response = await POST ( req , { params } )
340413
341- expect ( response . status ) . toBe ( 503 )
414+ expect ( response . status ) . toBe ( 500 )
342415
343416 const data = await response . json ( )
344417 expect ( data ) . toHaveProperty ( 'error' )
345- expect ( data ) . toHaveProperty ( 'message' , 'Chat workflow is not available ' )
418+ expect ( data ) . toHaveProperty ( 'message' , 'Execution failed ' )
346419
347420 if ( originalExecuteWorkflow ) {
348421 mockExecuteWorkflowForChat . mockImplementation ( originalExecuteWorkflow )
349422 }
350423 } )
424+
425+ it ( 'should handle invalid JSON in request body' , async ( ) => {
426+ // Create a request with invalid JSON
427+ const req = {
428+ method : 'POST' ,
429+ json : vi . fn ( ) . mockRejectedValue ( new Error ( 'Invalid JSON' ) ) ,
430+ } as any
431+
432+ const params = Promise . resolve ( { subdomain : 'test-chat' } )
433+
434+ const { POST } = await import ( './route' )
435+
436+ const response = await POST ( req , { params } )
437+
438+ expect ( response . status ) . toBe ( 400 )
439+
440+ const data = await response . json ( )
441+ expect ( data ) . toHaveProperty ( 'error' )
442+ expect ( data ) . toHaveProperty ( 'message' , 'Invalid request body' )
443+ } )
444+
445+ it ( 'should pass conversationId to executeWorkflowForChat when provided' , async ( ) => {
446+ const req = createMockRequest ( 'POST' , {
447+ message : 'Hello world' ,
448+ conversationId : 'test-conversation-123' ,
449+ } )
450+ const params = Promise . resolve ( { subdomain : 'test-chat' } )
451+
452+ const { POST } = await import ( './route' )
453+
454+ await POST ( req , { params } )
455+
456+ expect ( mockExecuteWorkflowForChat ) . toHaveBeenCalledWith (
457+ 'chat-id' ,
458+ 'Hello world' ,
459+ 'test-conversation-123'
460+ )
461+ } )
462+
463+ it ( 'should handle missing conversationId gracefully' , async ( ) => {
464+ const req = createMockRequest ( 'POST' , { message : 'Hello world' } )
465+ const params = Promise . resolve ( { subdomain : 'test-chat' } )
466+
467+ const { POST } = await import ( './route' )
468+
469+ await POST ( req , { params } )
470+
471+ expect ( mockExecuteWorkflowForChat ) . toHaveBeenCalledWith ( 'chat-id' , 'Hello world' , undefined )
472+ } )
351473 } )
352474} )
0 commit comments