import { describe, it, expect } from 'vitest'; import { MessageSocket } from './message-socket'; import type { SocketLike } from './types'; class FakeSocket implements SocketLike { handlers = new Map void>>(); acks: Array<{ event: string; payload: unknown }> = []; ackReturn: unknown = { threadId: 't5', status: 'OPEN', history: [] }; on(event: string, handler: (...a: unknown[]) => void): unknown { const list = this.handlers.get(event) ?? []; list.push(handler); this.handlers.set(event, list); return this; } off(): unknown { return this; } emit(): unknown { return this; } async emitWithAck(event: string, ...args: unknown[]): Promise { this.acks.push({ event, payload: args[0] }); return this.ackReturn; } connect(): unknown { return this; } disconnect(): unknown { return this; } trigger(event: string, ...args: unknown[]): void { (this.handlers.get(event) ?? []).forEach((h) => h(...args)); } } describe('MessageSocket reconnect resilience', () => { it('re-opens the active thread when the socket reconnects (no lost subscription)', async () => { const fake = new FakeSocket(); const ms = new MessageSocket({ serviceUrl: 'http://localhost:3200', token: 'tok' }, fake); await ms.openThread('t5'); const before = fake.acks.filter((a) => a.event === 'open_thread').length; fake.trigger('connect'); // simulate a reconnect const opens = fake.acks.filter((a) => a.event === 'open_thread'); expect(opens.length).toBe(before + 1); expect(opens.at(-1)?.payload).toMatchObject({ threadId: 't5' }); }); });