import assert from "node:assert/strict"; import { existsSync } from "node:fs"; import { createHash } from "node:crypto"; import { createClient } from "@supabase/supabase-js"; if (existsSync(".env.local")) process.loadEnvFile(".env.local"); const origin = new URL( process.argv[2] || process.env.MINEGEO_URL || "http://localhost:3000", ).origin; const db = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.SUPABASE_SECRET_KEY, { auth: { persistSession: false, autoRefreshToken: false } }, ); let sessionId, cookie; async function request(path, body, options = {}) { return fetch(origin + path, { method: body === undefined ? "GET" : "POST", headers: { ...(body === undefined ? {} : { "content-type": "application/json", origin }), ...(cookie ? { cookie } : {}), ...options.headers, }, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(30000), }); } async function json(path, body) { const response = await request("/api/v1/" + path, body); assert.equal( response.status, 200, `${path}: ${await response.clone().text()}`, ); assert.match(response.headers.get("cache-control"), /no-store/); return response.json(); } try { for (const path of [ "/", "/servers", "/downloads", "/about", "/log-in", "/fonts/VT323-Regular.ttf", "/images/example-map.webp", "/tools/plugin/minegeo-plugin-1.21.11.jar", "/tools/mod/minegeo-fabric-1.21.11.jar", ]) { const response = await request(path); assert.equal(response.status, 200, path); assert.equal(response.headers.get("x-content-type-options"), "nosniff"); assert.equal(response.headers.get("x-frame-options"), "DENY"); assert.ok((await response.arrayBuffer()).byteLength > 0, path); } assert.equal((await json("health")).ok, true); for (const path of [ "/servers/not-a-uuid", "/servers/00000000-0000-4000-8000-000000000000", "/api/v1/catalog/extra", ]) { assert.equal((await request(path)).status, 404, path); } for (const path of ["datasets", "tokens", "profile"]) { assert.equal( (await request("/api/v1/" + path)).status, 401, `guest ${path}`, ); } const forbidden = await request( "/api/v1/sessions", {}, { headers: { origin: "https://foreign.example" } }, ); assert.equal(forbidden.status, 403); const malformed = await fetch(origin + "/api/v1/sessions", { method: "POST", headers: { "content-type": "application/json", origin }, body: "{", signal: AbortSignal.timeout(30000), }); assert.equal(malformed.status, 400); assert.equal((await malformed.json()).error, "Invalid JSON request"); const catalog = await json("catalog"); assert.ok( catalog.maps.length, "Publish at least one map before verifying gameplay", ); const map = catalog.maps[0]; const created = await request("/api/v1/sessions", { datasetId: map.id, difficulty: "hard", }); assert.equal(created.status, 200, await created.clone().text()); sessionId = (await created.json()).id; cookie = created.headers.get("set-cookie").split(";")[0]; const session = await json("sessions/" + sessionId); assert.equal(session.rounds.length, 5); assert.ok( session.rounds.every( (round) => round.faces.length === 6 && round.faces.every(Boolean), ), ); assert.equal( (await fetch(origin + "/api/v1/sessions/" + sessionId)).status, 404, "another guest cannot read the session", ); // Exercise actual signed Storage reads, not just the URL response shape. for (const url of [session.map, ...session.rounds[0].faces]) { const image = await fetch(url, { signal: AbortSignal.timeout(30000) }); assert.equal(image.status, 200); assert.match(image.headers.get("content-type"), /image\/webp/); assert.ok((await image.arrayBuffer()).byteLength > 0); } for (let round = 0; round < 5; round++) { const first = await json(`sessions/${sessionId}/activate`, { round }); const duplicate = await json(`sessions/${sessionId}/activate`, { round }); assert.equal( first.deadline, duplicate.deadline, "activation does not reset the timer", ); assert.equal( ( await request(`/api/v1/sessions/${sessionId}/guess`, { round, x: map.bounds.maxX + 1, z: map.bounds.minZ, }) ).status, 400, ); const guess = { round, x: (map.bounds.minX + map.bounds.maxX) / 2, z: (map.bounds.minZ + map.bounds.maxZ) / 2, }; const result = await json(`sessions/${sessionId}/guess`, guess); assert.ok(result.score >= 0 && result.score <= 5000); assert.equal(result.timedOut, false); assert.deepEqual( await json(`sessions/${sessionId}/guess`, guess), result, "submission retries are idempotent", ); } assert.equal((await json("sessions/" + sessionId)).round, 5); console.log( "Production HTTP/API/Storage smoke passed: pages, headers, privacy, origin checks, signed media and all five rounds.", ); } finally { if (sessionId && cookie) { const secret = cookie.slice(cookie.indexOf("=") + 1); const { error } = await db .from("game_sessions") .delete() .eq("id", sessionId) .eq("access_hash", createHash("sha256").update(secret).digest("hex")) .is("owner_id", null); if (error) throw new Error("Smoke session cleanup failed: " + error.message); } }