From 928357f221fc45ed05401c73c0b156e97f591675 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Fri, 8 May 2026 21:56:18 +0200 Subject: [PATCH] feat(core-testing): RecordingRealtimeBroadcaster (local-type-alias pattern) --- .../core-testing/src/instrumentation/index.ts | 1 + .../recording-realtime-broadcaster.test.ts | 27 +++++++++++++++++ .../recording-realtime-broadcaster.ts | 29 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 packages/core-testing/src/instrumentation/recording-realtime-broadcaster.test.ts create mode 100644 packages/core-testing/src/instrumentation/recording-realtime-broadcaster.ts diff --git a/packages/core-testing/src/instrumentation/index.ts b/packages/core-testing/src/instrumentation/index.ts index 053357c..60ee39f 100644 --- a/packages/core-testing/src/instrumentation/index.ts +++ b/packages/core-testing/src/instrumentation/index.ts @@ -2,3 +2,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"; +export { RecordingRealtimeBroadcaster } from "./recording-realtime-broadcaster"; diff --git a/packages/core-testing/src/instrumentation/recording-realtime-broadcaster.test.ts b/packages/core-testing/src/instrumentation/recording-realtime-broadcaster.test.ts new file mode 100644 index 0000000..1e3d14b --- /dev/null +++ b/packages/core-testing/src/instrumentation/recording-realtime-broadcaster.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect } from "vitest"; +import { z } from "zod"; +import { RecordingRealtimeBroadcaster } from "@/instrumentation/recording-realtime-broadcaster"; + +const ch = { + name: "test.ch" as const, + schema: z.object({ x: z.number() }).strict(), + scope: "public" as const, +}; + +describe("RecordingRealtimeBroadcaster", () => { + it("records every broadcast call after schema validation", async () => { + const b = new RecordingRealtimeBroadcaster(); + await b.broadcast(ch, { x: 1 }); + await b.broadcast(ch, { x: 2 }); + expect(b.broadcasts).toEqual([ + { channel: "test.ch", payload: { x: 1 } }, + { channel: "test.ch", payload: { x: 2 } }, + ]); + }); + + it("rejects payloads that fail schema validation", async () => { + const b = new RecordingRealtimeBroadcaster(); + await expect(b.broadcast(ch, { x: "wrong" } as never)).rejects.toThrow(); + expect(b.broadcasts).toHaveLength(0); + }); +}); diff --git a/packages/core-testing/src/instrumentation/recording-realtime-broadcaster.ts b/packages/core-testing/src/instrumentation/recording-realtime-broadcaster.ts new file mode 100644 index 0000000..2ea6ce8 --- /dev/null +++ b/packages/core-testing/src/instrumentation/recording-realtime-broadcaster.ts @@ -0,0 +1,29 @@ +// Local type aliases mirroring @repo/core-realtime's contracts. Kept inline to +// avoid a build-graph cycle between core-testing (tooling) and core-realtime (core). +// Same pattern recording-event-bus + recording-job-queue use. +import type { z } from "zod"; + +type RealtimeChannelDescriptor = { + readonly name: TName; + readonly schema: TSchema; + readonly scope?: string; +}; + +interface IRealtimeBroadcaster { + broadcast( + descriptor: RealtimeChannelDescriptor>, + payload: T, + ): Promise; +} + +export class RecordingRealtimeBroadcaster implements IRealtimeBroadcaster { + readonly broadcasts: { channel: string; payload: unknown }[] = []; + + async broadcast( + descriptor: RealtimeChannelDescriptor>, + payload: T, + ): Promise { + descriptor.schema.parse(payload); + this.broadcasts.push({ channel: descriptor.name, payload }); + } +}