import { randomUUID } from 'node:crypto'; import type { Pool } from 'pg'; import { afterAll, beforeEach, describe, expect, it } from 'vitest'; import '../src/pipeline/projections/index.ts'; import { processEvents } from '../src/pipeline/process.ts'; import { getLeaderboard, getPlayerRanks } from '../src/stats/leaderboards.ts'; import { BOARDS } from '../src/stats/metrics.ts'; import { takeSnapshots } from '../src/stats/snapshots.ts'; import { closeTestPool, getTestPool, resetDb } from './helpers/db.ts'; import { ALICE, BOB, ev, T0 } from './helpers/events.ts'; let pool: Pool; beforeEach(async () => { pool = await getTestPool(); await resetDb(pool); }); afterAll(closeTestPool); const DAY = 86_400_000; async function stats(who: { uuid: string; name: string }, stats: Record>, ts = T0) { await processEvents(pool, [ev('stats_snapshot', { uuid: who.uuid, name: who.name, full: false, ts, stats })], 'push', 'atm10'); } describe('boards', () => { it('defines the 14 boards from the spec', () => { expect(BOARDS.map((b) => b.id)).toEqual([ 'playtime', 'deaths', 'mobkills', 'mined', 'distance', 'flight', 'diamonds', 'tech', 'magic', 'farming', 'quests', 'advancements', 'loot', 'worldtier', ]); }); it('playtime converts ticks to hours', async () => { await stats(ALICE, { 'minecraft:custom': { 'minecraft:play_time': 1175736 } }); const [top] = await getLeaderboard(pool, 'playtime', 'all'); expect(top).toMatchObject({ rank: 1, uuid: ALICE.uuid, name: ALICE.name }); expect(top!.value).toBeCloseTo(16.33, 2); }); it('distance sums all *_one_cm keys except falling, in km', async () => { await stats(ALICE, { 'minecraft:custom': { 'minecraft:walk_one_cm': 150_000, 'minecraft:fly_one_cm': 50_000, 'minecraft:fall_one_cm': 999_999, 'minecraft:jump': 5, }, }); const [top] = await getLeaderboard(pool, 'distance', 'all'); expect(top!.value).toBe(2); }); it('tech sums crafted+used of tech namespaces only', async () => { await stats(ALICE, { 'minecraft:crafted': { 'mekanism:steel_casing': 3, 'minecraft:stick': 100, 'ars_nouveau:source_gem': 9 }, 'minecraft:used': { 'powah:energy_cell_basic': 2 }, 'minecraft:mined': { 'mekanism:osmium_ore': 50 }, }); const [top] = await getLeaderboard(pool, 'tech', 'all'); expect(top!.value).toBe(5); }); it('hidden player excluded', async () => { await stats(ALICE, { 'minecraft:custom': { 'minecraft:deaths': 3 } }); await stats(BOB, { 'minecraft:custom': { 'minecraft:deaths': 9 } }); await pool.query('UPDATE players SET hidden = true WHERE uuid = $1', [BOB.uuid]); const rows = await getLeaderboard(pool, 'deaths', 'all'); expect(rows.map((r) => r.uuid)).toEqual([ALICE.uuid]); }); it('ties share rank, ordered by name; zero values omitted', async () => { const carl = { uuid: randomUUID(), name: 'Carl' }; await stats(BOB, { 'minecraft:custom': { 'minecraft:mob_kills': 7 } }); await stats(ALICE, { 'minecraft:custom': { 'minecraft:mob_kills': 7 } }); await stats(carl, { 'minecraft:custom': { 'minecraft:mob_kills': 0 } }); const rows = await getLeaderboard(pool, 'mobkills', 'all'); expect(rows.map((r) => [r.rank, r.name])).toEqual([ [1, 'Bob'], [1, 'CDOGamer'], ]); }); it('7d period subtracts the daily snapshot at the period start', async () => { const now = new Date(T0 + 10 * DAY); await stats(ALICE, { 'minecraft:custom': { 'minecraft:deaths': 4 } }, T0); await takeSnapshots(pool, 'day', new Date(T0 + 2 * DAY)); await stats(ALICE, { 'minecraft:custom': { 'minecraft:deaths': 10 } }, T0 + 9 * DAY); const [top] = await getLeaderboard(pool, 'deaths', '7d', 10, now); expect(top!.value).toBe(6); }); it('quests uses team progress and counts only known quests when defs exist', async () => { await stats(ALICE, { 'minecraft:custom': { 'minecraft:jump': 1 } }); await pool.query( `INSERT INTO quest_progress(team_id, quest_id, completed_at) VALUES ($1, 'AAAAAAAAAAAAAAAA', now()), ($1, 'BBBBBBBBBBBBBBBB', now())`, [ALICE.uuid], ); expect((await getLeaderboard(pool, 'quests', 'all'))[0]!.value).toBe(2); await pool.query(`INSERT INTO quest_defs(quest_id, chapter_id) VALUES ('AAAAAAAAAAAAAAAA', 'C')`); expect((await getLeaderboard(pool, 'quests', 'all'))[0]!.value).toBe(1); }); it('player ranks cover every board the player appears on', async () => { await stats(ALICE, { 'minecraft:custom': { 'minecraft:deaths': 3, 'minecraft:play_time': 72000 } }); await stats(BOB, { 'minecraft:custom': { 'minecraft:deaths': 9 } }); const ranks = await getPlayerRanks(pool, ALICE.uuid); expect(ranks.deaths).toEqual({ rank: 2, value: 3 }); expect(ranks.playtime).toEqual({ rank: 1, value: 1 }); expect(ranks.magic).toBeUndefined(); }); });