114 lines
4.2 KiB
TypeScript
114 lines
4.2 KiB
TypeScript
import type { TenantId } from "@suaveplan/api-types/tenant";
|
|
import type { BrokerEnvelope } from "@suaveplan/api-ws/broker";
|
|
import {
|
|
createInMemoryWsBroker,
|
|
type InMemoryWsBroker,
|
|
} from "../broker/broker.js";
|
|
import type { InMemoryWsBrokerOptions } from "../options/options.js";
|
|
|
|
/**
|
|
* Result returned by {@link createInMemoryBrokerHarness}.
|
|
*
|
|
* The harness ships everything a test needs to drive the broker:
|
|
*
|
|
* - `broker` — the broker instance under test.
|
|
* - `received` — a chronological array of every envelope delivered
|
|
* to the auto-subscribed observer, useful for `expect(...).toEqual`.
|
|
* - `observerSubscription` — disposal handle for the observer.
|
|
* - `publishOne(payload, opts?)` — convenience for publishing a
|
|
* single envelope on the harness's default topic + tenant.
|
|
* - `dispose()` — tears down the observer and disposes the broker
|
|
* (idempotent).
|
|
*/
|
|
export interface InMemoryBrokerHarness<Payload = unknown> {
|
|
readonly broker: InMemoryWsBroker<Payload>;
|
|
readonly received: ReadonlyArray<BrokerEnvelope<Payload>>;
|
|
readonly observerSubscription: { readonly topic: string };
|
|
publishOne(
|
|
payload: Payload,
|
|
opts?: { readonly topic?: string; readonly tenant?: TenantId }
|
|
): Promise<void>;
|
|
dispose(): void;
|
|
}
|
|
|
|
/**
|
|
* Options accepted by {@link createInMemoryBrokerHarness}. The defaults
|
|
* cover the 90% test path; supply `topic` / `tenant` only when the
|
|
* scenario needs to vary them.
|
|
*/
|
|
export interface CreateInMemoryBrokerHarnessOptions<Payload = unknown> {
|
|
readonly broker?: InMemoryWsBrokerOptions;
|
|
readonly topic?: string;
|
|
readonly tenant?: TenantId;
|
|
readonly extraTopics?: ReadonlyArray<string>;
|
|
readonly initialPayload?: Payload;
|
|
}
|
|
|
|
const DEFAULT_TOPIC = "test.events";
|
|
const DEFAULT_TENANT = "tenant-test" as unknown as TenantId;
|
|
|
|
/**
|
|
* Build a ready-to-use broker harness for tests. The harness wires up
|
|
* a single observer subscription on `topic` so callers can `publish`
|
|
* and assert on `received` without any boilerplate.
|
|
*
|
|
* The harness is intentionally additive: it does NOT replace the
|
|
* broker's public surface — `harness.broker` is the real
|
|
* {@link InMemoryWsBroker} returned by
|
|
* {@link createInMemoryWsBroker}, so any test can drop down to direct
|
|
* subscribe/publish/dispose when the convenience helpers don't fit.
|
|
*/
|
|
export function createInMemoryBrokerHarness<Payload = unknown>(
|
|
options: CreateInMemoryBrokerHarnessOptions<Payload> = {}
|
|
): InMemoryBrokerHarness<Payload> {
|
|
const topic = options.topic ?? DEFAULT_TOPIC;
|
|
const tenant = options.tenant ?? DEFAULT_TENANT;
|
|
const broker = createInMemoryWsBroker<Payload>(options.broker);
|
|
const received: BrokerEnvelope<Payload>[] = [];
|
|
const observerSubscription = broker.subscribe(topic, (envelope) => {
|
|
received.push(envelope);
|
|
});
|
|
// Pre-warm extra topics by registering a no-op subscriber so
|
|
// subscriberCount reports them. Useful for tests that probe the
|
|
// registry shape before publishing.
|
|
const extraHandles = (options.extraTopics ?? []).map((extra) =>
|
|
broker.subscribe(extra, () => {
|
|
/* probe subscriber */
|
|
})
|
|
);
|
|
let disposed = false;
|
|
return {
|
|
broker,
|
|
received,
|
|
observerSubscription: { topic: observerSubscription.topic },
|
|
async publishOne(
|
|
payload: Payload,
|
|
opts?: { readonly topic?: string; readonly tenant?: TenantId }
|
|
): Promise<void> {
|
|
const envelope: BrokerEnvelope<Payload> = {
|
|
topic: opts?.topic ?? topic,
|
|
tenant: opts?.tenant ?? tenant,
|
|
payload,
|
|
};
|
|
await broker.publish(envelope.topic, envelope);
|
|
},
|
|
dispose(): void {
|
|
if (disposed) return;
|
|
disposed = true;
|
|
try {
|
|
observerSubscription.unsubscribe();
|
|
} catch {
|
|
/* already torn down by broker.dispose */
|
|
}
|
|
for (const handle of extraHandles) {
|
|
try {
|
|
handle.unsubscribe();
|
|
} catch {
|
|
/* same */
|
|
}
|
|
}
|
|
broker.dispose();
|
|
},
|
|
};
|
|
}
|