From 19560ca6a705c1f58e8c2662ad4af0664e525da9 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 26 Sep 2026 23:40:59 +0200 Subject: [PATCH] Hold the runtime's NATS client to the shared fixtures Read back from the stream rather than from the client that wrote it, so the check is what reached the wire. The runner lives with the implementation; the fixture stays in one place. --- test/conformance.mjs | 54 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 test/conformance.mjs diff --git a/test/conformance.mjs b/test/conformance.mjs new file mode 100644 index 0000000..0d8517c --- /dev/null +++ b/test/conformance.mjs @@ -0,0 +1,54 @@ +// 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);