feat(core-testing): RecordingEventBus

Adds RecordingEventBus implementing IEventBus for use in unit tests.
Validates payloads via the descriptor schema, records all publish calls,
and delivers events to subscribed handlers synchronously in subscription order.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-08 12:05:34 +02:00
parent 590b92f190
commit 1357e45f55
5 changed files with 74 additions and 0 deletions

View File

@@ -1,3 +1,4 @@
export { RecordingTracer, type RecordedSpan } from "./recording-tracer";
export { RecordingLogger, type RecordedCapture } from "./recording-logger";
export { RecordingJobQueue } from "./recording-job-queue";
export { RecordingEventBus } from "./recording-event-bus";

View File

@@ -0,0 +1,39 @@
import { describe, it, expect } from "vitest";
import { z } from "zod";
import { defineEvent } from "@repo/core-events";
import { RecordingEventBus } from "@/instrumentation/recording-event-bus";
const evt = defineEvent("test.evt", z.object({ id: z.string() }).strict());
describe("RecordingEventBus", () => {
it("records every publish call after schema validation", async () => {
const bus = new RecordingEventBus();
await bus.publish(evt, { id: "a" });
await bus.publish(evt, { id: "b" });
expect(bus.published).toEqual([
{ name: "test.evt", payload: { id: "a" } },
{ name: "test.evt", payload: { id: "b" } },
]);
});
it("rejects invalid payloads", async () => {
const bus = new RecordingEventBus();
await expect(
bus.publish(evt, { id: 1 } as unknown as { id: string }),
).rejects.toThrow();
expect(bus.published).toHaveLength(0);
});
it("invokes registered handlers sequentially in subscription order", async () => {
const bus = new RecordingEventBus();
const order: string[] = [];
bus.subscribe(evt, "consumer-a", async () => {
order.push("a");
});
bus.subscribe(evt, "consumer-b", async () => {
order.push("b");
});
await bus.publish(evt, { id: "x" });
expect(order).toEqual(["a", "b"]);
});
});

View File

@@ -0,0 +1,26 @@
import type { z } from "zod";
import type { EventDescriptor, EventHandler, IEventBus } from "@repo/core-events";
export class RecordingEventBus implements IEventBus {
readonly published: { name: string; payload: unknown }[] = [];
private readonly handlers = new Map<string, EventHandler<unknown>[]>();
async publish<T>(
descriptor: EventDescriptor<string, z.ZodType<T>>,
payload: T,
): Promise<void> {
descriptor.schema.parse(payload);
this.published.push({ name: descriptor.name, payload });
for (const h of this.handlers.get(descriptor.name) ?? []) await h(payload);
}
subscribe<T>(
descriptor: EventDescriptor<string, z.ZodType<T>>,
_consumerFeature: string,
handler: EventHandler<T>,
): void {
const arr = this.handlers.get(descriptor.name) ?? [];
arr.push(handler as EventHandler<unknown>);
this.handlers.set(descriptor.name, arr);
}
}