Files
MineGeo/tests/discord.test.ts
pavel444-byte 1f254b29a2
All checks were successful
Deploy MineGeo to Dokploy / deploy (push) Successful in 5m57s
Update MineGeo domain and harden process supervision
2026-10-15 20:35:39 +05:00

294 lines
9.0 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import {
announcementMessage,
discordConfiguration,
DiscordClient,
DiscordDeliveryError,
closedPartyMessage,
} from "../lib/discord-client";
import { deliverDiscordAnnouncement } from "../lib/discord-delivery";
import type { DiscordAnnouncement } from "../shared/discord";
const channel = "123456789012345678",
messageId = "223456789012345678",
target = "00000000-0000-4000-8000-000000000001";
const job: DiscordAnnouncement = {
id: target,
kind: "party",
target_id: target,
state: "sending",
attempts: 1,
next_attempt_at: new Date(0).toISOString(),
lease_owner: "worker",
lease_until: new Date(100000).toISOString(),
channel_id: null,
message_id: null,
last_error: null,
created_at: new Date(0).toISOString(),
sent_at: null,
};
const source = {
id: target,
name: "Map @everyone [click](https://evil.example)",
owner: "Host <@123>",
difficulty: "normal",
players: 1,
limit: 8,
};
test("closed Discord parties turn red, lose their join prompt, and delete idempotently", async () => {
const payload = closedPartyMessage("https://minegeo.site", target);
assert.equal(payload.embeds[0].color, 0xed4245);
assert.ok(payload.embeds[0].description.includes("five minutes"));
assert.ok(!JSON.stringify(payload).includes("Join this party"));
assert.deepEqual(payload.allowed_mentions, { parse: [] });
const methods: string[] = [];
const client = new DiscordClient("private-token", async (url, init) => {
assert.equal(
url,
`https://discord.com/api/v10/channels/${channel}/messages/${messageId}`,
);
methods.push(init!.method!);
if (init?.method === "PATCH") {
assert.deepEqual(JSON.parse(String(init.body)), payload);
return Response.json({ id: messageId, channel_id: channel });
}
assert.equal(init?.body, undefined);
return new Response(null, { status: 204 });
});
assert.equal(
await client.closeParty(
channel,
messageId,
"https://minegeo.site",
target,
),
true,
);
await client.remove(channel, messageId);
assert.deepEqual(methods, ["PATCH", "DELETE"]);
const missing = new DiscordClient("token", async () =>
Response.json({ code: 10008 }, { status: 404 }),
);
assert.equal(
await missing.closeParty(
channel,
messageId,
"https://minegeo.site",
target,
),
false,
);
await missing.remove(channel, messageId);
await assert.rejects(
new DiscordClient("token", async () =>
Response.json({ code: 10003 }, { status: 404 }),
).remove(channel, messageId),
/channel was not found/,
);
await assert.rejects(
new DiscordClient("token", async () =>
Response.json({ retry_after: 10 }, { status: 429 }),
).remove(channel, messageId),
(e) => e instanceof DiscordDeliveryError && e.retryAfter === 10,
);
});
test("Discord config requires a public HTTPS site and valid channel IDs without exposing a public token", () => {
const env = {
MINEGEO_URL: "https://minegeo.site",
DISCORD_BOT_TOKEN: "test-token",
DISCORD_PARTIES_CHANNEL_ID: channel,
};
assert.equal(discordConfiguration(env).partyChannel, channel);
for (const url of [
"http://localhost:3000",
"https://user:secret@example.com",
"https://example.com/private?key=secret",
])
assert.equal(
discordConfiguration({ ...env, MINEGEO_URL: url }).origin,
null,
);
for (const id of ["bad", "123", `${channel}/messages`])
assert.equal(
discordConfiguration({ ...env, DISCORD_PARTIES_CHANNEL_ID: id })
.partyChannel,
null,
);
assert.equal(
discordConfiguration({ ...env, DISCORD_BOT_TOKEN: "secret\nheader" }).token,
null,
);
});
test("Discord announcements use fixed public links, bounded escaped text, suppressed mentions and a stable nonce", () => {
const body = announcementMessage(
job.id,
"party",
"https://minegeo.site",
source,
);
assert.deepEqual(body.allowed_mentions, { parse: [] });
assert.equal(
body.embeds[0].url,
`https://minegeo.site/multiplayer/${target}`,
);
assert.ok(!body.embeds[0].title.includes("[click]("));
assert.equal(body.nonce.length, 24);
assert.equal(body.enforce_nonce, true);
assert.equal(
body.nonce,
announcementMessage(job.id, "party", "https://minegeo.site", source)
.nonce,
);
assert.notEqual(
body.nonce,
announcementMessage(
"another",
"party",
"https://minegeo.site",
source,
).nonce,
);
const map = announcementMessage(job.id, "map", "https://minegeo.site", {
...source,
name: "A".repeat(500),
locations: 10,
});
assert.equal(map.embeds[0].title.length, 256);
assert.equal(map.embeds[0].fields[1].value, "10");
});
test("Discord REST sends the bot token only to Discord, confirms message and channel identity, and honors rate limits", async () => {
const payload = announcementMessage(
job.id,
"party",
"https://minegeo.site",
source,
);
const client = new DiscordClient("private-token", async (url, init) => {
assert.equal(
url,
`https://discord.com/api/v10/channels/${channel}/messages`,
);
assert.equal(
(init?.headers as Record<string, string>).Authorization,
"Bot private-token",
);
assert.equal(init?.redirect, "error");
assert.deepEqual(JSON.parse(String(init?.body)), payload);
return Response.json({ id: messageId, channel_id: channel });
});
assert.equal(await client.send(channel, payload), messageId);
const limited = new DiscordClient("token", async () =>
Response.json({ retry_after: 65.4 }, { status: 429 }),
);
await assert.rejects(
limited.send(channel, payload),
(e) =>
e instanceof DiscordDeliveryError && e.retryAfter === 66 && !e.permanent,
);
for (const status of [401, 403, 404]) {
const bad = new DiscordClient("secret-token", async () =>
Response.json({ message: "secret-token internal raw error" }, { status }),
);
await assert.rejects(
bad.send(channel, payload),
(e) =>
e instanceof DiscordDeliveryError &&
e.permanent &&
!e.message.includes("secret-token"),
);
}
await assert.rejects(
new DiscordClient("token", async () =>
Response.json({ id: messageId, channel_id: "wrong" }),
).send(channel, payload),
/did not confirm/,
);
await assert.rejects(
new DiscordClient("token", async () => {
throw new Error("token-secret");
}).send(channel, payload),
/could not be reached/,
);
});
test("durable delivery skips unavailable parties, records acknowledged sends and preserves recoverable failures", async () => {
const run = async (options: {
source: typeof source | null;
fail?: Error;
attempts?: number;
}) => {
const patches: Partial<DiscordAnnouncement>[] = [],
requests: unknown[] = [];
await deliverDiscordAnnouncement(
{ ...job, attempts: options.attempts ?? 1 },
{
origin: "https://minegeo.site",
channel,
now: () => 1000,
load: async () => options.source,
client: {
send: async (...args) => {
requests.push(args);
if (options.fail) throw options.fail;
return messageId;
},
},
finish: async (patch) => {
patches.push(patch);
},
},
);
return { patch: patches.at(-1)!, requests };
};
const skipped = await run({ source: null });
assert.equal(skipped.patch.state, "skipped");
assert.equal(skipped.requests.length, 0);
const sent = await run({ source });
assert.equal(sent.patch.state, "sent");
assert.equal(sent.patch.message_id, messageId);
assert.equal(sent.patch.lease_owner, null);
const rate = await run({
source,
fail: new DiscordDeliveryError("Rate limited", 65),
});
assert.equal(rate.patch.state, "pending");
assert.equal(Date.parse(rate.patch.next_attempt_at!), 66000);
assert.equal(
(
await run({
source,
fail: new DiscordDeliveryError("Bad credentials", 60, true),
})
).patch.state,
"failed",
);
assert.equal(
(await run({ source, attempts: 8, fail: new Error("private raw error") }))
.patch.state,
"failed",
);
});
test("Discord setup check only reads bot/channel details and rejects non-server text channels", async () => {
const paths: string[] = [];
const bot = new DiscordClient("token", async (url, init) => {
assert.equal(init?.method, "GET");
paths.push(String(url));
return Response.json(
String(url).endsWith("@me")
? { bot: true, username: "MineGeo" }
: { guild_id: channel, type: 0 },
);
});
assert.equal((await bot.check([channel, channel])).botName, "MineGeo");
assert.equal(paths.length, 2);
await assert.rejects(
new DiscordClient("token", async (url) =>
Response.json(
String(url).endsWith("@me")
? { bot: true, username: "Test" }
: { guild_id: channel, type: 2 },
),
).check([channel]),
/text or announcement channel/,
);
});