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.
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);
|