Show profiles for players without qualifying matches
CI / checks (push) Successful in 4m56s

This commit is contained in:
2026-10-10 13:50:53 -04:00
parent cc9c10226e
commit bd34c3c9e7
11 changed files with 271 additions and 87 deletions
+75 -6
View File
@@ -28,6 +28,27 @@ beforeEach(async () => {
});
afterEach(async () => fixture.cleanup());
const emptyProfile = {
stats: {
highestRound: 0,
kills: 0,
roundsSurvived: 0,
headshots: 0,
revives: 0,
downs: 0,
gamesPlayed: 0,
gamesCompleted: 0,
bestKills: 0,
bestScore: 0
},
records: null,
maps: [],
matches: [],
matchesTotal: 0,
offset: 0,
limit: 25
};
async function report(
event: GameEvent['event'],
round = 0,
@@ -94,6 +115,54 @@ it('keeps report revisions stable during a match and invalidates completed data
expect(revision()).toBeGreaterThan(reopened);
});
it.each([
['t5', server, 'zombie_theater'],
['t6', 't6_zombie_town_1', 'zm_transit']
])(
'loads a known %s player before qualification and refreshes cached profiles after game end',
async (game, source, map) => {
const { load } =
await import('../../src/routes/leaderboard/players/[guid]/+page.server.js');
const request = {
params: { guid: player().guid },
url: new URL(
`http://localhost/leaderboard/players/${player().guid}?game=${game}&offset=999`
)
};
const send = (event: GameEvent['event'], round = 0) =>
report(event, round, [player(undefined, { kills: 20 })], source, map);
await expect(load(request)).rejects.toMatchObject({ status: 404 });
await send('game_start');
const started = await load(request);
expect(started).toMatchObject({
profile: { ...emptyProfile, guid: player().guid, name: player().name },
scope: { entry: null }
});
expect(started.profile.firstSeen).toBe('2026-09-18T20:00:00.000Z');
expect(started.profile.lastSeen).toBe(started.profile.firstSeen);
await send('game_end', 1);
expect((await load(request)).profile).toMatchObject(emptyProfile);
await send('game_start');
await send('heartbeat', 2);
expect((await load(request)).profile).toMatchObject(emptyProfile);
await send('game_end', 2);
const completed = await load(request);
expect(completed).toMatchObject({
profile: {
stats: { highestRound: 2, kills: 20, gamesPlayed: 1 },
records: { highestRound: { value: 2 } },
matchesTotal: 1,
offset: 0
},
scope: { entry: { rank: 1, kills: 20 } }
});
await send('game_start');
await send('heartbeat', 10);
expect((await load(request)).profile.stats).toEqual(completed.profile.stats);
}
);
it('publishes round 2 or higher games only after game end, preserving early stats and excluding round 1 departures', async () => {
const { getLeaderboard } = await import('../../src/lib/server/fleet/leaderboard.js');
const { getPlayerProfile } = await import('../../src/lib/server/fleet/player-profile.js');
@@ -107,7 +176,7 @@ it('publishes round 2 or higher games only after game end, preserving early stat
total: 0,
summary: { players: 0, matches: 0, kills: 0 }
});
expect(getPlayerProfile(player().guid)).toBeNull();
expect(getPlayerProfile(player().guid)).toMatchObject(emptyProfile);
expect(serverMatchHistory()).toEqual({ total: 0, matches: [], maps: [], page: 1, limit: 25 });
await report('round_change', 2, [player(undefined, { kills: 6, score: 500 })]);
expect(getLeaderboard()).toMatchObject({
@@ -116,9 +185,9 @@ it('publishes round 2 or higher games only after game end, preserving early stat
total: 0,
summary: { players: 0, matches: 0, kills: 0 }
});
expect(getPlayerProfile(player().guid)).toBeNull();
expect(getPlayerProfile(player().guid)).toMatchObject(emptyProfile);
expect(serverMatchHistory()).toMatchObject({ total: 1, matches: [{ status: 'active' }] });
expect(getPlayerProfile(departed.guid)).toBeNull();
expect(getPlayerProfile(departed.guid)).toMatchObject(emptyProfile);
await report('game_end', 2, [player(undefined, { kills: 10 })]);
const board = getLeaderboard();
const profile = getPlayerProfile(player().guid);
@@ -137,7 +206,7 @@ it('publishes round 2 or higher games only after game end, preserving early stat
],
summary: { players: 1, matches: 1, kills: 10 }
});
expect(getPlayerProfile(departed.guid)).toBeNull();
expect(getPlayerProfile(departed.guid)).toMatchObject(emptyProfile);
expect(history).toMatchObject({
total: 1,
matches: [{ startPlayers: 2, uniquePlayers: 2, durationMs: 3000, finalRound: 2 }]
@@ -478,7 +547,7 @@ it('keeps disconnected player records while completing only the final roster', a
await report('game_start', 0, [player(), other]);
await report('round_change', 4, [player(undefined, { kills: 80 }), other]);
expect(getLeaderboard()).toMatchObject({ entries: [], total: 0 });
expect(getPlayerProfile(other.guid)).toBeNull();
expect(getPlayerProfile(other.guid)).toMatchObject(emptyProfile);
await report('game_end', 5, [player(undefined, { kills: 100 })]);
expect(await records(other.guid)).toEqual([
expect.objectContaining({ kills: 40, highest_round: 4, rounds_survived: 3, completed: 0 })
@@ -682,7 +751,7 @@ it('keeps deleted match identities and starts a separate match when the server n
server_deleted_at: null,
status: 'active'
});
expect(getPlayerProfile(player().guid)).toBeNull();
expect(getPlayerProfile(player().guid)).toMatchObject(emptyProfile);
await report('game_end', 8, [player(undefined, { kills: 120 })]);
const profile = getPlayerProfile(player().guid)!;
expect(profile.stats).toMatchObject({ gamesPlayed: 1, kills: 120 });