// Row 236 (#1742, #436b r5): `mosaic grants status|run|revoke|sweep|admin`. A fake transport records the // calls; no bus runs here. The bus side of each verb is in packages/bus/tests/gitea-grant.test.mjs. The // admin token is a fake made here, read once from stdin and never printed. import test from "node:test"; import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { randomBytes } from "node:crypto"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { main } from "../src/cli.mjs"; import { io, tmp } from "./helpers.mjs"; const MOSAIC = fileURLToPath(new URL("../../../scripts/mosaic", import.meta.url)); const STATUS = { admin: { url: "https://git.example.test", account: "mosaic-grant", rotateBy: "2026-12-20", setAt: "2026-10-21T00:00:00.000Z", rotationDue: false }, adminTokenLeft: "mosaic-grant-bootstrap", users: [{ agent: "coder", user: "demo-coder", id: 7 }], grants: [{ agent: "coder", user: "demo-coder", tokenId: 9, projects: ["app", "lib"], rotateBy: "2026-11-20", decision: "d1" }], open: [{ decision: "d2", agent: "coder", spentAtNextStart: true }], running: [{ decision: "d3", agent: null, step: null }], settlePending: [{ decision: "d4", agent: "coder" }], tokenUnknown: [{ agent: "ops", decision: "d5" }], serviceLeft: [{ agent: "coder", user: "demo-coder", id: 11 }], revoking: [{ agent: "coder", revoke: "r1" }], }; function deps(calls) { const answer = (who) => (business) => async (verb, args) => { calls.push({ who, business, verb, args }); if (verb === "grants.status") return STATUS; if (verb === "grants.run") return { decision: args.decision, agent: "coder", started: true }; return { ok: true }; }; return { system: { dataRoot: "/nonexistent" }, transport: answer("human"), testerTransport: answer("tester") }; } const withStdin = (x, text) => (x.stdin.end(text), x); test("row 236: grants status prints one line per entry, and --json the answer", async () => { const calls = []; const x = io(); await main(["grants", "status", "gitea", "--business", "acme"], x, deps(calls)); assert.deepEqual(calls, [{ who: "human", business: "acme", verb: "grants.status", args: { service: "gitea" } }]); assert.equal(x.out.text, [ "admin: mosaic-grant on https://git.example.test, rotate by 2026-12-20", "admin token left: mosaic-grant-bootstrap", "grant\tcoder\tdemo-coder\ttoken 9\tapp,lib\trotate by 2026-11-20\td1", "open\td2\tcoder", "running\td3\t\tspent", "settle-pending\td4\tcoder", "token-unknown\tops\td5", "left\tcoder\tdemo-coder\t11", "revoking\tcoder\tr1", ].join("\n") + "\n"); const y = io(); await main(["grants", "status", "gitea", "--json", "--tester", "--business", "acme"], y, deps(calls)); assert.deepEqual(JSON.parse(y.out.text), STATUS); assert.equal(calls[1].who, "tester"); const z = io(); const due = { ...deps([]), transport: () => async () => ({ ...STATUS, admin: { ...STATUS.admin, rotationDue: true }, adminTokenLeft: null }) }; await main(["grants", "status", "gitea", "--business", "acme"], z, due); assert.match(z.out.text, /^admin: mosaic-grant on https:\/\/git\.example\.test, rotate by 2026-12-20 \(due\)\ngrant\t/); const none = { ...deps([]), transport: () => async () => ({ ...STATUS, admin: null, adminTokenLeft: null }) }; const w = io(); await main(["grants", "status", "gitea", "--business", "acme"], w, none); assert.match(w.out.text, /^admin: none\n/); }); test("row 236: run, revoke, sweep and admin rotate and remove send their args", async () => { const calls = []; const x = io(); await main(["grants", "run", "d1", "--business", "acme"], x, deps(calls)); await main(["grants", "revoke", "coder", "gitea", "--reason", "left the team", "--business", "acme"], x, deps(calls)); await main(["grants", "revoke", "coder", "gitea", "--reason", "r", "--tester", "--business", "acme"], x, deps(calls)); await main(["grants", "sweep", "gitea", "--tester", "--business", "acme"], x, deps(calls)); await main(["grants", "admin", "rotate", "gitea", "--rotate-by", "2027-01-20", "--business", "acme"], x, deps(calls)); await main(["grants", "admin", "rotate", "gitea", "--finish", "--business", "acme"], x, deps(calls)); await main(["grants", "admin", "remove", "gitea", "--business", "acme"], x, deps(calls)); assert.deepEqual(calls.map(({ who, verb, args }) => ({ who, verb, args })), [ { who: "human", verb: "grants.run", args: { decision: "d1" } }, { who: "human", verb: "grants.revoke", args: { service: "gitea", agent: "coder", reason: "left the team" } }, { who: "tester", verb: "grants.revoke", args: { service: "gitea", agent: "coder", reason: "r" } }, { who: "tester", verb: "grants.sweep", args: { service: "gitea" } }, { who: "human", verb: "grants.admin.rotate", args: { service: "gitea", rotateBy: "2027-01-20" } }, { who: "human", verb: "grants.admin.rotate", args: { service: "gitea", finish: true } }, { who: "human", verb: "grants.admin.remove", args: { service: "gitea" } }, ]); assert.match(x.out.text, /^\{\n {2}"decision": "d1",\n {2}"agent": "coder",\n {2}"started": true\n\}\n/); }); test("row 236: admin set sends the stdin token once to grants.admin.set and prints no value", async () => { const calls = []; const token = randomBytes(20).toString("hex"); const x = withStdin(io(), token + "\n"); await main(["grants", "admin", "set", "gitea", "--url", "https://git.example.test", "--account", "mosaic-grant", "--rotate-by", "2026-12-20", "--business", "acme"], x, deps(calls)); assert.equal(calls.length, 1); assert.equal(calls[0].verb, "grants.admin.set"); const { token: sent, ...rest } = calls[0].args; assert.ok(sent === token + "\n", "the stdin token went to the bus as read"); assert.deepEqual(rest, { service: "gitea", url: "https://git.example.test", account: "mosaic-grant", rotateBy: "2026-12-20" }); assert.ok(!x.out.text.includes(token) && !x.err.text.includes(token), "no token value printed"); // An empty stdin, and one too long, refuse before any call. await assert.rejects(main(["grants", "admin", "set", "gitea", "--url", "u", "--account", "a", "--rotate-by", "2026-12-20", "--business", "acme"], withStdin(io(), "\n"), deps(calls)), (e) => (assert.equal(e.exitCode, 4), assert.match(e.message, /no token on stdin/), true)); await assert.rejects(main(["grants", "admin", "set", "gitea", "--url", "u", "--account", "a", "--rotate-by", "2026-12-20", "--business", "acme"], withStdin(io(), "x".repeat(20000)), deps(calls)), (e) => (assert.equal(e.exitCode, 2), assert.match(e.message, /too long/), true)); assert.equal(calls.length, 1); }); test("row 236: a malformed grants command is a usage error and calls nothing", async () => { const calls = []; for (const argv of [ ["grants"], ["grants", "status"], ["grants", "status", "host"], ["grants", "status", "gitea", "x"], ["grants", "run"], ["grants", "run", "d1", "d2"], ["grants", "run", "d1", "--tester"], ["grants", "revoke", "coder", "gitea"], ["grants", "revoke", "coder", "host", "--reason", "r"], ["grants", "revoke", "gitea", "--reason", "r"], ["grants", "sweep"], ["grants", "sweep", "host"], ["grants", "sweep", "gitea", "--reason", "r"], ["grants", "admin"], ["grants", "admin", "set", "gitea"], ["grants", "admin", "set", "gitea", "--url", "u", "--account", "a"], ["grants", "admin", "set", "gitea", "--url", "u", "--account", "a", "--rotate-by", "d", "--tester"], ["grants", "admin", "rotate", "gitea"], ["grants", "admin", "rotate", "gitea", "--finish", "--rotate-by", "2027-01-01"], ["grants", "admin", "rotate", "gitea", "--finish", "--finish"], ["grants", "admin", "remove", "gitea", "--tester"], ["grants", "admin", "purge", "gitea"], ["grants", "status", "gitea", "--business"], ["grants", "status", "gitea", "--force"], ["grants", "constructor", "gitea"], ["grants", "__proto__", "gitea"], ["grants", "admin", "constructor", "gitea"], ["grants", "status", "gitea", "--json", "--json"], ]) await assert.rejects(main([...argv], io(), deps(calls)), (e) => (assert.equal(e.exitCode, 4, argv.join(" ")), assert.match(e.message, /mosaic grants/), true)); assert.equal(calls.length, 0); }); test("row 236: a bus refusal exits 2 with its code", async () => { const refusing = { system: { dataRoot: "/nonexistent" }, transport: () => async () => { throw new Error("grant-admin-missing"); } }; await assert.rejects(main(["grants", "run", "d1", "--business", "acme"], io(), refusing), (e) => (assert.equal(e.exitCode, 2), assert.match(e.message, /grant-admin-missing/), true)); }); test("row 236: scripts/mosaic sends grants to packages/cli, not the seat CLI", (t) => { const root = tmp(t, "mosaic-cli-grants-route-"); const env = { PATH: process.env.PATH, HOME: join(root, "home"), MOSAIC_CONFIG: join(root, "none.json") }; const r = spawnSync(MOSAIC, ["grants", "purge", "gitea"], { env, encoding: "utf8" }); assert.equal(r.status, 4); assert.match(r.stderr, /^mosaic: mosaic grants status gitea/); assert.doesNotMatch(r.stderr, /mosaic launch