import assert from "node:assert/strict"; import { test } from "node:test"; import { did, load, now } from "../../../atlibs/rust/src/harness.mjs"; import { runModule } from "../../../atlibs/javascript/src/run-module/index.js"; const url = new URL("../../target/wasm32-unknown-unknown/release/atirc.wasm", import.meta.url); const { module, run, handle } = await load(url); const start = (fields) => ({ did, now, ...fields, state: null, event: { type: "start" } }); // handle(), answering every call with answer(call) and recording { nsid, params, input, proxy }. async function recorded(entry, input, answer) { const calls = []; const ran = await handle(entry, input, { respond: ({ nsid, params, input, proxy }) => (calls.push({ nsid, ...(params && { params }), ...(input && { input }), ...(proxy && { proxy }) }), answer()) }); return { ...ran, calls }; } const ENTRIES = ["read", "post", "irc_post"]; const PROXY = "did:web:irc.fedfork.com#atirc"; const channel = "#alice_bsky_social"; const message = (seq, text = `line ${seq}`) => ({ seq, channel, did, nick: "alice", text, ts: "2026-11-08T00:00:00Z", source: "xrpc" }); test("module has no imports and exports the ABI", () => { assert.equal(WebAssembly.Module.imports(module).length, 0); const names = WebAssembly.Module.exports(module).map((e) => e.name); for (const name of ["memory", "alloc", ...ENTRIES]) assert.ok(names.includes(name), name); }); test("read defaults to the handle channel and pages through atirc via the proxy", async () => { const pages = [ { messages: Array.from({ length: 200 }, (_, i) => message(i + 1)), cursor: 200 }, { messages: [message(201), message(202)], cursor: 202 }, ]; const ran = await recorded("read", { handle: "Alice.bsky.social", limit: 3 }, () => ({ ok: true, output: pages.shift() })); assert.deepEqual(ran.calls[0], { nsid: "chat.atirc.list", params: { channel, limit: "200", cursor: "0" }, proxy: PROXY }); assert.equal(ran.calls[1].params.cursor, "200"); assert.deepEqual(ran.result, { channel, messages: [message(200), message(201), message(202)], cursor: 202, more: false }); }); test("read stops after six full pages and says there is more", async () => { let seq = 0; const ran = await recorded("read", { channel: "#x", cursor: 7 }, () => ({ ok: true, output: { messages: Array.from({ length: 200 }, () => message(++seq)), cursor: seq } })); assert.equal(ran.calls.length, 6); assert.equal(ran.calls[0].params.cursor, "7"); assert.equal(ran.result.more, true); assert.equal(ran.result.cursor, 1200); assert.equal(ran.result.messages.length, 50); }); test("read needs a channel or handle, and a valid channel", () => { assert.match(run("read", start({})).error, /channel or handle/); assert.match(run("read", start({ channel: "no-hash" })).error, /channel must be/); assert.match(run("read", start({ channel: "#a b" })).error, /channel must be/); }); test("post sends one proxied chat.atirc.post and returns the message", async () => { const ran = await recorded("post", { channel: "#atirc", text: " hi ", nick: "" }, () => ({ ok: true, output: message(9, "hi") })); assert.deepEqual(ran.calls, [{ nsid: "chat.atirc.post", input: { text: "hi", channel: "#atirc" }, proxy: PROXY }]); assert.deepEqual(ran.result, message(9, "hi")); assert.deepEqual(run("post", start({ text: "x" })).calls[0].input, { text: "x" }); }); test("post rejects empty text and surfaces a failed call", async () => { assert.match(run("post", start({ text: " " })).error, /text is required/); assert.match((await recorded("post", { text: "x" }, () => ({ ok: false, error: "Forbidden: channel is bound to another handle" }))).error, /Forbidden/); }); // The host side of `proxy`: runModule validates it and hands it to call(). test("runModule passes proxy to call and rejects a malformed one", async () => { const ctx = { wasm: { run: async ({ entry, input }) => run(entry, input) } }; const seen = []; const ran = await runModule(ctx, { module, calls: ["chat.atirc.post"] }, "post", { did, text: "hi" }, async (nsid, request) => { seen.push([nsid, request]); return message(1, "hi"); }); assert.deepEqual(seen.map(([nsid, { params, input, proxy }]) => [nsid, { params, input, proxy }]), [["chat.atirc.post", { params: undefined, input: { text: "hi" }, proxy: PROXY }]]); assert.equal(ran.result.seq, 1); const bad = { wasm: { run: async ({ input }) => (input.event.type === "start" ? { calls: [{ id: "x", nsid: "chat.atirc.post", proxy: "https://evil" }] } : { result: input.event, done: true }) } }; const refused = await runModule(bad, { module }, "post", { did }, async () => ({})); assert.match(refused.result.error, /proxy must be did#service/); }); const b64url = (o) => Buffer.from(JSON.stringify(o)).toString("base64url"); const token = `${b64url({ alg: "ES256" })}.${b64url({ endpoint: "endpointabc", handle: "alice.bsky.social", nick: "alice-bot" })}.sig`; test("irc_post plans one iroh atirc/irc/1 exchange from the token's endpoint claim", async () => { const welcome = ":irc.fedfork.com 001 alice-bot :Welcome\r\n:alice-bot!alice-bot@x JOIN #alice_bsky_social\r\nERROR :Closing link\r\n"; const ran = await recorded("irc_post", { token, text: "hi" }, () => ({ ok: true, output: { output: welcome } })); assert.deepEqual(ran.calls, [{ nsid: "com.fedfork.exec.iroh.connect", input: { ticket: "endpointabc", alpn: "atirc/irc/1", send: `PASS ${token}\r\nNICK alice-bot\r\nUSER alice-bot 0 * :alice-bot\r\nJOIN #alice_bsky_social\r\nPRIVMSG #alice_bsky_social :hi\r\nQUIT :done\r\n`, }, }]); assert.deepEqual(ran.result, { channel: "#alice_bsky_social", nick: "alice-bot", transport: "iroh", alpn: "atirc/irc/1" }); }); test("irc_post surfaces refusals and rejects bad input before any call", async () => { const reply = (output) => () => ({ ok: true, output: { output } }); assert.match((await recorded("irc_post", { token, text: "hi" }, reply(":s 433 * alice-bot :Nickname is already in use\r\n"))).error, /refused: .*433/); assert.match((await recorded("irc_post", { token, text: "hi" }, reply("ERROR :bad token\r\n"))).error, /not registered: ERROR/); assert.match((await recorded("irc_post", { token, text: "hi", nick: "other" }, reply(":s 001 other :w\r\n"))).error, /did not join/); assert.match(run("irc_post", start({ text: "hi" })).error, /token/); assert.match(run("irc_post", start({ token: "nope", text: "hi" })).error, /JWT/); assert.match(run("irc_post", start({ token, text: "a\nQUIT" })).error, /one line/); assert.match(run("irc_post", start({ token, text: "hi", nick: "a b" })).error, /one word/); assert.equal(run("irc_post", start({ token, text: "hi", channel: "#x" })).state.channel, "#x"); });