Initial Commit
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
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();
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user