Add player timing and profile links to server scoreboard

This commit is contained in:
2026-09-20 22:03:58 -04:00
parent 3a63e6d3ce
commit 1e6ff3abbf
7 changed files with 79 additions and 13 deletions
+31 -4
View File
@@ -13,6 +13,33 @@ afterEach(async () => fixture.cleanup());
const payload = gameEvent('round_change', 17);
it('persists player timing across matches and resets after absence or a telemetry gap', async () => {
const { saveTelemetry, listTelemetry } = await import('../../src/lib/server/fleet/telemetry.js');
const { closeDb } = await import('../../src/lib/server/db/index.js');
const guid = roster(1)[0].guid;
const at = (seconds: number) => new Date(Date.parse('2026-09-20T12:00:00Z') + seconds * 1000).toISOString();
const save = (event: Parameters<typeof gameEvent>[0], seconds: number, round = 1, players = 1) =>
saveTelemetry({ ...gameEvent(event, round, players), receivedAt: at(seconds) });
save('game_start', 0);
save('heartbeat', 60, 5);
const end = save('game_end', 90, 6);
expect(end.timing?.players[guid]).toEqual({ serverJoinedAt: at(0), matchJoinedAt: at(0), roundStarted: 1 });
const next = save('game_start', 100);
expect(next.timing?.players[guid]).toEqual({ serverJoinedAt: at(0), matchJoinedAt: at(100), roundStarted: 1 });
closeDb();
expect(listTelemetry()[0].timing).toEqual(next.timing);
save('heartbeat', 120, 2, 0);
expect(save('heartbeat', 140, 3).timing?.players[guid]).toEqual({
serverJoinedAt: at(140), matchJoinedAt: at(140), roundStarted: 3
});
expect(save('heartbeat', 400, 8).timing?.players[guid]).toEqual({
serverJoinedAt: at(400), matchJoinedAt: at(400), roundStarted: 8
});
expect(save('heartbeat', 420, 1).timing?.players[guid]).toEqual({
serverJoinedAt: at(400), matchJoinedAt: at(420), roundStarted: 1
});
});
it('timestamps initial telemetry and subsequent status and telemetry events with the server clock', async () => {
const { GET } = await import('../../src/routes/api/events/servers/+server.js');
const { emit } = await import('../../src/lib/server/pipeline/bus.js');
@@ -43,14 +70,14 @@ it.each(['round_change', 'heartbeat'] as const)('persists %s stats, broadcasts u
const { subscribe } = await import('../../src/lib/server/pipeline/bus.js');
const listener = vi.fn();
const unsubscribe = subscribe(listener);
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) }] });
expect(await (await post(payload)).json()).toEqual({ ok: true, telemetry: { ...payload, receivedAt: expect.any(String), timing: expect.any(Object) } });
expect(listener).toHaveBeenCalledWith({ type: 'server-telemetry', entries: [{ ...payload, receivedAt: expect.any(String), timing: expect.any(Object) }] });
unsubscribe();
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([{ ...payload, round: 1, receivedAt: expect.any(String) }]);
expect(listTelemetry()).toEqual([{ ...payload, round: 1, receivedAt: expect.any(String), timing: expect.any(Object) }]);
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');
@@ -68,7 +95,7 @@ it('accepts all four events without an active beta and updates match lifecycle',
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(listTelemetry()).toEqual([{ ...start, receivedAt: expect.any(String), timing: expect.any(Object) }]);
expect(getServer(start.server)?.game_started_at).toBeTruthy();
expect(getServer(start.server)?.game_ended_at).toBeNull();
expect((await post(payload)).status).toBe(200);