@saga-bus/test 0.1.1

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Dean Foran
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # @saga-bus/test
2
+
3
+ Testing utilities for saga-bus applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add -D @saga-bus/test
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { TestHarness } from "@saga-bus/test";
15
+ import { orderSaga } from "./sagas/OrderSaga";
16
+
17
+ describe("OrderSaga", () => {
18
+ let harness: TestHarness;
19
+
20
+ beforeEach(async () => {
21
+ harness = await TestHarness.create({
22
+ sagas: [orderSaga],
23
+ });
24
+ });
25
+
26
+ afterEach(async () => {
27
+ await harness.stop();
28
+ });
29
+
30
+ it("should process order", async () => {
31
+ // Publish a message
32
+ await harness.publish({
33
+ type: "OrderSubmitted",
34
+ orderId: "123",
35
+ });
36
+
37
+ // Wait for processing
38
+ await harness.waitForIdle();
39
+
40
+ // Check saga state
41
+ const state = await harness.getSagaState("OrderSaga", "123");
42
+ expect(state.status).toBe("pending");
43
+
44
+ // Check published messages
45
+ const messages = harness.getPublishedMessages();
46
+ expect(messages).toContainEqual(
47
+ expect.objectContaining({ type: "PaymentRequested" })
48
+ );
49
+ });
50
+ });
51
+ ```
52
+
53
+ ## API
54
+
55
+ ### `TestHarness.create(options)`
56
+
57
+ Create a test harness with given sagas.
58
+
59
+ ### `harness.publish(message)`
60
+
61
+ Publish a message to the bus.
62
+
63
+ ### `harness.waitForIdle()`
64
+
65
+ Wait for all messages to be processed.
66
+
67
+ ### `harness.getSagaState(sagaName, correlationId)`
68
+
69
+ Get the current state of a saga instance.
70
+
71
+ ### `harness.getPublishedMessages()`
72
+
73
+ Get all messages published by handlers.
74
+
75
+ ### `harness.stop()`
76
+
77
+ Clean up resources.
78
+
79
+ ## License
80
+
81
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ TestHarness: () => TestHarness
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/TestHarness.ts
28
+ var import_core = require("@saga-bus/core");
29
+ var import_transport_inmemory = require("@saga-bus/transport-inmemory");
30
+ var import_store_inmemory = require("@saga-bus/store-inmemory");
31
+ var TestHarness = class _TestHarness {
32
+ bus;
33
+ transport;
34
+ stores;
35
+ capturedMessages = [];
36
+ constructor(bus, transport, stores) {
37
+ this.bus = bus;
38
+ this.transport = transport;
39
+ this.stores = stores;
40
+ }
41
+ /**
42
+ * Create a new test harness.
43
+ */
44
+ static async create(options) {
45
+ const transport = new import_transport_inmemory.InMemoryTransport({ defaultConcurrency: 1 });
46
+ const stores = /* @__PURE__ */ new Map();
47
+ let sharedStoreInstance;
48
+ if (options.store && options.store instanceof import_store_inmemory.InMemorySagaStore) {
49
+ sharedStoreInstance = options.store;
50
+ }
51
+ const registrations = options.sagas.map((definition) => {
52
+ let store;
53
+ if (options.stores?.[definition.name]) {
54
+ store = options.stores[definition.name];
55
+ if (store instanceof import_store_inmemory.InMemorySagaStore) {
56
+ stores.set(definition.name, store);
57
+ }
58
+ } else if (options.store) {
59
+ store = options.store;
60
+ if (sharedStoreInstance) {
61
+ stores.set(definition.name, sharedStoreInstance);
62
+ }
63
+ } else {
64
+ const memStore = new import_store_inmemory.InMemorySagaStore();
65
+ stores.set(definition.name, memStore);
66
+ store = memStore;
67
+ }
68
+ return {
69
+ definition,
70
+ store
71
+ };
72
+ });
73
+ const bus = (0, import_core.createBus)({
74
+ transport,
75
+ sagas: registrations,
76
+ // Disable retries in tests - fail fast, surface errors immediately
77
+ worker: {
78
+ retryPolicy: {
79
+ maxAttempts: 1,
80
+ baseDelayMs: 0,
81
+ maxDelayMs: 0,
82
+ backoff: "linear"
83
+ }
84
+ }
85
+ });
86
+ const harness = new _TestHarness(bus, transport, stores);
87
+ await bus.start();
88
+ return harness;
89
+ }
90
+ /**
91
+ * Publish a message to the bus.
92
+ * Waits for message processing to complete.
93
+ */
94
+ async publish(message, options) {
95
+ const endpoint = options?.endpoint ?? message.type;
96
+ this.capturedMessages.push({
97
+ message,
98
+ endpoint,
99
+ timestamp: /* @__PURE__ */ new Date()
100
+ });
101
+ await this.bus.publish(message, { endpoint });
102
+ await this.flush();
103
+ }
104
+ /**
105
+ * Get a saga state by saga name and correlation/saga ID.
106
+ *
107
+ * @param sagaName - Name of the saga
108
+ * @param id - Either the saga ID or correlation ID
109
+ */
110
+ async getSagaState(sagaName, id) {
111
+ const store = this.stores.get(sagaName);
112
+ if (!store) {
113
+ throw new Error(`No store found for saga "${sagaName}"`);
114
+ }
115
+ let state = await store.getByCorrelationId(sagaName, id);
116
+ if (state) {
117
+ return state;
118
+ }
119
+ state = await store.getById(sagaName, id);
120
+ return state;
121
+ }
122
+ /**
123
+ * Get all saga states for a given saga name.
124
+ */
125
+ getAllSagaStates(sagaName) {
126
+ const store = this.stores.get(sagaName);
127
+ if (!store) {
128
+ throw new Error(`No store found for saga "${sagaName}"`);
129
+ }
130
+ return store.getAll();
131
+ }
132
+ /**
133
+ * Get all messages that were published during the test.
134
+ */
135
+ getPublishedMessages() {
136
+ return [...this.capturedMessages];
137
+ }
138
+ /**
139
+ * Get published messages filtered by type.
140
+ */
141
+ getPublishedMessagesByType(type) {
142
+ return this.capturedMessages.filter(
143
+ (m) => m.message.type === type
144
+ );
145
+ }
146
+ /**
147
+ * Clear captured messages.
148
+ */
149
+ clearPublishedMessages() {
150
+ this.capturedMessages.length = 0;
151
+ }
152
+ /**
153
+ * Reset the harness state (clears all stores and captured messages).
154
+ */
155
+ reset() {
156
+ for (const store of this.stores.values()) {
157
+ store.clear();
158
+ }
159
+ this.capturedMessages.length = 0;
160
+ }
161
+ /**
162
+ * Wait for any pending async operations to complete.
163
+ */
164
+ async flush(delayMs = 10) {
165
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
166
+ }
167
+ /**
168
+ * Stop the test harness.
169
+ */
170
+ async stop() {
171
+ await this.bus.stop();
172
+ }
173
+ /**
174
+ * Get the underlying bus instance.
175
+ */
176
+ getBus() {
177
+ return this.bus;
178
+ }
179
+ /**
180
+ * Get the underlying transport.
181
+ */
182
+ getTransport() {
183
+ return this.transport;
184
+ }
185
+ /**
186
+ * Get the store for a specific saga.
187
+ */
188
+ getStore(sagaName) {
189
+ return this.stores.get(sagaName);
190
+ }
191
+ };
192
+ // Annotate the CommonJS export names for ESM import in node:
193
+ 0 && (module.exports = {
194
+ TestHarness
195
+ });
196
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/TestHarness.ts"],"sourcesContent":["export { TestHarness } from \"./TestHarness.js\";\nexport type { TestHarnessOptions, CapturedMessage } from \"./TestHarness.js\";\n","import type {\n BaseMessage,\n SagaState,\n SagaDefinition,\n Bus,\n SagaStore,\n} from \"@saga-bus/core\";\nimport { createBus } from \"@saga-bus/core\";\nimport { InMemoryTransport } from \"@saga-bus/transport-inmemory\";\nimport { InMemorySagaStore } from \"@saga-bus/store-inmemory\";\n\n/**\n * Options for creating a TestHarness.\n */\nexport interface TestHarnessOptions<\n TState extends SagaState,\n TMessages extends BaseMessage\n> {\n /**\n * Saga definitions to register with the test bus.\n */\n sagas: SagaDefinition<TState, TMessages>[];\n\n /**\n * Optional shared store for all sagas.\n * If not provided, an InMemorySagaStore is created per saga.\n */\n store?: SagaStore<TState>;\n\n /**\n * Optional custom stores per saga (by name).\n * Takes precedence over the shared store.\n */\n stores?: Record<string, SagaStore<TState>>;\n}\n\n/**\n * A captured published message.\n */\nexport interface CapturedMessage<T extends BaseMessage = BaseMessage> {\n message: T;\n endpoint: string;\n timestamp: Date;\n}\n\n/**\n * Test harness for saga testing.\n * Provides an isolated environment with in-memory transport and stores.\n *\n * @example\n * ```typescript\n * const harness = await TestHarness.create({ sagas: [orderSaga] });\n *\n * await harness.publish({ type: \"OrderSubmitted\", orderId: \"123\" });\n *\n * const state = await harness.getSagaState(\"OrderSaga\", \"123\");\n * expect(state?.status).toBe(\"submitted\");\n *\n * const messages = harness.getPublishedMessages();\n * expect(messages).toContainEqual(expect.objectContaining({ type: \"OrderCreated\" }));\n * ```\n */\nexport class TestHarness<\n TState extends SagaState,\n TMessages extends BaseMessage\n> {\n private readonly bus: Bus;\n private readonly transport: InMemoryTransport;\n private readonly stores: Map<string, InMemorySagaStore<TState>>;\n private readonly capturedMessages: CapturedMessage<TMessages>[] = [];\n\n private constructor(\n bus: Bus,\n transport: InMemoryTransport,\n stores: Map<string, InMemorySagaStore<TState>>\n ) {\n this.bus = bus;\n this.transport = transport;\n this.stores = stores;\n }\n\n /**\n * Create a new test harness.\n */\n static async create<TState extends SagaState, TMessages extends BaseMessage>(\n options: TestHarnessOptions<TState, TMessages>\n ): Promise<TestHarness<TState, TMessages>> {\n const transport = new InMemoryTransport({ defaultConcurrency: 1 });\n const stores = new Map<string, InMemorySagaStore<TState>>();\n\n // Track shared store for getSagaState lookups\n let sharedStoreInstance: InMemorySagaStore<TState> | undefined;\n if (options.store && options.store instanceof InMemorySagaStore) {\n sharedStoreInstance = options.store;\n }\n\n // Create registrations\n // Priority: per-saga store > shared store > auto-create InMemory\n const registrations = options.sagas.map((definition) => {\n let store: SagaStore<TState>;\n if (options.stores?.[definition.name]) {\n // Per-saga store override\n store = options.stores[definition.name]!;\n if (store instanceof InMemorySagaStore) {\n stores.set(definition.name, store);\n }\n } else if (options.store) {\n // Shared store\n store = options.store;\n if (sharedStoreInstance) {\n stores.set(definition.name, sharedStoreInstance);\n }\n } else {\n // Auto-create InMemory store\n const memStore = new InMemorySagaStore<TState>();\n stores.set(definition.name, memStore);\n store = memStore;\n }\n\n return {\n definition,\n store,\n };\n });\n\n const bus = createBus({\n transport,\n sagas: registrations,\n // Disable retries in tests - fail fast, surface errors immediately\n worker: {\n retryPolicy: {\n maxAttempts: 1,\n baseDelayMs: 0,\n maxDelayMs: 0,\n backoff: \"linear\",\n },\n },\n });\n\n const harness = new TestHarness<TState, TMessages>(bus, transport, stores);\n\n // Start the bus\n await bus.start();\n\n return harness;\n }\n\n /**\n * Publish a message to the bus.\n * Waits for message processing to complete.\n */\n async publish<T extends TMessages>(\n message: T,\n options?: { endpoint?: string }\n ): Promise<void> {\n const endpoint = options?.endpoint ?? message.type;\n\n // Capture the message\n this.capturedMessages.push({\n message: message as TMessages,\n endpoint,\n timestamp: new Date(),\n });\n\n // Publish through the bus\n await this.bus.publish(message, { endpoint });\n\n // Small delay to ensure async processing completes\n await this.flush();\n }\n\n /**\n * Get a saga state by saga name and correlation/saga ID.\n *\n * @param sagaName - Name of the saga\n * @param id - Either the saga ID or correlation ID\n */\n async getSagaState(sagaName: string, id: string): Promise<TState | null> {\n const store = this.stores.get(sagaName);\n if (!store) {\n throw new Error(`No store found for saga \"${sagaName}\"`);\n }\n\n // Try by correlation ID first\n let state = await store.getByCorrelationId(sagaName, id);\n if (state) {\n return state;\n }\n\n // Fall back to saga ID\n state = await store.getById(sagaName, id);\n return state;\n }\n\n /**\n * Get all saga states for a given saga name.\n */\n getAllSagaStates(sagaName: string): TState[] {\n const store = this.stores.get(sagaName);\n if (!store) {\n throw new Error(`No store found for saga \"${sagaName}\"`);\n }\n\n return store.getAll();\n }\n\n /**\n * Get all messages that were published during the test.\n */\n getPublishedMessages(): CapturedMessage<TMessages>[] {\n return [...this.capturedMessages];\n }\n\n /**\n * Get published messages filtered by type.\n */\n getPublishedMessagesByType<T extends TMessages[\"type\"]>(\n type: T\n ): CapturedMessage<Extract<TMessages, { type: T }>>[] {\n return this.capturedMessages.filter(\n (m) => m.message.type === type\n ) as CapturedMessage<Extract<TMessages, { type: T }>>[];\n }\n\n /**\n * Clear captured messages.\n */\n clearPublishedMessages(): void {\n this.capturedMessages.length = 0;\n }\n\n /**\n * Reset the harness state (clears all stores and captured messages).\n */\n reset(): void {\n for (const store of this.stores.values()) {\n store.clear();\n }\n this.capturedMessages.length = 0;\n }\n\n /**\n * Wait for any pending async operations to complete.\n */\n async flush(delayMs = 10): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n /**\n * Stop the test harness.\n */\n async stop(): Promise<void> {\n await this.bus.stop();\n }\n\n /**\n * Get the underlying bus instance.\n */\n getBus(): Bus {\n return this.bus;\n }\n\n /**\n * Get the underlying transport.\n */\n getTransport(): InMemoryTransport {\n return this.transport;\n }\n\n /**\n * Get the store for a specific saga.\n */\n getStore(sagaName: string): InMemorySagaStore<TState> | undefined {\n return this.stores.get(sagaName);\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,117 @@
1
+ import { SagaState, BaseMessage, SagaDefinition, SagaStore, Bus } from '@saga-bus/core';
2
+ import { InMemoryTransport } from '@saga-bus/transport-inmemory';
3
+ import { InMemorySagaStore } from '@saga-bus/store-inmemory';
4
+
5
+ /**
6
+ * Options for creating a TestHarness.
7
+ */
8
+ interface TestHarnessOptions<TState extends SagaState, TMessages extends BaseMessage> {
9
+ /**
10
+ * Saga definitions to register with the test bus.
11
+ */
12
+ sagas: SagaDefinition<TState, TMessages>[];
13
+ /**
14
+ * Optional shared store for all sagas.
15
+ * If not provided, an InMemorySagaStore is created per saga.
16
+ */
17
+ store?: SagaStore<TState>;
18
+ /**
19
+ * Optional custom stores per saga (by name).
20
+ * Takes precedence over the shared store.
21
+ */
22
+ stores?: Record<string, SagaStore<TState>>;
23
+ }
24
+ /**
25
+ * A captured published message.
26
+ */
27
+ interface CapturedMessage<T extends BaseMessage = BaseMessage> {
28
+ message: T;
29
+ endpoint: string;
30
+ timestamp: Date;
31
+ }
32
+ /**
33
+ * Test harness for saga testing.
34
+ * Provides an isolated environment with in-memory transport and stores.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const harness = await TestHarness.create({ sagas: [orderSaga] });
39
+ *
40
+ * await harness.publish({ type: "OrderSubmitted", orderId: "123" });
41
+ *
42
+ * const state = await harness.getSagaState("OrderSaga", "123");
43
+ * expect(state?.status).toBe("submitted");
44
+ *
45
+ * const messages = harness.getPublishedMessages();
46
+ * expect(messages).toContainEqual(expect.objectContaining({ type: "OrderCreated" }));
47
+ * ```
48
+ */
49
+ declare class TestHarness<TState extends SagaState, TMessages extends BaseMessage> {
50
+ private readonly bus;
51
+ private readonly transport;
52
+ private readonly stores;
53
+ private readonly capturedMessages;
54
+ private constructor();
55
+ /**
56
+ * Create a new test harness.
57
+ */
58
+ static create<TState extends SagaState, TMessages extends BaseMessage>(options: TestHarnessOptions<TState, TMessages>): Promise<TestHarness<TState, TMessages>>;
59
+ /**
60
+ * Publish a message to the bus.
61
+ * Waits for message processing to complete.
62
+ */
63
+ publish<T extends TMessages>(message: T, options?: {
64
+ endpoint?: string;
65
+ }): Promise<void>;
66
+ /**
67
+ * Get a saga state by saga name and correlation/saga ID.
68
+ *
69
+ * @param sagaName - Name of the saga
70
+ * @param id - Either the saga ID or correlation ID
71
+ */
72
+ getSagaState(sagaName: string, id: string): Promise<TState | null>;
73
+ /**
74
+ * Get all saga states for a given saga name.
75
+ */
76
+ getAllSagaStates(sagaName: string): TState[];
77
+ /**
78
+ * Get all messages that were published during the test.
79
+ */
80
+ getPublishedMessages(): CapturedMessage<TMessages>[];
81
+ /**
82
+ * Get published messages filtered by type.
83
+ */
84
+ getPublishedMessagesByType<T extends TMessages["type"]>(type: T): CapturedMessage<Extract<TMessages, {
85
+ type: T;
86
+ }>>[];
87
+ /**
88
+ * Clear captured messages.
89
+ */
90
+ clearPublishedMessages(): void;
91
+ /**
92
+ * Reset the harness state (clears all stores and captured messages).
93
+ */
94
+ reset(): void;
95
+ /**
96
+ * Wait for any pending async operations to complete.
97
+ */
98
+ flush(delayMs?: number): Promise<void>;
99
+ /**
100
+ * Stop the test harness.
101
+ */
102
+ stop(): Promise<void>;
103
+ /**
104
+ * Get the underlying bus instance.
105
+ */
106
+ getBus(): Bus;
107
+ /**
108
+ * Get the underlying transport.
109
+ */
110
+ getTransport(): InMemoryTransport;
111
+ /**
112
+ * Get the store for a specific saga.
113
+ */
114
+ getStore(sagaName: string): InMemorySagaStore<TState> | undefined;
115
+ }
116
+
117
+ export { type CapturedMessage, TestHarness, type TestHarnessOptions };
@@ -0,0 +1,117 @@
1
+ import { SagaState, BaseMessage, SagaDefinition, SagaStore, Bus } from '@saga-bus/core';
2
+ import { InMemoryTransport } from '@saga-bus/transport-inmemory';
3
+ import { InMemorySagaStore } from '@saga-bus/store-inmemory';
4
+
5
+ /**
6
+ * Options for creating a TestHarness.
7
+ */
8
+ interface TestHarnessOptions<TState extends SagaState, TMessages extends BaseMessage> {
9
+ /**
10
+ * Saga definitions to register with the test bus.
11
+ */
12
+ sagas: SagaDefinition<TState, TMessages>[];
13
+ /**
14
+ * Optional shared store for all sagas.
15
+ * If not provided, an InMemorySagaStore is created per saga.
16
+ */
17
+ store?: SagaStore<TState>;
18
+ /**
19
+ * Optional custom stores per saga (by name).
20
+ * Takes precedence over the shared store.
21
+ */
22
+ stores?: Record<string, SagaStore<TState>>;
23
+ }
24
+ /**
25
+ * A captured published message.
26
+ */
27
+ interface CapturedMessage<T extends BaseMessage = BaseMessage> {
28
+ message: T;
29
+ endpoint: string;
30
+ timestamp: Date;
31
+ }
32
+ /**
33
+ * Test harness for saga testing.
34
+ * Provides an isolated environment with in-memory transport and stores.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const harness = await TestHarness.create({ sagas: [orderSaga] });
39
+ *
40
+ * await harness.publish({ type: "OrderSubmitted", orderId: "123" });
41
+ *
42
+ * const state = await harness.getSagaState("OrderSaga", "123");
43
+ * expect(state?.status).toBe("submitted");
44
+ *
45
+ * const messages = harness.getPublishedMessages();
46
+ * expect(messages).toContainEqual(expect.objectContaining({ type: "OrderCreated" }));
47
+ * ```
48
+ */
49
+ declare class TestHarness<TState extends SagaState, TMessages extends BaseMessage> {
50
+ private readonly bus;
51
+ private readonly transport;
52
+ private readonly stores;
53
+ private readonly capturedMessages;
54
+ private constructor();
55
+ /**
56
+ * Create a new test harness.
57
+ */
58
+ static create<TState extends SagaState, TMessages extends BaseMessage>(options: TestHarnessOptions<TState, TMessages>): Promise<TestHarness<TState, TMessages>>;
59
+ /**
60
+ * Publish a message to the bus.
61
+ * Waits for message processing to complete.
62
+ */
63
+ publish<T extends TMessages>(message: T, options?: {
64
+ endpoint?: string;
65
+ }): Promise<void>;
66
+ /**
67
+ * Get a saga state by saga name and correlation/saga ID.
68
+ *
69
+ * @param sagaName - Name of the saga
70
+ * @param id - Either the saga ID or correlation ID
71
+ */
72
+ getSagaState(sagaName: string, id: string): Promise<TState | null>;
73
+ /**
74
+ * Get all saga states for a given saga name.
75
+ */
76
+ getAllSagaStates(sagaName: string): TState[];
77
+ /**
78
+ * Get all messages that were published during the test.
79
+ */
80
+ getPublishedMessages(): CapturedMessage<TMessages>[];
81
+ /**
82
+ * Get published messages filtered by type.
83
+ */
84
+ getPublishedMessagesByType<T extends TMessages["type"]>(type: T): CapturedMessage<Extract<TMessages, {
85
+ type: T;
86
+ }>>[];
87
+ /**
88
+ * Clear captured messages.
89
+ */
90
+ clearPublishedMessages(): void;
91
+ /**
92
+ * Reset the harness state (clears all stores and captured messages).
93
+ */
94
+ reset(): void;
95
+ /**
96
+ * Wait for any pending async operations to complete.
97
+ */
98
+ flush(delayMs?: number): Promise<void>;
99
+ /**
100
+ * Stop the test harness.
101
+ */
102
+ stop(): Promise<void>;
103
+ /**
104
+ * Get the underlying bus instance.
105
+ */
106
+ getBus(): Bus;
107
+ /**
108
+ * Get the underlying transport.
109
+ */
110
+ getTransport(): InMemoryTransport;
111
+ /**
112
+ * Get the store for a specific saga.
113
+ */
114
+ getStore(sagaName: string): InMemorySagaStore<TState> | undefined;
115
+ }
116
+
117
+ export { type CapturedMessage, TestHarness, type TestHarnessOptions };
package/dist/index.js ADDED
@@ -0,0 +1,169 @@
1
+ // src/TestHarness.ts
2
+ import { createBus } from "@saga-bus/core";
3
+ import { InMemoryTransport } from "@saga-bus/transport-inmemory";
4
+ import { InMemorySagaStore } from "@saga-bus/store-inmemory";
5
+ var TestHarness = class _TestHarness {
6
+ bus;
7
+ transport;
8
+ stores;
9
+ capturedMessages = [];
10
+ constructor(bus, transport, stores) {
11
+ this.bus = bus;
12
+ this.transport = transport;
13
+ this.stores = stores;
14
+ }
15
+ /**
16
+ * Create a new test harness.
17
+ */
18
+ static async create(options) {
19
+ const transport = new InMemoryTransport({ defaultConcurrency: 1 });
20
+ const stores = /* @__PURE__ */ new Map();
21
+ let sharedStoreInstance;
22
+ if (options.store && options.store instanceof InMemorySagaStore) {
23
+ sharedStoreInstance = options.store;
24
+ }
25
+ const registrations = options.sagas.map((definition) => {
26
+ let store;
27
+ if (options.stores?.[definition.name]) {
28
+ store = options.stores[definition.name];
29
+ if (store instanceof InMemorySagaStore) {
30
+ stores.set(definition.name, store);
31
+ }
32
+ } else if (options.store) {
33
+ store = options.store;
34
+ if (sharedStoreInstance) {
35
+ stores.set(definition.name, sharedStoreInstance);
36
+ }
37
+ } else {
38
+ const memStore = new InMemorySagaStore();
39
+ stores.set(definition.name, memStore);
40
+ store = memStore;
41
+ }
42
+ return {
43
+ definition,
44
+ store
45
+ };
46
+ });
47
+ const bus = createBus({
48
+ transport,
49
+ sagas: registrations,
50
+ // Disable retries in tests - fail fast, surface errors immediately
51
+ worker: {
52
+ retryPolicy: {
53
+ maxAttempts: 1,
54
+ baseDelayMs: 0,
55
+ maxDelayMs: 0,
56
+ backoff: "linear"
57
+ }
58
+ }
59
+ });
60
+ const harness = new _TestHarness(bus, transport, stores);
61
+ await bus.start();
62
+ return harness;
63
+ }
64
+ /**
65
+ * Publish a message to the bus.
66
+ * Waits for message processing to complete.
67
+ */
68
+ async publish(message, options) {
69
+ const endpoint = options?.endpoint ?? message.type;
70
+ this.capturedMessages.push({
71
+ message,
72
+ endpoint,
73
+ timestamp: /* @__PURE__ */ new Date()
74
+ });
75
+ await this.bus.publish(message, { endpoint });
76
+ await this.flush();
77
+ }
78
+ /**
79
+ * Get a saga state by saga name and correlation/saga ID.
80
+ *
81
+ * @param sagaName - Name of the saga
82
+ * @param id - Either the saga ID or correlation ID
83
+ */
84
+ async getSagaState(sagaName, id) {
85
+ const store = this.stores.get(sagaName);
86
+ if (!store) {
87
+ throw new Error(`No store found for saga "${sagaName}"`);
88
+ }
89
+ let state = await store.getByCorrelationId(sagaName, id);
90
+ if (state) {
91
+ return state;
92
+ }
93
+ state = await store.getById(sagaName, id);
94
+ return state;
95
+ }
96
+ /**
97
+ * Get all saga states for a given saga name.
98
+ */
99
+ getAllSagaStates(sagaName) {
100
+ const store = this.stores.get(sagaName);
101
+ if (!store) {
102
+ throw new Error(`No store found for saga "${sagaName}"`);
103
+ }
104
+ return store.getAll();
105
+ }
106
+ /**
107
+ * Get all messages that were published during the test.
108
+ */
109
+ getPublishedMessages() {
110
+ return [...this.capturedMessages];
111
+ }
112
+ /**
113
+ * Get published messages filtered by type.
114
+ */
115
+ getPublishedMessagesByType(type) {
116
+ return this.capturedMessages.filter(
117
+ (m) => m.message.type === type
118
+ );
119
+ }
120
+ /**
121
+ * Clear captured messages.
122
+ */
123
+ clearPublishedMessages() {
124
+ this.capturedMessages.length = 0;
125
+ }
126
+ /**
127
+ * Reset the harness state (clears all stores and captured messages).
128
+ */
129
+ reset() {
130
+ for (const store of this.stores.values()) {
131
+ store.clear();
132
+ }
133
+ this.capturedMessages.length = 0;
134
+ }
135
+ /**
136
+ * Wait for any pending async operations to complete.
137
+ */
138
+ async flush(delayMs = 10) {
139
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
140
+ }
141
+ /**
142
+ * Stop the test harness.
143
+ */
144
+ async stop() {
145
+ await this.bus.stop();
146
+ }
147
+ /**
148
+ * Get the underlying bus instance.
149
+ */
150
+ getBus() {
151
+ return this.bus;
152
+ }
153
+ /**
154
+ * Get the underlying transport.
155
+ */
156
+ getTransport() {
157
+ return this.transport;
158
+ }
159
+ /**
160
+ * Get the store for a specific saga.
161
+ */
162
+ getStore(sagaName) {
163
+ return this.stores.get(sagaName);
164
+ }
165
+ };
166
+ export {
167
+ TestHarness
168
+ };
169
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/TestHarness.ts"],"sourcesContent":["import type {\n BaseMessage,\n SagaState,\n SagaDefinition,\n Bus,\n SagaStore,\n} from \"@saga-bus/core\";\nimport { createBus } from \"@saga-bus/core\";\nimport { InMemoryTransport } from \"@saga-bus/transport-inmemory\";\nimport { InMemorySagaStore } from \"@saga-bus/store-inmemory\";\n\n/**\n * Options for creating a TestHarness.\n */\nexport interface TestHarnessOptions<\n TState extends SagaState,\n TMessages extends BaseMessage\n> {\n /**\n * Saga definitions to register with the test bus.\n */\n sagas: SagaDefinition<TState, TMessages>[];\n\n /**\n * Optional shared store for all sagas.\n * If not provided, an InMemorySagaStore is created per saga.\n */\n store?: SagaStore<TState>;\n\n /**\n * Optional custom stores per saga (by name).\n * Takes precedence over the shared store.\n */\n stores?: Record<string, SagaStore<TState>>;\n}\n\n/**\n * A captured published message.\n */\nexport interface CapturedMessage<T extends BaseMessage = BaseMessage> {\n message: T;\n endpoint: string;\n timestamp: Date;\n}\n\n/**\n * Test harness for saga testing.\n * Provides an isolated environment with in-memory transport and stores.\n *\n * @example\n * ```typescript\n * const harness = await TestHarness.create({ sagas: [orderSaga] });\n *\n * await harness.publish({ type: \"OrderSubmitted\", orderId: \"123\" });\n *\n * const state = await harness.getSagaState(\"OrderSaga\", \"123\");\n * expect(state?.status).toBe(\"submitted\");\n *\n * const messages = harness.getPublishedMessages();\n * expect(messages).toContainEqual(expect.objectContaining({ type: \"OrderCreated\" }));\n * ```\n */\nexport class TestHarness<\n TState extends SagaState,\n TMessages extends BaseMessage\n> {\n private readonly bus: Bus;\n private readonly transport: InMemoryTransport;\n private readonly stores: Map<string, InMemorySagaStore<TState>>;\n private readonly capturedMessages: CapturedMessage<TMessages>[] = [];\n\n private constructor(\n bus: Bus,\n transport: InMemoryTransport,\n stores: Map<string, InMemorySagaStore<TState>>\n ) {\n this.bus = bus;\n this.transport = transport;\n this.stores = stores;\n }\n\n /**\n * Create a new test harness.\n */\n static async create<TState extends SagaState, TMessages extends BaseMessage>(\n options: TestHarnessOptions<TState, TMessages>\n ): Promise<TestHarness<TState, TMessages>> {\n const transport = new InMemoryTransport({ defaultConcurrency: 1 });\n const stores = new Map<string, InMemorySagaStore<TState>>();\n\n // Track shared store for getSagaState lookups\n let sharedStoreInstance: InMemorySagaStore<TState> | undefined;\n if (options.store && options.store instanceof InMemorySagaStore) {\n sharedStoreInstance = options.store;\n }\n\n // Create registrations\n // Priority: per-saga store > shared store > auto-create InMemory\n const registrations = options.sagas.map((definition) => {\n let store: SagaStore<TState>;\n if (options.stores?.[definition.name]) {\n // Per-saga store override\n store = options.stores[definition.name]!;\n if (store instanceof InMemorySagaStore) {\n stores.set(definition.name, store);\n }\n } else if (options.store) {\n // Shared store\n store = options.store;\n if (sharedStoreInstance) {\n stores.set(definition.name, sharedStoreInstance);\n }\n } else {\n // Auto-create InMemory store\n const memStore = new InMemorySagaStore<TState>();\n stores.set(definition.name, memStore);\n store = memStore;\n }\n\n return {\n definition,\n store,\n };\n });\n\n const bus = createBus({\n transport,\n sagas: registrations,\n // Disable retries in tests - fail fast, surface errors immediately\n worker: {\n retryPolicy: {\n maxAttempts: 1,\n baseDelayMs: 0,\n maxDelayMs: 0,\n backoff: \"linear\",\n },\n },\n });\n\n const harness = new TestHarness<TState, TMessages>(bus, transport, stores);\n\n // Start the bus\n await bus.start();\n\n return harness;\n }\n\n /**\n * Publish a message to the bus.\n * Waits for message processing to complete.\n */\n async publish<T extends TMessages>(\n message: T,\n options?: { endpoint?: string }\n ): Promise<void> {\n const endpoint = options?.endpoint ?? message.type;\n\n // Capture the message\n this.capturedMessages.push({\n message: message as TMessages,\n endpoint,\n timestamp: new Date(),\n });\n\n // Publish through the bus\n await this.bus.publish(message, { endpoint });\n\n // Small delay to ensure async processing completes\n await this.flush();\n }\n\n /**\n * Get a saga state by saga name and correlation/saga ID.\n *\n * @param sagaName - Name of the saga\n * @param id - Either the saga ID or correlation ID\n */\n async getSagaState(sagaName: string, id: string): Promise<TState | null> {\n const store = this.stores.get(sagaName);\n if (!store) {\n throw new Error(`No store found for saga \"${sagaName}\"`);\n }\n\n // Try by correlation ID first\n let state = await store.getByCorrelationId(sagaName, id);\n if (state) {\n return state;\n }\n\n // Fall back to saga ID\n state = await store.getById(sagaName, id);\n return state;\n }\n\n /**\n * Get all saga states for a given saga name.\n */\n getAllSagaStates(sagaName: string): TState[] {\n const store = this.stores.get(sagaName);\n if (!store) {\n throw new Error(`No store found for saga \"${sagaName}\"`);\n }\n\n return store.getAll();\n }\n\n /**\n * Get all messages that were published during the test.\n */\n getPublishedMessages(): CapturedMessage<TMessages>[] {\n return [...this.capturedMessages];\n }\n\n /**\n * Get published messages filtered by type.\n */\n getPublishedMessagesByType<T extends TMessages[\"type\"]>(\n type: T\n ): CapturedMessage<Extract<TMessages, { type: T }>>[] {\n return this.capturedMessages.filter(\n (m) => m.message.type === type\n ) as CapturedMessage<Extract<TMessages, { type: T }>>[];\n }\n\n /**\n * Clear captured messages.\n */\n clearPublishedMessages(): void {\n this.capturedMessages.length = 0;\n }\n\n /**\n * Reset the harness state (clears all stores and captured messages).\n */\n reset(): void {\n for (const store of this.stores.values()) {\n store.clear();\n }\n this.capturedMessages.length = 0;\n }\n\n /**\n * Wait for any pending async operations to complete.\n */\n async flush(delayMs = 10): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n /**\n * Stop the test harness.\n */\n async stop(): Promise<void> {\n await this.bus.stop();\n }\n\n /**\n * Get the underlying bus instance.\n */\n getBus(): Bus {\n return this.bus;\n }\n\n /**\n * Get the underlying transport.\n */\n getTransport(): InMemoryTransport {\n return this.transport;\n }\n\n /**\n * Get the store for a specific saga.\n */\n getStore(sagaName: string): InMemorySagaStore<TState> | undefined {\n return this.stores.get(sagaName);\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@saga-bus/test",
3
+ "version": "0.1.1",
4
+ "description": "Testing utilities for saga-bus applications",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/d-e-a-n-f/saga-bus.git",
26
+ "directory": "packages/test"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/d-e-a-n-f/saga-bus/issues"
30
+ },
31
+ "homepage": "https://github.com/d-e-a-n-f/saga-bus#readme",
32
+ "keywords": [
33
+ "saga",
34
+ "message-bus",
35
+ "testing",
36
+ "test-harness"
37
+ ],
38
+ "dependencies": {
39
+ "@saga-bus/core": "0.1.1",
40
+ "@saga-bus/transport-inmemory": "0.1.1",
41
+ "@saga-bus/store-inmemory": "0.1.1"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^20.0.0",
45
+ "tsup": "^8.0.0",
46
+ "typescript": "^5.9.2",
47
+ "vitest": "^3.0.0",
48
+ "@repo/eslint-config": "0.0.0",
49
+ "@repo/typescript-config": "0.0.0"
50
+ },
51
+ "peerDependencies": {
52
+ "@saga-bus/core": ">=0.1.1"
53
+ },
54
+ "scripts": {
55
+ "build": "tsup",
56
+ "dev": "tsup --watch",
57
+ "lint": "eslint src/",
58
+ "check-types": "tsc --noEmit",
59
+ "test": "vitest run",
60
+ "test:watch": "vitest"
61
+ }
62
+ }