Files
Flight-Ops/lib/sse.test.ts
T
2026-09-26 18:37:11 +00:00

66 lines
2.7 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { broadcast, subscribedPlayerIds, subscribe } from './sse';
// ISS-129 (v0.57.0): the fan-out registry behind /api/events — no DB or server needed. Subscribers
// leak across tests in this file (module-level state), which is fine: each test asserts on its own
// arrays and uses distinct player ids where isolation matters. v1.4.0 (ISS-112): keyed by PLAYER ID
// — a frame for one player must never reach another player's streams.
describe('lib/sse', () => {
it('delivers broadcasts to subscribers of the SAME player only, and stops after unsubscribe', () => {
const a: string[] = [];
const b: string[] = []; // same player as a — both get its frames
const c: string[] = []; // different player — never sees their frames
const unsubA = subscribe('p-a', (t) => a.push(t));
const unsubB = subscribe('p-a', (t) => b.push(t));
const unsubC = subscribe('p-c', (t) => c.push(t));
broadcast(new Map([['p-a', 'x']]));
expect(a).toEqual(['x']);
expect(b).toEqual(['x']); // same group
expect(c).toEqual([]); // other player's frame never crosses over
unsubA();
broadcast(new Map([['p-a', 'y'], ['p-c', 'z']]));
expect(a).toEqual(['x']); // unsubscribed — no more events
expect(b).toEqual(['x', 'y']);
expect(c).toEqual(['z']);
unsubB();
unsubC();
});
it('subscribedPlayerIds lists exactly the players with live streams, dropping empty groups', () => {
const got: string[] = [];
const unsub1 = subscribe('p-ids-1', (t) => got.push(t));
const unsub2 = subscribe('p-ids-1', (t) => got.push(t)); // second member of the same group
const unsub3 = subscribe('p-ids-2', (t) => got.push(t));
expect(subscribedPlayerIds().sort()).toEqual(['p-ids-1', 'p-ids-2']);
unsub1(); // p-ids-1 still has one member — id stays listed
expect(subscribedPlayerIds().sort()).toEqual(['p-ids-1', 'p-ids-2']);
unsub3(); // p-ids-2's last stream goes → its projections stop
expect(subscribedPlayerIds()).toEqual(['p-ids-1']);
unsub2();
});
it('a throwing subscriber does not break the others in its group', () => {
const got: string[] = [];
subscribe('p-boom', () => {
throw new Error('boom');
});
const unsub = subscribe('p-boom', (t) => got.push(t));
broadcast(new Map([['p-boom', 'z']]));
expect(got).toEqual(['z']);
unsub();
});
it('unsubscribe is idempotent and broadcasting to an unknown player is a no-op', () => {
const got: string[] = [];
const unsub = subscribe('p-idem', (t) => got.push(t));
unsub();
unsub(); // second call must not throw
broadcast(new Map([['nobody-subscribed-here', 'w']])); // no group → dropped, no throw
expect(got).toEqual([]);
});
});