import assert from "node:assert/strict"; import { test } from "node:test"; import { createServer } from "node:http"; // A pull request's merge check (novox/hq to-be 45 §9): each new head announced once, and the verdict set // where the pull request shows it — an error never as a success. test("each open pull request's new head is announced once, and nothing closed or merged", async () => { const { headsToAnnounce } = await import("../pulls.ts"); const pulls = [ { number: 1, title: "a", state: "open", merged: false, head_sha: "aaa", base: "main", html_url: "" }, { number: 2, title: "b", state: "open", merged: false, head_sha: "bbb", html_url: "" }, { number: 3, title: "c", state: "closed", merged: true, head_sha: "ccc", html_url: "" }, { number: 4, title: "d", state: "open", merged: false, html_url: "" }, ]; const announced = { "novox/mesh-catalog#1@main": "aaa", "novox/mesh-catalog#2@": "old" }; assert.deepEqual(headsToAnnounce("novox/mesh-catalog", pulls, announced).map((p) => p.number), [2], "only a head not announced, of a pull request that is open"); }); test("a pull request retargeted keeps its head and is announced again, into its new base (de-spiegel #6)", async () => { const { headsToAnnounce, headKey } = await import("../pulls.ts"); const was = { number: 6, title: "a", state: "open", merged: false, head_sha: "aaa", base: "old-trunk", html_url: "" }; const announced: Record = { [headKey("novox/de-spiegel", 6, "old-trunk")]: "aaa" }; assert.deepEqual(headsToAnnounce("novox/de-spiegel", [was], announced), [], "the same head into the same base is announced once"); const retargeted = { ...was, base: "main" }; assert.deepEqual(headsToAnnounce("novox/de-spiegel", [retargeted], announced).map((p) => p.number), [6], "the same head into a new base is judged afresh"); assert.notEqual(headKey("novox/de-spiegel", 6, "main"), headKey("novox/de-spiegel", 6, "old-trunk")); }); test("a commit status is set on the commit, under the merge check's context", async () => { const { GiteaClient } = await import("../client.ts"); let seen: { path: string; body: any } | null = null; const server = createServer((req, res) => { let raw = ""; req.on("data", (c) => (raw += c)); req.on("end", () => { seen = { path: `${req.method} ${req.url}`, body: JSON.parse(raw) }; res.statusCode = 201; res.end("{}"); }); }); await new Promise((r) => server.listen(0, r)); const port = (server.address() as any).port; const client = new GiteaClient(`http://127.0.0.1:${port}`, "t"); try { await client.setCommitStatus("novox", "mesh-catalog", "abc1234", { state: "failure", context: "mesh/merge-gate", description: "x" }); } finally { server.close(); } assert.equal(seen!.path, "POST /api/v1/repos/novox/mesh-catalog/statuses/abc1234"); assert.equal(seen!.body.state, "failure"); assert.equal(seen!.body.context, "mesh/merge-gate"); }); // **A pending check says where it is** (novox/hq issue 446): announced, it waits in the build queue, since the time the // operator reads — not "mapping this head onto its module graph" for the ten minutes of a controller check. test("a head announced is pending in the build queue, since the local time it was announced", async () => { const { pendingWords } = await import("../pulls.ts"); assert.equal(pendingWords(new Date(2026, 9, 11, 3, 8, 50)), "waiting in the build queue since 03:08"); assert.equal(pendingWords(new Date(2026, 9, 11, 14, 0, 0)), "waiting in the build queue since 14:00"); }); // Issue 1301 (ADR 0503 §3, §5): gitea writes the controller's statuses only where the base still requires them. hq's main // requires mesh/repo-check alone, so no mesh/merge-gate is written on its heads. test("a base's required statuses decide which of the controller's statuses are written", async () => { const { maintained, writesGate, CHECK_STATUS } = await import("../pulls.ts"); assert.equal(CHECK_STATUS, "mesh/check"); assert.deepEqual(maintained(undefined), ["mesh/merge-gate", "mesh/repo-check"], "a rule not read: as before"); assert.deepEqual(maintained(["mesh/repo-check"]), ["mesh/repo-check"]); assert.deepEqual(maintained(["mesh/check", "other/x"]), []); assert.deepEqual(maintained([]), []); assert.equal(writesGate(undefined), true); assert.equal(writesGate(["mesh/repo-check"]), false); assert.equal(writesGate(["mesh/check"]), false); assert.equal(writesGate(["mesh/merge-gate", "mesh/repo-check"]), true); }); test("a rule the forge gives with no contexts (null) requires nothing, and an unread rule is unknown", async () => { const { requiredOf } = await import("../pulls.ts"); // The forge's BranchProtection says status_check_contexts may be null (protection.ts); requiredOf takes it as it comes. assert.deepEqual(requiredOf({ enable_status_check: true, status_check_contexts: null }), []); assert.deepEqual(requiredOf({ enable_status_check: true, status_check_contexts: ["mesh/check", ""] }), ["mesh/check"]); assert.deepEqual(requiredOf(null), []); assert.equal(requiredOf(undefined), undefined); });