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).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[] = [], 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/, ); });