eff3c615d5
MessageProvider/useThread/useMessages over the kernel-client socket (live append, typing, dedupe). Reconnect test (re-opens active thread). Rewrote import-boundary as an allowlist so SDK<->service sibling imports are forbidden both ways. 28 tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { MessageSocket } from './message-socket';
|
|
import type { SocketLike } from './types';
|
|
|
|
class FakeSocket implements SocketLike {
|
|
handlers = new Map<string, Array<(...a: unknown[]) => 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<unknown> {
|
|
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' });
|
|
});
|
|
});
|