import { EventEmitter } from 'node:events'; import { describe, expect, it } from 'vitest'; import { createGlobalUiEventBroadcaster, createMessageStreamWsRuntime } from './runtime.js'; class FakeSocket extends EventEmitter { constructor() { super(); this.readyState = 1; this.sent = []; this.closeCalls = []; } send(payload) { this.sent.push(JSON.parse(payload)); } ping() { void 0; } close(code, reason) { if (this.readyState === 3) { return; } this.readyState = 3; this.closeCalls.push({ code, reason }); this.emit('close'); } } function createSseResponse({ blocks = [], signal, holdOpen = false }) { const encoder = new TextEncoder(); let index = 0; return { ok: true, body: { getReader() { return { async read() { if (index < blocks.length) { const next = blocks[index++]; return { value: encoder.encode(next), done: false }; } if (!holdOpen) { return { value: undefined, done: true }; } return new Promise((resolve, reject) => { const onAbort = () => { signal.removeEventListener('abort', onAbort); const error = new Error('Aborted'); error.name = 'AbortError'; reject(error); }; signal.addEventListener('abort', onAbort, { once: true }); }); }, }; }, }, }; } describe('event stream broadcaster', () => { it('fans out synthetic events to SSE and WS clients', () => { const sseEvents = []; const wsPayloads = []; const sseClient = { id: 'sse-1' }; const wsClient = { readyState: 1, send(payload) { wsPayloads.push(JSON.parse(payload)); }, }; const broadcast = createGlobalUiEventBroadcaster({ sseClients: new Set([sseClient]), wsClients: new Set([wsClient]), writeSseEvent(res, payload) { sseEvents.push({ res, payload }); }, }); broadcast({ type: 'openchamber:session-status' }, { eventId: 'evt-1', directory: '/tmp/project' }); expect(sseEvents).toEqual([ { res: sseClient, payload: { type: 'openchamber:session-status' }, }, ]); expect(wsPayloads).toEqual([ { type: 'event', payload: { type: 'openchamber:session-status' }, eventId: 'evt-1', directory: '/tmp/project', }, ]); }); it('removes websocket clients that fail to receive a payload', () => { const wsClients = new Set([ { readyState: 1, send() { throw new Error('socket write failed'); }, }, ]); const broadcast = createGlobalUiEventBroadcaster({ sseClients: new Set(), wsClients, writeSseEvent() { throw new Error('should not be called'); }, }); broadcast({ type: 'openchamber:notification' }); expect(wsClients.size).toBe(0); }); }); describe('message stream websocket runtime', () => { it('reconnects a stalled upstream SSE stream and resumes from the last event id', async () => { const server = new EventEmitter(); const wsClients = new Set(); let triggerHealthCheckCalls = 0; const fetchCalls = []; let upstreamAttempt = 0; const runtime = createMessageStreamWsRuntime({ server, uiAuthController: null, isRequestOriginAllowed: async () => true, rejectWebSocketUpgrade() { throw new Error('upgrade should not be used in this test'); }, buildOpenCodeUrl: (path) => `http://127.0.0.1:4096${path}`, getOpenCodeAuthHeaders: () => ({}), processForwardedEventPayload() {}, wsClients, triggerHealthCheck: () => { triggerHealthCheckCalls += 1; }, heartbeatIntervalMs: 50, upstreamStallTimeoutMs: 20, upstreamReconnectDelayMs: 0, fetchImpl: async (_url, options) => { const lastEventId = options?.headers?.['Last-Event-ID'] ?? null; fetchCalls.push(lastEventId); upstreamAttempt += 1; if (upstreamAttempt === 1) { return createSseResponse({ signal: options.signal, holdOpen: true, blocks: [ 'id: evt-1\ndata: {"type":"server.connected","properties":{}}\n\n', ], }); } return createSseResponse({ signal: options.signal, holdOpen: false, blocks: [ 'id: evt-2\ndata: {"type":"server.connected","properties":{}}\n\n', ], }); }, }); const socket = new FakeSocket(); runtime.wsServer.emit('connection', socket, { url: '/api/global/event/ws' }); await new Promise((resolve) => setTimeout(resolve, 35)); const readyFrames = socket.sent.filter((frame) => frame.type === 'ready'); const eventFrames = socket.sent.filter((frame) => frame.type === 'event' && frame.payload?.type === 'server.connected'); expect(readyFrames).toHaveLength(1); expect(eventFrames.length).toBeGreaterThanOrEqual(2); expect(fetchCalls.slice(0, 2)).toEqual([null, 'evt-1']); expect(triggerHealthCheckCalls).toBe(0); socket.close(); await runtime.close(); }); });