import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { WsFrameSchema, KNOWN_FRAME_TYPES, type WsFrame, } from '../../types/ws-frames.js'; import { createBroker } from '../broker.js'; const VALID_UUID_A = '00000000-0000-0000-0000-000000000001'; const VALID_UUID_B = '00000000-0000-0000-0000-000000000002'; const VALID_UUID_C = '00000000-0000-0000-0000-000000000003'; const VALID_TIMESTAMP = '2026-05-22T14:30:00.000Z'; describe('WsFrameSchema (v1.13.11-a)', () => { it('accepts a well-formed chat_status frame', () => { const result = WsFrameSchema.safeParse({ type: 'chat_status', chat_id: VALID_UUID_A, status: 'streaming', at: VALID_TIMESTAMP, }); expect(result.success).toBe(true); }); it('rejects an unknown frame type', () => { const result = WsFrameSchema.safeParse({ type: 'cosmic_ray_strike', chat_id: VALID_UUID_A, }); expect(result.success).toBe(false); }); it('rejects a chat_status frame with invalid status enum', () => { // v1.12.1 dropped the legacy 'working' status. Any frame still emitting it // should fail validation — that's a drift catcher. const result = WsFrameSchema.safeParse({ type: 'chat_status', chat_id: VALID_UUID_A, status: 'working', at: VALID_TIMESTAMP, }); expect(result.success).toBe(false); }); it('rejects a UUID field with a non-UUID string', () => { const result = WsFrameSchema.safeParse({ type: 'chat_status', chat_id: 'not-a-uuid', status: 'idle', at: VALID_TIMESTAMP, }); expect(result.success).toBe(false); }); it('rejects negative token counts in usage frame', () => { const result = WsFrameSchema.safeParse({ type: 'usage', message_id: VALID_UUID_A, chat_id: VALID_UUID_B, completion_tokens: -1, ctx_used: 100, ctx_max: 1000, }); expect(result.success).toBe(false); }); it('accepts a usage frame with nullable token counts (pre-v1.13.7 history)', () => { const result = WsFrameSchema.safeParse({ type: 'usage', message_id: VALID_UUID_A, chat_id: VALID_UUID_B, completion_tokens: null, ctx_used: null, ctx_max: null, }); expect(result.success).toBe(true); }); it('accepts a tool_result frame with non-UUID tool_call_id (model-emitted)', () => { // Model-emitted tool_call_ids look like "call_abc123", not UUIDs. const result = WsFrameSchema.safeParse({ type: 'tool_result', tool_message_id: VALID_UUID_A, chat_id: VALID_UUID_B, tool_call_id: 'call_abc123', output: { whatever: true }, truncated: false, }); expect(result.success).toBe(true); }); it('accepts a compacted frame', () => { const result = WsFrameSchema.safeParse({ type: 'compacted', session_id: VALID_UUID_A, chat_id: VALID_UUID_B, summary_message_id: VALID_UUID_C, }); expect(result.success).toBe(true); }); it('accepts a session_workspace_updated frame', () => { const result = WsFrameSchema.safeParse({ type: 'session_workspace_updated', session_id: VALID_UUID_A, workspace_panes: [{ id: 'p1', kind: 'chat', chatIds: [], activeChatIdx: 0 }], }); expect(result.success).toBe(true); }); it('every KNOWN_FRAME_TYPES entry has a discriminated branch', () => { // Probe each known type by attempting a minimal valid construction. // Failure here means the union and the KNOWN_FRAME_TYPES list drifted. for (const type of KNOWN_FRAME_TYPES) { const probe = WsFrameSchema.safeParse({ type, __dummy__: true }); // We expect FAILURE on every type because we're missing required fields, // but the failure must be ABOUT the missing fields, not about an unknown // type. A "Invalid discriminator value" error means the type isn't in // the union — that's a drift. if (probe.success) continue; const issues = probe.error.issues; const hasInvalidDiscriminator = issues.some( (i) => i.code === 'invalid_union_discriminator', ); expect(hasInvalidDiscriminator, `frame type '${type}' is missing from the discriminated union`).toBe(false); } }); }); describe('ws-frames.ts file mirror parity', () => { it('apps/server and apps/web copies are byte-identical', () => { const here = fileURLToPath(import.meta.url); const serverPath = resolve(here, '../../../types/ws-frames.ts'); const webPath = resolve(here, '../../../../../web/src/api/ws-frames.ts'); const serverContent = readFileSync(serverPath, 'utf8'); const webContent = readFileSync(webPath, 'utf8'); expect(webContent, 'apps/web/src/api/ws-frames.ts must be byte-identical to apps/server/src/types/ws-frames.ts').toBe(serverContent); }); }); describe('broker.publishFrame / publishUserFrame fail-closed behavior', () => { let logErrors: Array<{ obj: unknown; msg: string }>; let mockLog: Parameters[0]; beforeEach(() => { logErrors = []; mockLog = { error: (obj: unknown, msg: string) => { logErrors.push({ obj, msg }); }, info: () => {}, warn: () => {}, debug: () => {}, trace: () => {}, fatal: () => {}, child: () => mockLog as never, level: 'info', silent: () => {}, } as unknown as Parameters[0]; }); afterEach(() => { vi.restoreAllMocks(); }); it('publishFrame delivers a valid frame to subscribers', () => { const broker = createBroker(mockLog); const received: WsFrame[] = []; broker.subscribe('sess-1', (f) => received.push(f as WsFrame)); broker.publishFrame('sess-1', { type: 'delta', message_id: VALID_UUID_A, chat_id: VALID_UUID_B, content: 'hello', }); expect(received).toHaveLength(1); expect((received[0] as { type: string }).type).toBe('delta'); expect(logErrors).toHaveLength(0); }); it('publishFrame drops + logs an invalid frame instead of delivering it', () => { const broker = createBroker(mockLog); const received: WsFrame[] = []; broker.subscribe('sess-1', (f) => received.push(f as WsFrame)); broker.publishFrame('sess-1', { type: 'delta', message_id: 'not-a-uuid', content: 'hello', } as never); expect(received).toHaveLength(0); expect(logErrors).toHaveLength(1); expect(logErrors[0]!.msg).toMatch(/ws-frame-validation-failed/); }); it('publishUserFrame drops + logs an invalid user-channel frame', () => { const broker = createBroker(mockLog); const received: WsFrame[] = []; broker.subscribeUser('default', (f) => received.push(f as WsFrame)); broker.publishUserFrame('default', { type: 'chat_status', chat_id: VALID_UUID_A, status: 'working', // v1.12.1 dropped this enum value at: VALID_TIMESTAMP, } as never); expect(received).toHaveLength(0); expect(logErrors).toHaveLength(1); }); it('publishFrame validation failure does not throw (no cascade into stream-phase)', () => { const broker = createBroker(mockLog); expect(() => broker.publishFrame('sess-1', { type: 'unknown_type' } as never), ).not.toThrow(); }); });