The controller read a changed file as shared code unless its directory was a module it holds or the merge also changed that directory's manifest. A merge touching modules/showcase/index.ts - the catalogue's reference module, held by no machine - therefore rebuilt all 103 modules built from this repository on 2026-10-06, 88 of them byte-identical, with the build agent first only because everything else is built by it. Whether a directory is a module is a fact of the repository at the commit, so the announcer now looks it up: every directory above a changed file (never the root) is asked for its module.json at the merge commit, and the ones that have one go out as module_dirs, with module_dirs_said. Not said when the file list is cut, past 300 directories, or when the forge cannot be asked; the controller then keeps its old rule, which rebuilds too much rather than too little. modules/showcase stays: TestTheShowcaseModuleIsAValidManifest in mesh-controller parses it and hq to-be 18 and 20 name it as the reference module.
74 lines
4.0 KiB
TypeScript
74 lines
4.0 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { createServer } from "node:http";
|
|
import { GiteaClient } from "../client.ts";
|
|
|
|
test("every page of a pull request's files is read, though the forge caps a page at fifty", async () => {
|
|
const total = 59;
|
|
const server = createServer((req, res) => {
|
|
const url = new URL(req.url ?? "", "http://x");
|
|
const page = Number(url.searchParams.get("page") ?? "1");
|
|
const start = (page - 1) * 50;
|
|
const files = Array.from({ length: Math.max(0, Math.min(50, total - start)) }, (_, i) => ({
|
|
filename: `modules/m${start + i}/x`, status: start + i === 3 ? "deleted" : "changed" }));
|
|
res.setHeader("content-type", "application/json");
|
|
res.end(JSON.stringify(files));
|
|
});
|
|
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");
|
|
const got = await client.listPullFiles("novox", "mesh-catalog", 60);
|
|
server.close();
|
|
assert.equal(got.paths.length, total);
|
|
assert.equal(got.truncated, false);
|
|
assert.equal(new Set(got.paths).size, total);
|
|
assert.deepEqual(got.removed, ["modules/m3/x"], "a file the merge deleted is said as deleted");
|
|
});
|
|
|
|
test("a pass asks only the repositories that moved since the last look, every one before the first", async () => {
|
|
const { movedSince } = await import("../client.ts");
|
|
const repos = [
|
|
{ full_name: "a/old", name: "old", owner: "a", private: false, html_url: "", updated_at: "2026-10-05T10:00:00Z" },
|
|
{ full_name: "a/new", name: "new", owner: "a", private: false, html_url: "", updated_at: "2026-10-05T16:10:02Z" },
|
|
{ full_name: "a/unknown", name: "unknown", owner: "a", private: false, html_url: "" },
|
|
];
|
|
assert.deepEqual(movedSince(repos, "").map((r) => r.name), ["old", "new", "unknown"]);
|
|
assert.deepEqual(movedSince(repos, "2026-10-05T16:09:00.000Z").map((r) => r.name), ["new", "unknown"]);
|
|
assert.deepEqual(movedSince(repos, "2026-10-05T18:10:02+02:00").map((r) => r.name), ["new", "unknown"], "an offset is a moment, not a string");
|
|
});
|
|
|
|
test("the directories above a merge's files that hold a module at the commit are said, the root never", async () => {
|
|
const { directoriesAbove } = await import("../client.ts");
|
|
assert.deepEqual(directoriesAbove(["modules/showcase/index.ts", "modules/showcase/daemon/x.ts", "README.md", "/modules/lib/a.go"]),
|
|
["modules", "modules/lib", "modules/showcase", "modules/showcase/daemon"]);
|
|
const asked: string[] = [];
|
|
const server = createServer((req, res) => {
|
|
const url = new URL(req.url ?? "", "http://x");
|
|
asked.push(`${url.pathname}@${url.searchParams.get("ref")}`);
|
|
const held = ["/api/v1/repos/novox/mesh-catalog/contents/modules/showcase/module.json"];
|
|
res.statusCode = held.includes(url.pathname) ? 200 : 404;
|
|
res.end(res.statusCode === 200 ? "{}" : '{"message":"not found"}');
|
|
});
|
|
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");
|
|
const got = await client.moduleDirsAt("novox", "mesh-catalog", "abc", ["modules/showcase/index.ts", "modules/lib/x.go"]);
|
|
const capped = await client.moduleDirsAt("novox", "mesh-catalog", "abc", ["a/b/c/d.ts"], 2);
|
|
server.close();
|
|
assert.deepEqual(got, ["modules/showcase"], "a directory holding a manifest is a module; one holding none is not");
|
|
assert.ok(asked.every((a) => a.endsWith("@abc")), "looked up at the merge commit");
|
|
assert.equal(capped, null, "past the bound it is not said, and the mesh keeps its old rule");
|
|
});
|
|
|
|
test("a lookup the forge refuses is thrown, not read as no module", async () => {
|
|
const server = createServer((_req, res) => {
|
|
res.statusCode = 500;
|
|
res.end("down");
|
|
});
|
|
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 assert.rejects(client.moduleDirsAt("novox", "mesh-catalog", "abc", ["modules/x/y.ts"]));
|
|
server.close();
|
|
});
|