diff --git a/test/integration/mesh.test.ts b/test/integration/mesh.test.ts index 645c007..d3dddc8 100644 --- a/test/integration/mesh.test.ts +++ b/test/integration/mesh.test.ts @@ -1109,3 +1109,73 @@ test("a new commit reaches a machine that is already running the old one", { assert.doesNotMatch(after, /delivered.*<.*[0-9a-f]{8}/, `the module is still reported as behind after catching up:\n${after}`); }); + +test("the board names the machine that is not doing what it was told", { + skip, timeout: 900_000, +}, async () => { + // novox/hq 03-DESIGN/01-to-be/11-a-board.md. The board being replaced reads every context's + // database directly; this one asks the same questions through the same functions the commands + // use, and holds nothing. So the check is that what it says matches what the mesh says, and + // that it says the thing a person opened it for. + await must("anchor", `printf %s '{"module":"board","version":"1",` + + `"listens":[{"port":8090,"from":"mesh","why":"the board"}],` + + `"resources":[]}' > /tmp/board.json`); + await must("anchor", `docker cp /tmp/board.json mesh-control:/board.json`); + await mesh("module add /board.json"); + + // Served from the control plane's own container, reading the mesh on every request. + await must("anchor", `docker exec -d mesh-control /mesh-control board --listen 0.0.0.0:8090`); + await new Promise((r) => setTimeout(r, 3000)); + + const read = async (path: string) => + await on("anchor", `curl -sf http://127.0.0.1:8090${path}`, 30_000); + + let up = false; + let said = { out: "", ok: false }; + for (let i = 0; i < 15 && !up; i++) { + said = await read("/"); + up = said.ok; + if (!up) await new Promise((r) => setTimeout(r, 2000)); + } + assert.ok(up, `the board does not answer:\n${said.out}`); + + // Something a person opened it for: give a machine a declaration it cannot apply. + await must("anchor", `printf %s '{"module":"impossible","version":"1",` + + `"resources":[{"id":"nowhere","type":"file","path":"/does/not/exist/at/all/file",` + + `"content":"x","mode":"0644"}]}' > /tmp/impossible.json`); + await must("anchor", `docker cp /tmp/impossible.json mesh-control:/impossible.json`); + await mesh("module add /impossible.json"); + await mesh("assign laptop impossible"); + await mesh("push laptop"); + + let named = false; + let page = ""; + for (let i = 0; i < 20 && !named; i++) { + page = (await read("/")).out; + named = page.includes("laptop") && page.includes(">failed<"); + if (!named) await new Promise((r) => setTimeout(r, 3000)); + } + assert.ok(named, `the board does not name the machine that failed:\n${page}`); + + // The host's own words, which say exactly what it could not do. A board that said only + // "failed" would make a person go and ask the thing they opened the board to avoid asking. + assert.match(page, /nowhere/, `the board does not say what failed:\n${page}`); + + // It agrees with the command, because both read the same thing. Two answers to "which machine + // is broken" is worse than either alone. + const asJSON = JSON.parse((await read("/mesh.json")).out); + assert.equal(asJSON.wrong[0].node, "laptop", `the page and the JSON disagree: ${JSON.stringify(asJSON)}`); + assert.equal(asJSON.wrong[0].outcome, "failed"); + + const fromCommand = JSON.parse(await mesh("status --json")); + assert.deepEqual(asJSON.wrong, fromCommand.wrong, + "the board and the command disagree about which machine is broken"); + + // And it changes nothing: the mesh is exactly as it was after being read. + const before = await mesh("status"); + await read("/"); + assert.equal(await mesh("status"), before, "reading the board changed the mesh"); + + await mesh("unassign laptop impossible"); + await mesh("push laptop"); +});