import Database from 'better-sqlite3'; import { afterEach, beforeEach, expect, it } from 'vitest'; import { migrateLeaderboardReports } from '../../src/lib/server/db/leaderboard-reports.js'; import { runMigrations } from '../../src/lib/server/db/migration-runner.js'; import { migrations } from '../../src/lib/server/db/migrations.js'; let handle: Database.Database; const migration = migrations.find( (entry) => entry.name === 'durable leaderboard report identity and sequences' )!; beforeEach(() => { handle = new Database(':memory:'); handle.pragma('foreign_keys = ON'); runMigrations(handle, migrations.slice(0, migration.version - 1)); handle.exec(` INSERT INTO leaderboard_matches (id,server,server_deleted_at,game,map,map_key,map_name,started_at,last_received_at, status,last_event,last_fingerprint,highest_round) VALUES (1,'old-server','2026-10-01T15:00:00.000Z','t5','zombie_moon','zombie_moon','Moon', '2026-10-01T12:00:00.000Z','2026-10-01T14:00:00.000Z','completed','game_end','old',32); INSERT INTO leaderboard_match_players (match_id,guid,kills,first_seen_at,last_seen_at) VALUES (1,'player',1000,'2026-10-01T12:00:00.000Z','2026-10-01T14:00:00.000Z'); INSERT INTO match_chat(match_id,message_key,occurrence,entry) VALUES (1,'message',1,'{"message":"Saved chat"}'); `); }); afterEach(() => handle.close()); function insertMatch(server: string, key: string | null): number { return Number( handle .prepare( `INSERT INTO leaderboard_matches (server,game,map,map_key,map_name,started_at,last_received_at,last_event, last_fingerprint,reporter_match_key) VALUES (?,'t5','zombie_moon','zombie_moon','Moon', '2026-10-12T12:00:00.000Z','2026-10-12T12:00:00.000Z','game_start','start',?)` ) .run(server, key).lastInsertRowid ); } it('upgrades version 17 without inventing reporter identity or changing historical matches', () => { const original = handle.prepare('SELECT * FROM leaderboard_matches').get(); const players = handle.prepare('SELECT * FROM leaderboard_match_players').all(); const chat = handle.prepare('SELECT * FROM match_chat').all(); runMigrations(handle, migrations); expect(handle.pragma('user_version', { simple: true })).toBe(migrations.length); expect(handle.prepare('SELECT * FROM leaderboard_matches').get()).toEqual({ ...original!, reporter_match_key: null, last_seq: 0, report_count: 0 }); expect(handle.prepare('SELECT * FROM leaderboard_match_players').all()).toEqual( players.map((player) => ({ ...(player as Record), score_total: null })) ); expect(handle.prepare('SELECT * FROM match_chat').all()).toEqual(chat); expect(handle.prepare('SELECT * FROM leaderboard_match_reports').all()).toEqual([]); expect(handle.pragma('foreign_key_check')).toEqual([]); }); it('enforces reporter keys per server while preserving unkeyed legacy history', () => { runMigrations(handle, migrations); const first = insertMatch('server-a', 'cycle-1'); expect(() => insertMatch('server-a', 'cycle-1')).toThrow('UNIQUE constraint failed'); expect(() => insertMatch('server-b', 'cycle-1')).not.toThrow(); expect(() => insertMatch('server-a', 'cycle-2')).not.toThrow(); expect(() => insertMatch('server-a', null)).not.toThrow(); expect(() => insertMatch('server-a', null)).not.toThrow(); handle .prepare('UPDATE leaderboard_matches SET server_deleted_at=? WHERE id=?') .run('2026-10-12T13:00:00.000Z', first); expect(() => insertMatch('server-a', 'cycle-1')).toThrow('UNIQUE constraint failed'); }); it('stores received and applied sequence receipts once and removes them with retained matches', () => { runMigrations(handle, migrations); const id = insertMatch('server-a', 'cycle-1'); const receipt = handle.prepare( 'INSERT INTO leaderboard_match_reports(match_id,seq,applied) VALUES(?,?,?)' ); receipt.run(id, 3, 1); receipt.run(id, 1, 0); expect(() => receipt.run(id, 3, 0)).toThrow('UNIQUE constraint failed'); for (const seq of [0, -1, 1.5]) expect(() => receipt.run(id, seq, 1)).toThrow('CHECK constraint failed'); expect(() => receipt.run(id, 2, 2)).toThrow('CHECK constraint failed'); expect(() => receipt.run(id + 100, 1, 1)).toThrow('FOREIGN KEY constraint failed'); expect( handle.prepare('SELECT seq,applied FROM leaderboard_match_reports ORDER BY seq').all() ).toEqual([ { seq: 1, applied: 0 }, { seq: 3, applied: 1 } ]); handle.prepare('DELETE FROM leaderboard_matches WHERE id=?').run(id); expect(handle.prepare('SELECT * FROM leaderboard_match_reports').all()).toEqual([]); expect(handle.pragma('foreign_key_check')).toEqual([]); }); it('preserves reporter keys, counters and receipts when migration 18 is replayed', () => { runMigrations(handle, migrations); const id = insertMatch('server-a', 'cycle-1'); handle.prepare('UPDATE leaderboard_matches SET last_seq=3,report_count=2 WHERE id=?').run(id); handle .prepare('INSERT INTO leaderboard_match_reports(match_id,seq,applied) VALUES(?,3,1)') .run(id); handle .prepare('INSERT INTO leaderboard_match_reports(match_id,seq,applied) VALUES(?,1,0)') .run(id); const matches = handle.prepare('SELECT * FROM leaderboard_matches ORDER BY id').all(); const reports = handle.prepare('SELECT * FROM leaderboard_match_reports ORDER BY seq').all(); const schemaVersion = handle.pragma('schema_version', { simple: true }); migrateLeaderboardReports(handle); handle.pragma(`user_version = ${migration.version - 1}`); runMigrations(handle, migrations); expect(handle.prepare('SELECT * FROM leaderboard_matches ORDER BY id').all()).toEqual(matches); expect(handle.prepare('SELECT * FROM leaderboard_match_reports ORDER BY seq').all()).toEqual( reports ); expect(handle.pragma('schema_version', { simple: true })).toBe(schemaVersion); expect(handle.pragma('foreign_key_check')).toEqual([]); });