60 lines
2.5 KiB
TypeScript
60 lines
2.5 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
// ISS-129 (v0.57.0): /api/events pushes full state snapshots over SSE — the current snapshot on
|
|
// connect, then one after every sim tick (~1 s; the background tick runs even while idle since
|
|
// v0.56.0, so no scale hook is needed). Reached via the same base URL as playwright.config.ts
|
|
// (E2E_BASE_URL, defaulting to the live instance) — node-side fetch, no browser involved.
|
|
const baseUrl = process.env.E2E_BASE_URL ?? 'https://flight.dev.kkhost.pl';
|
|
|
|
interface StateBody {
|
|
simState: { clockMs: number };
|
|
}
|
|
|
|
test('GET /api/events streams state snapshots over SSE', async () => {
|
|
const res = await fetch(`${baseUrl}/api/events`, { headers: { Accept: 'text/event-stream' } });
|
|
expect(res.status).toBe(200);
|
|
expect(res.headers.get('content-type')).toContain('text/event-stream');
|
|
expect(res.body).not.toBeNull();
|
|
|
|
const reader = res.body!.getReader();
|
|
const decoder = new TextDecoder();
|
|
let buffer = '';
|
|
const clocks: number[] = []; // simState.clockMs of each parsed snapshot event
|
|
|
|
try {
|
|
// Collect two snapshot events: the initial one, then at least one pushed by a background tick.
|
|
const deadline = Date.now() + 15_000;
|
|
while (clocks.length < 2 && Date.now() < deadline) {
|
|
let chunk: ReadableStreamReadResult<Uint8Array>;
|
|
try {
|
|
chunk = await Promise.race([
|
|
reader.read(),
|
|
new Promise<never>((_, reject) =>
|
|
setTimeout(() => reject(new Error('SSE stream stalled')), Math.max(1, deadline - Date.now())),
|
|
),
|
|
]);
|
|
} catch {
|
|
break; // stalled or past the deadline — let the length assertion report it
|
|
}
|
|
if (chunk.done) break; // stream ended early — same
|
|
buffer += decoder.decode(chunk.value, { stream: true });
|
|
let sep: number;
|
|
while ((sep = buffer.indexOf('\n\n')) >= 0) {
|
|
const frame = buffer.slice(0, sep);
|
|
buffer = buffer.slice(sep + 2);
|
|
for (const line of frame.split('\n')) {
|
|
if (!line.startsWith('data: ')) continue; // heartbeats are comment lines (" : ping")
|
|
const snap = JSON.parse(line.slice('data: '.length)) as StateBody;
|
|
clocks.push(snap.simState.clockMs);
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
await reader.cancel(); // close the stream → server unsubscribes
|
|
}
|
|
|
|
// The initial snapshot arrives on connect; the second comes from a background tick within ~1 s.
|
|
expect(clocks.length).toBeGreaterThanOrEqual(2);
|
|
expect(clocks[1]).toBeGreaterThan(clocks[0]); // clock advanced between pushes — real updates, not replays
|
|
});
|