/** * What a runtime serves, it announces (novox/hq ADR 0197): the TypeScript runtime the per-module * containers still run answers the NATS services protocol's discovery in the same shape as the Go * tool runtime — its module's tools on every subject issued, and the seat verbs it serves. * * MESH_TEST_NATS=nats://127.0.0.1:14232 node --test --experimental-strip-types test/announce.test.ts */ import assert from "node:assert/strict"; import { test } from "node:test"; import { fileURLToPath } from "node:url"; import { connect, StringCodec } from "nats"; import { resetTools } from "@novox/mesh-sdk/tools"; import { connectNats, membershipSubject } from "../dist/broker-nats.js"; import { runTools } from "../dist/runtime.js"; const url = process.env.MESH_TEST_NATS; const fixture = (name: string) => fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)); const sc = StringCodec(); test("the runtime answers $SRV.INFO with what it serves, in the services protocol's format", async (t) => { if (!url) return t.skip("MESH_TEST_NATS unset"); resetTools(); const nc = await connect({ servers: url }); const jsm = await nc.jetstreamManager(); try { await jsm.streams.delete("ASSIGNMENTS"); } catch { // none yet } await jsm.streams.add({ name: "ASSIGNMENTS", subjects: ["mesh.assignment.>"], max_msgs_per_subject: 1, allow_direct: true } as never); await nc.jetstream().publish(membershipSubject("anchor", "shop"), sc.encode(JSON.stringify({ node: "anchor", module: "shop", serves: [{ subject: "mesh.mod.shop.tool.{tool}.anchor" }, { subject: "mesh.mod.shop.tool.{tool}", queue: "serve.shop" }], emits: "mesh.mod.shop.event.{event}", tools: "mesh.mod.shop.tool.tools", }))); const shop = await connectNats({ url, node: "anchor", module: "shop" }); let stop = () => {}; try { stop = await runTools({ broker: shop, moduleEntrypoints: [fixture("shop-tools.mjs")] }); const msg = await nc.request("$SRV.INFO.shop.anchor", sc.encode(""), { timeout: 2000 }); const info = JSON.parse(sc.decode(msg.data)) as { type: string; name: string; id: string; version: string; endpoints: { name: string; subject: string; queue_group: string; metadata: Record }[]; }; assert.equal(info.type, "io.nats.micro.v1.info_response"); assert.equal(info.name, "shop"); assert.equal(info.id, "anchor"); assert.ok(info.version); const price = info.endpoints.filter((e) => e.metadata.tool === "price").map((e) => `${e.subject}|${e.queue_group}`).sort(); assert.deepEqual(price, ["mesh.mod.shop.tool.price.anchor|", "mesh.mod.shop.tool.price|serve.shop"]); const one = info.endpoints.find((e) => e.metadata.tool === "price")!; assert.equal(one.metadata.kind, "tool"); assert.equal(one.metadata.module, "shop"); assert.equal(one.metadata.node, "anchor"); assert.equal(one.metadata.interchangeable, "true"); assert.ok(JSON.parse(one.metadata.schema).type === "object"); // Ping answers with the same identity; another service's request is not answered. const ping = JSON.parse(sc.decode((await nc.request("$SRV.PING", sc.encode(""), { timeout: 2000 })).data)); assert.equal(ping.type, "io.nats.micro.v1.ping_response"); await assert.rejects(nc.request("$SRV.INFO.somebody-else", sc.encode(""), { timeout: 300 })); } finally { stop(); await shop.close(); await nc.close(); resetTools(); } });