/** * @vitest-environment node */ import { describe, expect, it, vi } from 'vitest' import type { ExecutionEvent } from '@/lib/workflows/executor/execution-events' import { processSSEStream } from '@/hooks/use-execution-stream' function streamEvents(events: ExecutionEvent[]): ReadableStream { const encoder = new TextEncoder() return new ReadableStream({ start(controller) { for (const event of events) { controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`)) } controller.enqueue(encoder.encode('data: [DONE]\n\n')) controller.close() }, }) } describe('processSSEStream', () => { it('acknowledges event ids only after the matching handler completes', async () => { const order: string[] = [] const event: ExecutionEvent = { type: 'block:started', eventId: 5, timestamp: new Date().toISOString(), executionId: 'exec-1', workflowId: 'wf-1', data: { blockId: 'block-1', blockName: 'Block 1', blockType: 'function', executionOrder: 1, }, } await processSSEStream( streamEvents([event]).getReader(), { onBlockStarted: async () => { order.push('handler:start') await Promise.resolve() order.push('handler:end') }, onEventId: vi.fn(async () => { order.push('event-id') }), }, 'test' ) expect(order).toEqual(['handler:start', 'handler:end', 'event-id']) }) it('propagates callback failures without acknowledging the event id', async () => { const event: ExecutionEvent = { type: 'block:started', eventId: 6, timestamp: new Date().toISOString(), executionId: 'exec-1', workflowId: 'wf-1', data: { blockId: 'block-1', blockName: 'Block 1', blockType: 'function', executionOrder: 1, }, } const onEventId = vi.fn() await expect( processSSEStream( streamEvents([event]).getReader(), { onBlockStarted: async () => { throw new Error('handler failed') }, onEventId, }, 'test' ) ).rejects.toThrow('handler failed') expect(onEventId).not.toHaveBeenCalled() }) it('releases the reader lock after the stream completes', async () => { const stream = streamEvents([]) const reader = stream.getReader() expect(stream.locked).toBe(true) await processSSEStream(reader, {}, 'test') expect(stream.locked).toBe(false) }) it('releases the reader lock even when a handler throws', async () => { const event: ExecutionEvent = { type: 'block:started', eventId: 7, timestamp: new Date().toISOString(), executionId: 'exec-1', workflowId: 'wf-1', data: { blockId: 'block-1', blockName: 'Block 1', blockType: 'function', executionOrder: 1, }, } const stream = streamEvents([event]) const reader = stream.getReader() await expect( processSSEStream( reader, { onBlockStarted: () => { throw new Error('boom') }, }, 'test' ) ).rejects.toThrow('boom') expect(stream.locked).toBe(false) }) })