npm-package-registry is form none, so the registry pair the lab's clones of mesh-tools-go used is no longer minted. The lab requires forge-account as the build modules do; its clones go over ssh through a git and ssh configuration the mesh writes from that binding. The secret, contributes and MESH_LAB_FORGE_SECRET go. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
502 lines
30 KiB
TypeScript
502 lines
30 KiB
TypeScript
import { test } from "node:test";
|
||
import assert from "node:assert/strict";
|
||
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||
import { spawnSync } from "node:child_process";
|
||
import { tmpdir } from "node:os";
|
||
import { join } from "node:path";
|
||
|
||
import { baseScript, installPlan, installStep, NPM_REPOSITORIES, outcomeOf, readStatus, REPOSITORIES, script } from "../tools/runs.ts";
|
||
|
||
// Defends novox/hq issue 624: lab_run ran `npm ci` at the root of every checkout it installs, and
|
||
// mesh-tools keeps its package in node-tools/ with no lock file at its root, so every run failed at its
|
||
// setup, in 9 seconds, before anything was built.
|
||
|
||
function aCheckout(files: Record<string, string>): string {
|
||
const root = mkdtempSync(join(tmpdir(), "lab-checkout-"));
|
||
for (const [path, content] of Object.entries(files)) {
|
||
mkdirSync(join(root, path, ".."), { recursive: true });
|
||
writeFileSync(join(root, path), content);
|
||
}
|
||
return root;
|
||
}
|
||
|
||
test("a checkout with a lock file at its root is installed there", () => {
|
||
const root = aCheckout({ "package.json": "{}", "package-lock.json": "{}", "node-tools/package.json": "{}" });
|
||
assert.deepEqual(installPlan(root), { install: [root], unlocked: [] });
|
||
rmSync(root, { recursive: true, force: true });
|
||
});
|
||
|
||
test("a checkout whose package lives one directory down is installed there, as mesh-tools' node-tools", () => {
|
||
const root = aCheckout({ "node-tools/package.json": "{}", "node-tools/package-lock.json": "{}", "Dockerfile": "" });
|
||
assert.deepEqual(installPlan(root), { install: [join(root, "node-tools")], unlocked: [] });
|
||
rmSync(root, { recursive: true, force: true });
|
||
});
|
||
|
||
test("a package.json with no lock file is named, never installed", () => {
|
||
const root = aCheckout({ "package.json": "{}" });
|
||
assert.deepEqual(installPlan(root), { install: [], unlocked: [root] });
|
||
rmSync(root, { recursive: true, force: true });
|
||
});
|
||
|
||
// The shell a run executes follows the same rule: npm is asked where installPlan says, and a checkout
|
||
// with nothing installable fails the run loudly, naming it.
|
||
function runStep(root: string, build: boolean): { ok: boolean; said: string; asked: string[] } {
|
||
const bin = mkdtempSync(join(tmpdir(), "lab-npm-"));
|
||
const record = join(bin, "asked");
|
||
writeFileSync(join(bin, "npm"), `#!/bin/sh\necho "$(pwd) $*" >> ${JSON.stringify(record)}\n`);
|
||
chmodSync(join(bin, "npm"), 0o755);
|
||
const ran = spawnSync("bash", ["-c", `set -euo pipefail\n${installStep(root, build)}`], {
|
||
encoding: "utf8", env: { ...process.env, PATH: `${bin}:${process.env.PATH}` },
|
||
});
|
||
let asked: string[] = [];
|
||
try {
|
||
asked = readFileSync(record, "utf8").trim().split("\n").filter(Boolean);
|
||
} catch { /* nothing asked */ }
|
||
rmSync(bin, { recursive: true, force: true });
|
||
return { ok: ran.status === 0, said: `${ran.stdout}${ran.stderr}`, asked };
|
||
}
|
||
|
||
test("a run installs and builds mesh-tools in node-tools, where its lock file is", () => {
|
||
const root = aCheckout({ "node-tools/package.json": "{}", "node-tools/package-lock.json": "{}" });
|
||
const r = runStep(root, true);
|
||
assert.ok(r.ok, r.said);
|
||
assert.deepEqual(r.asked, [
|
||
`${join(root, "node-tools")} ci --no-audit --no-fund --loglevel=error`,
|
||
`${join(root, "node-tools")} run build --if-present`,
|
||
]);
|
||
rmSync(root, { recursive: true, force: true });
|
||
});
|
||
|
||
test("a run installs a checkout with its lock file at the root there, and builds only when asked", () => {
|
||
const root = aCheckout({ "package.json": "{}", "package-lock.json": "{}" });
|
||
const r = runStep(root, false);
|
||
assert.ok(r.ok, r.said);
|
||
assert.deepEqual(r.asked, [`${root} ci --no-audit --no-fund --loglevel=error`]);
|
||
rmSync(root, { recursive: true, force: true });
|
||
});
|
||
|
||
test("a run fails at its setup, naming the checkout, when nothing of it can be installed", () => {
|
||
const layouts: Record<string, string>[] = [{ "package.json": "{}" }, { "Dockerfile": "" }, { "sub/package.json": "{}" }];
|
||
for (const files of layouts) {
|
||
const root = aCheckout(files);
|
||
const r = runStep(root, false);
|
||
assert.equal(r.ok, false, `installed ${JSON.stringify(files)}`);
|
||
assert.match(r.said, /issue 624/);
|
||
assert.deepEqual(r.asked, []);
|
||
rmSync(root, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
// A run checks out mesh-tools-go too (novox/hq issue 616): genesis builds the Go toolchain from the
|
||
// repository beside mesh-tools, and a bed that raises genesis serves it. And it tells the beds where the
|
||
// checkouts are.
|
||
test("a run checks out mesh-tools-go and says where its checkouts are", () => {
|
||
assert.ok(REPOSITORIES.includes("mesh-tools-go"));
|
||
const s = script("/work", "run-x", "http://forge", ["test/integration/genesis-keeps-its-bus.test.ts"], {});
|
||
assert.match(s, /novox\/mesh-tools-go\.git' '\/work\/run-x\/mesh-tools-go'/);
|
||
assert.match(s, /export MESH_LAB_REPOS='\/work\/run-x'/);
|
||
assert.doesNotMatch(s, /cd '\/work\/run-x'\/\$repo && npm ci/);
|
||
for (const repo of NPM_REPOSITORIES) assert.ok(s.includes(`'/work/run-x/${repo}'/package-lock.json`), repo);
|
||
});
|
||
|
||
// Defends novox/hq issue 624, its second half: mesh-tools-go is private, and the lab cloned over HTTP with
|
||
// no credential ("could not read Username"). The lab clones as the build modules do (issue 1966): it requires
|
||
// the forge account, the mesh writes the account's git and ssh configuration into the lab's state, and a run's
|
||
// clones go through it over ssh with the account's key. No password is bound, sealed or written for a run.
|
||
import { cleanCredentials, forgeArgs, forgeConfig } from "../tools/runs.ts";
|
||
|
||
const labDir = join(import.meta.dirname, "..");
|
||
|
||
/** The lab's forge files as the mesh writes them for a bound forge account, into a state directory of its own. */
|
||
function renderedForgeFiles(forge: string): string {
|
||
const state = mkdtempSync(join(tmpdir(), "lab-forge-"));
|
||
const values: Record<string, string> = {
|
||
ssh_alias: "mesh-forge", ssh_host: "forge.invalid", ssh_port: "222", host_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOn",
|
||
https_base: "https://git.example.org/",
|
||
};
|
||
for (const f of ["forge.gitconfig", "forge.ssh", "forge.known_hosts"]) {
|
||
const text = readFileSync(join(labDir, "files", f), "utf8")
|
||
.replace(/\$\{bound:forge-account:([a-z_]+)\}/g, (_, k: string) => values[k] ?? `<unknown ${k}>`)
|
||
.replace(/\$\{config:forge\}/g, forge).replace(/\$\{dir:state\}/g, state);
|
||
writeFileSync(join(state, f), text);
|
||
}
|
||
return state;
|
||
}
|
||
|
||
test("the lab requires the forge account for its clones, and holds no registry credential", () => {
|
||
const m = JSON.parse(readFileSync(join(labDir, "module.json"), "utf8")) as {
|
||
requires: string[]; contributes?: unknown; secrets?: unknown; resources: { id: string; path?: string; template?: string; package?: string }[];
|
||
build: { artifacts: { name: string; env?: Record<string, string> }[] };
|
||
};
|
||
assert.ok(m.requires.includes("forge-account") && m.requires.includes("npm-package-registry"));
|
||
assert.equal(m.contributes, undefined, "npm-package-registry reads are anonymous: nothing is contributed");
|
||
assert.equal(m.secrets, undefined, "no registry secret is sealed to the lab");
|
||
const env = m.build.artifacts.find((a) => a.name === "code")?.env ?? {};
|
||
assert.equal(env.MESH_LAB_FORGE_SECRET, undefined);
|
||
assert.equal(env.MESH_LAB_FORGE_GIT, m.resources.find((r) => r.id === "forge-git")?.path);
|
||
assert.match(readFileSync(join(labDir, "files", "lab.env"), "utf8"), /^MESH_LAB_FORGE_GIT=\$\{dir:state\}\/forge\.gitconfig$/m);
|
||
assert.doesNotMatch(readFileSync(join(labDir, "files", "lab.env"), "utf8"), /SECRET/);
|
||
assert.ok(m.resources.some((r) => r.package === "openssh"), "the clones need ssh");
|
||
for (const id of ["forge-git", "forge-ssh", "forge-known-hosts"]) {
|
||
const r = m.resources.find((x) => x.id === id);
|
||
assert.match(readFileSync(join(labDir, String(r?.template)), "utf8"), /\$\{bound:forge-account:/, id);
|
||
}
|
||
});
|
||
|
||
test("a run clones through the forge account's git configuration, and its script holds no credential", () => {
|
||
const s = script("/work", "run-x", "http://novox.internal:20000", ["t.test.ts"], {}, "/state/forge.gitconfig");
|
||
assert.match(s, /git -c include\.path='\/state\/forge\.gitconfig' clone --quiet --depth 50 --branch 'main' 'http:\/\/novox\.internal:20000\/novox\/mesh-tools-go\.git'/);
|
||
assert.match(s, /export GIT_TERMINAL_PROMPT=0/);
|
||
assert.doesNotMatch(s, /credential|^trap /m);
|
||
assert.match(baseScript("/work/base-x", "http://forge", "/state/forge.gitconfig"), /git -c include\.path='\/state\/forge\.gitconfig' clone/);
|
||
assert.equal(forgeArgs(undefined), "");
|
||
});
|
||
|
||
test("the forge account's configuration is taken only when the mesh wrote it", () => {
|
||
const state = renderedForgeFiles("http://novox.internal:20000");
|
||
assert.equal(forgeConfig(join(state, "forge.gitconfig")), join(state, "forge.gitconfig"));
|
||
assert.equal(forgeConfig(join(state, "nowhere")), undefined);
|
||
assert.equal(forgeConfig(undefined), undefined);
|
||
writeFileSync(join(state, "empty"), "");
|
||
assert.equal(forgeConfig(join(state, "empty")), undefined);
|
||
rmSync(state, { recursive: true, force: true });
|
||
});
|
||
|
||
function withoutSsh(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||
const { GIT_SSH_COMMAND: _c, GIT_SSH: _s, ...rest } = env;
|
||
return rest;
|
||
}
|
||
|
||
// Held against git and ssh themselves: a clone of the forge's address, as the lab's setting names it or as the
|
||
// forge names itself, goes over ssh to the forge account's host with the account's key and the forge's host key.
|
||
test("git sends a clone of the forge's address over ssh, as the forge account's configuration says", () => {
|
||
const state = renderedForgeFiles("http://novox.internal:20000");
|
||
const bin = mkdtempSync(join(tmpdir(), "lab-ssh-"));
|
||
writeFileSync(join(bin, "ssh"), `#!/bin/sh\nprintf '%s\\n' "$@" >> ${JSON.stringify(join(bin, "asked"))}\nexit 1\n`, { mode: 0o755 });
|
||
for (const url of ["http://novox.internal:20000/novox/mesh-tools-go.git", "https://git.example.org/novox/mesh-tools-go.git"]) {
|
||
rmSync(join(bin, "asked"), { force: true });
|
||
spawnSync("sh", ["-c", `git ${forgeArgs(join(state, "forge.gitconfig"))}ls-remote ${url}`],
|
||
{ encoding: "utf8", env: { ...withoutSsh(process.env), PATH: `${bin}:${process.env.PATH}`, GIT_TERMINAL_PROMPT: "0" } });
|
||
const asked = readFileSync(join(bin, "asked"), "utf8").split("\n");
|
||
assert.ok(asked.includes("-F") && asked.includes(join(state, "forge.ssh")), `${url}: ${asked.join(" ")}`);
|
||
assert.ok(asked.includes("mesh-forge"), url);
|
||
assert.ok(asked.some((a) => a.includes("novox/mesh-tools-go.git")), url);
|
||
}
|
||
const g = spawnSync("ssh", ["-G", "-F", join(state, "forge.ssh"), "mesh-forge"], { encoding: "utf8" });
|
||
for (const line of ["hostname forge.invalid", "port 222", "user git", "identitiesonly yes", "batchmode yes",
|
||
"stricthostkeychecking true", `userknownhostsfile ${join(state, "forge.known_hosts")}`, "hostkeyalias mesh-forge"]) {
|
||
assert.ok(g.stdout.split("\n").includes(line), `ssh -G lacks ${line}`);
|
||
}
|
||
assert.match(g.stdout, /^identityfile ~\/\.ssh\/mesh_ed25519$/m);
|
||
assert.equal(readFileSync(join(state, "forge.known_hosts"), "utf8"), "mesh-forge ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOn\n");
|
||
rmSync(state, { recursive: true, force: true });
|
||
rmSync(bin, { recursive: true, force: true });
|
||
});
|
||
|
||
// What a run of before left on disk, its credential line, is removed at the next run's start and in lab_check.
|
||
test("credentials an earlier run left are removed", () => {
|
||
const work = mkdtempSync(join(tmpdir(), "lab-work-"));
|
||
for (const n of [".run-a.git-credentials", ".base-b.git-credentials", "run-a", "other"]) writeFileSync(join(work, n), "x");
|
||
const removed = cleanCredentials(work).sort();
|
||
assert.deepEqual(removed, [join(work, ".base-b.git-credentials"), join(work, ".run-a.git-credentials")]);
|
||
assert.ok(existsSync(join(work, "run-a")) && existsSync(join(work, "other")));
|
||
rmSync(work, { recursive: true, force: true });
|
||
});
|
||
|
||
// lab_check failed for ever on "destination path '<work>/check' already exists": it cloned into one fixed
|
||
// path and ignored whether removing the last one worked (novox/hq issue 624). Each check now works in a
|
||
// fresh directory, and what earlier checks left is cleared or named.
|
||
import { checkDir, clearChecks } from "../tools/runs.ts";
|
||
|
||
test("each check gets a fresh directory of its own", () => {
|
||
const work = mkdtempSync(join(tmpdir(), "lab-work-"));
|
||
const a = checkDir(work);
|
||
const b = checkDir(work);
|
||
assert.notEqual(a, b);
|
||
assert.ok(a.startsWith(join(work, "check-")) && existsSync(a) && existsSync(b));
|
||
rmSync(work, { recursive: true, force: true });
|
||
});
|
||
|
||
// As root nothing is locked against removal, so the unremovable tree cannot be made: skipped there, before
|
||
// it makes anything (the merge check runs as root).
|
||
test("what earlier checks left is cleared, and what cannot be is named rather than tripped over", {
|
||
skip: process.getuid?.() === 0 ? "running as root, which no directory mode stops from removing anything" : false,
|
||
}, () => {
|
||
const work = mkdtempSync(join(tmpdir(), "lab-work-"));
|
||
for (const d of ["check/src", "check-setup/mesh-lab", "check-abc", "run-x", "keep"]) mkdirSync(join(work, d), { recursive: true });
|
||
// A tree this account cannot empty: a directory it may not write, with something in it.
|
||
mkdirSync(join(work, "check-stuck", "locked"), { recursive: true });
|
||
writeFileSync(join(work, "check-stuck", "locked", "file"), "x");
|
||
chmodSync(join(work, "check-stuck", "locked"), 0o555);
|
||
const said = clearChecks(work);
|
||
chmodSync(join(work, "check-stuck", "locked"), 0o755);
|
||
for (const gone of ["check", "check-setup", "check-abc"]) assert.equal(existsSync(join(work, gone)), false, gone);
|
||
assert.ok(existsSync(join(work, "run-x")) && existsSync(join(work, "keep")));
|
||
assert.equal(said.length, 1);
|
||
assert.match(said[0] as string, /check-stuck, left by an earlier check, could not be removed/);
|
||
rmSync(work, { recursive: true, force: true });
|
||
});
|
||
|
||
// And as root too: what earlier checks left is cleared.
|
||
test("leftover checks are cleared, whoever runs the check", () => {
|
||
const work = mkdtempSync(join(tmpdir(), "lab-work-"));
|
||
for (const d of ["check/src", "check-setup/mesh-lab", "check-abc", "run-x"]) mkdirSync(join(work, d), { recursive: true });
|
||
assert.deepEqual(clearChecks(work), []);
|
||
for (const gone of ["check", "check-setup", "check-abc"]) assert.equal(existsSync(join(work, gone)), false, gone);
|
||
assert.ok(existsSync(join(work, "run-x")));
|
||
rmSync(work, { recursive: true, force: true });
|
||
});
|
||
|
||
// The next setup failure of novox/hq issue 624 (run-2026-10-13T13-15-26-634Z on shanks): npm 12 refused
|
||
// mesh-tools' locked @novox/mesh-sdk with EALLOWREMOTE. The lab named the registry by its forge setting's
|
||
// address (http://novox.internal:20000/…), the lock file holds the address the forge writes every tarball
|
||
// URL at (https://git.novox.be/…), and npm 12 installs a locked tarball only from its configured registry.
|
||
// A run names the registry by the address the registry names itself by.
|
||
import { npmRegistry } from "../tools/runs.ts";
|
||
|
||
function aRegistry(answer: () => Response | Promise<Response>): { get: typeof fetch; asked: string[] } {
|
||
const asked: string[] = [];
|
||
const get = (async (url: string | URL | Request) => {
|
||
asked.push(String(url));
|
||
return answer();
|
||
}) as typeof fetch;
|
||
return { get, asked };
|
||
}
|
||
|
||
const packument = (tarball: string) => new Response(JSON.stringify({
|
||
name: "@novox/mesh-sdk", "dist-tags": { latest: "0.1.14" },
|
||
versions: { "0.1.1": { dist: { tarball: "http://old.example/api/packages/novox/npm/x.tgz" } }, "0.1.14": { dist: { tarball } } },
|
||
}), { status: 200, headers: { "content-type": "application/json" } });
|
||
|
||
test("a run names the package registry by the address the registry gives its tarballs", async () => {
|
||
const r = aRegistry(() => packument("https://forge.example.org/api/packages/novox/npm/%40novox%2Fmesh-sdk/-/0.1.14/mesh-sdk-0.1.14.tgz"));
|
||
const got = await npmRegistry("http://forge.example.org:3000/", r.get, "https://forge.example.org/api/packages/novox/npm/");
|
||
assert.deepEqual(got, { registry: "https://forge.example.org/api/packages/novox/npm/" });
|
||
assert.deepEqual(r.asked, ["http://forge.example.org:3000/api/packages/novox/npm/@novox%2fmesh-sdk"]);
|
||
// The forge's own scheme is taken too, with its port.
|
||
const plain = await npmRegistry("http://forge.example.org:3000",
|
||
aRegistry(() => packument("http://forge.example.org:3000/api/packages/novox/npm/%40novox%2Fmesh-sdk/-/0.1.14/mesh-sdk-0.1.14.tgz")).get);
|
||
assert.deepEqual(plain, { registry: "http://forge.example.org:3000/api/packages/novox/npm/" });
|
||
});
|
||
|
||
// What the registry answers may come over plain HTTP, so it never decides where packages come from: only
|
||
// the forge's public root or its own address, scheme, host and port, under the npm path, with nothing after
|
||
// it (review of mesh-catalog #275, ADR 0346).
|
||
test("a registry that does not give an address the lab takes leaves the forge's, and the reason is said", async () => {
|
||
const sdk = "/api/packages/novox/npm/%40novox%2Fmesh-sdk/-/0.1.14/mesh-sdk-0.1.14.tgz";
|
||
for (const [answer, why] of [
|
||
[() => new Response("not found", { status: 404 }), /it answered 404/],
|
||
[() => new Response("{}", { status: 200 }), /no tarball/],
|
||
[() => { throw new Error("connect ECONNREFUSED"); }, /ECONNREFUSED/],
|
||
[() => packument(`https://forge.example.org/elsewhere/mesh-sdk-0.1.14.tgz`), /not under \/api\/packages\/novox\/npm\//],
|
||
[() => packument(`https://attacker.example.net${sdk}`), /at https:\/\/attacker\.example\.net, which is neither the forge's public root nor its own address/],
|
||
[() => packument(`https://attacker.example.net/x?u=https://novox.internal${sdk}`), /query or a fragment/],
|
||
[() => packument(`https://novox.internal${sdk}?x=1`), /query or a fragment/],
|
||
[() => packument(`https://novox.internal${sdk}#frag`), /query or a fragment/],
|
||
[() => packument(`https://attacker.example.net/#https://novox.internal${sdk}`), /query or a fragment/],
|
||
[() => packument(`ftp://novox.internal${sdk}`), /at ftp:\/\/novox\.internal, which is neither/],
|
||
[() => packument(`file:///api/packages/novox/npm/x.tgz`), /which is neither/],
|
||
[() => packument(`https://novox.internal/api/packages/novox/npm/\nregistry=https://attacker.example.net/\nallow-remote=all`), /whitespace or a control character/],
|
||
[() => packument(`https://user:pw@novox.internal${sdk}`), /user or password/],
|
||
] as [() => Response, RegExp][]) {
|
||
const got = await npmRegistry("http://novox.internal:20000", aRegistry(answer).get);
|
||
assert.equal(got.registry, "http://novox.internal:20000/api/packages/novox/npm/", String(why));
|
||
assert.match(got.problem ?? "", /did not give an address the lab takes/);
|
||
assert.match(got.problem ?? "", why);
|
||
}
|
||
});
|
||
|
||
test("a run's npm configuration names the registry it was given, and the forge's own address only without one", () => {
|
||
const given = script("/work", "run-x", "http://novox.internal:20000", ["t.test.ts"], {}, undefined,
|
||
"https://forge.example.org/api/packages/novox/npm/");
|
||
assert.match(given, / '@novox:registry=https:\/\/forge\.example\.org\/api\/packages\/novox\/npm\/' > '\/work\/run-x\/\.npmrc'\n/);
|
||
assert.doesNotMatch(given, /@novox:registry=http:\/\/novox\.internal/);
|
||
const none = script("/work", "run-x", "http://novox.internal:20000", ["t.test.ts"], {});
|
||
assert.match(none, /'@novox:registry=http:\/\/novox\.internal:20000\/api\/packages\/novox\/npm\/'/);
|
||
});
|
||
|
||
// The public root the forge serves in the lab's binding (novox/hq ADR 0346) is the one other origin a
|
||
// tarball may come from, by scheme, host and port: the review of #275 found the port ignored.
|
||
import { publicRootOf } from "../tools/runs.ts";
|
||
|
||
test("a tarball is taken at the forge's public root, and at no other port or scheme of its host", async () => {
|
||
const sdk = "/api/packages/novox/npm/%40novox%2Fmesh-sdk/-/0.1.14/mesh-sdk-0.1.14.tgz";
|
||
const root = "https://git.example.org/api/packages/novox/npm/";
|
||
const at = (tarball: string, publicRoot?: string) =>
|
||
npmRegistry("http://novox.internal:20000", aRegistry(() => packument(tarball)).get, publicRoot);
|
||
assert.deepEqual(await at(`https://git.example.org${sdk}`, root), { registry: root });
|
||
for (const tarball of [`https://git.example.org:8443${sdk}`, `http://git.example.org${sdk}`, `https://example.org${sdk}`]) {
|
||
const got = await at(tarball, root);
|
||
assert.equal(got.registry, "http://novox.internal:20000/api/packages/novox/npm/", tarball);
|
||
assert.match(got.problem ?? "", /neither the forge's public root nor its own address/, tarball);
|
||
}
|
||
// Without a public root, the forge's public name is not trusted on its own word.
|
||
assert.match((await at(`https://git.example.org${sdk}`)).problem ?? "", /neither/);
|
||
// And the forge's own address only at its own port.
|
||
assert.match((await at(`http://novox.internal${sdk}`, root)).problem ?? "", /neither/);
|
||
});
|
||
|
||
test("the public root is read from the binding, and only an https root at the npm path is taken", () => {
|
||
const dir = mkdtempSync(join(tmpdir(), "lab-root-"));
|
||
const binding = (root: unknown) => {
|
||
const f = join(dir, `b-${Math.random()}.json`);
|
||
writeFileSync(f, JSON.stringify({ provision: "npm-package-registry", at: "home", serves: { scheme: "http", port: 3000,
|
||
"npm-path": "/api/packages/novox/npm/", ...(root === undefined ? {} : { "public-root": root }) } }));
|
||
return f;
|
||
};
|
||
assert.deepEqual(publicRootOf(binding("https://git.example.org/api/packages/novox/npm/")),
|
||
{ root: "https://git.example.org/api/packages/novox/npm/" });
|
||
assert.deepEqual(publicRootOf(binding("https://git.example.org:8443/api/packages/novox/npm/")),
|
||
{ root: "https://git.example.org:8443/api/packages/novox/npm/" });
|
||
for (const [root, why] of [
|
||
[undefined, /serves no public-root/],
|
||
["http://git.example.org/api/packages/novox/npm/", /is not https:\/\/<host>/],
|
||
["https://git.example.org/elsewhere/", /is not https:\/\/<host>/],
|
||
["https://git.example.org/api/packages/novox/npm/?x=1", /is not https:\/\/<host>/],
|
||
["https://u:p@git.example.org/api/packages/novox/npm/", /is not https:\/\/<host>/],
|
||
["https://git.example.org/api/packages/novox/npm/\nallow-remote=all", /whitespace or a control character/],
|
||
["https://${bound:route:name-web}/api/packages/novox/npm/", /not a URL|is not https/],
|
||
] as [unknown, RegExp][]) {
|
||
const got = publicRootOf(binding(root));
|
||
assert.equal(got.root, undefined, String(root));
|
||
assert.match(got.problem ?? "", why, String(root));
|
||
}
|
||
assert.match(publicRootOf(join(dir, "none.json")).problem ?? "", /could not be read/);
|
||
assert.match(publicRootOf(undefined).problem ?? "", /no npm-package-registry binding/);
|
||
rmSync(dir, { recursive: true, force: true });
|
||
});
|
||
|
||
// A forge setting that is not http(s) is never a trusted origin (review of #280): a `file:` tarball's origin
|
||
// is "null", as is a `file:` forge's, and the two must not match.
|
||
test("a forge setting that is not http(s) trusts no origin of its own", async () => {
|
||
const got = await npmRegistry("file:///srv/forge", aRegistry(() => packument("file:///api/packages/novox/npm/x.tgz")).get);
|
||
assert.match(got.problem ?? "", /neither the forge's public root nor its own address/);
|
||
const root = "https://git.example.org/api/packages/novox/npm/";
|
||
const viaRoot = await npmRegistry("ftp://novox.internal", aRegistry(() =>
|
||
packument("ftp://novox.internal/api/packages/novox/npm/x.tgz")).get, root);
|
||
assert.match(viaRoot.problem ?? "", /neither/);
|
||
});
|
||
|
||
// The runtime's env file keeps its text once, in files/ as its template (novox/hq ADR 0349 §2, issue 656): no
|
||
// file resource carries its text inline, and the build packs files/ into the one archive the controller reads
|
||
// the templates back from.
|
||
test("every file takes its text from a template in files/", () => {
|
||
const dir = join(import.meta.dirname, "..");
|
||
const m = JSON.parse(readFileSync(join(dir, "module.json"), "utf8")) as {
|
||
resources: Record<string, unknown>[]; build: { artifacts: { name: string; kind?: string; from?: string }[] };
|
||
};
|
||
for (const r of m.resources) {
|
||
assert.equal("content" in r, false, `${String(r.id)} keeps its text inline`);
|
||
if (r.type === "file") assert.ok(existsSync(join(dir, String(r.template))), `${String(r.id)} names ${String(r.template)}`);
|
||
}
|
||
const packs = m.build.artifacts.filter((a) => a.kind === "archive" && a.from === "files");
|
||
assert.deepEqual(packs.map((a) => a.name), ["files"]);
|
||
const env = m.resources.find((r) => r.id === "runtime-env");
|
||
assert.match(readFileSync(join(dir, String(env?.template)), "utf8"), /^MESH_LAB_FORGE=\$\{config:forge\}\n/);
|
||
});
|
||
|
||
// Defends novox/hq issue 693: a run whose only test skipped as NOT REACHED read "0 passed, 0 failed", exit 0,
|
||
// and lab_status said passed. The log's tail, as run-2026-10-14T00-21-44-014Z on shanks wrote it.
|
||
const oldLog = [
|
||
"STOPPED at step 16 of 24, base: \t\t\t\tMake sure you have all the referenced content and try again.",
|
||
"\ufe63 genesis adopts its bus as the nats module in place: one bus, its streams kept, the node connected (588152.016874ms) # ADR 0333 §5 NOT REACHED: the first run stopped at step 16 of 24, base, before the broker step:",
|
||
" \t\t\t\tMake sure you have all the referenced content and try again.",
|
||
"NotFound: content digest sha256:e2de96513ba9: not found",
|
||
"ℹ tests 1", "ℹ suites 0", "ℹ pass 0", "ℹ fail 0", "ℹ cancelled 0", "ℹ skipped 1", "ℹ todo 0",
|
||
"ℹ duration_ms 590254.163993",
|
||
"",
|
||
"recorded: test/integration/genesis-keeps-its-bus.test.ts — 0 passed, 0 failed, against mesh-lab 7ed7da1",
|
||
].join("\n");
|
||
|
||
// The same run, as the lab's suite says it once it names its skips (mesh-lab, issue 693).
|
||
const newLog = [
|
||
"ℹ tests 1", "ℹ pass 0", "ℹ fail 0", "ℹ skipped 1",
|
||
"",
|
||
"recorded: test/integration/genesis-keeps-its-bus.test.ts — 0 passed, 0 failed, 1 skipped, against mesh-lab 7ed7da1",
|
||
"NOT PASSED: no test passed: 1 skipped, 1 of them as NOT REACHED",
|
||
"skipped: genesis adopts its bus — one bus (test/integration/genesis-keeps-its-bus.test.ts) # ADR 0333 §5 NOT REACHED: the first run stopped at step 16 of 24, base: NotFound: content digest",
|
||
].join("\n");
|
||
|
||
function anEndedRun(log: string, state: "passed" | "failed", exit: number): { work: string; id: string } {
|
||
const work = mkdtempSync(join(tmpdir(), "lab-runs-"));
|
||
const id = "run-2026-10-14T00-21-44-014Z";
|
||
mkdirSync(join(work, id));
|
||
writeFileSync(join(work, id, "run.log"), log);
|
||
writeFileSync(join(work, id, "status.json"), JSON.stringify({
|
||
id, state, exit, started: "2026-10-14T00:21:44.014Z", ended: "2026-10-14T00:33:11.179Z",
|
||
tests: ["test/integration/genesis-keeps-its-bus.test.ts"], refs: {},
|
||
}));
|
||
return { work, id };
|
||
}
|
||
|
||
test("a run whose only test skipped as NOT REACHED, from a lab of before, reads as failed with the reason", () => {
|
||
const { work, id } = anEndedRun(oldLog, "passed", 0);
|
||
const s = readStatus(work, id);
|
||
assert.equal(s?.state, "failed", JSON.stringify(s));
|
||
assert.equal(s?.exit, 0);
|
||
assert.equal(s?.outcome?.passed, 0);
|
||
assert.equal(s?.outcome?.skipped, 1);
|
||
assert.equal(s?.outcome?.notPassed, "no test passed: 1 skipped, 1 of them as NOT REACHED");
|
||
assert.match(s?.outcome?.skips[0]?.reason ?? "", /^ADR 0333 §5 NOT REACHED: the first run stopped at step 16 of 24, base, before the broker step:$/);
|
||
// Kept in the run's record, not only answered.
|
||
const kept = JSON.parse(readFileSync(join(work, id, "status.json"), "utf8"));
|
||
assert.equal(kept.state, "failed");
|
||
assert.equal(kept.outcome.notPassed, "no test passed: 1 skipped, 1 of them as NOT REACHED");
|
||
rmSync(work, { recursive: true, force: true });
|
||
});
|
||
|
||
test("the lab's own NOT PASSED and skipped lines are read, a test name's punctuation and all", () => {
|
||
const { work, id } = anEndedRun(newLog, "failed", 1);
|
||
const s = readStatus(work, id);
|
||
assert.equal(s?.state, "failed");
|
||
assert.equal(s?.outcome?.notPassed, "no test passed: 1 skipped, 1 of them as NOT REACHED");
|
||
assert.deepEqual(s?.outcome?.skips, [{
|
||
file: "test/integration/genesis-keeps-its-bus.test.ts",
|
||
test: "genesis adopts its bus — one bus",
|
||
reason: "ADR 0333 §5 NOT REACHED: the first run stopped at step 16 of 24, base: NotFound: content digest",
|
||
}]);
|
||
rmSync(work, { recursive: true, force: true });
|
||
});
|
||
|
||
test("a run that passed tests and skipped one for a declared reason stays passed, its skip listed", () => {
|
||
const log = [
|
||
"✔ a module lands (12ms)",
|
||
"﹣ a builder builds (0.1ms) # set MESH_LAB_BUILDER to a built mesh-builder",
|
||
"ℹ tests 2", "ℹ pass 1", "ℹ fail 0", "ℹ skipped 1",
|
||
].join("\n");
|
||
const { work, id } = anEndedRun(log, "passed", 0);
|
||
const s = readStatus(work, id);
|
||
assert.equal(s?.state, "passed");
|
||
assert.equal(s?.outcome?.notPassed, undefined);
|
||
assert.equal(s?.outcome?.skips[0]?.reason, "set MESH_LAB_BUILDER to a built mesh-builder");
|
||
rmSync(work, { recursive: true, force: true });
|
||
});
|
||
|
||
test("a run that ran no test at all is not passed", () => {
|
||
assert.equal(outcomeOf("ℹ tests 0\nℹ pass 0\nℹ fail 0\nℹ skipped 0\n").notPassed, "no test passed: none ran");
|
||
});
|
||
|
||
// Review 254 on issue 693: a test's own output, printed before the suite reports, is not the suite's verdict.
|
||
test("a line a test printed that looks like the suite's is not read as the suite's", () => {
|
||
const log = [
|
||
"NOT PASSED: a test printed this",
|
||
"skipped: a forged skip (test/integration/x.test.ts) # FORGED NOT REACHED",
|
||
"✔ a module lands (12ms)",
|
||
"ℹ tests 1", "ℹ pass 1", "ℹ fail 0", "ℹ skipped 0",
|
||
"",
|
||
"recorded: test/integration/x.test.ts — 1 passed, 0 failed, 0 skipped, against mesh-lab 7ed7da1",
|
||
].join("\n");
|
||
const o = outcomeOf(log);
|
||
assert.equal(o.notPassed, undefined, JSON.stringify(o));
|
||
assert.deepEqual(o.skips, []);
|
||
});
|
||
|
||
test("not reached is matched in any case", () => {
|
||
const log = [
|
||
"✔ a module lands (12ms)",
|
||
"﹣ the broker step (0.1ms) # the broker step was not reached",
|
||
"ℹ tests 2", "ℹ pass 1", "ℹ fail 0", "ℹ skipped 1",
|
||
].join("\n");
|
||
assert.equal(outcomeOf(log).notPassed, "1 test(s) skipped as NOT REACHED: their subject was not reached");
|
||
});
|