# `testing` — `createInMemoryBrokerHarness` _(no submodules)_ ## Purpose This module exposes a small, opinionated test harness around the in-memory broker. It removes the boilerplate of wiring an observer subscription and a teardown helper, so most tests collapse to "build harness, publish, assert on `received`, dispose". The harness deliberately shares the production broker — there is no separate "fake" broker hiding underneath — which means every assertion exercises the real fan-out pipeline and every regression caught in tests is one that would also manifest in production single-host deployments. The harness lives under `./testing` (not the main entry) so consumer production bundles never pay for the helper machinery; only test bundles import this subpath. ## Features - `createInMemoryBrokerHarness(opts?)` factory that returns a ready-to-use bundle of `{ broker, received, observerSubscription, publishOne, dispose }`. - A pre-registered observer subscription on the harness's default topic (`"test.events"`) plus an extensible `extraTopics` list so tests that probe the registry shape don't need to subscribe manually. - `publishOne(payload, opts?)` convenience that materialises a full `BrokerEnvelope` from a payload, defaulting `topic` and `tenant` from the harness configuration. - `dispose()` is idempotent and tolerant of partial teardown — it can be called from `afterEach` even if the test itself disposed the broker directly. ## Usage: Basic ```ts import { describe, expect, it } from "@suaveplan/testing/runner"; import { createInMemoryBrokerHarness } from "@suaveplan/api-ws-broker-in-memory/testing"; describe("broker harness", () => { it("captures every published envelope", { timeout: 5000 }, async () => { const harness = createInMemoryBrokerHarness<{ id: number }>(); try { await harness.publishOne({ id: 1 }); await harness.publishOne({ id: 2 }); expect(harness.received.map((e) => e.payload.id)).toEqual([1, 2]); } finally { harness.dispose(); } }); }); ``` ## Usage: Advanced ```ts import { describe, expect, it } from "@suaveplan/testing/runner"; import type { TenantId } from "@suaveplan/api-types/tenant"; import { createInMemoryBrokerHarness } from "@suaveplan/api-ws-broker-in-memory/testing"; describe("broker harness — await-all + multi-topic", () => { it("delivers across additional topics", { timeout: 5000 }, async () => { const harness = createInMemoryBrokerHarness({ broker: { deliveryMode: "await-all" }, topic: "orders", tenant: "t1" as unknown as TenantId, extraTopics: ["audit", "billing"], }); try { await harness.publishOne("ok", { topic: "orders" }); // The probe subscribers on audit/billing keep them // registered for `subscriberCount` introspection. expect(harness.broker.subscriberCount("orders")).toBe(1); expect(harness.broker.subscriberCount("audit")).toBe(1); expect(harness.received).toHaveLength(1); expect(harness.received[0]?.payload).toBe("ok"); } finally { harness.dispose(); } }); }); ``` ## API Reference - `createInMemoryBrokerHarness(opts?): InMemoryBrokerHarness` - `interface CreateInMemoryBrokerHarnessOptions` — `{ broker?, topic?, tenant?, extraTopics?, initialPayload? }`. - `interface InMemoryBrokerHarness` — `{ broker, received, observerSubscription, publishOne, dispose }`. ## Implementation Notes - The harness is deliberately tiny — it intentionally does NOT attempt to drive the `runWsEngineConformance` harness, because that one expects a full `(bus, registry, broker, makeConnection)` tuple while this package only owns the broker leg. The package's conformance test in `tests/conformance/` runs broker-shaped type assertions instead, which is the right scope for a broker-only package. - `extraTopics` register a no-op subscriber so the topic survives in the registry for introspection (`subscriberCount(topic) === 1`). Without a probe subscriber the in-memory broker reaps zero-size topic entries from its `Map` and `subscriberCount` would report 0, which is the correct production behaviour but inconvenient when a test wants to assert "this topic exists right now". - The harness wraps the observer unsubscribe and broker dispose in separate `try`/`catch` blocks so an out-of-order teardown (test disposes the broker, then `afterEach` calls `harness.dispose()`) does not surface stale `BROKER_DISPOSED` errors.