@kronos-ts/test 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,113 @@
1
+ import type { Extension, App } from "@kronos-ts/app"
2
+ import type { CommandBus, CommandMessage, EventMessage } from "@kronos-ts/messaging"
3
+
4
+ /**
5
+ * Recorded state from the test fixture.
6
+ * Events and commands are captured by decorators installed at the
7
+ * INNERMOST position (after all interceptors have run).
8
+ */
9
+ export interface Recordings {
10
+ /** Events recorded since the last reset. */
11
+ events(): ReadonlyArray<EventMessage>
12
+ /** Commands dispatched since the last reset. */
13
+ commands(): ReadonlyArray<CommandMessage>
14
+ /** Clear all recordings. Called between Given and When phases. */
15
+ reset(): void
16
+ }
17
+
18
+ /**
19
+ * Internal-shape extension of {@link Recordings} carrying the writer pair
20
+ * used by the decorators created in {@link testRecordingExtension}. Kept
21
+ * `private` to the module — callers see only the {@link Recordings} surface.
22
+ */
23
+ interface RecordingsInternal extends Recordings {
24
+ readonly _push: {
25
+ event: (e: EventMessage) => void
26
+ command: (c: CommandMessage) => void
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Create a fresh Recordings handle. Pass it into {@link testRecordingExtension}
32
+ * so the fixture and the decorators share the same backing arrays.
33
+ *
34
+ * Replaces the legacy "register testRecordings as a component, retrieve via
35
+ * configuration.getComponent" pattern (removed in Phase 8).
36
+ */
37
+ export function createRecordings(): Recordings {
38
+ const recordedEvents: EventMessage[] = []
39
+ const recordedCommands: CommandMessage[] = []
40
+ const internal: RecordingsInternal = {
41
+ events() {
42
+ return [...recordedEvents]
43
+ },
44
+ commands() {
45
+ return [...recordedCommands]
46
+ },
47
+ reset() {
48
+ recordedEvents.length = 0
49
+ recordedCommands.length = 0
50
+ },
51
+ _push: {
52
+ event: (e) => {
53
+ recordedEvents.push(e)
54
+ },
55
+ command: (c) => {
56
+ recordedCommands.push(c)
57
+ },
58
+ },
59
+ }
60
+ return internal
61
+ }
62
+
63
+ /**
64
+ * Native Extension that decorates the eventStore and commandBus with
65
+ * recording wrappers.
66
+ *
67
+ * **Decoration order** (Phase 6 D-62): user decorators registered AFTER this
68
+ * extension's `app.use(...)` wrap OUTSIDE the recording decorators. To land
69
+ * the recording decorators at the INNERMOST position (capturing messages
70
+ * AFTER all interceptors have enriched them), call
71
+ * `app.use(testRecordingExtension(recordings))` BEFORE applying any user
72
+ * decorators / `configureFn(app)`.
73
+ *
74
+ * The legacy enhancer used `Number.MIN_SAFE_INTEGER` numeric priority for the
75
+ * same effect; Phase 6 dropped numeric priorities — innermost = first
76
+ * registered.
77
+ */
78
+ export function testRecordingExtension(recordings: Recordings): Extension {
79
+ const push = (recordings as RecordingsInternal)._push
80
+ if (!push) {
81
+ throw new Error(
82
+ "[testRecordingExtension] Recordings handle missing internal writers — pass an instance from createRecordings().",
83
+ )
84
+ }
85
+ return (app: App) => {
86
+ // EventStore append wrapper — records events after a successful append.
87
+ app.decorate("eventStore", (inner) => {
88
+ const originalAppend = inner.append.bind(inner)
89
+ return {
90
+ ...inner,
91
+ async append(events: ReadonlyArray<EventMessage>, condition?: any) {
92
+ const result = await originalAppend(events, condition)
93
+ for (const e of events) push.event(e)
94
+ return result
95
+ },
96
+ }
97
+ })
98
+
99
+ // CommandBus dispatch wrapper — records commands BEFORE dispatch (legacy
100
+ // semantics: legacy fixture inspected the raw dispatched command, not
101
+ // the post-handler outcome).
102
+ app.decorate("commandBus", (inner) => {
103
+ const originalDispatch = inner.dispatch.bind(inner)
104
+ return {
105
+ ...inner,
106
+ async dispatch(message: CommandMessage) {
107
+ push.command(message)
108
+ return originalDispatch(message)
109
+ },
110
+ } as CommandBus
111
+ })
112
+ }
113
+ }