import assert from "node:assert/strict"; import { randomUUID } from "node:crypto"; import { readFile, readdir } from "node:fs/promises"; import { PGlite } from "../.data/sql-verification/node_modules/@electric-sql/pglite/dist/index.js"; const db = new PGlite(); try { await db.exec(`create role anon;create role authenticated;create role service_role; create schema auth;create table auth.users(id uuid primary key); create function auth.uid() returns uuid language sql as $$select null::uuid$$; create schema storage;create table storage.buckets(id text primary key,name text,public boolean,file_size_limit bigint,allowed_mime_types text[]); create table storage.objects(id uuid primary key,bucket_id text,name text,owner_id text); create function storage.foldername(text) returns text[] language sql as $$select string_to_array($1,'/')$$;`); for (const file of (await readdir("supabase/migrations")) .filter((f) => f.endsWith(".sql")) .sort()) await db.exec(await readFile(`supabase/migrations/${file}`, "utf8")); const q = async (sql, args = []) => (await db.query(sql, args)).rows; const users = []; for (let i = 0; i < 34; i++) { const id = randomUUID(); users.push(id); await q("insert into auth.users values($1)", [id]); await q( "insert into public.profiles(id,display_name) values($1,$2) on conflict(id) do update set display_name=excluded.display_name", [id, `Player ${i}`], ); } const map = ( await q( `insert into public.datasets(owner_id,name,source,bounds,state,panorama_count) values($1,'Tournament fixture','plugin','{"minX":0,"minZ":0,"maxX":64,"maxZ":64}','published',5) returning id`, [users[0]], ) )[0].id; const manifest = { name: "Tournament fixture", bounds: { minX: 0, minZ: 0, maxX: 64, maxZ: 64 }, viewpoints: Array.from({ length: 5 }, (_, i) => ({ id: `p${i}`, x: 10 + i, y: 64, z: 10 + i, })), }; const rev = ( await q( "insert into public.revisions(dataset_id,asset_root,manifest) values($1,'fixture',$2) returning id", [map, JSON.stringify(manifest)], ) )[0].id; await q("update public.datasets set revision_id=$1 where id=$2", [rev, map]); const base = { name: "Community Cup", datasetId: map, difficulty: "normal", playerLimit: 32, roundCount: 2, roundTimeSeconds: 30, timerMode: "perMini", startsAt: new Date(Date.now() + 3600000).toISOString(), serverTimezone: "UTC", announceDiscord: true, }; const create = async (payload = {}) => ( await q("select public.create_tournament($1,$2) id", [ users[0], JSON.stringify({ ...base, ...payload }), ]) )[0].id; const voidEvent = await create({ difficulty: "void", roundTimeSeconds: 10, timerMode: "perMini", }); const voidRules = ( await q( "select difficulty_settings,round_time_seconds,timer_mode from public.tournaments where id=$1", [voidEvent], ) )[0]; assert.equal(voidRules.difficulty_settings.seconds, 10); assert.equal(voidRules.difficulty_settings.mapPan, false); assert.equal(voidRules.difficulty_settings.scoring, "strict"); assert.equal(voidRules.round_time_seconds, 10); assert.equal(voidRules.timer_mode, "perMini"); for (const settings of [ { roundTimeSeconds: 90, timerMode: "perMini" }, { roundTimeSeconds: 10, timerMode: "shared" }, ]) await assert.rejects( create({ difficulty: "void", ...settings }), /Void requires a separate 10-second timer/, ); await q("delete from public.tournaments where id=$1", [voidEvent]); const act = async (id, user, action = "view", payload = {}) => ( await q("select public.tournament_action($1,$2,$3,$4) value", [ id, user, action, JSON.stringify(payload), ]) )[0].value; for (const payload of [ { playerLimit: 33 }, { playerLimit: 1 }, { roundCount: 0 }, { roundTimeSeconds: 16 }, { serverTimezone: "Invalid/Zone" }, { startsAt: new Date(0).toISOString() }, ]) await assert.rejects(create(payload)); await assert.rejects( q("select public.create_tournament(null,$1)", [JSON.stringify(base)]), /Active account/, ); const tid = await create(); for (let i = 1; i < 32; i++) await act(tid, users[i], "join"); assert.equal((await act(tid, users[1], "join")).players.length, 32); await assert.rejects(act(tid, users[32], "join"), /full/); await assert.rejects(act(tid, users[1], "edit", {}), /owner/); await assert.rejects( act(tid, users[0], "edit", { playerLimit: 31 }), /exclude/, ); await assert.rejects(act(tid, users[0], "start"), /scheduled start/); const worker = randomUUID(); assert.equal( ( await q("select * from public.claim_tournament_announcements($1,$2)", [ worker, tid, ]) ).length, 0, ); await act(tid, users[0], "edit", { startsAt: new Date(Date.now() + 14 * 60000).toISOString(), }); assert.equal( ( await q("select * from public.claim_tournament_announcements($1,$2)", [ worker, tid, ]) ).length, 1, ); assert.equal( ( await q("select * from public.claim_tournament_announcements($1,$2)", [ randomUUID(), tid, ]) ).length, 0, ); await q( "update public.tournament_announcements set attempts=8,lease_until=clock_timestamp()-interval '1 second' where tournament_id=$1", [tid], ); assert.equal( ( await q("select * from public.claim_tournament_announcements($1,$2)", [ worker, tid, ]) ).length, 0, ); assert.equal( ( await q( "select state from public.tournament_announcements where tournament_id=$1", [tid], ) )[0].state, "failed", ); await act(tid, users[0], "edit", { startsAt: new Date(Date.now() + 13 * 60000).toISOString(), }); assert.equal( ( await q("select * from public.claim_tournament_announcements($1,$2)", [ worker, tid, ]) ).length, 1, ); await q( "update public.tournament_announcements set lease_until=clock_timestamp()-interval '1 second' where tournament_id=$1", [tid], ); assert.equal( ( await q("select * from public.claim_tournament_announcements($1,$2)", [ worker, tid, ]) ).length, 1, ); await act(tid, users[0], "edit", { announceDiscord: false }); assert.equal( ( await q( "select state from public.tournament_announcements where tournament_id=$1", [tid], ) )[0].state, "skipped", ); await q( "update public.tournaments set starts_at=clock_timestamp()-interval '1 second' where id=$1", [tid], ); await q("select public.tick_tournaments()"); let state = await act(tid, users[0]); assert.equal(state.status, "running"); assert.equal(state.currentRoom.players.length, 32); assert.equal(state.currentRoom.rounds.length, 5); assert.equal(state.currentRoom.difficultySettings.seconds, 30); assert.equal(state.currentRoom.difficultySettings.roundCount, 5); assert.equal(state.announcementState, "skipped"); assert.equal((await act(tid, users[1])).announcementState, null); assert(!JSON.stringify(state).includes("viewpoint")); for (const row of await q( "select access_hash from public.tournament_members where tournament_id=$1", [tid], )) assert(!JSON.stringify(state).includes(row.access_hash)); assert.equal((await act(tid, null)).currentRoom, null); await assert.rejects(act(tid, users[32], "join"), /Registration/); await assert.rejects( q( "select public.multiplayer_action($1,$2,'join','{\"name\":\"Intruder\"}')", [state.roomId, "f".repeat(64)], ), /already started/, ); await assert.rejects( q( "insert into public.multiplayer_players(room_id,access_hash,display_name,account_id) values($1,$2,'Intruder',$3)", [state.roomId, "f".repeat(64), users[32]], ), /Tournament membership/, ); await assert.rejects( q("select public.create_map_room($1,'normal','Host',32,$2,$3)", [ map, "a".repeat(64), users[0], ]), /16/, ); // Play all ten server-scored mini rounds; the owner wins perfect guesses. for (let outer = 0; outer < 2; outer++) { state = await act(tid, users[0]); const room = state.roomId; for (let mini = 0; mini < 5; mini++) { await q( "update public.multiplayer_rooms set transition_at=clock_timestamp()-interval '1 second' where id=$1", [room], ); await q("select public.tournament_tick($1)", [tid]); await q( "update public.multiplayer_rooms set transition_at=clock_timestamp()-interval '1 second' where id=$1 and status='loading'", [room], ); await q("select public.tournament_tick($1)", [tid]); const answer = ( await q( "select viewpoint from public.multiplayer_rounds where room_id=$1 and round_number=$2", [room, mini], ) )[0].viewpoint; await assert.rejects( act(tid, users[0], "guess", { round: mini, tournamentRound: outer - 1, x: answer.x, z: answer.z, }), /round changed/, ); state = await act(tid, users[0], "guess", { round: mini, tournamentRound: outer, x: answer.x, z: answer.z, score: 999999, }); assert.equal(state.players[0].total, outer * 25000 + mini * 5000); // open round remains private/unscored for (let i = 1; i < 32; i++) state = await act(tid, users[i], "guess", { round: mini, tournamentRound: outer, x: 64, z: 64, score: 999999, }); } } state = await act(tid, users[0]); assert.equal(state.status, "finished"); assert.equal(state.winnerId, users[0]); assert.equal(state.players[0].total, 50000); const finishedAt = state.finishedAt; await q("select public.tick_tournaments()"); assert.equal((await act(tid, users[0])).finishedAt, finishedAt); const board = (await q("select public.player_leaderboard() value"))[0].value; assert.equal(board.entries[0].player_id, users[0]); assert.equal(board.entries[0].rank, 1); assert.equal(board.entries[0].best_score, 0); assert.equal(board.entries[0].tournament_champion.tournament_id, tid); assert.equal( (await q("select public.player_leaderboard('normal') value"))[0].value .entries.length, 0, ); assert.equal( (await q("select public.player_leaderboard('all','nonexistent') value"))[0] .value.matching_players, 0, ); // Solo scores remain unchanged; winner insertion/de-duplication preserves pagination. for (let i = 0; i < 3; i++) { const sid = ( await q("select public.create_game($1,'normal',$2,$3) id", [ map, users[i], String(i).repeat(64), ]) )[0].id; await q("update public.game_sessions set current_round=5 where id=$1", [ sid, ]); await q("update public.game_rounds set result=$2 where session_id=$1", [ sid, JSON.stringify({ score: 1000 + i }), ]); } let ranked = (await q("select public.player_leaderboard() value"))[0].value; assert.equal(ranked.total_players, 3); assert.equal(ranked.entries[0].player_id, users[0]); assert.equal(ranked.entries[0].best_score, 5000); const secondPage = ( await q("select public.player_leaderboard('all','',2,2) value") )[0].value; assert.equal(secondPage.pages, 2); assert.equal(secondPage.entries.length, 1); assert.equal(secondPage.entries[0].rank, 3); const newer = await create({ announceDiscord: false }); await q( "update public.tournaments set status='finished',finished_at=clock_timestamp(),winner_id=$2 where id=$1", [newer, users[1]], ); ranked = (await q("select public.player_leaderboard() value"))[0].value; assert.equal(ranked.entries[0].player_id, users[1]); assert.equal(ranked.entries[0].tournament_champion.tournament_id, newer); assert.equal( ranked.entries.filter((p) => p.tournament_champion !== null).length, 1, ); await q( "update public.tournaments set finished_at=clock_timestamp()-interval '8 days' where id=$1", [tid], ); await q( "update public.tournaments set finished_at=clock_timestamp()-interval '8 days' where id=$1", [newer], ); ranked = (await q("select public.player_leaderboard() value"))[0].value; assert.equal(ranked.entries[0].player_id, users[2]); assert(ranked.entries.every((p) => p.tournament_champion === null)); for (const role of ["anon", "authenticated"]) for (const table of [ "tournaments", "tournament_members", "tournament_rounds", "tournament_announcements", ]) assert.equal( ( await q("select has_table_privilege($1,$2,'SELECT') allowed", [ role, `public.${table}`, ]) )[0].allowed, false, ); for (const role of ["anon", "authenticated"]) assert.equal( ( await q( "select has_function_privilege($1,'public.tournament_action(uuid,uuid,text,jsonb)','EXECUTE') allowed", [role], ) )[0].allowed, false, ); for (const fn of await q( "select proname,prosecdef,proconfig from pg_catalog.pg_proc p join pg_catalog.pg_namespace n on n.oid=p.pronamespace where n.nspname='public' and (p.proname like '%tournament%' or p.proname='player_leaderboard')", )) { assert.equal(fn.prosecdef, false, fn.proname); assert( fn.proconfig.some((setting) => setting.startsWith("search_path=")), fn.proname, ); } const alone = await create({ announceDiscord: false }); await q( "update public.tournaments set starts_at=clock_timestamp()-interval '1 second' where id=$1", [alone], ); assert.equal((await act(alone, users[0])).status, "cancelled"); // A shared round clock starts after loading and caps subsequent mini rounds. const shared = await create({ timerMode: "shared", roundCount: 1, roundTimeSeconds: 90, announceDiscord: false, }); await act(shared, users[1], "join"); await q( "update public.tournaments set starts_at=clock_timestamp()-interval '1 second' where id=$1", [shared], ); let sharedState = await act(shared, users[0]); assert.equal(sharedState.timerMode, "shared"); assert.equal(sharedState.roundDeadline, null); await act(shared, users[0], "ready", { round: 0, tournamentRound: 0 }); sharedState = await act(shared, users[1], "ready", { round: 0, tournamentRound: 0, }); const sharedDeadline = sharedState.roundDeadline; assert(sharedDeadline); assert.equal(sharedState.currentRoom.status, "playing"); const firstAnswer = ( await q( "select viewpoint from public.multiplayer_rounds where room_id=$1 and round_number=0", [sharedState.roomId], ) )[0].viewpoint; for (const user of users.slice(0, 2)) await act(shared, user, "guess", { round: 0, tournamentRound: 0, x: firstAnswer.x, z: firstAnswer.z, }); await q( "update public.multiplayer_rooms set transition_at=clock_timestamp()-interval '1 second' where id=$1", [sharedState.roomId], ); await act(shared, users[0], "ready", { round: 1, tournamentRound: 0 }); sharedState = await act(shared, users[1], "ready", { round: 1, tournamentRound: 0, }); assert.equal(sharedState.roundDeadline, sharedDeadline); assert.equal(sharedState.currentRoom.rounds[1].deadline, sharedDeadline); await q( "update public.tournament_rounds set deadline=clock_timestamp()-interval '1 second' where tournament_id=$1", [shared], ); await assert.rejects( act(shared, users[0], "guess", { round: 1, tournamentRound: 0, x: 64, z: 64, score: 999999, }), ); // An invalid action rolls back its transaction, so a normal poll settles it. sharedState = await act(shared, users[0]); assert.equal(sharedState.status, "finished"); assert.equal(sharedState.players[0].total, 5000); assert.equal( sharedState.currentRoom.rounds.filter((r) => r.closedAt !== null).length, 5, ); assert( sharedState.currentRoom.rounds .slice(1) .every((r) => r.results.every((result) => result.score === 0 && result.timedOut), ), ); await assert.rejects(create({ timerMode: "perMini", roundTimeSeconds: 615 })); await assert.rejects(create({ timerMode: "shared", roundTimeSeconds: 3015 })); const bigShared = await create({ timerMode: "shared", roundTimeSeconds: 3000, announceDiscord: false, }); assert.equal( (await act(bigShared, users[0])).difficultySettings.seconds, 600, ); await act(bigShared, users[1], "join"); await q( "update public.tournaments set starts_at=clock_timestamp()-interval '1 second' where id=$1", [bigShared], ); await act(bigShared, users[0], "ready", { round: 0, tournamentRound: 0 }); let bigState = await act(bigShared, users[1], "ready", { round: 0, tournamentRound: 0, }); const bigDeadline = bigState.roundDeadline; assert(bigDeadline); assert.equal(bigState.currentRoom.rounds[0].deadline, bigDeadline); assert(Date.parse(bigDeadline) - bigState.serverNow > 2990 * 1000); // The ordinary engine's 600-second mini timer must never cut a shared budget short. await q( "update public.multiplayer_rounds set deadline=clock_timestamp()-interval '1 second' where room_id=$1 and round_number=0", [bigState.roomId], ); bigState = await act(bigShared, users[0]); assert.equal(bigState.currentRoom.status, "playing"); assert.equal(bigState.roundDeadline, bigDeadline); assert.equal(bigState.currentRoom.rounds[0].deadline, bigDeadline); for (const user of users.slice(0, 2)) await act(bigShared, user, "guess", { round: 0, tournamentRound: 0, x: 10, z: 10, }); await q( "update public.multiplayer_rooms set transition_at=clock_timestamp()-interval '1 second' where id=$1", [bigState.roomId], ); await act(bigShared, users[0], "ready", { round: 1, tournamentRound: 0 }); bigState = await act(bigShared, users[1], "ready", { round: 1, tournamentRound: 0, }); assert.equal(bigState.roundDeadline, bigDeadline); assert.equal(bigState.currentRoom.rounds[1].deadline, bigDeadline); const moderated = await create({ announceDiscord: false }); await act(moderated, users[1], "join"); await q("update public.datasets set state='draft' where id=$1", [map]); assert.equal((await act(moderated, users[0])).status, "cancelled"); await q("update public.datasets set state='published' where id=$1", [map]); const abandoned = await create({ announceDiscord: false }); await act(abandoned, users[1], "join"); await q( "update public.tournaments set starts_at=clock_timestamp()-interval '1 second' where id=$1", [abandoned], ); await act(abandoned, users[0]); await q( "update public.profiles set account_status='banned' where id=any($1::uuid[])", [users.slice(0, 2)], ); assert.equal((await act(abandoned, null)).status, "cancelled"); await assert.rejects(create(), /Active account/); await q( "update public.profiles set account_status='active' where id=any($1::uuid[])", [users.slice(0, 2)], ); // Account removal must preserve event history instead of blocking Auth deletion. await q("delete from auth.users where id=$1", [users[0]]); assert.equal((await act(tid, null)).ownerId, null); console.log( "Tournament SQL passed: 32-player cap, admission/auth, private rooms, 2×5 mini-rounds, shared/per-mini clocks, server scores, schedules/outbox leases, immutable results, seven-day champion and account deletion.", ); } catch (error) { console.error( error.message, error.where ?? "", error.stack?.split("\n").slice(0, 3).join("\n"), ); process.exitCode = 1; } finally { await db.close(); }