Files
mesh-catalog/modules/gitea/test/paging.test.ts
T
jschoubben 961e57f7e4
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
Page the announcer through every open pull request updated since its last look
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.
2026-10-12 20:52:27 +02:00

93 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}
});