mesh/merge-gate head 961e57f7e403 merged into trunk 22071dabfc7b — pass: builds gitea → novox; no bus step; every machine composes with the change as it di…
mesh/repo-check head 961e57f7e403 merged into trunk 22071dabfc7b — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
A pass read only the 20 most recently updated open pull requests of a repository, so a new head beyond them went unannounced and its merge check never started (novox/hq issue 579). Pages stop at the floor; a sweep reads all.
93 lines
4.8 KiB
TypeScript
93 lines
4.8 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import { test } from "node:test";
|
||
import { createServer } from "node:http";
|
||
|
||
// **Every open pull request's new head is announced, however many are open** (novox/hq issue 579). A pass asked the
|
||
// forge for the 20 most recently updated open pull requests of a repository, so a new head on the 21st or later was
|
||
// not announced and its merge check never started. A pass now pages through the open pull requests updated since its
|
||
// last look — every page on a sweep or the first pass — and stops at the first page that reaches back past the floor.
|
||
|
||
const HOUR = 3600_000;
|
||
const T0 = Date.parse("2026-10-12T12:00:00Z");
|
||
const sha = (n: number, v: string) => `${v}${String(n).padStart(4, "0")}`.padEnd(40, "0");
|
||
|
||
/** A forge holding `count` open pull requests of novox/hq, #n updated n hours after T0 (#count the newest), paged and
|
||
* sorted the way gitea answers `sort=recentupdate`: by update time, newest first. */
|
||
function forge(count: number, heads: Record<number, string>) {
|
||
const asked: URLSearchParams[] = [];
|
||
const server = createServer((req, res) => {
|
||
const url = new URL(req.url ?? "", "http://x");
|
||
if (url.pathname !== "/api/v1/repos/novox/hq/pulls") {
|
||
res.statusCode = 404;
|
||
return res.end("{}");
|
||
}
|
||
asked.push(url.searchParams);
|
||
const limit = Number(url.searchParams.get("limit") ?? 30);
|
||
const page = Number(url.searchParams.get("page") ?? 1);
|
||
const all = Array.from({ length: count }, (_, i) => count - i).map((n) => ({
|
||
number: n, state: "open", base: { ref: "main" }, head: { ref: `b${n}`, sha: heads[n] ?? sha(n, "a") },
|
||
updated_at: new Date(T0 + n * HOUR).toISOString(), html_url: `h/${n}`,
|
||
}));
|
||
res.end(JSON.stringify(all.slice((page - 1) * limit, page * limit)));
|
||
});
|
||
return { server, asked };
|
||
}
|
||
|
||
async function serve(count: number, heads: Record<number, string>) {
|
||
const { GiteaClient } = await import("../client.ts");
|
||
const f = forge(count, heads);
|
||
await new Promise<void>((r) => f.server.listen(0, r));
|
||
const client = new GiteaClient(`http://127.0.0.1:${(f.server.address() as any).port}`, "t");
|
||
return { client, ...f };
|
||
}
|
||
|
||
test("a new head beyond the newest 20 open pull requests is announced, exactly once", async () => {
|
||
const { headsToAnnounce } = await import("../pulls.ts");
|
||
// 25 open, every head announced before; #3 — the 23rd most recently updated — got a new head.
|
||
const announced: Record<string, string> = {};
|
||
for (let n = 1; n <= 25; n++) announced[`novox/hq#${n}`] = sha(n, "a");
|
||
const { client, server, asked } = await serve(25, { 3: sha(3, "b") });
|
||
try {
|
||
// The fault: the newest 20 alone do not hold it.
|
||
const newest = await client.listPullRequests("novox", "hq", { state: "open", sort: "recentupdate", limit: "20" });
|
||
assert.deepEqual(headsToAnnounce("novox/hq", newest, announced), [], "the newest 20 miss #3's new head");
|
||
asked.length = 0;
|
||
const said: string[] = [];
|
||
for (let pass = 0; pass < 2; pass++) {
|
||
// The sweep, as index.ts's pollPulls asks: no floor, every page.
|
||
const open = await client.listOpenPullsSince("novox", "hq", "");
|
||
for (const p of headsToAnnounce("novox/hq", open, announced)) {
|
||
said.push(`#${p.number}@${p.head_sha}`);
|
||
announced[`novox/hq#${p.number}`] = String(p.head_sha);
|
||
}
|
||
}
|
||
assert.deepEqual(said, [`#3@${sha(3, "b")}`], "the new head past the newest 20, announced once over two passes");
|
||
assert.ok(asked.every((q) => q.get("state") === "open" && q.get("sort") === "recentupdate"), "asked newest first");
|
||
} finally {
|
||
server.close();
|
||
}
|
||
});
|
||
|
||
test("a pass pages until it reaches back past its floor, and no further", async () => {
|
||
// 130 open pull requests, #n updated n hours after T0: page 1 holds #130–#81, page 2 #80–#31, page 3 #30–#1.
|
||
const { client, server, asked } = await serve(130, {});
|
||
try {
|
||
// A floor between #60 and #61: page 2 reaches back past it, so page 3 is never asked.
|
||
const floor = new Date(T0 + 60.5 * HOUR).toISOString();
|
||
const open = await client.listOpenPullsSince("novox", "hq", floor);
|
||
assert.deepEqual(asked.map((q) => q.get("page")), ["1", "2"]);
|
||
assert.equal(open.length, 100);
|
||
assert.ok(open.some((p) => p.number === 61), "every one updated since the floor is read");
|
||
// A floor no page reaches past: the last short page ends it.
|
||
asked.length = 0;
|
||
assert.equal((await client.listOpenPullsSince("novox", "hq", "")).length, 130);
|
||
assert.deepEqual(asked.map((q) => q.get("page")), ["1", "2", "3"]);
|
||
// Every page holds fewer than 50 updated since a recent floor: one page.
|
||
asked.length = 0;
|
||
await client.listOpenPullsSince("novox", "hq", new Date(T0 + 129 * HOUR).toISOString());
|
||
assert.deepEqual(asked.map((q) => q.get("page")), ["1"]);
|
||
} finally {
|
||
server.close();
|
||
}
|
||
});
|