diff --git a/test/integration/one-node-mesh.test.ts b/test/integration/one-node-mesh.test.ts index 710961d..3ce18e6 100644 --- a/test/integration/one-node-mesh.test.ts +++ b/test/integration/one-node-mesh.test.ts @@ -103,6 +103,12 @@ const MUST_RUN = ["mesh-control", "mesh-registry", "mesh-broker", "mesh-store", /** Named once, because the step title is also how later steps say what they waited on. */ const NEEDS = "the mesh runs a broker for that module to talk to"; const GENESIS = "a bare machine becomes a mesh of one, raised by the installer"; +const SUBSTRATE = "the substrate is up — a store and a broker of the mesh's own"; +const BUILT_CP = "the control plane is one this mesh built, not one it was handed"; +const PIVOTED = "the pivot finished — what raised the mesh is gone"; +const HAS_REGISTRY = "the registry serves this mesh its own images"; +const ENROLLED = "the machine is enrolled, and an agent is running on it"; +const HAS_BUILDER = "the builder is installed as a module, with an account"; const BASE_BUILT = "the mesh builds the shared base from source"; const STORE_RUNS = "the mesh builds and runs a store of its own"; const CATALOGUE_RUNS = "the mesh builds and runs its own catalogue"; @@ -365,6 +371,69 @@ before(async () => { return raised.report.join("\n"); }); + // ---- WHAT GENESIS CLAIMED, ASKED OF THE MACHINE ---------------------------------------------- + // + // **Genesis is twelve steps and was reported as one line.** All the work of raising a mesh + // happens inside it, so "genesis failed" said nothing about which of its claims broke, and + // "genesis passed" was one tick standing in for six separate things being true. + // + // Each is asked of the machine rather than read from the installer's own output — the installer + // saying it published an image and the registry serving one are different facts, and it is the + // second that matters. + + await step(SUBSTRATE, GENESIS, async () => { + await waitForContainer(CONTROL, "mesh-store", 120); + await waitForContainer(CONTROL, "mesh-broker", 120); + return (await on(CONTROL, `docker ps --format '{{.Names}}\t{{.Status}}'`)).out; + }); + + // **The whole reason the installer carries a builder.** A carried control-plane image would + // satisfy "a control plane is running" equally well, which is what makes this worth asserting: + // the image has to be one this mesh's own registry serves. + await step(BUILT_CP, GENESIS, async () => { + const image = (await on(CONTROL, + `docker inspect -f '{{.Config.Image}}' mesh-control 2>&1`)).out.trim(); + assert.match(image, /@sha256:[0-9a-f]{64}/, + `the control plane names its image by tag, not by digest: ${image}`); + assert.ok(image.includes(":5000/"), + `the control plane runs an image no registry of this mesh served: ${image}`); + return image; + }); + + await step(PIVOTED, BUILT_CP, async () => { + const ps = (await on(CONTROL, `docker ps -a --format '{{.Names}}'`)).out; + assert.doesNotMatch(ps, /^temp-mesh-control$/m, + `the temporary control plane is still here, so the pivot did not finish:\n${ps}`); + return ps; + }); + + await step(HAS_REGISTRY, SUBSTRATE, async () => { + await waitForContainer(CONTROL, "mesh-registry", 120); + const held = (await on(CONTROL, + `curl -sS --max-time 15 http://127.0.0.1:5000/v2/_catalog`)).out; + assert.match(held, /mesh-control/, + `the registry serves no mesh-control, so nothing was published into it:\n${held}`); + return held.trim(); + }); + + await step(ENROLLED, GENESIS, async () => { + const nodes = await mesh("node list"); + assert.match(nodes, new RegExp(`^${CONTROL}\\b`, "m"), + `the mesh has not heard from the machine it is running on:\n${nodes}`); + // Heard from once is not an agent running, and the difference is the whole of joining. + const agent = (await on(CONTROL, `pgrep -af '[m]esh-host run' | head -3`)).out.trim(); + assert.ok(agent, `no host agent is running, so nothing would apply what the mesh sends`); + return `${nodes.trim()}\n${agent}`; + }); + + await step(HAS_BUILDER, HAS_REGISTRY, async () => { + await waitForContainer(CONTROL, "mesh-builder", 120); + const modules = await mesh("module list"); + assert.match(modules, /^builder\b/m, + `the builder is running but the mesh holds no record of it as a module:\n${modules}`); + return modules; + }); + // ---- 2..6. THE MESH BECOMES ONE -------------------------------------------------------------- // // **Joining used to be here, second, and that was the wrong order.** Three machines were enrolled @@ -380,7 +449,7 @@ before(async () => { // The toolchain and runtime every module with code of its own stands on. It is a module, and it // is built like one — cloned from the forge by the builder installing put here, compiled on the // machine, published into the mesh's own registry. - await step(BASE_BUILT, GENESIS, async () => { + await step(BASE_BUILT, HAS_BUILDER, async () => { // Registered from the manifest the builder will also read, so what the mesh holds and what it // builds are the same description of the same module. // @@ -507,7 +576,8 @@ before(async () => { // And the catalogue, ASKED rather than observed. These five questions are what it exists for. const ask = async (tool: string, args = "{}") => - must(CONTROL, `docker exec mesh-catalog mesh-tools invoke mesh-catalog ${tool} ${quote(args)}`, + must(CONTROL, `docker exec mesh-catalog node /app/dist/main.js invoke mesh-catalog ` + + `${tool} ${quote(args)}`, 120_000); const held = await ask("catalog_modules"); @@ -631,6 +701,12 @@ after(async () => { for (const name of [ GENESIS, + SUBSTRATE, + BUILT_CP, + PIVOTED, + HAS_REGISTRY, + ENROLLED, + HAS_BUILDER, BASE_BUILT, STORE_RUNS, CATALOGUE_RUNS,