test(bus): live integration smoke against the real unibus cluster
A network smoke (self-skips unless BUS_HTTP/BUS_WS are set) that points the SDK at the live 3-node cluster. With a fresh, unregistered identity it asserts BOTH planes reject with an AUTHORIZATION error (not a signature/protocol error), proving the SDK speaks the control plane (signed canonical request) and the data plane (nats.ws + nkey) correctly end-to-end. Verified 2026-06-14 against datardos: control-plane 401 'identity not authorized', data-plane 'authorization violation'. Issue 0001, Phase 3.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
// Live integration smoke against the real unibus cluster. NOT part of the unit
|
||||
// suite (needs network + a running cluster + TLS bypass), so it self-skips unless
|
||||
// BUS_HTTP / BUS_WS are set. Run it explicitly:
|
||||
//
|
||||
// NODE_TLS_REJECT_UNAUTHORIZED=0 \
|
||||
// BUS_HTTP=https://51.91.100.142:8470 BUS_WS=wss://51.91.100.142:8480 \
|
||||
// pnpm exec vitest run src/bus/integration.test.ts
|
||||
//
|
||||
// What it proves WITHOUT a registered user: a fresh random identity is NOT in the
|
||||
// bus allowlist, so both planes must reject it with an AUTHORIZATION error — not a
|
||||
// signature/protocol error. That result confirms the SDK speaks both planes
|
||||
// correctly end-to-end (busauth canonical+signature on HTTP, nkey handshake on the
|
||||
// data plane); only the allowlist gate stops it. Issue uniweb/0001, Phase 3.
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { ed25519, x25519 } from "@noble/curves/ed25519.js";
|
||||
import { concatBytes } from "@noble/hashes/utils.js";
|
||||
import { signedHeaders, freshNonce } from "./busauth.js";
|
||||
import { WsNatsTransport } from "./wstransport.js";
|
||||
import type { Identity } from "./client.js";
|
||||
|
||||
const BUS_HTTP = process.env.BUS_HTTP;
|
||||
const BUS_WS = process.env.BUS_WS;
|
||||
const live = !!(BUS_HTTP && BUS_WS);
|
||||
|
||||
function freshIdentity(): Identity {
|
||||
const seed = crypto.getRandomValues(new Uint8Array(32));
|
||||
const signPub = ed25519.getPublicKey(seed);
|
||||
const signPriv = concatBytes(seed, signPub); // 64-byte Go layout
|
||||
const kexPriv = crypto.getRandomValues(new Uint8Array(32));
|
||||
const kexPub = x25519.getPublicKey(kexPriv);
|
||||
return { signPub, signPriv, kexPub, kexPriv };
|
||||
}
|
||||
|
||||
describe.skipIf(!live)("live cluster smoke", () => {
|
||||
const id = freshIdentity();
|
||||
|
||||
it("control plane: a signed request is processed (rejected by allowlist, not by signature)", async () => {
|
||||
const ts = String(Math.floor(Date.now() / 1000));
|
||||
const path = "/rooms/smoke-probe/members";
|
||||
const headers = signedHeaders(id.signPub, id.signPriv, "GET", path, ts, freshNonce(), new Uint8Array(0));
|
||||
const resp = await fetch(BUS_HTTP + path, { method: "GET", headers });
|
||||
const body = await resp.text();
|
||||
// The server verified our X-Unibus-* signature (busauth canonical + Ed25519 are
|
||||
// correct) and then rejected us for not being in the allowlist. A 401 whose body
|
||||
// is an authorization message — NOT "signature"/"canonical" — is the pass.
|
||||
expect(resp.status).toBe(401);
|
||||
expect(body.toLowerCase()).toContain("unauthorized");
|
||||
expect(body.toLowerCase()).not.toContain("signature");
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[control-plane] status=${resp.status} body=${body.trim()}`);
|
||||
});
|
||||
|
||||
it("data plane: nats.ws handshake reaches the nkey authenticator (authorization violation)", async () => {
|
||||
let connected = false;
|
||||
let errMsg = "";
|
||||
try {
|
||||
const t = await WsNatsTransport.connect([BUS_WS!], id);
|
||||
connected = true;
|
||||
await t.close();
|
||||
} catch (e) {
|
||||
errMsg = String((e as Error).message || e).toLowerCase();
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[data-plane] connected=${connected} err=${errMsg}`);
|
||||
// A fresh identity is not allow-listed, so the nkey authenticator must refuse the
|
||||
// connection. Reaching an "authorization"/"nkey" rejection proves the WS transport
|
||||
// + nkey signing path work against the real server. (If the user WERE registered,
|
||||
// connected would be true.)
|
||||
expect(connected || /authorization|nkey|permission|violation/.test(errMsg)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user