Add MineGeo gameplay systems and interface
This commit is contained in:
247
scripts/test-admin-permissions-production.mjs
Normal file
247
scripts/test-admin-permissions-production.mjs
Normal file
@@ -0,0 +1,247 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { randomUUID, randomBytes } from "node:crypto";
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
process.loadEnvFile(".env.local");
|
||||
const origin = process.argv[2] || "https://minegeo.vercel.app";
|
||||
const db = createClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
process.env.SUPABASE_SECRET_KEY,
|
||||
{ auth: { persistSession: false, autoRefreshToken: false } },
|
||||
);
|
||||
const accounts = [],
|
||||
maps = [];
|
||||
const checked = (r) => {
|
||||
if (r.error) throw new Error(r.error.message);
|
||||
return r.data;
|
||||
};
|
||||
async function request(
|
||||
account,
|
||||
path,
|
||||
body,
|
||||
method = body === undefined ? "GET" : "POST",
|
||||
status = 200,
|
||||
) {
|
||||
const response = await fetch(`${origin}/api/v1/${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
origin,
|
||||
"content-type": "application/json",
|
||||
...(account?.cookie ? { cookie: account.cookie } : {}),
|
||||
},
|
||||
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
||||
signal: AbortSignal.timeout(60000),
|
||||
});
|
||||
const data = await response.json();
|
||||
assert.equal(response.status, status, `${path}: ${JSON.stringify(data)}`);
|
||||
return data;
|
||||
}
|
||||
async function account(role) {
|
||||
const email = `admin-permissions-${randomUUID()}@example.com`,
|
||||
password = randomBytes(24).toString("base64url");
|
||||
const { user } = checked(
|
||||
await db.auth.admin.createUser({
|
||||
email,
|
||||
password,
|
||||
email_confirm: true,
|
||||
user_metadata: { display_name: `Permissions ${role} fixture` },
|
||||
}),
|
||||
);
|
||||
const fixture = { id: user.id, cookie: "" };
|
||||
accounts.push(fixture);
|
||||
checked(
|
||||
await db.from("profiles").insert({
|
||||
id: user.id,
|
||||
role,
|
||||
display_name: `Permissions ${role} fixture`,
|
||||
}),
|
||||
);
|
||||
const response = await fetch(`${origin}/api/v1/auth`, {
|
||||
method: "POST",
|
||||
headers: { origin, "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
action: "login",
|
||||
email,
|
||||
password,
|
||||
next: "/moderation",
|
||||
}),
|
||||
});
|
||||
assert.equal(response.status, 200, "Fixture login failed");
|
||||
fixture.cookie = response.headers
|
||||
.getSetCookie()
|
||||
.map((value) => value.split(";")[0])
|
||||
.join("; ");
|
||||
assert.ok(fixture.cookie, "Login did not set a session cookie");
|
||||
return fixture;
|
||||
}
|
||||
try {
|
||||
const player = await account("contributor"),
|
||||
moderator = await account("moderator"),
|
||||
admin = await account("admin"),
|
||||
missing = randomUUID();
|
||||
const bounds = { minX: 0, minZ: 0, maxX: 64, maxZ: 64 };
|
||||
for (const state of ["review", "unpublished"]) {
|
||||
const map = checked(
|
||||
await db
|
||||
.from("datasets")
|
||||
.insert({
|
||||
owner_id: player.id,
|
||||
name: `Permissions ${state} fixture`,
|
||||
source: "plugin",
|
||||
bounds,
|
||||
state,
|
||||
panorama_count: 5,
|
||||
})
|
||||
.select("id")
|
||||
.single(),
|
||||
);
|
||||
maps.push(map.id);
|
||||
checked(
|
||||
await db.from("dataset_inputs").insert({
|
||||
dataset_id: map.id,
|
||||
validated: true,
|
||||
manifest: {
|
||||
format: "minegeo-dataset/v1",
|
||||
minecraftVersion: "1.21.11",
|
||||
dimension: "minecraft:overworld",
|
||||
source: "plugin",
|
||||
name: "Permissions map fixture",
|
||||
description: "Disposable permission verification",
|
||||
serverName: "Fixture server",
|
||||
north: "-z",
|
||||
bounds,
|
||||
viewpoints: Array.from({ length: 5 }, (_, i) => ({
|
||||
id: `p${i}`,
|
||||
x: i * 4,
|
||||
y: 64,
|
||||
z: i * 4,
|
||||
})),
|
||||
assets: [
|
||||
{ path: "terrain.json.gz", sha256: "a".repeat(64), bytes: 1 },
|
||||
],
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
await request(player, "moderation", undefined, "GET", 403);
|
||||
const queue = await request(moderator, "moderation");
|
||||
assert.equal(queue.role, "moderator");
|
||||
assert.deepEqual(queue.reports, []);
|
||||
assert.deepEqual(queue.appeals, []);
|
||||
assert.ok(
|
||||
queue.maps.every(
|
||||
(m) =>
|
||||
m.state === "review" ||
|
||||
(m.state === "published" && m.update_state === "review"),
|
||||
),
|
||||
);
|
||||
assert.ok(queue.maps.some((m) => m.id === maps[0]));
|
||||
assert.ok(!queue.maps.some((m) => m.id === maps[1]));
|
||||
assert.ok(
|
||||
(await request(moderator, `moderation/${maps[0]}/panoramas`)).locations
|
||||
.length === 5,
|
||||
);
|
||||
await request(
|
||||
moderator,
|
||||
`moderation/${maps[1]}/panoramas`,
|
||||
undefined,
|
||||
"GET",
|
||||
403,
|
||||
);
|
||||
for (const actor of [player, moderator]) {
|
||||
for (const [path, body, method] of [
|
||||
["moderation/users", undefined, "GET"],
|
||||
[
|
||||
`moderation/users/${player.id}`,
|
||||
{ prefix: "Spoof", icon: "star" },
|
||||
"PATCH",
|
||||
],
|
||||
["moderation/settings", { betaBadgeEnabled: false }, "PATCH"],
|
||||
[
|
||||
`moderation/appeals/${missing}`,
|
||||
{ decision: "approve", reason: "A valid reason here." },
|
||||
"POST",
|
||||
],
|
||||
[`moderation/reports/${missing}/panoramas`, undefined, "GET"],
|
||||
[
|
||||
`reports/${missing}`,
|
||||
{ decision: "confirm", reason: "A valid reason here." },
|
||||
"PATCH",
|
||||
],
|
||||
["moderation", { datasetId: maps[0], decision: "unpublish" }, "POST"],
|
||||
])
|
||||
await request(actor, path, body, method, 403);
|
||||
}
|
||||
await request(moderator, "moderation", {
|
||||
datasetId: maps[0],
|
||||
decision: "reject",
|
||||
reason: "Returned by the permission verification fixture.",
|
||||
});
|
||||
assert.equal(
|
||||
checked(
|
||||
await db.from("datasets").select("state").eq("id", maps[0]).single(),
|
||||
).state,
|
||||
"rejected",
|
||||
);
|
||||
const full = await request(admin, "moderation");
|
||||
assert.equal(full.role, "admin");
|
||||
assert.ok(full.maps.some((m) => m.id === maps[1]));
|
||||
assert.equal(
|
||||
(await request(admin, `moderation/users?q=${player.id}`)).users[0].id,
|
||||
player.id,
|
||||
);
|
||||
await request(
|
||||
admin,
|
||||
`moderation/users/${player.id}`,
|
||||
{ prefix: "Builder", icon: "gem" },
|
||||
"PATCH",
|
||||
);
|
||||
assert.equal(
|
||||
(await request(player, "profile")).profile.custom_prefix,
|
||||
"Builder",
|
||||
);
|
||||
await request(
|
||||
admin,
|
||||
`moderation/users/${player.id}`,
|
||||
{ prefix: null, icon: "star" },
|
||||
"PATCH",
|
||||
);
|
||||
await request(
|
||||
admin,
|
||||
"moderation/settings",
|
||||
{ betaBadgeEnabled: "invalid" },
|
||||
"PATCH",
|
||||
400,
|
||||
);
|
||||
await request(
|
||||
admin,
|
||||
`reports/${missing}`,
|
||||
{ decision: "dismiss", reason: "A valid reason here." },
|
||||
"PATCH",
|
||||
400,
|
||||
);
|
||||
await request(
|
||||
admin,
|
||||
`moderation/appeals/${missing}`,
|
||||
{ decision: "approve", reason: "A valid reason here." },
|
||||
"POST",
|
||||
400,
|
||||
);
|
||||
for (const actor of [player, moderator, admin])
|
||||
await request(actor, "profile", { role: "admin" }, "PATCH", 400);
|
||||
assert.equal((await request(admin, "profile")).profile.role, "admin");
|
||||
console.log(
|
||||
"Production permissions passed: signed-in players denied, moderator queue restricted, pending panoramas and feedback work, admin controls available, role spoofing denied.",
|
||||
);
|
||||
} finally {
|
||||
if (maps.length)
|
||||
checked(await db.from("review_decisions").delete().in("dataset_id", maps));
|
||||
for (const id of maps)
|
||||
checked(await db.from("datasets").delete().eq("id", id));
|
||||
for (const fixture of accounts) {
|
||||
if (fixture.cookie)
|
||||
await request(fixture, "auth/logout", {}).catch(() => {});
|
||||
checked(await db.from("profiles").delete().eq("id", fixture.id));
|
||||
checked(await db.auth.admin.deleteUser(fixture.id));
|
||||
}
|
||||
console.log("Disposable permission fixtures removed.");
|
||||
}
|
||||
Reference in New Issue
Block a user