The lab raised an underlay and put nothing on it: correct, and useless, because the thing it exists to test did not exist. Tier 0 now does, so `place: [host]` works and a raised scenario finally contains something. The refusal narrows rather than disappearing. A scenario placing a host and a substrate is told which half is missing, by name — not that `place:` is unsupported when half of it now works. Placement reads back rather than assuming. A file arriving is not a host working, so the binary is run before it is trusted to answer questions, and what it reports is read from the machine (ADR 0035). The binary comes from an explicit path, because the declaration design leaves where artifacts come from open and a search would harden into the answer by accident. The integration test that matters is the one asserting the host reports the MACHINE and not the workstation that placed it. A raised VM and this workstation differ in every capability — root versus uid 1000, a clean init versus a degraded one, no docker versus docker, no wireguard versus wg0 — so a host reporting the wrong machine is obvious here and invisible anywhere else. And the placed host independently confirms ADR 0031: overlay absent on a freshly raised machine. The underlay suite already asserted that by looking for wireguard interfaces; this is a second witness rather than the same check twice. Two tests failed the moment placement worked, which is what they were for. They defended "there is nothing to place yet" while that was true; the decision changed, so they change with it rather than being deleted. Gate: 75 unit, 20 integration.
90 lines
3.4 KiB
TypeScript
90 lines
3.4 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { parseScenario } from "../src/declaration/parse.ts";
|
|
import { planPlacements, PLACEABLE } 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 0034.
|
|
*/
|
|
|
|
function scenario(place: string): ReturnType<typeof parseScenario> {
|
|
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));
|
|
}
|
|
}
|
|
});
|