From 4f6904c39563abda1462b07c374bead5aa6d1f1c Mon Sep 17 00:00:00 2001 From: William Valentin Date: Mon, 16 Feb 2026 19:42:52 -0800 Subject: [PATCH] test(companion): cover waitForAnyEvent socket-close rejection --- docs/plans/state.json | 11 +++++++++ src/companion/runtimeClient.test.ts | 38 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/docs/plans/state.json b/docs/plans/state.json index f9470ff..1230449 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -785,6 +785,17 @@ ], "test_status": "pnpm test:run src/companion/runtimeClient.test.ts src/companion/platformClients.test.ts src/companion/heartbeatLoop.test.ts src/companion/platformClients.integration.test.ts + pnpm typecheck passing" }, + "companion-runtime-wait-for-any-close-rejection-coverage": { + "status": "completed", + "date": "2026-02-17", + "updated": "2026-02-17", + "summary": "Added regression coverage proving `waitForAnyEvent()` rejects with `WebSocket closed` when the runtime socket closes unexpectedly.", + "files_modified": [ + "src/companion/runtimeClient.test.ts", + "docs/plans/state.json" + ], + "test_status": "pnpm test:run src/companion/runtimeClient.test.ts src/companion/platformClients.test.ts src/companion/heartbeatLoop.test.ts src/companion/platformClients.integration.test.ts + pnpm typecheck passing" + }, "browser-tools-activation-clarity": { "status": "completed", "date": "2026-02-17", diff --git a/src/companion/runtimeClient.test.ts b/src/companion/runtimeClient.test.ts index 7e8bb9a..650e257 100644 --- a/src/companion/runtimeClient.test.ts +++ b/src/companion/runtimeClient.test.ts @@ -418,6 +418,44 @@ describe('CompanionRuntimeClient', () => { expect(client.connected).toBe(false); }); + it('waitForAnyEvent rejects when websocket closes unexpectedly', async () => { + class FakeWebSocket extends EventEmitter { + readyState: number = WebSocket.CONNECTING; + + constructor() { + super(); + queueMicrotask(() => { + this.readyState = WebSocket.OPEN; + this.emit('open'); + }); + } + + send(_payload: string, callback?: (error?: Error) => void): void { + callback?.(); + } + + close(_code?: number, _reason?: string): void { + this.readyState = WebSocket.CLOSED; + this.emit('close'); + } + } + + const client = new CompanionRuntimeClient({ + url: 'ws://127.0.0.1:1', + websocketFactory: () => new FakeWebSocket() as unknown as WebSocket, + }); + await client.connect(); + + const awaited = expect( + client.waitForAnyEvent(['agent.stream', 'agent.typing'], { timeoutMs: 10_000 }), + ).rejects.toThrow('WebSocket closed'); + + const ws = (client as unknown as { ws: WebSocket | null }).ws; + ws?.close(); + await awaited; + expect(client.connected).toBe(false); + }); + it('waitForAgentStream resolves on agent.stream events', async () => { const client = new CompanionRuntimeClient({ url: 'ws://127.0.0.1:1',