The controller asks its planner what a pull request reaches, as if merged: a module whose manifest the change deletes is one the merge removes, and the plan's dependents are said on the gate's status beside the modules it moves.
86 lines
5.5 KiB
TypeScript
86 lines
5.5 KiB
TypeScript
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", 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": "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 verdict is the head commit's status; an error is the forge's error, never a success", async () => {
|
|
const { statusFor, commentFor, CHECK_CONTEXT } = await import("../pulls.ts");
|
|
const base = { owner: "novox", repo: "mesh-catalog", number: 7, commit: "0123456789abcdef", id: "build-1", on: "laptop" };
|
|
assert.equal(statusFor({ ...base, verdict: "pass", summary: "every machine composes" }).state, "success");
|
|
assert.equal(statusFor({ ...base, verdict: "warning", summary: "a merge rebuilds 14 module(s)" }).state, "warning");
|
|
assert.equal(statusFor({ ...base, verdict: "fail", summary: "x" }).state, "failure");
|
|
assert.equal(statusFor({ ...base, verdict: "error", summary: "the check could not run" }).state, "error");
|
|
assert.equal(statusFor({ ...base, verdict: "", summary: "" }).state, "error", "no verdict is no pass");
|
|
const long = statusFor({ ...base, verdict: "fail", summary: "y".repeat(500) });
|
|
assert.ok(long.description.length <= 140 && long.context === CHECK_CONTEXT);
|
|
assert.equal(commentFor({ ...base, verdict: "pass", summary: "fine" }), null, "a pass leaves no comment");
|
|
const said = commentFor({ ...base, verdict: "fail", summary: "lemurs refused", report: "fails:\n - ```x```" }) ?? "";
|
|
assert.match(said, /mesh\/merge-gate`: \*\*FAIL\*\*/);
|
|
assert.match(said, /01234567/);
|
|
assert.match(said, /builds --log build-1/);
|
|
assert.ok(!said.slice(said.indexOf("```") + 3, said.lastIndexOf("```")).includes("```"), "the report cannot close its own block");
|
|
});
|
|
|
|
test("each layer is its own status: the gate with the modules it judged, the repository's own check beside it", async () => {
|
|
const { statusesFor, commentFor, CHECK_CONTEXT, REPO_CHECK_CONTEXT } = await import("../pulls.ts");
|
|
const base = { owner: "novox", repo: "mesh-catalog", number: 7, commit: "0123456789abcdef", id: "build-1" };
|
|
const both = statusesFor({ ...base, verdict: "pass", summary: "every machine composes",
|
|
gate: { verdict: "pass", summary: "every machine composes", modules: ["gitea", "keycloak"], dependents: ["node-tools"] },
|
|
"repo-check": { verdict: "fail", summary: "its merge-check.sh failed: FAIL x" } });
|
|
assert.deepEqual(both.map((s) => [s.context, s.state]), [[CHECK_CONTEXT, "success"], [REPO_CHECK_CONTEXT, "failure"]]);
|
|
assert.match(both[0].description, /gitea, keycloak \+1 dependent/);
|
|
assert.match(commentFor({ ...base, verdict: "pass", summary: "", gate: { verdict: "pass", summary: "" },
|
|
"repo-check": { verdict: "fail", summary: "its merge-check.sh failed" } }) ?? "", /mesh\/repo-check`: \*\*FAIL/);
|
|
|
|
// Nothing of the graph touched, no script: a pass and a warning, and no comment on every push.
|
|
const quiet = { ...base, verdict: "pass", summary: "the change touches no module of the mesh's graph",
|
|
gate: { verdict: "pass", summary: "the change touches no module of the mesh's graph" },
|
|
"repo-check": { verdict: "warning", summary: "the repository declares no merge-check.sh" } };
|
|
assert.deepEqual(statusesFor(quiet).map((s) => s.state), ["success", "warning"]);
|
|
assert.equal(commentFor(quiet), null);
|
|
|
|
// A repository outside the mesh, touching nothing: the gate alone, a pass.
|
|
assert.equal(statusesFor({ ...base, verdict: "pass", summary: "x", gate: { verdict: "pass", summary: "x" } }).length, 1);
|
|
|
|
// A controller from before the layers: its verdict is the gate's.
|
|
assert.deepEqual(statusesFor({ ...base, verdict: "warning", summary: "wide" }).map((s) => [s.context, s.state]),
|
|
[[CHECK_CONTEXT, "warning"]]);
|
|
});
|
|
|
|
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<void>((r) => server.listen(0, r));
|
|
const port = (server.address() as any).port;
|
|
const client = new GiteaClient(`http://127.0.0.1:${port}`, "t");
|
|
await client.setCommitStatus("novox", "mesh-catalog", "abc123", { state: "failure", context: "mesh/merge-gate", description: "x" });
|
|
server.close();
|
|
assert.equal(seen!.path, "POST /api/v1/repos/novox/mesh-catalog/statuses/abc123");
|
|
assert.equal(seen!.body.state, "failure");
|
|
assert.equal(seen!.body.context, "mesh/merge-gate");
|
|
});
|