88 lines
5.0 KiB
TypeScript
88 lines
5.0 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", 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<string, string> = { [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<void>((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);
|
|
});
|