Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
@@ -3,46 +3,95 @@ import type Database from 'better-sqlite3';
|
||||
import { newFixture, type Fixture } from '../helpers/fixture.js';
|
||||
|
||||
let fixture: Fixture;
|
||||
beforeEach(async () => { vi.resetModules(); fixture = await newFixture([]); });
|
||||
afterEach(async () => { await fixture.cleanup(); });
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
fixture = await newFixture([]);
|
||||
});
|
||||
afterEach(async () => {
|
||||
await fixture.cleanup();
|
||||
});
|
||||
|
||||
function seed(handle: Database.Database, id: number, players: number | null, options: {
|
||||
guid?: string; round?: number; game?: string; map?: string; mapName?: string; status?: string; hasStats?: number;
|
||||
} = {}) {
|
||||
const { guid = 'ace', round = 20, game = 't5', map = 'theater', mapName = 'Kino Der Toten', status = 'completed', hasStats = 1 } = options;
|
||||
function seed(
|
||||
handle: Database.Database,
|
||||
id: number,
|
||||
players: number | null,
|
||||
options: {
|
||||
guid?: string;
|
||||
round?: number;
|
||||
game?: string;
|
||||
map?: string;
|
||||
mapName?: string;
|
||||
status?: string;
|
||||
hasStats?: number;
|
||||
} = {}
|
||||
) {
|
||||
const {
|
||||
guid = 'ace',
|
||||
round = 20,
|
||||
game = 't5',
|
||||
map = 'theater',
|
||||
mapName = 'Kino Der Toten',
|
||||
status = 'completed',
|
||||
hasStats = 1
|
||||
} = options;
|
||||
const timestamp = new Date(Date.UTC(2026, 9, 1, 0, id)).toISOString();
|
||||
handle.prepare(`INSERT INTO leaderboard_matches
|
||||
handle
|
||||
.prepare(
|
||||
`INSERT INTO leaderboard_matches
|
||||
(id,server,game,map,map_key,map_name,started_at,ended_at,last_received_at,status,start_players,highest_round,last_event,last_fingerprint)
|
||||
VALUES (?,'removed-server',?,?,?,?,?,?,?,?,?,?,'game_end','fixture')`)
|
||||
.run(id, game, map, `${game}:${map}`, mapName, timestamp, timestamp, timestamp, status, players, round);
|
||||
handle.prepare(`INSERT OR IGNORE INTO leaderboard_players(game,guid,name,first_seen_at,last_seen_at) VALUES(?,?,?,?,?)`)
|
||||
VALUES (?,'removed-server',?,?,?,?,?,?,?,?,?,?,'game_end','fixture')`
|
||||
)
|
||||
.run(
|
||||
id,
|
||||
game,
|
||||
map,
|
||||
`${game}:${map}`,
|
||||
mapName,
|
||||
timestamp,
|
||||
timestamp,
|
||||
timestamp,
|
||||
status,
|
||||
players,
|
||||
round
|
||||
);
|
||||
handle
|
||||
.prepare(
|
||||
`INSERT OR IGNORE INTO leaderboard_players(game,guid,name,first_seen_at,last_seen_at) VALUES(?,?,?,?,?)`
|
||||
)
|
||||
.run(game, guid, guid, timestamp, timestamp);
|
||||
handle.prepare(`INSERT INTO leaderboard_match_players
|
||||
handle
|
||||
.prepare(
|
||||
`INSERT INTO leaderboard_match_players
|
||||
(match_id,guid,highest_round,kills,best_score,rounds_survived,has_stats,completed,first_seen_at,last_seen_at)
|
||||
VALUES(?,?,?,100,1000,?, ?,1,?,?)`).run(id, guid, round, round, hasStats, timestamp, timestamp);
|
||||
VALUES(?,?,?,100,1000,?, ?,1,?,?)`
|
||||
)
|
||||
.run(id, guid, round, round, hasStats, timestamp, timestamp);
|
||||
}
|
||||
|
||||
describe('leaderboard starting-player scopes', () => {
|
||||
it('separates each squad and its cache, with unknown starts available only in All players', async () => {
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
seed(db(), 1, null);
|
||||
for (const players of [1, 2, 3, 4] as const) seed(db(), players + 1, players, { round: players < 3 ? 20 : players * 10 });
|
||||
for (const players of [1, 2, 3, 4] as const)
|
||||
seed(db(), players + 1, players, { round: players < 3 ? 20 : players * 10 });
|
||||
seed(db(), 6, 1, { guid: 'blake', round: 30 });
|
||||
seed(db(), 7, 2, { guid: 'blake', round: 10 });
|
||||
seed(db(), 8, null, { guid: 'legacy', round: 99 });
|
||||
const { getLeaderboard, getPlayerLeaderboardEntry } = await import('../../src/lib/server/fleet/leaderboard.js');
|
||||
const { getLeaderboard, getPlayerLeaderboardEntry } =
|
||||
await import('../../src/lib/server/fleet/leaderboard.js');
|
||||
const all = getLeaderboard();
|
||||
expect(all).toMatchObject({ total: 3, summary: { matches: 8, highestRound: 99 } });
|
||||
for (const players of [1, 2, 3, 4, 2, 1] as const) {
|
||||
const board = getLeaderboard({ players });
|
||||
expect(board.summary.matches).toBe(players < 3 ? 2 : 1);
|
||||
expect(board.entries.some(entry => entry.guid === 'legacy')).toBe(false);
|
||||
const ace = board.entries.find(entry => entry.guid === 'ace')!;
|
||||
expect(board.entries.some((entry) => entry.guid === 'legacy')).toBe(false);
|
||||
const ace = board.entries.find((entry) => entry.guid === 'ace')!;
|
||||
expect(ace.gamesPlayed).toBe(1);
|
||||
expect(ace.rank).toBe(players === 1 ? 2 : 1);
|
||||
// All three equal-valued records must come from this squad, not
|
||||
// the earlier unknown-start or solo match with matching values.
|
||||
for (const record of Object.values(ace.records)) expect(record.matchId).toBe(players + 1);
|
||||
for (const record of Object.values(ace.records))
|
||||
expect(record.matchId).toBe(players + 1);
|
||||
expect(getPlayerLeaderboardEntry('ace', { players })).toEqual(ace);
|
||||
}
|
||||
expect(getLeaderboard()).toEqual(all);
|
||||
@@ -51,36 +100,75 @@ describe('leaderboard starting-player scopes', () => {
|
||||
it('filters profile history and rank while keeping explicitly lifetime records intact', async () => {
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
for (let id = 1; id <= 30; id++) seed(db(), id, 2);
|
||||
seed(db(), 31, 1, { round: 40 }); seed(db(), 32, null, { round: 99 });
|
||||
seed(db(), 31, 1, { round: 40 });
|
||||
seed(db(), 32, null, { round: 99 });
|
||||
const { getPlayerProfile } = await import('../../src/lib/server/fleet/player-profile.js');
|
||||
const lifetime = getPlayerProfile('ace')!;
|
||||
const first = getPlayerProfile('ace', 0, { players: 2 })!;
|
||||
const last = getPlayerProfile('ace', 999, { players: 2, map: 't5:theater', game: 't5' })!;
|
||||
expect(first).toMatchObject({ matchesTotal: 30, offset: 0 }); expect(first.matches).toHaveLength(25);
|
||||
expect(last).toMatchObject({ matchesTotal: 30, offset: 25 }); expect(last.matches).toHaveLength(5);
|
||||
expect(new Set([...first.matches, ...last.matches].map(match => match.id))).toEqual(new Set(Array.from({ length: 30 }, (_, i) => i + 1)));
|
||||
expect(first.stats).toEqual(lifetime.stats); expect(first.records).toEqual(lifetime.records); expect(first.maps).toEqual(lifetime.maps);
|
||||
expect(getPlayerProfile('ace', 25, { players: 4 })).toMatchObject({ matchesTotal: 0, matches: [], offset: 0, stats: lifetime.stats });
|
||||
const { load } = await import('../../src/routes/leaderboard/players/[guid]/+page.server.js');
|
||||
const scoped = await load({ params: { guid: 'ace' }, url: new URL('http://localhost/leaderboard/players/ace?players=2&map=t5%3Atheater') });
|
||||
expect(scoped).toMatchObject({ profile: { matchesTotal: 30, stats: { gamesPlayed: 32, highestRound: 99 } },
|
||||
scope: { players: '2', entry: { gamesPlayed: 30, highestRound: 20, records: { highestRound: { matchId: 1 } } } } });
|
||||
expect(first).toMatchObject({ matchesTotal: 30, offset: 0 });
|
||||
expect(first.matches).toHaveLength(25);
|
||||
expect(last).toMatchObject({ matchesTotal: 30, offset: 25 });
|
||||
expect(last.matches).toHaveLength(5);
|
||||
expect(new Set([...first.matches, ...last.matches].map((match) => match.id))).toEqual(
|
||||
new Set(Array.from({ length: 30 }, (_, i) => i + 1))
|
||||
);
|
||||
expect(first.stats).toEqual(lifetime.stats);
|
||||
expect(first.records).toEqual(lifetime.records);
|
||||
expect(first.maps).toEqual(lifetime.maps);
|
||||
expect(getPlayerProfile('ace', 25, { players: 4 })).toMatchObject({
|
||||
matchesTotal: 0,
|
||||
matches: [],
|
||||
offset: 0,
|
||||
stats: lifetime.stats
|
||||
});
|
||||
const { load } =
|
||||
await import('../../src/routes/leaderboard/players/[guid]/+page.server.js');
|
||||
const scoped = await load({
|
||||
params: { guid: 'ace' },
|
||||
url: new URL('http://localhost/leaderboard/players/ace?players=2&map=t5%3Atheater')
|
||||
});
|
||||
expect(scoped).toMatchObject({
|
||||
profile: { matchesTotal: 30, stats: { gamesPlayed: 32, highestRound: 99 } },
|
||||
scope: {
|
||||
players: '2',
|
||||
entry: {
|
||||
gamesPlayed: 30,
|
||||
highestRound: 20,
|
||||
records: { highestRound: { matchId: 1 } }
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps API absent-map searches global while filtering all contributions by the selected squad', async () => {
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
seed(db(), 1, 2); seed(db(), 2, 2, { map: 'moon', mapName: 'Moon' }); seed(db(), 3, null, { round: 99 });
|
||||
seed(db(), 1, 2);
|
||||
seed(db(), 2, 2, { map: 'moon', mapName: 'Moon' });
|
||||
seed(db(), 3, null, { round: 99 });
|
||||
const { GET } = await import('../../src/routes/api/leaderboard/+server.js');
|
||||
expect(await (await GET({ url: new URL('http://localhost/api/leaderboard?players=2') })).json())
|
||||
.toMatchObject({ summary: { matches: 2, highestRound: 20 }, entries: [{ gamesPlayed: 2 }] });
|
||||
expect(await (await GET({ url: new URL('http://localhost/api/leaderboard') })).json())
|
||||
.toMatchObject({ summary: { matches: 3, highestRound: 99 } });
|
||||
expect(
|
||||
await (await GET({ url: new URL('http://localhost/api/leaderboard?players=2') })).json()
|
||||
).toMatchObject({
|
||||
summary: { matches: 2, highestRound: 20 },
|
||||
entries: [{ gamesPlayed: 2 }]
|
||||
});
|
||||
expect(
|
||||
await (await GET({ url: new URL('http://localhost/api/leaderboard') })).json()
|
||||
).toMatchObject({ summary: { matches: 3, highestRound: 99 } });
|
||||
for (const players of ['0', '5', '', '2.0', '-1', 'two', '01']) {
|
||||
const response = await GET({ url: new URL(`http://localhost/api/leaderboard?players=${players}`) });
|
||||
const response = await GET({
|
||||
url: new URL(`http://localhost/api/leaderboard?players=${players}`)
|
||||
});
|
||||
expect(response.status).toBe(400);
|
||||
}
|
||||
const { load } = await import('../../src/routes/leaderboard/players/[guid]/+page.server.js');
|
||||
await expect(load({ params: { guid: 'ace' }, url: new URL('http://localhost/leaderboard/players/ace?players=5') }))
|
||||
.rejects.toMatchObject({ status: 400 });
|
||||
const { load } =
|
||||
await import('../../src/routes/leaderboard/players/[guid]/+page.server.js');
|
||||
await expect(
|
||||
load({
|
||||
params: { guid: 'ace' },
|
||||
url: new URL('http://localhost/leaderboard/players/ace?players=5')
|
||||
})
|
||||
).rejects.toMatchObject({ status: 400 });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user