import { test } from "node:test"; import assert from "node:assert/strict"; import { parseScenario } from "../src/declaration/parse.ts"; import { planPlacements, PLACEABLE, isPlaceable } from "../src/lifecycle/place.ts"; import { assertSupported, UnsupportedError } from "../src/lifecycle/supported.ts"; /** * `place:` is the seam where the lab stops being infrastructure with no consumer. Each test * names what it defends, per novox/hq ADR 0017. */ function scenario(place: string): ReturnType { return parseScenario(` scenario: placing segments: hosting: kind: public cidr: [192.0.2.0/24] machines: anchor: at: { segment: hosting, address: [192.0.2.10] } peer: at: { segment: hosting, address: [192.0.2.20] } ${place} `); } test("`all:` reaches every machine", () => { const placements = planPlacements(scenario("place:\n all: [host]")); assert.deepEqual( placements.map((p) => p.machine).sort(), ["anchor", "peer"], ); for (const p of placements) assert.deepEqual(p.artifacts, ["host"]); }); test("a per-machine entry OVERRIDES `all:`, it does not add to it", () => { // Worth being exact about: a scenario naming one artifact for one machine gets that // artifact, not that artifact plus everything in `all:`. The opposite reading would place // things nobody asked for, which is the shape of fault this lab exists to catch. const placements = planPlacements(scenario("place:\n all: [host]\n anchor: [substrate]")); const byMachine = new Map(placements.map((p) => [p.machine, p.artifacts])); assert.deepEqual(byMachine.get("anchor"), ["substrate"], "anchor should have ONLY substrate"); assert.deepEqual(byMachine.get("peer"), ["host"]); }); test("a machine placed with nothing is not a placement", () => { const placements = planPlacements(scenario("place:\n all: [host]\n anchor: []")); assert.deepEqual(placements.map((p) => p.machine), ["peer"]); }); test("no `place:` at all is no placements, not an error", () => { assert.deepEqual(planPlacements(scenario("")), []); }); test("placing the host is supported", () => { // The whole point of stage 1: this used to be refused. assert.doesNotThrow(() => assertSupported(scenario("place:\n all: [host]"))); }); test("a tier that does not exist is refused BY NAME", () => { // Named individually rather than refused as a whole, so a scenario placing a host and a // substrate is told exactly which half the lab cannot do — rather than being told `place:` // is unsupported when half of it now works. try { assertSupported(scenario("place:\n all: [host, substrate]\n peer: [control]")); assert.fail("expected a refusal"); } catch (err) { assert.ok(err instanceof UnsupportedError); const missing = err.missing.join("\n"); assert.match(missing, /substrate/, "the substrate was not named"); assert.match(missing, /control/, "the control plane was not named"); assert.doesNotMatch(missing, /place: host/, "the host is placeable and was refused anyway"); } }); test("the refusal says what CAN be placed", () => { // A refusal that does not say what is possible sends someone to the source to find out. try { assertSupported(scenario("place:\n all: [forge]")); assert.fail("expected a refusal"); } catch (err) { assert.ok(err instanceof UnsupportedError); for (const placeable of PLACEABLE) { assert.match(err.missing.join("\n"), new RegExp(placeable)); } } }); // --- runtime and image placement (novox/hq ADR 0006: the lab places what a sealed scenario // cannot fetch) --- test("an image reference is placeable, and a bare 'image:' is not", () => { assert.ok(isPlaceable("image:alpine@sha256:abc"), "a reference should be placeable"); assert.ok(isPlaceable("image:postgres:17"), "a tag is the scenario's business, not the lab's"); assert.ok(!isPlaceable("image:"), "there is nothing to place"); assert.ok(!isPlaceable("image: "), "whitespace is not a reference"); }); test("the placeables are host, runtime and an image", () => { assert.ok(isPlaceable("host")); assert.ok(isPlaceable("runtime")); assert.ok(!isPlaceable("substrate"), "the tiers above tier 0 do not exist yet"); assert.ok(!isPlaceable("control-plane")); }); test("an unplaceable artifact is named, not refused as a whole", () => { // A scenario placing a host and a substrate is told which half the lab cannot do — refusing // wholesale would send somebody looking for the wrong problem. const scenario = { name: "s", segments: {}, machines: { a: {} as never }, place: { a: ["host", "runtime", "image:alpine@sha256:x", "substrate"] }, } as unknown as Parameters[0]; assert.throws( () => assertSupported(scenario), (err: Error) => { // Checked as `place: —`, which is how an artifact is REPORTED as // unplaceable. Searching for the bare word matched the message's own list of what CAN // be placed, which mentions runtime — so the test failed on a correct message. assert.ok(err.message.includes("place: substrate —"), "the unplaceable one is named"); assert.ok(!err.message.includes("place: image:alpine"), "a placeable one is not"); assert.ok(!err.message.includes("place: runtime —"), "nor is runtime"); assert.ok(!err.message.includes("place: host —"), "nor is host"); return true; }, ); });