import assert from "node:assert/strict"; import { randomUUID } from "node:crypto"; import fs from "node:fs"; import { chromium } from "@playwright/test"; import { createClient } from "@supabase/supabase-js"; import sharp from "sharp"; import { datasetSchema, faces } from "../shared/protocol"; import { readMapArchive } from "../shared/map-archive"; process.loadEnvFile(".env.local"); const origin = new URL(process.argv[2] || "https://minegeo.site").origin; const db = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SECRET_KEY!, { auth: { persistSession: false, autoRefreshToken: false } }, ); const output = ".data/sharing-browser"; fs.mkdirSync(output, { recursive: true }); const records: { users: string[]; datasets: string[]; server?: string; assets: string[]; } = { users: [], datasets: [], assets: [] }; const save = () => fs.writeFileSync(`${output}/fixtures.json`, JSON.stringify(records)); const checked = (result: { data: T; error: { message: string } | null }) => { if (result.error) throw new Error(result.error.message); return result.data!; }; const run = randomUUID(); let browser: Awaited> | undefined; try { const accounts = []; for (const role of ["Owner", "Friend"]) { const email = `zip-${role.toLowerCase()}-${run}@example.com`, password = `Zip-test-${randomUUID()}!`; const created = await db.auth.admin.createUser({ email, password, email_confirm: true, user_metadata: { display_name: `ZIP verification ${role}` }, }); if (created.error || !created.data.user) throw new Error("Could not create a temporary fixture account"); const account = created.data.user; accounts.push({ email, password, id: account.id }); records.users.push(account.id); save(); checked( await db .from("profiles") .upsert({ id: account.id, display_name: `ZIP verification ${role}` }), ); } const viewpoints = Array.from({ length: 5 }, (_, i) => ({ id: `p${i}`, x: i, y: 70, z: i, })); const manifest = datasetSchema.parse({ format: "minegeo-dataset/v1", minecraftVersion: "26.2", dimension: "minecraft:overworld", source: "plugin", name: "ZIP verification world", description: "Temporary browser fixture", serverName: `ZIP verification ${run}`, bounds: { minX: 0, minZ: 0, maxX: 32, maxZ: 32 }, north: "-z", viewpoints, assets: [{ path: "terrain.json.gz", bytes: 1, sha256: "0".repeat(64) }], }); const original = checked( await db.rpc("create_upload", { p_owner: accounts[0].id, p_manifest: manifest, }), ); records.datasets.push(original); save(); records.server = checked( await db.from("datasets").select("server_id").eq("id", original).single(), ).server_id; save(); const image = await sharp({ create: { width: 256, height: 256, channels: 3, background: "#729343" }, }) .webp() .toBuffer(); records.assets = [ "map.webp", ...viewpoints.flatMap((p) => [ ...faces.map((f) => `${p.id}/${f}.webp`), `${p.id}/preview.webp`, ]), ]; save(); for (const path of records.assets) checked( await db.storage .from("minegeo-media") .upload(`${original}/${path}`, image, { contentType: "image/webp" }), ); checked( await db .from("dataset_inputs") .update({ validated: true }) .eq("dataset_id", original), ); checked( await db .from("datasets") .update({ state: "draft", panorama_count: 5, map_path: `${original}/map.webp`, cover_path: `${original}/p0/preview.webp`, }) .eq("id", original), ); browser = await chromium.launch({ headless: true }); const owner = await browser.newContext({ locale: "en-US" }); const friend = await browser.newContext({ locale: "en-US" }); for (const [index, context] of [owner, friend].entries()) { const response = await context.request.post(`${origin}/api/v1/auth`, { headers: { origin }, data: { action: "login", email: accounts[index].email, password: accounts[index].password, }, }); assert.equal(response.status(), 200, "Fixture login failed"); } const ownerPage = await owner.newPage(); await ownerPage.goto(`${origin}/dashboard/${original}`); const downloadEvent = ownerPage.waitForEvent("download"); await ownerPage .getByRole("button", { name: "Export ZIP", exact: true }) .click(); const download = await downloadEvent; const zipPath = `${output}/map.zip`; await download.saveAs(zipPath); const restored = await readMapArchive( new Uint8Array(fs.readFileSync(zipPath)), ); assert.equal(restored.archive.originalOwner, "ZIP verification Owner"); assert.equal(restored.archive.source, "plugin"); console.log("Owner exported a portable ZIP in the browser."); const page = await friend.newPage(); await page.goto(`${origin}/servers`); await page.getByRole("button", { name: "Import", exact: true }).click(); await page.getByLabel("Map ZIP file", { exact: true }).setInputFiles(zipPath); await page.waitForURL(/\/dashboard\/[0-9a-f-]{36}$/, { timeout: 120000 }); const copy = page.url().split("/").pop()!; records.datasets.push(copy); save(); await page .getByRole("heading", { name: "Owned by ZIP verification Owner" }) .waitFor(); await page .getByRole("button", { name: "Start exploring", exact: true }) .waitFor({ timeout: 120000 }); await page.screenshot({ path: `${output}/imported-desktop.png`, fullPage: true, }); assert.equal( await page.getByRole("button", { name: "Export ZIP", exact: true }).count(), 0, ); assert.equal( await page .getByRole("button", { name: "Submit for review →", exact: true }) .isDisabled(), true, ); const blocked = await friend.request.post( `${origin}/api/v1/datasets/${copy}/submit`, { headers: { origin }, data: {} }, ); assert.equal(blocked.status(), 403); assert.match( (await blocked.json()).error, /Owned by "ZIP verification Owner"/, ); const foreignExport = await friend.request.get( `${origin}/api/v1/datasets/${original}/export`, ); assert.equal(foreignExport.status(), 404); const foreignCopy = await owner.request.post(`${origin}/api/v1/sessions`, { headers: { origin }, data: { datasetId: copy, difficulty: "normal" }, }); assert.notEqual(foreignCopy.status(), 200); console.log( "Friend imported and processed the ZIP; original-owner attribution, export restriction, and submission block passed.", ); await page .getByRole("button", { name: "Start exploring", exact: true }) .click(); await page.waitForURL(/\/play\//); const sessionId = page.url().split("/").pop()!; const sessionResponse = await friend.request.get( `${origin}/api/v1/sessions/${sessionId}`, ); assert.equal(sessionResponse.status(), 200); const session = await sessionResponse.json(); assert.equal(session.rounds.length, 5); assert.ok( session.rounds.every( (r: { faces: string[] }) => r.faces.length === 6 && r.faces.every(Boolean), ), ); const roomResponse = await friend.request.post(`${origin}/api/v1/rooms`, { headers: { origin }, data: { datasetId: copy, difficulty: "normal", displayName: "ZIP Friend", playerLimit: 4, }, }); assert.equal(roomResponse.status(), 200, "Private room failed"); const roomId = (await roomResponse.json()).id; const invited = await browser.newContext(); const join = await invited.request.post( `${origin}/api/v1/rooms/${roomId}/join`, { headers: { origin }, data: { displayName: "ZIP Guest" } }, ); assert.equal(join.status(), 200); console.log( "Private solo game and invited multiplayer guest passed without moderation.", ); await page.setViewportSize({ width: 390, height: 844 }); await page.goto(`${origin}/servers`); await page.getByRole("button", { name: "Import", exact: true }).click(); await page.getByRole("heading", { name: "Import a shared map" }).waitFor(); await page.screenshot({ path: `${output}/import-mobile.png`, fullPage: true, }); assert.equal( await page.evaluate( () => document.documentElement.scrollWidth > window.innerWidth, ), false, ); console.log("Mobile import dialog passed at 390px."); } finally { await browser?.close(); save(); console.log("Temporary fixture IDs saved for cleanup."); }