Review finding K1 of R4e, issue 1301 S4 (ADR 0503 §3, ADR 0513 §4: the forge requires mesh/check alone). gitea_check_status assumed only the bridge could call it, but a tool is told no caller and node-tools invokes every tool, so any session could set mesh/check to success and merge past the checker: the fault issue 293 closed for the two statuses before it. gitea now consumes mesh-delivery-forge-bridge.check.status, a subject the bus grants to the bridge's own account alone, and check.ts writes mesh/check from it: the source is checked, the head's whole sha and the state, and a revision older than one already written on the head is dropped so a redelivered stale pending never hides a verdict; a forge refusal throws, so the event is delivered again. gitea_check_status is removed, and gitea_commit_status keeps refusing mesh/check.
103 lines
6.2 KiB
TypeScript
103 lines
6.2 KiB
TypeScript
// **mesh/check is written from the forge bridge's own event, never from a tool** (review finding K1 of R4e; issue 1301 S4;
|
|
// ADR 0503 §3, ADR 0513 §4). A tool cannot know its caller, and node-tools invokes every tool ("*"), so any session could
|
|
// have set mesh/check through gitea_check_status. The bus grants the subject mesh.mod.mesh-delivery-forge-bridge.event.
|
|
// check.status to the bridge's own account alone; this module consumes it and writes the status.
|
|
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
|
|
const sha = "0123456789abcdef0123456789abcdef01234567";
|
|
const body = (over: Record<string, unknown> = {}) => ({ owner: "novox", repo: "mesh-sdk", sha, state: "success",
|
|
description: "pass 0123456789ab on aaaaaaaaaaaa", target_url: "https://view.example/checks/g.1",
|
|
key: `novox/mesh-sdk.12.${sha}.status=mesh~check`, revision: 7, ...over });
|
|
const event = (b: unknown, source = "mesh-delivery-forge-bridge") => ({ type: "check.status", id: "x", source, node: "n1", at: "", body: b });
|
|
|
|
function forge() {
|
|
const sent: string[] = [];
|
|
let fail = false;
|
|
return {
|
|
sent,
|
|
failNext: () => { fail = true; },
|
|
setCommitStatus: async (o: string, r: string, s: string, st: any) => {
|
|
if (fail) { fail = false; throw new Error("Gitea API /repos/x/statuses: 502 bad gateway"); }
|
|
sent.push(`${o}/${r}@${s.slice(0, 8)} ${st.context} ${st.state} ${st.description}${st.target_url ? " " + st.target_url : ""}`);
|
|
},
|
|
};
|
|
}
|
|
|
|
test("the bridge's check.status event is the subject this module consumes, and the bridge is its one source", async () => {
|
|
const { CHECK_EVENT, CHECK_WRITER } = await import("../check.ts");
|
|
assert.equal(CHECK_EVENT, "mesh-delivery-forge-bridge.check.status");
|
|
assert.equal(CHECK_WRITER, "mesh-delivery-forge-bridge");
|
|
const manifest = JSON.parse(readFileSync(new URL("../module.json", import.meta.url), "utf8"));
|
|
assert.ok(manifest.consumes.includes(CHECK_EVENT), "gitea's manifest consumes the bridge's check.status");
|
|
});
|
|
|
|
test("a check.status from the bridge writes mesh/check on the head, its state, its line cut and its link", async () => {
|
|
const { CheckStatuses } = await import("../check.ts");
|
|
const f = forge();
|
|
const checks = new CheckStatuses(f, () => {});
|
|
assert.equal(await checks.apply(event(body())), "written");
|
|
assert.equal(await checks.apply(event(body({ sha: sha.replace(/0/g, "f"), state: "pending", description: "y".repeat(300), target_url: "javascript:x", revision: 8 }))), "written");
|
|
assert.equal(f.sent[0], "novox/mesh-sdk@01234567 mesh/check success pass 0123456789ab on aaaaaaaaaaaa https://view.example/checks/g.1");
|
|
assert.ok(f.sent[1].startsWith("novox/mesh-sdk@f1234567 mesh/check pending yyyy"), f.sent[1]);
|
|
assert.ok(f.sent[1].length < 200, "the line is cut as every status's is");
|
|
assert.ok(!f.sent[1].includes("javascript"), "a link that is no web page is not written");
|
|
});
|
|
|
|
test("an event whose source is not the bridge writes nothing", async () => {
|
|
const { CheckStatuses } = await import("../check.ts");
|
|
const f = forge();
|
|
const said: string[] = [];
|
|
const checks = new CheckStatuses(f, (l) => said.push(l));
|
|
for (const source of ["node-tools", "gitea", "unknown", ""]) assert.equal(await checks.apply(event(body(), source)), "dropped", source);
|
|
assert.deepEqual(f.sent, []);
|
|
assert.match(said[0], /not the forge bridge/);
|
|
});
|
|
|
|
test("a malformed event writes nothing and is not asked for again", async () => {
|
|
const { CheckStatuses } = await import("../check.ts");
|
|
const f = forge();
|
|
const checks = new CheckStatuses(f, () => {});
|
|
for (const b of [body({ sha: sha.slice(0, 12) }), body({ sha: "../../admin" }), body({ state: "green" }), body({ owner: "../x" }),
|
|
body({ repo: ".." }), body({ revision: undefined }), body({ revision: -1 }), body({ revision: "7" }), null, "text", body({ context: "mesh/delivery" })]) {
|
|
assert.equal(await checks.apply(event(b)), "dropped", JSON.stringify(b));
|
|
}
|
|
assert.deepEqual(f.sent, []);
|
|
// The context may be named, and is mesh/check then.
|
|
assert.equal(await checks.apply(event(body({ context: "mesh/check" }))), "written");
|
|
});
|
|
|
|
test("a revision older than one written on the head is dropped; the same one is written again (a redelivery)", async () => {
|
|
const { CheckStatuses } = await import("../check.ts");
|
|
const f = forge();
|
|
const checks = new CheckStatuses(f, () => {});
|
|
await checks.apply(event(body({ state: "success", revision: 10 })));
|
|
assert.equal(await checks.apply(event(body({ state: "pending", revision: 9 }))), "dropped", "a stale pending never hides the verdict");
|
|
assert.equal(await checks.apply(event(body({ state: "success", revision: 10 }))), "written");
|
|
// Another head is its own.
|
|
assert.equal(await checks.apply(event(body({ sha: sha.replace(/1/g, "2"), state: "pending", revision: 3 }))), "written");
|
|
assert.equal(await checks.apply(event(body({ state: "pending", revision: 11 }))), "written");
|
|
assert.deepEqual(f.sent.map((s) => s.split(" ").slice(0, 3).join(" ")), ["novox/mesh-sdk@01234567 mesh/check success",
|
|
"novox/mesh-sdk@01234567 mesh/check success", "novox/mesh-sdk@02234567 mesh/check pending", "novox/mesh-sdk@01234567 mesh/check pending"]);
|
|
});
|
|
|
|
test("a forge that refuses throws, so the bus delivers the event again, and the revision is not taken as written", async () => {
|
|
const { CheckStatuses } = await import("../check.ts");
|
|
const f = forge();
|
|
const checks = new CheckStatuses(f, () => {});
|
|
f.failNext();
|
|
await assert.rejects(checks.apply(event(body({ revision: 5 }))), /502/);
|
|
assert.equal(await checks.apply(event(body({ state: "pending", revision: 4 }))), "written", "nothing of 5 was kept");
|
|
assert.equal(await checks.apply(event(body({ revision: 5 }))), "written");
|
|
});
|
|
|
|
test("no tool writes mesh/check: gitea_check_status is gone and gitea_commit_status refuses it", async () => {
|
|
const delivery = await import("../delivery.ts");
|
|
assert.equal((delivery as Record<string, unknown>).checkStatusTool, undefined, "delivery.ts serves no gitea_check_status");
|
|
assert.throws(() => delivery.deliveryStatus("mesh/check", "success", "x"), /check\.status/);
|
|
const index = readFileSync(new URL("../index.ts", import.meta.url), "utf8");
|
|
assert.ok(!/gitea_check_status|checkStatusTool/.test(index), "index.ts serves no gitea_check_status");
|
|
});
|