Gitea grant executor (steps 0-8, settle, restart, revoke and fire, sweep, status), the grants.* verbs and mosaic grants, the 3j-3k migration with gitea_users and gitea_grants, and the section 9 tests with a fake Gitea and a scratch gitea/gitea:1.27.1 X-G3 test. Build notes in agents/megavolt/work/queue-236/BUILD.md.
139 lines
9.0 KiB
JavaScript
139 lines
9.0 KiB
JavaScript
// 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 </);
|
|
});
|