Files
Flight-Ops/app/api/events/route.test.ts
T
2026-09-26 14:40:56 +00:00

57 lines
2.5 KiB
TypeScript

import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { createSession } from '@/lib/auth';
import { getGameDb } from '@/lib/db/client';
import { GET } from './route';
let dir: string;
let sessionId: string;
beforeAll(async () => {
// Same pattern as app/api/state/route.test.ts: point the DB singleton at a throwaway file so
// this test never touches ./db/flight.db. getGameDb() reads the env var on first call, and
// vitest gives each test file its own module registry (fresh singleton).
dir = await fs.mkdtemp(path.join(os.tmpdir(), 'flight-events-'));
process.env.FLIGHT_DB_PATH = path.join(dir, 'events.db');
// v1.1.0 (ISS-107): /api/events is session-guarded — mint a session for the seeded admin user.
sessionId = await createSession(await getGameDb(), 'admin-kamilkrzywda');
});
afterAll(async () => {
delete process.env.FLIGHT_DB_PATH;
await fs.rm(dir, { recursive: true, force: true });
});
describe('GET /api/events', () => {
// v1.1.0 (ISS-107): EventSource cannot set headers, so the client passes the session as a query
// param — this test exercises exactly that path (requireAuthSse).
const req = () => new Request(`http://localhost/api/events?session=${sessionId}`);
it('rejects an unauthenticated request with 401', async () => {
const res = await GET(new Request('http://localhost/api/events'));
expect(res.status).toBe(401);
expect(((await res.json()) as { error: string }).error).toMatch(/authentication required/i);
});
it('streams SSE with the current snapshot as the first event', async () => {
const res = await GET(req());
expect(res.status).toBe(200);
expect(res.headers.get('content-type')).toContain('text/event-stream');
expect(res.headers.get('cache-control')).toBe('no-cache');
expect(res.headers.get('x-accel-buffering')).toBe('no');
const reader = (res.body as ReadableStream<Uint8Array>).getReader();
try {
const { value } = await reader.read(); // first chunk: the initial snapshot event
const text = new TextDecoder().decode(value);
expect(text.startsWith('data: ')).toBe(true);
const snap = JSON.parse(text.slice('data: '.length)) as Record<string, unknown>;
expect(typeof (snap.simState as { clockMs: number }).clockMs).toBe('number');
} finally {
await reader.cancel(); // → stream cancel → unsubscribe from the registry
}
});
});