Files
RedditBot/tests/dashboard/realtime-client.test.ts
T

114 lines
3.6 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import {
createDashboardRealtimeClient,
queryKeysForDashboardEvent,
type DashboardEventSource,
} from '../../apps/dashboard/src/features/operations/realtime-client.js';
class FakeEventSource implements DashboardEventSource {
readonly listeners = new Map<string, Array<(event: unknown) => void>>();
readonly close = vi.fn();
addEventListener(type: string, listener: (event: unknown) => void): void {
const listeners = this.listeners.get(type) ?? [];
listeners.push(listener);
this.listeners.set(type, listeners);
}
emit(type: string, event: unknown = {}): void {
for (const listener of this.listeners.get(type) ?? []) listener(event);
}
}
describe('dashboard realtime client', () => {
it('tracks connect/reconnect state and parses typed events before delivery', () => {
const source = new FakeEventSource();
const states: string[] = [];
const events = vi.fn();
const invalid = vi.fn();
const client = createDashboardRealtimeClient({
sourceFactory: () => source,
onEvent: events,
onStateChange: (state) => states.push(`${state.status}:${state.isStale}`),
onInvalidEvent: invalid,
});
client.start();
source.emit('open');
source.emit('log.created', {
data: JSON.stringify({
id: 'event-1',
occurredAt: '2026-08-08T12:00:00.000Z',
type: 'log.created',
payload: { level: 'info', message: 'Connected' },
}),
});
source.emit('log.created', { data: '{bad json' });
source.emit('error');
expect(states).toEqual(['connecting:true', 'connected:false', 'reconnecting:true']);
expect(events).toHaveBeenCalledOnce();
expect(events).toHaveBeenCalledWith(expect.objectContaining({ id: 'event-1', type: 'log.created' }));
expect(invalid).toHaveBeenCalledOnce();
expect(client.getSnapshot()).toMatchObject({ status: 'reconnecting', isStale: true, lastEventId: 'event-1' });
client.stop();
expect(source.close).toHaveBeenCalledOnce();
expect(client.getSnapshot()).toMatchObject({ status: 'closed', isStale: true });
});
it('requests an authoritative repair on initial connection and every reconnect', () => {
const source = new FakeEventSource();
const connected = vi.fn();
const client = createDashboardRealtimeClient({
sourceFactory: () => source,
onEvent: vi.fn(),
onConnected: connected,
});
client.start();
source.emit('open');
source.emit('error');
source.emit('open');
expect(connected).toHaveBeenNthCalledWith(1, { isReconnect: false });
expect(connected).toHaveBeenNthCalledWith(2, { isReconnect: true });
});
it('maps events to the queries whose authoritative snapshots need refresh', () => {
expect(
queryKeysForDashboardEvent({
id: '1',
occurredAt: '2026-08-08T12:00:00.000Z',
type: 'sync.progress',
payload: { syncId: 'sync-1', kind: 'media', completed: 2, total: 5 },
}),
).toEqual([
['operations', 'sync-status'],
]);
expect(
queryKeysForDashboardEvent({
id: 'completed',
occurredAt: '2026-08-08T12:00:01.000Z',
type: 'sync.completed',
payload: { syncId: 'sync-1', kind: 'conversations', processed: 5 },
}),
).toEqual([
['operations', 'sync-status'],
['conversations'],
['media'],
]);
expect(
queryKeysForDashboardEvent({
id: '2',
occurredAt: '2026-08-08T12:00:00.000Z',
type: 'rate-limit.changed',
payload: { limited: true },
}),
).toEqual([
['dashboard', 'stats'],
['dashboard', 'timers'],
]);
});
});