Fix active and match timer drift between status polls
Persist occupancy transitions with telemetry and carry timer anchors through live updates. Cover delayed polling, lobby rejoining, and lifecycle boundaries with regression tests. Fixes #47
This commit is contained in:
@@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { get } from 'svelte/store';
|
||||
import { freshServer, matchesFleet, type FleetServer } from '../../src/lib/fleet-overview.js';
|
||||
import type { ServerStatus } from '../../src/lib/ui/stores/servers.js';
|
||||
import type { GameEvent, ServerTiming } from '../../src/lib/telemetry.js';
|
||||
import { gameEvent } from '../helpers/telemetry.js';
|
||||
|
||||
vi.mock('$app/environment', () => ({ browser: true }));
|
||||
@@ -66,6 +67,46 @@ async function setup() {
|
||||
}
|
||||
|
||||
describe('Fleet report clock', () => {
|
||||
it('keeps persisted occupancy and match anchors through live reports before the next status poll', async () => {
|
||||
const at = (seconds: number) => new Date(epoch + seconds * 1000).toISOString();
|
||||
snapshot.servers = [server(epoch, { container_state: 'running', container_checked_at: at(0) })];
|
||||
const { fleetServers } = await setup();
|
||||
const report = (event: GameEvent['event'], seconds: number, players: number, timing: ServerTiming) => {
|
||||
source.dispatchEvent(new MessageEvent('server-telemetry', { data: JSON.stringify({ serverNow: epoch + seconds * 1000,
|
||||
entries: [{ ...gameEvent(event, 2, players), receivedAt: at(seconds),
|
||||
timing: { matchId: 1, players: {}, server: timing } }] }) }));
|
||||
return get(fleetServers)[0];
|
||||
};
|
||||
const active = { activeSince: at(1), inactiveSince: null, gameStartedAt: at(1), gameEndedAt: null };
|
||||
report('game_start', 1, 1, active);
|
||||
expect(report('heartbeat', 20, 1, active)).toMatchObject({
|
||||
active_since: at(1), inactive_since: null, game_started_at: at(1), game_ended_at: null
|
||||
});
|
||||
const ended = { ...active, gameEndedAt: at(30) };
|
||||
report('game_end', 30, 1, ended);
|
||||
expect(report('heartbeat', 31, 1, ended).game_ended_at).toBe(at(30));
|
||||
const nextMatch = { ...active, gameStartedAt: at(32) };
|
||||
report('game_start', 32, 1, nextMatch);
|
||||
expect(report('heartbeat', 33, 1, nextMatch)).toMatchObject({
|
||||
active_since: at(1), game_started_at: at(32), game_ended_at: null
|
||||
});
|
||||
expect(report('heartbeat', 34, 0, { activeSince: null, inactiveSince: at(34), gameStartedAt: null, gameEndedAt: null }))
|
||||
.toMatchObject({ active_since: null, inactive_since: at(34), game_started_at: null, game_ended_at: null });
|
||||
expect(report('heartbeat', 35, 1, { activeSince: at(35), inactiveSince: null, gameStartedAt: null, gameEndedAt: null }))
|
||||
.toMatchObject({ active_since: at(35), inactive_since: null, game_started_at: null, game_ended_at: null });
|
||||
});
|
||||
|
||||
it('restores timers from a telemetry snapshot even when the browser missed game_start', async () => {
|
||||
const startedAt = new Date(epoch - 19_000).toISOString();
|
||||
snapshot.servers = [server(epoch, { container_state: 'running', container_checked_at: new Date(epoch).toISOString() })];
|
||||
const { fleetServers } = await setup();
|
||||
source.dispatchEvent(new MessageEvent('server-telemetry', { data: JSON.stringify({ serverNow: epoch, snapshot: true,
|
||||
entries: [{ ...gameEvent('heartbeat', 2, 1), receivedAt: new Date(epoch).toISOString(),
|
||||
timing: { matchId: 1, players: {}, server: { activeSince: startedAt, inactiveSince: null,
|
||||
gameStartedAt: startedAt, gameEndedAt: null } } }] }) }));
|
||||
expect(get(fleetServers)[0]).toMatchObject({ active_since: startedAt, game_started_at: startedAt });
|
||||
});
|
||||
|
||||
it('updates count and roster immediately from telemetry and expires them together', async () => {
|
||||
snapshot.servers = [server(epoch, { container_state: 'running', container_checked_at: new Date(epoch).toISOString(), last_players: 4 })];
|
||||
const { fleetServers, uiNow } = await setup();
|
||||
|
||||
Reference in New Issue
Block a user