import { afterEach, beforeEach, expect, it, vi } from 'vitest'; import { JSON_BODY_LIMIT } from '../../src/lib/server/request-body.js'; import { newFixture, type Fixture } from '../helpers/fixture.js'; import { gameEvent, postTelemetry as post, TEST_TELEMETRY_TOKEN } from '../helpers/telemetry.js'; const events = ['game_start', 'round_change', 'heartbeat', 'game_end'] as const; const identityLimit = 255; let fixture: Fixture; beforeEach(async () => { vi.resetModules(); vi.stubEnv('EC_TELEMETRY_TOKEN', TEST_TELEMETRY_TOKEN); fixture = await newFixture(); const { seedServerRegistry } = await import('../../src/lib/server/fleet/servers.js'); await seedServerRegistry(); }); afterEach(async () => { vi.restoreAllMocks(); vi.unstubAllEnvs(); await fixture.cleanup(); }); async function storedEvidence() { const { db } = await import('../../src/lib/server/db/index.js'); return { telemetry: db().prepare('SELECT * FROM server_telemetry').all(), players: db().prepare('SELECT * FROM leaderboard_players').all(), matches: db().prepare('SELECT * FROM leaderboard_matches').all(), participation: db().prepare('SELECT * FROM leaderboard_match_players').all(), beta: db().prepare('SELECT * FROM beta_test_evidence').all(), lifecycle: db() .prepare( 'SELECT active_since, inactive_since, game_started_at, game_ended_at FROM servers' ) .all() }; } function streamedRequest(chunks: Uint8Array[], headers: Record = {}) { let index = 0; const pull = vi.fn((controller: ReadableStreamDefaultController) => { if (index === chunks.length) controller.close(); else controller.enqueue(chunks[index++]); }); const cancel = vi.fn(); const request = new Request('http://localhost/api/telemetry', { method: 'POST', headers: { 'Content-Type': 'application/json', ...headers }, body: new ReadableStream({ pull, cancel }, { highWaterMark: 0 }), duplex: 'half' } as RequestInit); return { request, pull, cancel }; } it.each(events)('preserves exact-limit player identities for %s', async (event) => { const payload = gameEvent(event, 17, 1); // Lengths use UTF-16 code units, while the request cap counts UTF-8 bytes. const name = ` ${'🍒'.repeat(126)}é `; const guid = `000${'9'.repeat(identityLimit - 3)}`; expect(name.length).toBe(identityLimit); payload.players[0] = { ...payload.players[0], name, guid }; const response = await post(payload); expect(response.status).toBe(200); expect((await response.json()).telemetry.players).toEqual(payload.players); const { listTelemetry } = await import('../../src/lib/server/fleet/telemetry.js'); expect(listTelemetry()[0].players).toEqual(payload.players); const { db } = await import('../../src/lib/server/db/index.js'); expect(db().prepare('SELECT name, guid FROM leaderboard_players').all()).toEqual([ { name, guid } ]); }); it('preserves an opaque Unicode GUID at the length limit', async () => { const payload = gameEvent('game_start', 0, 1); payload.players[0].guid = ` ${'識'.repeat(identityLimit - 2)} `; const response = await post(payload); expect(response.status).toBe(200); expect((await response.json()).telemetry.players).toEqual(payload.players); }); it.each(events)('rejects oversized %s identities without changing game evidence', async (event) => { expect((await post(gameEvent('heartbeat', 5))).status).toBe(200); const before = await storedEvidence(); const { subscribe } = await import('../../src/lib/server/pipeline/bus.js'); const listener = vi.fn(); const unsubscribe = subscribe(listener); try { for (const field of ['name', 'guid'] as const) { for (const length of [identityLimit + 1, 20_000]) { const payload = gameEvent(event, 17, 1); payload.players[0] = { ...payload.players[0], [field]: 'x'.repeat(length) }; const response = await post(payload); expect(response.status).toBe(400); const result = await response.json(); expect(result).toMatchObject({ ok: false }); expect(result.error).toContain(`players[0].${field}`); expect(result.error).toContain(String(identityLimit)); } } expect(await storedEvidence()).toEqual(before); expect(listener).not.toHaveBeenCalled(); } finally { unsubscribe(); } }); it('accepts a telemetry body at the exact 1 MiB UTF-8 byte limit', async () => { const payload = gameEvent('heartbeat', 17, 1); const json = JSON.stringify({ ...payload, diagnostic: 'é🍒' }); const bytes = Buffer.from(json + ' '.repeat(JSON_BODY_LIMIT - Buffer.byteLength(json))); expect(bytes.byteLength).toBe(1024 * 1024); const { request } = streamedRequest([bytes.subarray(0, 100), bytes.subarray(100)], { Authorization: `Bearer ${TEST_TELEMETRY_TOKEN}`, 'Content-Length': String(bytes.byteLength) }); const { POST } = await import('../../src/routes/api/telemetry/+server.js'); const response = await POST({ request }); expect(response.status).toBe(200); expect((await response.json()).telemetry).toMatchObject(payload); }); it.each(['declared', 'absent', 'understated'] as const)( 'rejects body overflow with %s Content-Length before storing or broadcasting it', async (length) => { expect((await post(gameEvent('heartbeat', 5))).status).toBe(200); const before = await storedEvidence(); const { db } = await import('../../src/lib/server/db/index.js'); const diagnostics = db().prepare('SELECT * FROM telemetry_requests').all(); const payload = JSON.stringify(gameEvent('game_end', 99)); const bytes = Buffer.from( payload + ' '.repeat(JSON_BODY_LIMIT - Buffer.byteLength(payload)) ); const headers: Record = { Authorization: `Bearer ${TEST_TELEMETRY_TOKEN}` }; if (length !== 'absent') headers['Content-Length'] = length === 'declared' ? String(JSON_BODY_LIMIT + 1) : '1'; const { request, pull, cancel } = streamedRequest( [bytes, Uint8Array.of(32), Buffer.from('unread')], headers ); const { POST } = await import('../../src/routes/api/telemetry/+server.js'); const { subscribe } = await import('../../src/lib/server/pipeline/bus.js'); const listener = vi.fn(); const unsubscribe = subscribe(listener); try { await expect(POST({ request })).rejects.toMatchObject({ status: 413 }); expect(pull).toHaveBeenCalledTimes(length === 'declared' ? 0 : 2); expect(cancel).toHaveBeenCalledOnce(); expect(await storedEvidence()).toEqual(before); expect(db().prepare('SELECT * FROM telemetry_requests').all()).toEqual(diagnostics); expect(listener).not.toHaveBeenCalled(); } finally { unsubscribe(); } } ); it.each([401, 503])('preserves authentication rejection %s before body limits', async (status) => { if (status === 503) vi.stubEnv('EC_TELEMETRY_TOKEN', undefined); const before = await storedEvidence(); const { request, pull } = streamedRequest([Buffer.alloc(JSON_BODY_LIMIT + 1)], { 'Content-Length': String(JSON_BODY_LIMIT + 1) }); const { POST } = await import('../../src/routes/api/telemetry/+server.js'); const { subscribe } = await import('../../src/lib/server/pipeline/bus.js'); const listener = vi.fn(); const unsubscribe = subscribe(listener); try { expect((await POST({ request })).status).toBe(status); expect(pull).not.toHaveBeenCalled(); expect(request.bodyUsed).toBe(false); expect(await storedEvidence()).toEqual(before); const { db } = await import('../../src/lib/server/db/index.js'); expect(db().prepare('SELECT * FROM telemetry_requests').all()).toEqual([]); expect(listener).not.toHaveBeenCalled(); } finally { unsubscribe(); } });