Support player ping in game telemetry
This commit is contained in:
@@ -56,6 +56,17 @@ describe('shared server observation', () => {
|
||||
expect(fallback).toMatchObject({ status_source: 'rcon', health: 'online', last_players: 4, last_seen_at: at(40_000) });
|
||||
});
|
||||
|
||||
it('displays telemetry pings and clears missing readings on subsequent reports', () => {
|
||||
const telemetry = report(0, 4);
|
||||
telemetry.players = telemetry.players.map((player, i) => ({ ...player, ping: [0, 42, 999, null][i] }));
|
||||
const selected = selectServerObservation(server(), telemetry, now);
|
||||
expect(selected.roster?.map(player => player.ping)).toEqual([0, 42, 999, null]);
|
||||
expect(selectServerObservation(selected, report(1, 4), now + 1).roster?.map(player => player.ping)).toEqual([null, null, null, null]);
|
||||
const start = { ...gameEvent('game_start', 0, 1, 'managed'), event: 'game_start' as const, receivedAt: at(),
|
||||
players: [{ name: 'Player 1', guid: '123', ping: 25 }] };
|
||||
expect(selectServerObservation(server(), start, now).roster?.[0]).toMatchObject({ score: null, ping: 25 });
|
||||
});
|
||||
|
||||
it.each(['starting', 'restarting', 'unhealthy', 'exited'])('telemetry cannot override container state %s', state => {
|
||||
const selected = selectServerObservation(server({ health: state === 'starting' || state === 'restarting' ? 'starting' : 'offline', container_state: state }), report(), now);
|
||||
expect(selected.health).not.toBe('online');
|
||||
|
||||
@@ -494,7 +494,9 @@ describe('database fleet and container readiness', () => {
|
||||
const { subscribe } = await import('../../src/lib/server/pipeline/bus.js');
|
||||
db.prepare("UPDATE servers SET last_players = 4, last_seen_at = ? WHERE name = 'managed'").run(new Date().toISOString());
|
||||
inspect.mockResolvedValue({ ok: true, state: 'running' });
|
||||
await postTelemetry(gameEvent('heartbeat', 15, 3, 'managed'));
|
||||
const telemetry = gameEvent('heartbeat', 15, 3, 'managed');
|
||||
telemetry.players[0].ping = 42;
|
||||
await postTelemetry(telemetry);
|
||||
const events: unknown[] = [];
|
||||
const unsubscribe = subscribe(event => events.push(event));
|
||||
try { await fanoutStatus(); } finally { unsubscribe(); }
|
||||
@@ -503,7 +505,8 @@ describe('database fleet and container readiness', () => {
|
||||
expect(server).toMatchObject({ health: 'online', last_players: 3, status_source: 'telemetry', last_error: null,
|
||||
rcon_status: { last_players: 4, last_error: 'timeout:old process' } });
|
||||
expect(server.roster).toHaveLength(3);
|
||||
expect(server.roster[0]).toMatchObject({ ping: null, score: 5000 });
|
||||
expect(server.roster[0]).toMatchObject({ ping: 42, score: 5000 });
|
||||
expect(server.roster[1].ping).toBeNull();
|
||||
expect(events).toContainEqual(expect.objectContaining({ type: 'server-status', entries: [expect.objectContaining({
|
||||
health: 'online', players: 3, roster: server.roster, lastSeenAt: server.last_seen_at
|
||||
})] }));
|
||||
|
||||
@@ -180,6 +180,33 @@ it('accepts all four events without an active beta and updates match lifecycle',
|
||||
expect((await post(gameEvent('game_end', 0, 0))).status).toBe(200);
|
||||
});
|
||||
|
||||
it.each(['game_start', 'round_change', 'heartbeat', 'game_end'] as const)('preserves %s player pings through ingestion, streaming and reload', async event => {
|
||||
const payload = gameEvent(event, 17);
|
||||
payload.players = payload.players.map((player, i) => ({ ...player, ping: [0, 42, 999, null][i] }));
|
||||
const { subscribe } = await import('../../src/lib/server/pipeline/bus.js');
|
||||
const listener = vi.fn();
|
||||
const unsubscribe = subscribe(listener);
|
||||
try {
|
||||
const response = await post(payload);
|
||||
expect(response.status).toBe(200);
|
||||
expect((await response.json()).telemetry.players).toEqual(payload.players);
|
||||
expect(listener).toHaveBeenCalledWith({ type: 'server-telemetry', entries: [expect.objectContaining({ players: payload.players })] });
|
||||
const { closeDb } = await import('../../src/lib/server/db/index.js');
|
||||
closeDb();
|
||||
const { listTelemetry } = await import('../../src/lib/server/fleet/telemetry.js');
|
||||
const { telemetryReport } = await import('../../src/lib/server/fleet/status-evidence.js');
|
||||
expect(listTelemetry()[0].players).toEqual(payload.players);
|
||||
expect(telemetryReport(payload.server)?.players).toEqual(payload.players);
|
||||
} finally { unsubscribe(); }
|
||||
});
|
||||
|
||||
it.each([-1, 1.5, '42', false, {}, [], Number.MAX_SAFE_INTEGER + 1])('rejects invalid player ping: %j', async ping => {
|
||||
for (const event of ['game_start', 'heartbeat'] as const) {
|
||||
const body = gameEvent(event, 17, 1);
|
||||
expect((await post({ ...body, players: [{ ...body.players[0], ping }] })).status).toBe(400);
|
||||
}
|
||||
});
|
||||
|
||||
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 },
|
||||
|
||||
Reference in New Issue
Block a user