// The controller's build-source check asks gitea_branch_protection_get for a repository's rules, which the forge // answers to a repository's administrator alone. The tools' account is not one (novox/hq ADR 0365 ยง2), so the read // goes with the forge's administrator account, and only the read (ADR 0375, issue 772): a protection edit through the // tools still gets the forge's refusal. import { test, after } from "node:test"; import assert from "node:assert/strict"; import { createServer } from "node:http"; import { GiteaAdmin, GiteaClient } from "../dist/client.js"; import { ConfiguredToken } from "../dist/token.js"; import { getGiteaTools } from "../dist/tools/index.js"; const adminBasic = "Basic " + Buffer.from("mesh-admin:secret").toString("base64"); const rules = [ { rule_name: "main", enable_push: false, enable_status_check: true, status_check_contexts: ["mesh/merge-gate"], block_admin_merge_override: true }, { branch_name: "release", enable_push: false }, ]; const seen: string[] = []; const server = createServer((req, res) => { const path = (req.url ?? "").replace(/^\/api\/v1/, ""); const as = req.headers.authorization === adminBasic ? "admin" : "tools"; seen.push(`${req.method} ${path} as ${as}`); const send = (status: number, body: unknown) => { res.writeHead(status, { "Content-Type": "application/json" }); res.end(JSON.stringify(body)); }; if (path.includes("/branch_protections")) { if (as !== "admin") return send(403, { message: "user should be an owner or a collaborator with admin write of a repository" }); if (req.method === "GET") return send(200, rules); } send(404, { message: "not found" }); }); await new Promise((r) => server.listen(0, "127.0.0.1", () => r())); after(() => new Promise((r) => server.close(() => r()))); const { port } = server.address() as { port: number }; const url = `http://127.0.0.1:${port}`; const tools = getGiteaTools(new GiteaClient(url, new ConfiguredToken("tools-token")), () => new GiteaAdmin(url, "mesh-admin", "secret")); const tool = (name: string) => tools.find((t) => t.name === name)!; test("gitea_branch_protection_get answers every rule, read with the administrator account the tools' token cannot read with", async () => { const out: any = await tool("gitea_branch_protection_get").run({ owner: "novox", repo: "mesh-controller" }); assert.deepEqual(out.rules.map((r: any) => r.rule), ["main", "release"]); }); test("gitea_branch_protection_get answers the rule for a branch, null when it has none", async () => { const main: any = await tool("gitea_branch_protection_get").run({ owner: "novox", repo: "mesh-controller", branch: "main" }); assert.equal(main.branch, "main"); assert.equal(main.rule.rule, "main"); assert.equal(main.rule.admin_may_override, false); const none: any = await tool("gitea_branch_protection_get").run({ owner: "novox", repo: "mesh-controller", branch: "dev" }); assert.equal(none.rule, null); }); test("gitea_branch_protection_set still gets the forge's refusal, as the tools' account", async () => { await assert.rejects( tool("gitea_branch_protection_set").run({ owner: "novox", repo: "mesh-controller", branch: "main", status_checks: "mesh/merge-gate" }), /403/, ); assert.ok(!seen.some((s) => /^(PATCH|POST|PUT|DELETE) .*branch_protections.* as admin$/.test(s)), seen.join("\n")); });