import { afterEach, beforeEach, expect, it, vi } from 'vitest'; import { newFixture, type Fixture } from '../helpers/fixture.js'; let fixture: Fixture; beforeEach(async () => { vi.resetModules(); fixture = await newFixture(); const { seedServerRegistry } = await import('../../src/lib/server/fleet/servers.js'); await seedServerRegistry(); }); afterEach(async () => fixture.cleanup()); const payload = { event: 'round_change', server: 't5_zombie_theater_1', round: 17, players: 4 }; async function post(body: unknown, contentType = 'application/json') { const { POST } = await import('../../src/routes/api/telemetry/+server.js'); return POST({ request: new Request('http://localhost/api/telemetry', { method: 'POST', headers: { 'Content-Type': contentType }, body: JSON.stringify(body) }) }); } it('persists reports, broadcasts updates, and leaves RCON safety data untouched', async () => { const { subscribe } = await import('../../src/lib/server/pipeline/bus.js'); const listener = vi.fn(); const unsubscribe = subscribe(listener); expect((await post(payload)).status).toBe(200); expect(listener).toHaveBeenCalledWith(expect.objectContaining({ type: 'server-telemetry' })); unsubscribe(); // A new match can legitimately report a lower round; retries overwrite one row. await post({ ...payload, round: 1 }); await post({ ...payload, round: 1 }); const { closeDb } = await import('../../src/lib/server/db/index.js'); closeDb(); const { listTelemetry } = await import('../../src/lib/server/fleet/telemetry.js'); expect(listTelemetry()).toEqual([expect.objectContaining({ round: 1, players: 4 })]); const { getServer } = await import('../../src/lib/server/fleet/servers.js'); expect(getServer(payload.server)?.last_players).toBeNull(); const { GET } = await import('../../src/routes/api/events/servers/+server.js'); const response = await GET(); const reader = response.body!.getReader(); await reader.read(); // greeting const snapshot = await reader.read(); expect(new TextDecoder().decode(snapshot.value)).toContain('"round":1'); await reader.cancel(); }); it.each([null, [], {}, { ...payload, event: 'other' }, { ...payload, round: -1 }, { ...payload, round: 1.5 }, { ...payload, round: '17' }, { ...payload, players: -1 }, { ...payload, players: null }])('rejects invalid telemetry: %j', async (body) => { expect((await post(body)).status).toBe(400); }); it('rejects unknown and destroyed servers and non-JSON requests', async () => { expect((await post({ ...payload, server: 'unknown' })).status).toBe(404); expect((await post(payload, 'text/plain')).status).toBe(415); const { setServerField } = await import('../../src/lib/server/fleet/servers.js'); setServerField(payload.server, 'destroyed_at', new Date().toISOString()); expect((await post(payload)).status).toBe(404); }); it('rejects malformed JSON', async () => { const { POST } = await import('../../src/routes/api/telemetry/+server.js'); expect((await POST({ request: new Request('http://localhost/api/telemetry', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{' }) })).status).toBe(400); });