import assert from "node:assert/strict"; 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()) { if (file.endsWith("fix_difficulty_preset_names.sql")) await assert.rejects( db.query( "insert into public.difficulty_presets(name,seconds,zoom) values('Normal',90,true)", ), (error) => error.code === "23514", ); await db.exec(await readFile(`supabase/migrations/${file}`, "utf8")); } const valid = [ "Normal", "Speed 90", "Hard", "Тест", "中文", "😎😎", "😎".repeat(40), "a".repeat(40), "a\\b", ]; for (const name of valid) await db.query( "insert into public.difficulty_presets(name,seconds,zoom) values($1,90,true)", [name], ); for (const name of [ "", "a", "😎", "a".repeat(41), "😎".repeat(41), " leading", "trailing ", "x\nname", "x\u0085name", "x\u202ename", "x\u2066name", ]) await assert.rejects( db.query( "insert into public.difficulty_presets(name,seconds,zoom) values($1,90,true)", [name], ), (error) => error.code === "23514", ); for (const seconds of [0, 14, 16, 601]) await assert.rejects( db.query( "insert into public.difficulty_presets(name,seconds,zoom) values('Invalid clock',$1,true)", [seconds], ), (error) => error.code === "23514", ); await assert.rejects( db.query( "insert into public.difficulty_presets(name,seconds,zoom) values('normal',90,true)", ), (error) => error.code === "23505", ); const preset = ( await db.query( "insert into public.difficulty_presets(name,seconds,zoom) values('Playable preset 45',45,false) returning id", ) ).rows[0].id; for (const [id, seconds] of [ ["easy", 180], ["normal", 90], ["hard", 30], ]) assert.equal( ( await db.query("select public.resolve_difficulty_preset($1) preset", [ id, ]) ).rows[0].preset.seconds, seconds, ); await assert.rejects( db.query( "select public.resolve_difficulty_preset('00000000-0000-4000-8000-000000000000')", ), /Choose a saved difficulty/, ); assert.equal( ( await db.query("select public.resolve_difficulty_preset($1) preset", [ preset.toUpperCase(), ]) ).rows[0].preset.id, preset, ); const owner = ( await db.query( "insert into auth.users values(gen_random_uuid()) returning id", ) ).rows[0].id; const map = ( await db.query( `insert into public.datasets(owner_id,name,source,bounds,state,panorama_count) values($1,'Difficulty fixture','plugin','{"minX":0,"minZ":0,"maxX":64,"maxZ":64}','published',5) returning id`, [owner], ) ).rows[0].id; const manifest = { bounds: { minX: 0, minZ: 0, maxX: 64, maxZ: 64 }, viewpoints: Array.from({ length: 5 }, (_, i) => ({ id: `p${i}`, x: i, y: 65, z: i, })), }; const revision = ( await db.query( "insert into public.revisions(dataset_id,asset_root,manifest) values($1,'fixture',$2) returning id", [map, JSON.stringify(manifest)], ) ).rows[0].id; await db.query("update public.datasets set revision_id=$1 where id=$2", [ revision, map, ]); const solo = ( await db.query("select public.create_game($1,$2,$3,$4) id", [ map, preset, owner, "a".repeat(64), ]) ).rows[0].id; const multi = ( await db.query("select public.create_map_room($1,$2,'Host',2,$3,$4) id", [ map, preset, "b".repeat(64), owner, ]) ).rows[0].id; for (const [table, id] of [ ["game_sessions", solo], ["multiplayer_rooms", multi], ]) assert.deepEqual( ( await db.query( `select difficulty_settings from public.${table} where id=$1`, [id], ) ).rows[0].difficulty_settings, { id: preset, name: "Playable preset 45", seconds: 45, zoom: false, pan: true, compass: true, coordinates: true, mapZoom: true, scoring: "normal", }, ); await assert.rejects( db.query( "update public.difficulty_presets set scoring='unsupported' where id=$1", [preset], ), (error) => error.code === "23514", ); await db.query( "update public.difficulty_presets set pan=false,compass=false,coordinates=false,map_zoom=false,scoring='strict' where id=$1", [preset], ); const settings = ( await db.query("select public.resolve_difficulty_preset($1) settings", [ preset, ]) ).rows[0].settings; assert.deepEqual(settings, { id: preset, name: "Playable preset 45", seconds: 45, zoom: false, pan: false, compass: false, coordinates: false, mapZoom: false, scoring: "strict", }); // Existing sessions retain the original curve even after the library changes. assert.equal( ( await db.query( "select difficulty_settings->>'scoring' scoring from public.game_sessions where id=$1", [solo], ) ).rows[0].scoring, "normal", ); for (const scoring of ["strict", "normal", "gentle"]) { await db.query( "update public.difficulty_presets set scoring=$1 where id=$2", [scoring, preset], ); const sid = ( await db.query("select public.create_game($1,$2,$3,$4) id", [ map, preset, owner, "score-" + scoring, ]) ).rows[0].id; assert.equal( ( await db.query( "select count(*)::int total from public.game_rounds where session_id=$1", [sid], ) ).rows[0].total, 5, ); await db.query("select public.activate_round($1,0,$2)", [ sid, "score-" + scoring, ]); const point = ( await db.query( "select viewpoint from public.game_rounds where session_id=$1 and round_number=0", [sid], ) ).rows[0].viewpoint; const expected = ( await db.query("select public.score_difficulty_guess($1,$2,$3) points", [ Math.hypot(32 - point.x, 32 - point.z), Math.hypot(64, 64), scoring, ]) ).rows[0].points; const actual = ( await db.query("select public.submit_guess($1,0,$2,32,32) result", [ sid, "score-" + scoring, ]) ).rows[0].result; assert.equal(actual.score, expected); const room = ( await db.query( "select public.create_map_room($1,$2,'Scoring host',2,$3,$4) id", [map, preset, "a".repeat(64), owner], ) ).rows[0].id; await db.query( "select public.multiplayer_action($1,$2,'join','{\"name\":\"Guest\"}')", [room, "c".repeat(64)], ); await db.query("select public.multiplayer_action($1,$2,'start')", [ room, "a".repeat(64), ]); await db.query( "select public.multiplayer_action($1,$2,'ready','{\"round\":0}')", [room, "a".repeat(64)], ); await db.query( "select public.multiplayer_action($1,$2,'ready','{\"round\":0}')", [room, "c".repeat(64)], ); const mp = ( await db.query( "select viewpoint from public.multiplayer_rounds where room_id=$1 and round_number=0", [room], ) ).rows[0].viewpoint; await db.query( 'select public.multiplayer_action($1,$2,\'guess\',\'{"round":0,"x":32,"z":32}\')', [room, "a".repeat(64)], ); const scored = ( await db.query( "select result from public.multiplayer_guesses where room_id=$1 and round_number=0", [room], ) ).rows[0].result; const expectedMulti = ( await db.query("select public.score_difficulty_guess($1,$2,$3) points", [ Math.hypot(32 - mp.x, 32 - mp.z), Math.hypot(64, 64), scoring, ]) ).rows[0].points; assert.equal(scored.score, expectedMulti); } const decay = ( await db.query( "select public.score_difficulty_guess(20,100,'strict') strict,public.score_difficulty_guess(20,100,'normal') normal,public.score_difficulty_guess(20,100,'gentle') gentle", ) ).rows[0]; assert.ok(decay.strict < decay.normal && decay.normal < decay.gentle); assert.equal( ( await db.query( "select public.score_difficulty_guess(0,100,'strict') points", ) ).rows[0].points, 5000, ); await assert.rejects( db.query("select public.score_difficulty_guess(1,100,'unknown')"), /Invalid scoring rules/, ); assert.equal( ( await db.query( "select has_function_privilege('anon','public.score_difficulty_guess(double precision,double precision,text)','EXECUTE') allowed", ) ).rows[0].allowed, false, ); assert.equal( ( await db.query( "select has_table_privilege('anon','public.difficulty_presets','INSERT') allowed", ) ).rows[0].allowed, false, ); console.log( "Difficulty SQL regressions passed: original failure reproduced, normal/Unicode names, unsafe names, duplicates, time limits, solo/multiplayer rules and permissions.", ); } finally { await db.close(); }