Add Prettier and ESLint tooling (Refs #77)

This commit is contained in:
2026-10-09 15:56:13 -04:00
parent 8ed059b8d3
commit 16469e2d43
711 changed files with 99737 additions and 28286 deletions
+206 -52
View File
@@ -7,7 +7,14 @@ let received = 0;
const server = 't5_zombie_theater_1';
const timestamp = (minute: number) => new Date(Date.UTC(2026, 8, 18, 20, minute)).toISOString();
const player = (guid: string, stats: Partial<TelemetryPlayerStats> = {}): TelemetryPlayerStats => ({
guid, name: `Player ${guid}`, kills: 0, headshots: 0, downs: 0, revives: 0, score: 0, ...stats
guid,
name: `Player ${guid}`,
kills: 0,
headshots: 0,
downs: 0,
revives: 0,
score: 0,
...stats
});
beforeEach(async () => {
@@ -15,49 +22,118 @@ beforeEach(async () => {
received = 0;
fixture = await newFixture([]);
const { db } = await import('../../src/lib/server/db/index.js');
db().prepare("INSERT INTO servers (name, game, map) VALUES (?, 't5', 'zombie_theater')")
db()
.prepare("INSERT INTO servers (name, game, map) VALUES (?, 't5', 'zombie_theater')")
.run(server);
});
afterEach(async () => fixture.cleanup());
async function report(event: GameEvent['event'], round = 0, players = [player('101')]) {
const { db } = await import('../../src/lib/server/db/index.js');
const { recordLeaderboardTelemetry } = await import('../../src/lib/server/fleet/leaderboard-ingest.js');
const { recordLeaderboardTelemetry } =
await import('../../src/lib/server/fleet/leaderboard-ingest.js');
const common = { server, map: 'zombie_theater', receivedAt: timestamp(received++) };
recordLeaderboardTelemetry(db(), event === 'game_start'
? { ...common, event, players: players.map(({ guid, name }) => ({ guid, name })) }
: { ...common, event, round, players });
recordLeaderboardTelemetry(
db(),
event === 'game_start'
? { ...common, event, players: players.map(({ guid, name }) => ({ guid, name })) }
: { ...common, event, round, players }
);
}
describe('completed match details', () => {
it('returns the history summary and every participant with recorded counters, including departed and late players', async () => {
await report('game_start', 0, [player('101'), player('202'), player('303')]);
await report('round_change', 2, [player('101', { kills: 30, headshots: 10, downs: 1, revives: 2, score: 2000 }),
player('202', { kills: 15, score: 1500 })]);
await report('heartbeat', 2, [player('101', { kills: 40, headshots: 12, downs: 1, revives: 2, score: 1000 })]);
await report('game_end', 4, [player('101', { kills: 50, headshots: 20, downs: 2, revives: 3, score: 500 }), player('404')]);
const { getMatchDetail, serverMatchHistory } = await import('../../src/lib/server/fleet/match-history.js');
await report('round_change', 2, [
player('101', { kills: 30, headshots: 10, downs: 1, revives: 2, score: 2000 }),
player('202', { kills: 15, score: 1500 })
]);
await report('heartbeat', 2, [
player('101', { kills: 40, headshots: 12, downs: 1, revives: 2, score: 1000 })
]);
await report('game_end', 4, [
player('101', { kills: 50, headshots: 20, downs: 2, revives: 3, score: 500 }),
player('404')
]);
const { getMatchDetail, serverMatchHistory } =
await import('../../src/lib/server/fleet/match-history.js');
const match = getMatchDetail(1)!;
const { game, startObserved, players, chat, ...summary } = match;
expect(chat).toEqual([]);
expect({ ...summary, game }).toEqual(serverMatchHistory(server).matches[0]);
expect({ ...summary, game, startObserved }).toMatchObject({
id: 1, game: 't5', startObserved: true, server, status: 'completed',
startedAt: timestamp(0), endedAt: timestamp(3), durationMs: 180_000,
startPlayers: 3, uniquePlayers: 4, endPlayers: 2, finalRound: 4
id: 1,
game: 't5',
startObserved: true,
server,
status: 'completed',
startedAt: timestamp(0),
endedAt: timestamp(3),
durationMs: 180_000,
startPlayers: 3,
uniquePlayers: 4,
endPlayers: 2,
finalRound: 4
});
expect(players.map((entry) => entry.guid)).toEqual(['101', '202', '404', '303']);
expect(players[0]).toEqual({
guid: '101',
name: 'Player 101',
hasStats: true,
presentAtEnd: true,
canViewProfile: true,
firstSeenAt: timestamp(0),
lastSeenAt: timestamp(3),
highestRound: 4,
roundsSurvived: 3,
kills: 50,
headshots: 20,
downs: 2,
revives: 3,
bestScore: 2000
});
expect(players[1]).toEqual({
guid: '202',
name: 'Player 202',
hasStats: true,
presentAtEnd: false,
canViewProfile: true,
firstSeenAt: timestamp(0),
lastSeenAt: timestamp(1),
highestRound: 2,
roundsSurvived: 1,
kills: 15,
headshots: 0,
downs: 0,
revives: 0,
bestScore: 1500
});
expect(players[2]).toMatchObject({
guid: '404',
presentAtEnd: true,
highestRound: 4,
roundsSurvived: 0,
firstSeenAt: timestamp(3),
lastSeenAt: timestamp(3),
kills: 0,
bestScore: 0
});
expect(players[3]).toEqual({
guid: '303',
name: 'Player 303',
hasStats: false,
presentAtEnd: false,
canViewProfile: false,
firstSeenAt: timestamp(0),
lastSeenAt: timestamp(0),
highestRound: null,
roundsSurvived: null,
kills: null,
headshots: null,
downs: null,
revives: null,
bestScore: null
});
expect(players.map(entry => entry.guid)).toEqual(['101', '202', '404', '303']);
expect(players[0]).toEqual({ guid: '101', name: 'Player 101', hasStats: true, presentAtEnd: true,
canViewProfile: true, firstSeenAt: timestamp(0), lastSeenAt: timestamp(3),
highestRound: 4, roundsSurvived: 3, kills: 50, headshots: 20, downs: 2, revives: 3, bestScore: 2000 });
expect(players[1]).toEqual({ guid: '202', name: 'Player 202', hasStats: true, presentAtEnd: false,
canViewProfile: true, firstSeenAt: timestamp(0), lastSeenAt: timestamp(1),
highestRound: 2, roundsSurvived: 1, kills: 15, headshots: 0, downs: 0, revives: 0, bestScore: 1500 });
expect(players[2]).toMatchObject({ guid: '404', presentAtEnd: true, highestRound: 4, roundsSurvived: 0,
firstSeenAt: timestamp(3), lastSeenAt: timestamp(3), kills: 0, bestScore: 0 });
expect(players[3]).toEqual({ guid: '303', name: 'Player 303', hasStats: false, presentAtEnd: false,
canViewProfile: false, firstSeenAt: timestamp(0), lastSeenAt: timestamp(0),
highestRound: null, roundsSurvived: null, kills: null, headshots: null, downs: null, revives: null, bestScore: null });
});
it('preserves an unknown start and an observed empty ending roster across database reopen', async () => {
@@ -66,19 +142,40 @@ describe('completed match details', () => {
const { closeDb } = await import('../../src/lib/server/db/index.js');
closeDb();
const { getMatchDetail } = await import('../../src/lib/server/fleet/match-history.js');
expect(getMatchDetail(1)).toMatchObject({ startObserved: false, startedAt: timestamp(0),
startPlayers: null, durationMs: null, endedAt: timestamp(1), endPlayers: 0,
uniquePlayers: 1, finalRound: 6, players: [expect.objectContaining({
guid: '101', presentAtEnd: false, highestRound: 5, kills: 40, roundsSurvived: 0
})] });
expect(getMatchDetail(1)).toMatchObject({
startObserved: false,
startedAt: timestamp(0),
startPlayers: null,
durationMs: null,
endedAt: timestamp(1),
endPlayers: 0,
uniquePlayers: 1,
finalRound: 6,
players: [
expect.objectContaining({
guid: '101',
presentAtEnd: false,
highestRound: 5,
kills: 40,
roundsSurvived: 0
})
]
});
});
it('loads an empty completed record without inventing player rows', async () => {
await report('game_start', 0, []);
await report('game_end', 2, []);
const { getMatchDetail } = await import('../../src/lib/server/fleet/match-history.js');
expect(getMatchDetail(1)).toMatchObject({ startPlayers: 0, uniquePlayers: 0, endPlayers: 0,
startObserved: true, durationMs: 60_000, finalRound: 2, players: [] });
expect(getMatchDetail(1)).toMatchObject({
startPlayers: 0,
uniquePlayers: 0,
endPlayers: 0,
startObserved: true,
durationMs: 60_000,
finalRound: 2,
players: []
});
});
it('preserves original match and server identity after deletion and reuse of the server name', async () => {
@@ -90,13 +187,27 @@ describe('completed match details', () => {
db().prepare('DELETE FROM servers WHERE name = ?').run(server);
const deleted = getMatchDetail(1)!;
expect(deleted).toEqual({ ...before, server_deleted_at: expect.any(String) });
db().prepare("INSERT INTO servers (name, game, map) VALUES (?, 't6', 'zm_transit')").run(server);
const { recordLeaderboardTelemetry } = await import('../../src/lib/server/fleet/leaderboard-ingest.js');
recordLeaderboardTelemetry(db(), { event: 'game_end', server, map: 'zm_transit', round: 20,
players: [player('999', { kills: 999 })], receivedAt: timestamp(4) });
db()
.prepare("INSERT INTO servers (name, game, map) VALUES (?, 't6', 'zm_transit')")
.run(server);
const { recordLeaderboardTelemetry } =
await import('../../src/lib/server/fleet/leaderboard-ingest.js');
recordLeaderboardTelemetry(db(), {
event: 'game_end',
server,
map: 'zm_transit',
round: 20,
players: [player('999', { kills: 999 })],
receivedAt: timestamp(4)
});
expect(getMatchDetail(1)).toEqual(deleted);
expect(getMatchDetail(2)).toMatchObject({ id: 2, game: 't6', server_deleted_at: null,
finalRound: 20, players: [expect.objectContaining({ guid: '999', kills: 999 })] });
expect(getMatchDetail(2)).toMatchObject({
id: 2,
game: 't6',
server_deleted_at: null,
finalRound: 20,
players: [expect.objectContaining({ guid: '999', kills: 999 })]
});
});
it('uses the latest shared player name and only offers profiles that actually exist', async () => {
@@ -105,32 +216,58 @@ describe('completed match details', () => {
await report('game_end', 2, [player('101')]);
const { db } = await import('../../src/lib/server/db/index.js');
const handle = db();
handle.prepare(`INSERT INTO leaderboard_players (game, guid, name, first_seen_at, last_seen_at)
VALUES ('t6', '101', 'Latest name', ?, ?), ('t7', '101', 'Unsupported name', ?, ?)`)
handle
.prepare(
`INSERT INTO leaderboard_players (game, guid, name, first_seen_at, last_seen_at)
VALUES ('t6', '101', 'Latest name', ?, ?), ('t7', '101', 'Unsupported name', ?, ?)`
)
.run(timestamp(0), timestamp(4), timestamp(0), timestamp(5));
const { getMatchDetail } = await import('../../src/lib/server/fleet/match-history.js');
const { getPlayerProfile } = await import('../../src/lib/server/fleet/player-profile.js');
let participants = getMatchDetail(1)!.players;
expect(participants.find(entry => entry.guid === '101')).toMatchObject({ name: 'Latest name', canViewProfile: true });
expect(participants.find(entry => entry.guid === '202')).toMatchObject({ hasStats: true, highestRound: 1, canViewProfile: false });
expect(participants.find(entry => entry.guid === '303')).toMatchObject({ hasStats: false, canViewProfile: false });
for (const participant of participants) expect(participant.canViewProfile).toBe(getPlayerProfile(participant.guid) !== null);
expect(participants.find((entry) => entry.guid === '101')).toMatchObject({
name: 'Latest name',
canViewProfile: true
});
expect(participants.find((entry) => entry.guid === '202')).toMatchObject({
hasStats: true,
highestRound: 1,
canViewProfile: false
});
expect(participants.find((entry) => entry.guid === '303')).toMatchObject({
hasStats: false,
canViewProfile: false
});
for (const participant of participants)
expect(participant.canViewProfile).toBe(getPlayerProfile(participant.guid) !== null);
// A start-only participant can still have a valid profile from another match.
await report('game_start', 0, [player('303')]);
await report('heartbeat', 3, [player('303')]);
expect(getMatchDetail(1)!.players.find(entry => entry.guid === '303')).toMatchObject({ canViewProfile: false });
expect(getMatchDetail(1)!.players.find((entry) => entry.guid === '303')).toMatchObject({
canViewProfile: false
});
expect(getPlayerProfile('303')).toBeNull();
await report('game_end', 3, [player('303')]);
participants = getMatchDetail(1)!.players;
expect(participants.find(entry => entry.guid === '303')).toMatchObject({ hasStats: false, canViewProfile: true });
expect(participants.find((entry) => entry.guid === '303')).toMatchObject({
hasStats: false,
canViewProfile: true
});
// Missing identities must not discard a saved roster row or produce a dead link.
handle.prepare("DELETE FROM leaderboard_players WHERE guid = '101'").run();
expect(getMatchDetail(1)!.players.find(entry => entry.guid === '101')).toMatchObject({ name: '101', canViewProfile: false });
expect(getMatchDetail(1)!.players.find((entry) => entry.guid === '101')).toMatchObject({
name: '101',
canViewProfile: false
});
handle.prepare("UPDATE leaderboard_matches SET game = 'custom' WHERE id = 2").run();
handle.prepare("UPDATE leaderboard_players SET game = 'custom' WHERE guid = '303'").run();
expect(getMatchDetail(2)!.players[0]).toMatchObject({ name: 'Player 303', hasStats: true, canViewProfile: false });
expect(getMatchDetail(2)!.players[0]).toMatchObject({
name: 'Player 303',
hasStats: true,
canViewProfile: false
});
});
it('only exposes completed records at round 2 or higher and rejects invalid IDs', async () => {
@@ -164,9 +301,26 @@ describe('match detail page load', () => {
await report('game_start');
await report('round_change', 2);
const { load } = await import('../../src/routes/matches/[id]/+page.server.js');
for (const id of ['1', '2', '3', '999', '', '0', '-1', '1.0', '01', '1e0', '1x', ' 1', 'Infinity',
'9007199254740992', "1' OR 1=1 --"]) {
expect(() => load({ params: { id } }), id).toThrow(expect.objectContaining({ status: 404 }));
for (const id of [
'1',
'2',
'3',
'999',
'',
'0',
'-1',
'1.0',
'01',
'1e0',
'1x',
' 1',
'Infinity',
'9007199254740992',
"1' OR 1=1 --"
]) {
expect(() => load({ params: { id } }), id).toThrow(
expect.objectContaining({ status: 404 })
);
}
});
});