66 lines
2.7 KiB
TypeScript
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([]);
|
|
});
|
|
});
|