Add heartbeat telemetry support

This commit is contained in:
2026-09-19 21:52:05 -04:00
parent 8ac420fe61
commit ce0321a6e9
14 changed files with 63 additions and 21 deletions
+18 -2
View File
@@ -38,7 +38,8 @@ it('timestamps initial telemetry and subsequent status and telemetry events with
}
});
it('persists full player stats, broadcasts updates, and leaves RCON safety data untouched', async () => {
it.each(['round_change', 'heartbeat'] as const)('persists %s stats, broadcasts updates, and leaves RCON safety data untouched', async (event) => {
const payload = gameEvent(event, 17);
const { subscribe } = await import('../../src/lib/server/pipeline/bus.js');
const listener = vi.fn();
const unsubscribe = subscribe(listener);
@@ -62,7 +63,7 @@ it('persists full player stats, broadcasts updates, and leaves RCON safety data
await reader.cancel();
});
it('accepts all three events without an active beta and updates match lifecycle', async () => {
it('accepts all four 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');
@@ -71,6 +72,12 @@ it('accepts all three events without an active beta and updates match lifecycle'
expect(getServer(start.server)?.game_started_at).toBeTruthy();
expect(getServer(start.server)?.game_ended_at).toBeNull();
expect((await post(payload)).status).toBe(200);
const startedAt = getServer(start.server)?.game_started_at;
const heartbeat = gameEvent('heartbeat', 17);
expect((await post(heartbeat)).status).toBe(200);
expect(listTelemetry()[0]).toMatchObject(heartbeat);
expect(getServer(start.server)?.game_started_at).toBe(startedAt);
expect(getServer(start.server)?.game_ended_at).toBeNull();
const end = gameEvent('game_end', 18);
expect((await post(end)).status).toBe(200);
expect(getServer(start.server)?.game_ended_at).toBeTruthy();
@@ -93,6 +100,15 @@ it.each([null, [], {}, ...['boot', 'session_start', 'match_end', 'feature', 'oth
expect((await post(body)).status).toBe(400);
});
it.each([
{ ...gameEvent('heartbeat'), round: undefined },
{ ...gameEvent('heartbeat'), round: -1 },
{ ...gameEvent('heartbeat'), players: [{ name: 'Player', guid: '123' }] },
{ ...gameEvent('heartbeat'), players: [{ ...roster(1)[0], kills: -1 }] }
])('requires round and full stats for heartbeats: %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);