import { test } from "node:test"; import assert from "node:assert/strict"; import { createRest, RestOutcome, USER_AGENT } from "../src/rest.mjs"; function fakeFetch(script) { const calls = []; const fetch = async (url, init) => { calls.push({ url, init }); const next = script.shift(); if (!next) throw new Error("fake fetch: no scripted response"); if (next.throw) throw Object.assign(new Error(next.throw), { code: "ECONNRESET" }); return { status: next.status, text: async () => (next.body === undefined ? "" : JSON.stringify(next.body)) }; }; return { fetch, calls }; } const TOKEN = "T0KEN-not-real-1234567890"; test("rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference", async () => { const f = fakeFetch([{ status: 200, body: { id: "m1" } }]); const rest = createRest({ token: TOKEN, fetch: f.fetch }); const r = await rest.createMessage("c1", { content: "hi", nonce: "n1", replyTo: "orig" }); assert.deepEqual(r, { messageId: "m1", status: 200 }); const call = f.calls[0]; assert.equal(call.url, "https://discord.com/api/v10/channels/c1/messages"); assert.equal(call.init.method, "POST"); assert.equal(call.init.headers.Authorization, `Bot ${TOKEN}`); assert.equal(call.init.headers["User-Agent"], USER_AGENT); assert.deepEqual(JSON.parse(call.init.body), { content: "hi", nonce: "n1", enforce_nonce: true, allowed_mentions: { parse: [], replied_user: false }, message_reference: { message_id: "orig", fail_if_not_exists: false }, }); }); test("rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown", async () => { const waits = []; const sleep = async (ms) => waits.push(ms); let f = fakeFetch([{ status: 429, body: { retry_after: 0.25 } }, { status: 200, body: { id: "m2" } }]); let rest = createRest({ token: TOKEN, fetch: f.fetch, sleep }); const r = await rest.createMessage("c1", { content: "hi", nonce: "n2" }); assert.equal(r.messageId, "m2"); assert.deepEqual(waits, [250]); assert.equal(f.calls.length, 2); f = fakeFetch([{ status: 403, body: { code: 50001, message: "Missing Access" } }]); rest = createRest({ token: TOKEN, fetch: f.fetch, sleep }); await assert.rejects(rest.createMessage("c1", { content: "hi", nonce: "n3" }), (err) => err instanceof RestOutcome && err.kind === "refused" && err.details.status === 403); f = fakeFetch([{ status: 502, body: { message: "bad gateway" } }]); rest = createRest({ token: TOKEN, fetch: f.fetch, sleep }); await assert.rejects(rest.createMessage("c1", { content: "hi", nonce: "n4" }), (err) => err instanceof RestOutcome && err.kind === "unknown"); f = fakeFetch([{ throw: "socket hang up" }]); rest = createRest({ token: TOKEN, fetch: f.fetch, sleep }); await assert.rejects(rest.createMessage("c1", { content: "hi", nonce: "n5" }), (err) => err instanceof RestOutcome && err.kind === "unknown" && !err.message.includes(TOKEN)); // Bounded 429 retries: four 429s in a row end refused. f = fakeFetch([429, 429, 429, 429].map(() => ({ status: 429, body: { retry_after: 0.01 } }))); rest = createRest({ token: TOKEN, fetch: f.fetch, sleep }); await assert.rejects(rest.createMessage("c1", { content: "hi", nonce: "n6" }), (err) => err.kind === "refused" && err.details.status === 429); assert.equal(f.calls.length, 4); }); test("rest: content and nonce limits are enforced locally; typing never throws", async () => { const f = fakeFetch([{ status: 500 }, { throw: "down" }]); const rest = createRest({ token: TOKEN, fetch: f.fetch }); await assert.rejects(rest.createMessage("c", { content: "x".repeat(2001), nonce: "n" }), /1..2000/); await assert.rejects(rest.createMessage("c", { content: "x", nonce: "n".repeat(26) }), /1..25/); await rest.typing("c"); await rest.typing("c"); assert.equal(f.calls.length, 2); }); test("rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws", async () => { const f = fakeFetch([{ status: 204 }, { status: 403, body: { message: "Missing Permissions", code: 50013 } }, { throw: "down" }]); const logs = []; const rest = createRest({ token: TOKEN, fetch: f.fetch, log: (m) => logs.push(m) }); assert.equal(await rest.react("c1", "m1", "\u{1F440}"), true); assert.equal(f.calls[0].url, "https://discord.com/api/v10/channels/c1/messages/m1/reactions/%F0%9F%91%80/@me"); assert.equal(f.calls[0].init.method, "PUT"); assert.equal(f.calls[0].init.body, undefined); assert.equal(f.calls[0].init.headers.Authorization, `Bot ${TOKEN}`); assert.equal(await rest.react("c1", "m2", "\u{1F440}"), false); assert.equal(await rest.react("c1", "m3", "\u{1F440}"), false); assert.equal(logs.length, 2); assert.match(logs[0], /HTTP 403/); assert.ok(logs.every((l) => !l.includes(TOKEN))); await assert.rejects(rest.react("c1", "m4", ""), /emoji required/); });