185 lines
5.7 KiB
JavaScript
185 lines
5.7 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { randomUUID, randomBytes } from "node:crypto";
|
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
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 = [],
|
|
rooms = [];
|
|
const checked = (r) => {
|
|
if (r.error) throw new Error(r.error.message);
|
|
return r.data;
|
|
};
|
|
await mkdir(".data/discord-check", { recursive: true });
|
|
const journal = () =>
|
|
writeFile(
|
|
".data/discord-check/http-fixtures.json",
|
|
JSON.stringify({ accounts, rooms }),
|
|
);
|
|
function session() {
|
|
const cookies = new Map();
|
|
return async (
|
|
path,
|
|
body,
|
|
method = body === undefined ? "GET" : "POST",
|
|
expected = 200,
|
|
) => {
|
|
const response = await fetch(`${origin}/api/v1/${path}`, {
|
|
method,
|
|
headers: {
|
|
origin,
|
|
"content-type": "application/json",
|
|
cookie: [...cookies]
|
|
.map(([key, value]) => `${key}=${value}`)
|
|
.join("; "),
|
|
},
|
|
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
signal: AbortSignal.timeout(60000),
|
|
});
|
|
for (const cookie of response.headers.getSetCookie()) {
|
|
const part = cookie.split(";")[0],
|
|
at = part.indexOf("=");
|
|
cookies.set(part.slice(0, at), part.slice(at + 1));
|
|
}
|
|
const data = await response.json();
|
|
assert.equal(response.status, expected, `${path}: ${JSON.stringify(data)}`);
|
|
assert.match(response.headers.get("cache-control") ?? "", /no-store/);
|
|
return data;
|
|
};
|
|
}
|
|
async function account(role) {
|
|
const email = `discord-check-${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: `Discord ${role} fixture` },
|
|
}),
|
|
);
|
|
accounts.push(user.id);
|
|
await journal();
|
|
checked(
|
|
await db
|
|
.from("profiles")
|
|
.insert({ id: user.id, role, display_name: `Discord ${role} fixture` }),
|
|
);
|
|
const api = session();
|
|
await api("auth", { action: "login", email, password, next: "/moderation" });
|
|
return api;
|
|
}
|
|
try {
|
|
const admin = await account("admin"),
|
|
moderator = await account("moderator"),
|
|
visitor = session();
|
|
const status = await admin("moderation/discord");
|
|
assert.equal(typeof status.botConfigured, "boolean");
|
|
assert.equal(typeof status.pending, "number");
|
|
assert.ok(
|
|
!Object.keys(status).some((key) => /token|secret|channel_?id/i.test(key)),
|
|
);
|
|
await visitor("moderation/discord", undefined, "GET", 401);
|
|
for (const [body, method] of [
|
|
[undefined, "GET"],
|
|
[{ action: "check" }, "POST"],
|
|
[{ action: "retry" }, "POST"],
|
|
])
|
|
await moderator("moderation/discord", body, method, 403);
|
|
if (
|
|
!status.botConfigured ||
|
|
!status.publicUrlConfigured ||
|
|
(!status.mapsConfigured && !status.partiesConfigured)
|
|
)
|
|
await admin("moderation/discord", { action: "check" }, "POST", 400);
|
|
const dispatch = await fetch(`${origin}/api/integrations/discord/dispatch`);
|
|
assert.equal(dispatch.status, 401);
|
|
console.log(
|
|
"Discord HTTP: admin-only setup, secret-free status, disabled configuration and private dispatch verified.",
|
|
);
|
|
const dataset = checked(
|
|
await db
|
|
.from("datasets")
|
|
.select("id")
|
|
.eq("state", "published")
|
|
.is("parent_dataset_id", null)
|
|
.is("imported_from_id", null)
|
|
.limit(1)
|
|
.single(),
|
|
);
|
|
const host = session(),
|
|
friend = session(),
|
|
outsider = session();
|
|
const created = await host("rooms", {
|
|
datasetId: dataset.id,
|
|
difficulty: "normal",
|
|
displayName: "Discord host fixture",
|
|
playerLimit: 4,
|
|
});
|
|
rooms.push(created.id);
|
|
await journal();
|
|
const room = await host(`rooms/${created.id}`);
|
|
assert.equal(typeof room.discord.available, "boolean");
|
|
assert.equal(room.discord.state, null);
|
|
await outsider(`rooms/${created.id}/discord`, {}, "POST", 403);
|
|
await friend(`rooms/${created.id}/join`, {
|
|
displayName: "Discord friend fixture",
|
|
});
|
|
assert.equal((await friend(`rooms/${created.id}`)).discord, undefined);
|
|
await friend(`rooms/${created.id}/discord`, {}, "POST", 403);
|
|
await host(
|
|
`rooms/${created.id}/discord`,
|
|
{ channelId: "123456789012345678" },
|
|
"POST",
|
|
400,
|
|
);
|
|
if (
|
|
!status.botConfigured ||
|
|
!status.publicUrlConfigured ||
|
|
!status.partiesConfigured
|
|
) {
|
|
assert.equal(room.discord.available, false);
|
|
await host(`rooms/${created.id}/discord`, {}, "POST", 503);
|
|
}
|
|
assert.equal(
|
|
checked(
|
|
await db
|
|
.from("discord_announcements")
|
|
.select("id")
|
|
.eq("kind", "party")
|
|
.eq("target_id", created.id),
|
|
).length,
|
|
0,
|
|
"disabled and unauthorized actions never queue a notification",
|
|
);
|
|
await host(`rooms/${created.id}/close`, {});
|
|
console.log(
|
|
"Discord HTTP passed: guests and members cannot publish, destination overrides denied, unconfigured hosts get an actionable error, no test Discord messages sent.",
|
|
);
|
|
} finally {
|
|
for (const id of rooms) {
|
|
checked(
|
|
await db
|
|
.from("discord_announcements")
|
|
.delete()
|
|
.eq("kind", "party")
|
|
.eq("target_id", id),
|
|
);
|
|
checked(await db.from("multiplayer_rooms").delete().eq("id", id));
|
|
}
|
|
for (const id of accounts) {
|
|
checked(await db.from("profiles").delete().eq("id", id));
|
|
checked(await db.auth.admin.deleteUser(id));
|
|
}
|
|
await writeFile(
|
|
".data/discord-check/http-fixtures.json",
|
|
JSON.stringify({ accounts: [], rooms: [] }),
|
|
);
|
|
console.log("Discord HTTP fixtures removed.");
|
|
}
|