5a590c7fda
MessageSocket (open/send/read/typing via emitWithAck, reconnect re-opens thread, no socket.io types leak) + RestClient (ingest/getThreadMessages/sendMessage). tsup ESM+dts. Facade unit test with injectable fake socket (3 tests). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
70 lines
2.4 KiB
TypeScript
70 lines
2.4 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: 't1', 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(event: string, handler: (...a: unknown[]) => void): unknown {
|
|
this.handlers.set(event, (this.handlers.get(event) ?? []).filter((h) => h !== handler));
|
|
return this;
|
|
}
|
|
emit(_event: string, ..._args: unknown[]): 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));
|
|
}
|
|
listenerCount(event: string): number {
|
|
return (this.handlers.get(event) ?? []).length;
|
|
}
|
|
}
|
|
|
|
const cfg = { serviceUrl: 'http://localhost:3200', token: 'tok' };
|
|
|
|
describe('MessageSocket facade', () => {
|
|
it('on() registers a handler and the returned fn unsubscribes', () => {
|
|
const fake = new FakeSocket();
|
|
const ms = new MessageSocket(cfg, fake);
|
|
const off = ms.on('message', () => {});
|
|
expect(fake.listenerCount('message')).toBe(1);
|
|
off();
|
|
expect(fake.listenerCount('message')).toBe(0);
|
|
});
|
|
|
|
it('sendMessage invokes emitWithAck("send_message", payload)', async () => {
|
|
const fake = new FakeSocket();
|
|
fake.ackReturn = { id: 'm1', threadId: 't1', content: 'hello' };
|
|
const ms = new MessageSocket(cfg, fake);
|
|
await ms.sendMessage('t1', 'hello', { contentRef: 'sas://x' });
|
|
const call = fake.acks.find((a) => a.event === 'send_message');
|
|
expect(call?.payload).toMatchObject({ threadId: 't1', content: 'hello', contentRef: 'sas://x' });
|
|
});
|
|
|
|
it('openThread returns the ack and tracks the thread', async () => {
|
|
const fake = new FakeSocket();
|
|
fake.ackReturn = { threadId: 't9', status: 'OPEN', history: [] };
|
|
const ms = new MessageSocket(cfg, fake);
|
|
const r = await ms.openThread();
|
|
expect(r.threadId).toBe('t9');
|
|
});
|
|
});
|