import assert from "node:assert/strict"; import { test } from "node:test"; // **One writer of a head's mesh/merge-gate** (novox/hq issue 446): the announcement's pending words, a recheck's pending and // a trunk's move, each write of a head in the order it was asked. const SHA = "a6b08de8507c1703528277e5cefc80548dbc9bb3"; /** A forge that keeps the newest status per context, as gitea does, and whose writes take as long as a test says. */ function aForge() { const kept: Record = {}; const written: string[] = []; let failNext = 0; let hold: Promise | null = null; let release = () => {}; return { kept, written, /** The next writes fail: a request the forge did not take. */ failWrites(n = 1) { failNext = n; }, /** The next writes wait until released: a request in flight. */ holdWrites() { hold = new Promise((r) => (release = r)); }, release() { hold = null; release(); }, async headChecks() { return {}; }, async setCommitStatus(_o: string, _r: string, _sha: string, s: { state: string; context: string; description: string }) { if (hold) await hold; if (failNext > 0) { failNext--; throw new Error("the forge answered 502"); } kept[s.context] = { state: s.state, description: s.description, updated_at: new Date().toISOString() }; written.push(`${s.state} ${s.description}`); }, }; } // **A recheck's pending goes through the one writer** (novox/hq issue 456). mesh-delivery's recheck puts the gate back // to pending through the gitea_commit_status tool. const recheckAt = new Date("2026-10-11T01:30:00Z"); const checkingAgain = { state: "pending" as const, context: "mesh/merge-gate", description: "checking again: main moved on" }; const gateOf = (forge: ReturnType) => `${forge.kept["mesh/merge-gate"].state} ${forge.kept["mesh/merge-gate"].description}`; test("a repository as the mesh clones it is told from another", async () => { const { clonedAs } = await import("../gate.ts"); assert.equal(clonedAs("http://127.0.0.1:20000/novox/hq.git", "novox", "hq"), true); assert.equal(clonedAs("http://127.0.0.1:20000/novox/mesh-hq.git", "novox", "hq"), false); assert.equal(clonedAs("http://127.0.0.1:20000/novox/mesh-controller.git", "novox", "hq"), false); }); test("a recheck the forge does not take fails its caller, and the next write still takes its turn", async () => { const { GateWriter } = await import("../gate.ts"); const forge = aForge(); const gates = new GateWriter(forge, () => recheckAt); await gates.pending("novox", "hq", SHA); forge.failWrites(); await assert.rejects(gates.rechecked("novox", "hq", SHA, checkingAgain)); await gates.rechecked("novox", "hq", SHA, checkingAgain); assert.equal(gateOf(forge), "pending checking again: main moved on"); }); test("the status tool writes the merge check's pending through the writer, and the mesh's other statuses as they were", async () => { const { GateWriter } = await import("../gate.ts"); const { commitStatusTool } = await import("../delivery.ts"); const forge = aForge(); const direct: string[] = []; const gates = new GateWriter(forge, () => recheckAt); const tool = commitStatusTool({ setCommitStatus: async (_o, _r, _sha, s) => void direct.push(`${s.context} ${s.state}`), gates, }); await tool.run({ owner: "novox", repo: "hq", sha: SHA, context: "mesh/merge-gate", state: "pending", description: "checking again: x" }); await tool.run({ owner: "novox", repo: "hq", sha: SHA, context: "mesh/repo-check", state: "pending", description: "checking again: x" }); await tool.run({ owner: "novox", repo: "hq", sha: SHA, context: "mesh/delivery", state: "success", description: "delivered" }); assert.equal(gateOf(forge), "pending checking again: x"); assert.equal(forge.kept["mesh/repo-check"].state, "pending"); assert.deepEqual(direct, ["mesh/delivery success"]); // A head named short is another head to the writer: refused, nothing sent. await assert.rejects(tool.run({ owner: "novox", repo: "hq", sha: SHA.slice(0, 12), context: "mesh/merge-gate", state: "pending" }), /whole sha/); await assert.rejects(tool.run({ owner: "novox", repo: "hq", sha: SHA, context: "mesh/merge-gate", state: "success" }), /set by its verdict only/); });