Simplify telemetry to three game events
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
|
||||
import { newFixture, type Fixture } from '../helpers/fixture.js';
|
||||
import { gameEvent, postTelemetry as post, roster } from '../helpers/telemetry.js';
|
||||
|
||||
let fixture: Fixture;
|
||||
beforeEach(async () => {
|
||||
@@ -10,42 +11,60 @@ beforeEach(async () => {
|
||||
});
|
||||
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)
|
||||
}) });
|
||||
}
|
||||
const payload = gameEvent('round_change', 17);
|
||||
|
||||
it('persists reports, broadcasts updates, and leaves RCON safety data untouched', async () => {
|
||||
it('persists full player stats, 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' }));
|
||||
expect(await (await post(payload)).json()).toEqual({ ok: true, telemetry: { ...payload, receivedAt: expect.any(String) } });
|
||||
expect(listener).toHaveBeenCalledWith({ type: 'server-telemetry', entries: [{ ...payload, receivedAt: expect.any(String) }] });
|
||||
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 })]);
|
||||
expect(listTelemetry()).toEqual([{ ...payload, round: 1, receivedAt: expect.any(String) }]);
|
||||
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');
|
||||
const reader = (await GET()).body!.getReader();
|
||||
await reader.read();
|
||||
const snapshot = new TextDecoder().decode((await reader.read()).value);
|
||||
expect(snapshot).toContain('"round":1');
|
||||
expect(snapshot).toContain('"headshots":50');
|
||||
expect(snapshot).toContain('"guid":"12345678901234560"');
|
||||
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) => {
|
||||
it('accepts all three events without an active beta and updates match lifecycle', async () => {
|
||||
const { getServer } = await import('../../src/lib/server/fleet/servers.js');
|
||||
const { listTelemetry } = await import('../../src/lib/server/fleet/telemetry.js');
|
||||
const start = gameEvent('game_start');
|
||||
expect((await post(start)).status).toBe(200);
|
||||
expect(listTelemetry()).toEqual([{ ...start, receivedAt: expect.any(String) }]);
|
||||
expect(getServer(start.server)?.game_started_at).toBeTruthy();
|
||||
expect(getServer(start.server)?.game_ended_at).toBeNull();
|
||||
expect((await post(payload)).status).toBe(200);
|
||||
const end = gameEvent('game_end', 18);
|
||||
expect((await post(end)).status).toBe(200);
|
||||
expect(getServer(start.server)?.game_ended_at).toBeTruthy();
|
||||
expect(listTelemetry()[0]).toMatchObject(end);
|
||||
await post(start);
|
||||
expect(listTelemetry()[0]).not.toHaveProperty('round');
|
||||
expect(getServer(start.server)?.game_ended_at).toBeNull();
|
||||
expect((await post(gameEvent('game_start', 0, 0))).status).toBe(200);
|
||||
expect((await post(gameEvent('game_end', 0, 0))).status).toBe(200);
|
||||
});
|
||||
|
||||
it.each([null, [], {}, ...['boot', 'session_start', 'match_end', 'feature', 'other'].map(event => ({ ...payload, event })),
|
||||
{ ...payload, map: '' }, { ...payload, map: undefined }, { ...payload, round: -1 },
|
||||
{ ...payload, round: 1.5 }, { ...payload, round: '17' }, { ...payload, players: 4 }, { ...payload, players: null },
|
||||
{ ...payload, players: [null] }, { ...payload, players: [{ name: 'Josh', guid: '123' }] },
|
||||
...['name', 'guid', 'kills', 'headshots', 'downs', 'revives', 'score'].map(field => ({ ...payload, players: [{ ...roster(1)[0], [field]: null }] })),
|
||||
{ ...payload, players: [{ ...roster(1)[0], guid: 12345678901234567 }] },
|
||||
{ ...payload, players: [{ ...roster(1)[0], score: -1 }] }
|
||||
])('rejects invalid telemetry: %j', async (body) => {
|
||||
expect((await post(body)).status).toBe(400);
|
||||
});
|
||||
|
||||
@@ -63,3 +82,15 @@ it('rejects malformed JSON', async () => {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{'
|
||||
}) })).status).toBe(400);
|
||||
});
|
||||
|
||||
it('migrates old round-only storage without fabricating player identities', async () => {
|
||||
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
|
||||
db().exec('DROP TABLE server_telemetry; CREATE TABLE server_telemetry (server TEXT PRIMARY KEY, round INTEGER NOT NULL, players INTEGER NOT NULL, received_at TEXT NOT NULL)');
|
||||
db().prepare('INSERT INTO server_telemetry VALUES (?, ?, ?, ?)').run(payload.server, 99, 4, new Date().toISOString());
|
||||
closeDb();
|
||||
const { listTelemetry } = await import('../../src/lib/server/fleet/telemetry.js');
|
||||
expect(listTelemetry()).toEqual([]);
|
||||
await post(payload);
|
||||
closeDb();
|
||||
expect(listTelemetry()[0]).toMatchObject(payload);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user