import { test } from "node:test"; import assert from "node:assert/strict"; import { openDirect } from "../src/notify.mjs"; import { RestOutcome } from "../src/rest.mjs"; import { DiscordError } from "../src/errors.mjs"; import { makeRoot, makeDeployment, IDS } from "./helpers.mjs"; const CHANNEL = "100000000000000900"; const TOKEN = "MTAw.abcdefghijklmnopqrstuvwxyz0123456789"; function fakeFetch(script) { const calls = []; const fetch = async (url, init) => { calls.push({ url, method: init.method, body: init.body ? JSON.parse(init.body) : null, auth: init.headers.Authorization }); const next = script.shift(); if (!next) throw new Error("fake fetch: no scripted response"); return { status: next.status, text: async () => (next.body === undefined ? "" : JSON.stringify(next.body)) }; }; return { fetch, calls }; } const secretFree = (err) => ![IDS.owner, CHANNEL, TOKEN].some((s) => err.message.includes(s) || JSON.stringify(err.details ?? {}).includes(s)); test("notify: the DM channel opens once, every send carries the nonce, and only the message id comes back", async () => { const { dataRoot } = makeDeployment(makeRoot(), { dmRecipient: IDS.owner }); const f = fakeFetch([ { status: 200, body: { id: CHANNEL, type: 1 } }, { status: 200, body: { id: "300000000000000001" } }, { status: 200, body: { id: "300000000000000002" } }, ]); const dm = openDirect({ dataRoot, name: "test-seat", fetch: f.fetch, base: "http://fake" }); assert.deepEqual(await dm.send({ content: "one", nonce: "dm1" }), { messageId: "300000000000000001" }); assert.deepEqual(await dm.send({ content: "two", nonce: "dm2" }), { messageId: "300000000000000002" }); assert.deepEqual(f.calls.map((c) => `${c.method} ${c.url}`), [ "POST http://fake/users/@me/channels", `POST http://fake/channels/${CHANNEL}/messages`, `POST http://fake/channels/${CHANNEL}/messages`, ]); assert.deepEqual(f.calls[0].body, { recipient_id: IDS.owner }); assert.equal(f.calls[1].body.nonce, "dm1"); assert.equal(f.calls[1].body.enforce_nonce, true); assert.deepEqual(f.calls[1].body.allowed_mentions, { parse: [], replied_user: false }); assert.ok(f.calls.every((c) => c.auth === `Bot ${TOKEN}`)); assert.equal(JSON.stringify(dm).includes(TOKEN) || JSON.stringify(dm).includes(IDS.owner), false); }); test("notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time", async () => { const { dataRoot } = makeDeployment(makeRoot(), { dmRecipient: IDS.owner }); const f = fakeFetch([ { status: 403, body: { code: 50007, message: `Cannot send messages to ${IDS.owner}` } }, { status: 200, body: { id: CHANNEL } }, { status: 502, body: { message: `channel ${CHANNEL} upstream` } }, { status: 200, body: { id: "300000000000000003" } }, { status: 403, body: { code: 50007, message: "Cannot send messages to this user" } }, { status: 200, body: { id: CHANNEL } }, { status: 200, body: { id: "300000000000000004" } }, ]); const dm = openDirect({ dataRoot, name: "test-seat", fetch: f.fetch, base: "http://fake" }); await assert.rejects(dm.send({ content: "x", nonce: "n1" }), (e) => e instanceof RestOutcome && e.kind === "refused" && e.details.status === 403 && secretFree(e)); await assert.rejects(dm.send({ content: "x", nonce: "n1" }), (e) => e instanceof RestOutcome && e.kind === "unknown" && secretFree(e)); // unknown keeps the channel; the retry reuses it with the same nonce. assert.deepEqual(await dm.send({ content: "x", nonce: "n1" }), { messageId: "300000000000000003" }); await assert.rejects(dm.send({ content: "y", nonce: "n2" }), (e) => e.kind === "refused" && secretFree(e)); assert.deepEqual(await dm.send({ content: "y", nonce: "n2" }), { messageId: "300000000000000004" }); assert.equal(f.calls.filter((c) => c.url.endsWith("/users/@me/channels")).length, 3); }); test("notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use", async () => { let calls = 0; const fetch = async () => { calls++; throw new Error("network"); }; const none = makeDeployment(makeRoot()); assert.throws(() => openDirect({ dataRoot: none.dataRoot, name: "test-seat", fetch }), (e) => e instanceof DiscordError && e.exitCode === 2 && /no dmRecipient/.test(e.message)); const loose = makeDeployment(makeRoot(), { dmRecipient: IDS.owner }, { tokenMode: 0o644 }); assert.throws(() => openDirect({ dataRoot: loose.dataRoot, name: "test-seat", fetch }), (e) => e instanceof DiscordError && /mode 0600/.test(e.message) && !e.message.includes(TOKEN)); assert.throws(() => openDirect({ dataRoot: none.dataRoot, name: "absent", fetch }), (e) => e instanceof DiscordError && /binding not found/.test(e.message)); assert.equal(calls, 0); });