One repository, two modules (ADR 0069). `node-tools/` holds the runtime — its code, tests, package and the manifest of the module the controller composes a process for on every machine it is assigned to: a bundle of `src/main.js`, the interpreter as a package, a place for the node's credential, the loopback port the console declared, and leave to call every tool. Nothing about how it runs: which bundles to load, where the credential is and whose machine it is are the controller's to compose (WP2). The root module `mesh-tools` keeps the two images TypeScript bundles are compiled in and a module's own service may run in; it is no longer how tools reach a node. As node-tools, `serve` is also the console (ADR 0175 §6): the same process answers MCP on loopback for whoever is on the machine, through which the tools it serves can be called. A module's own runtime in a container keeps serving without a listener. The toolchain image now carries /app/runtime — a package.json saying the compiled files are ES modules and the production node_modules — for the builder to copy into every TypeScript bundle, so a bundle unpacked on a machine starts (ADR 0188 §5; the builder's side is the controller's). Proven here by compiling node-tools with the toolchain's exact flags and starting the result. The AMQP probe script is gone with the bus it probed.
55 lines
2.8 KiB
JavaScript
55 lines
2.8 KiB
JavaScript
// The TypeScript implementation, held to the shared fixtures (novox/hq ADR 0074, design 19).
|
|
//
|
|
// Run against a NATS server, because the question is what actually reaches the wire:
|
|
//
|
|
// docker run -d --rm --name c -p 14222:4222 nats:2.10-alpine -js
|
|
// node test/conformance.mjs
|
|
//
|
|
// **The runner lives with the implementation it exercises; the fixture does not.** It is read
|
|
// from the sdk's conformance directory by sibling path — the same file the Go suite reads. A
|
|
// fixture copied into each implementation is two fixtures, and two fixtures drift, which is the
|
|
// failure the suite exists to prevent.
|
|
import { readFileSync } from "node:fs";
|
|
import { connect } from "nats";
|
|
|
|
const clientPath = process.argv[2] ?? "../dist/broker-nats.js";
|
|
const { connectNats } = await import(clientPath);
|
|
const f = JSON.parse(readFileSync(new URL("../../mesh-sdk/conformance/events/module-event.json", import.meta.url)));
|
|
const URL_ = process.env.MESH_TEST_NATS ?? "nats://127.0.0.1:14222";
|
|
|
|
let failed = 0;
|
|
const check = (ok, what) => { console.log(` ${ok ? "ok " : "FAIL"} ${what}`); if (!ok) failed++; };
|
|
|
|
const admin = await connect({ servers: URL_ });
|
|
const jsm = await admin.jetstreamManager();
|
|
await jsm.streams.add({ name: "EVENTS", subjects: ["mesh.mod.*.event.>", "mesh.seat.*.event.>"] });
|
|
|
|
const shop = await connectNats({ url: URL_, node: f.given.node, module: f.given.module });
|
|
await shop.publish({ key: f.given.key, node: f.given.node, body: f.given.body, headers: f.given.headers });
|
|
|
|
// What actually landed, read back from the stream rather than from the client that wrote it.
|
|
const msg = await jsm.streams.getMessage("EVENTS", { last_by_subj: f.wire.subject });
|
|
check(!!msg, `it lands on ${f.wire.subject}`);
|
|
|
|
if (msg) {
|
|
const got = {};
|
|
if (msg.header) for (const k of msg.header.keys()) got[k] = msg.header.get(k);
|
|
for (const h of f.wire.requiredHeaders) {
|
|
check(got[h] !== undefined && got[h] !== "", `${h} is set`);
|
|
}
|
|
check(got["content-type"] === f.wire.headerFormats["content-type"], "content-type is as pinned");
|
|
check(new RegExp(f.wire.headerFormats["x-event-id"]).test(got["x-event-id"]), "x-event-id is as pinned");
|
|
check(!Number.isNaN(Date.parse(got["x-time"])), "x-time parses as a date");
|
|
check(got["x-source"] === f.given.module, "x-source agrees with the subject's module");
|
|
|
|
const payload = JSON.parse(new TextDecoder().decode(msg.data));
|
|
check(payload.envelope === undefined && payload.key === undefined,
|
|
"the payload is the body alone, not the envelope (the fixture refuses nesting)");
|
|
check(JSON.stringify(payload) === JSON.stringify(f.given.body),
|
|
"the body round-trips — semantic, not byte-exact, per the README");
|
|
}
|
|
|
|
await shop.close(); await admin.close();
|
|
console.log(failed ? `\n${failed} failed` : "\nall passed");
|
|
process.exit(failed ? 1 : 0);
|