@othree.io/chisel-forge 1.0.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.
@@ -0,0 +1,380 @@
1
+ /** @module lowLevel */
2
+ import { expect } from 'vitest';
3
+ import { Empty, Optional, TryAsync } from '@othree.io/optional';
4
+ import { forEachSeries, mapSeries } from 'async';
5
+ import isMatch from 'lodash.ismatch';
6
+ /**
7
+ * Custom error class for representing errors that occur during command execution.
8
+ * @class
9
+ * @extends {Error}
10
+ */
11
+ export class CommandExecutionError extends Error {
12
+ static ERROR = 'CommandExecutionError';
13
+ command;
14
+ constructor(command, message) {
15
+ super(Optional(message).orElse(`Command ${command.type} failed`));
16
+ // https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
17
+ Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
18
+ this.name = CommandExecutionError.ERROR;
19
+ this.command = command;
20
+ this.stack = new Error().stack;
21
+ this.message = Optional(message).orElse(`Command ${command.type} failed`);
22
+ }
23
+ }
24
+ /**
25
+ * Creates a Chisel test builder based on a given run and validate function.
26
+ * @template State - The type representing the state.
27
+ * @param {RunAndValidate<State>} runAndValidate - The function to run and validate a command.
28
+ * @returns A function that accepts a command and returns a ChiselTest instance.
29
+ */
30
+ export const when = (runAndValidate) =>
31
+ /**
32
+ * Builds a Chisel test for a specific command.
33
+ * @param {Command} command - The command to be tested.
34
+ * @returns {ChiselTest<State>} The ChiselTest instance configured for the given command.
35
+ */
36
+ (command) => {
37
+ const expectedEvents = [];
38
+ const getCommands = [];
39
+ const expectedStateHolder = [Empty()];
40
+ const chiselTest = Object.freeze({
41
+ and: (getCommand) => {
42
+ getCommands.push(getCommand);
43
+ return chiselTest;
44
+ },
45
+ expectEvent: (event) => {
46
+ expectedEvents.push(event);
47
+ const narrowed = Object.freeze({
48
+ toPass: chiselTest.toPass,
49
+ expectState: chiselTest.expectState,
50
+ expectEvent: chiselTest.expectEvent,
51
+ });
52
+ return narrowed;
53
+ },
54
+ expectState: (state) => {
55
+ expectedStateHolder[0] = Optional(state);
56
+ const narrowed = Object.freeze({
57
+ toPass: chiselTest.toPass,
58
+ expectState: chiselTest.expectState,
59
+ expectEvent: chiselTest.expectEvent,
60
+ });
61
+ return narrowed;
62
+ },
63
+ toPass: async () => {
64
+ return await runAndValidate(command, getCommands, expectedStateHolder[0], expectedEvents);
65
+ },
66
+ toFail: async (errorMsg) => {
67
+ const result = await TryAsync(async () => {
68
+ return await runAndValidate(command, getCommands, expectedStateHolder[0], expectedEvents);
69
+ });
70
+ if (result.isPresent) {
71
+ throw new Error('Command expected to fail but succeeded');
72
+ }
73
+ if (!(result.getError() instanceof CommandExecutionError)) {
74
+ throw result.getError();
75
+ }
76
+ const errorMsgOpt = Optional(errorMsg);
77
+ if (errorMsgOpt.isPresent) {
78
+ expect(result.getError().message).toEqual(errorMsgOpt.get());
79
+ }
80
+ return result.getError();
81
+ },
82
+ });
83
+ return chiselTest;
84
+ };
85
+ /**
86
+ * Determines if the configuration is an ChiselTestConfiguration
87
+ * @param configuration Test configuration
88
+ * @returns {boolean} True is configuration is of type ChiselTestConfiguration
89
+ */
90
+ export const isChiselTestConfiguration = (configuration) => {
91
+ return 'commandHandlerArn' in configuration;
92
+ };
93
+ /**
94
+ * Determines if the configuration is an AsyncSqsChiselTestConfiguration
95
+ * @param configuration Test configuration
96
+ * @returns {boolean} True is configuration is of type AsyncSqsChiselTestConfiguration
97
+ */
98
+ export const isAsyncSqsChiselTestConfiguration = (configuration) => {
99
+ return 'commandQueueUrl' in configuration;
100
+ };
101
+ /**
102
+ * Performs cleanup operations including deleting a queue, events, and data.
103
+ * @function
104
+ * @param deps - Dependencies for cleanup operations.
105
+ * @returns {CleanUp} A function that performs cleanup operations.
106
+ */
107
+ export const cleanUp = (deps) => async (queue, maybeCommandResults) => {
108
+ const maybeDeleted = (await deps.deleteQueue({
109
+ queueUrl: queue.url,
110
+ })).map(_ => true)
111
+ .catch(e => {
112
+ throw new Error(`CleanUp::Failed to delete queue: ${queue.url}. Error: ${e.message}`);
113
+ });
114
+ await Optional(queue.subscriptionArn).mapAsync(async (subscription) => {
115
+ return await deps.unsubscribe({
116
+ subscriptionArn: subscription,
117
+ });
118
+ });
119
+ const dataCleaned = (await maybeCommandResults.mapAsync(async (result) => {
120
+ const events = (await deps.getBy({
121
+ contextId: result[0].events[0].contextId,
122
+ })).catch(e => {
123
+ throw new Error(`CleanUp::Failed to get events from events table. Error: ${e.message}`);
124
+ }).get();
125
+ await Promise.all(events.map(async (event) => {
126
+ return (await deps.deleteBy({
127
+ contextId: event.contextId,
128
+ eventDate: event.eventDate,
129
+ })).get();
130
+ }));
131
+ return true;
132
+ })).orElse(true);
133
+ return maybeDeleted.get() && dataCleaned;
134
+ };
135
+ /**
136
+ * Creates and subscribes to an SQS queue.
137
+ * @function
138
+ * @param deps - Dependencies for queue creation and subscription.
139
+ * @returns {CreateQueueAndSubscribe} A function that creates and subscribes to an SQS queue.
140
+ */
141
+ export const createQueueAndSubscribe = (deps) => async (configuration) => {
142
+ const queueName = `${configuration.testName}-${deps.now()}`;
143
+ const createQueueRequest = configuration.topicArn.endsWith('fifo') ? {
144
+ queueName: `${queueName}.fifo`,
145
+ fifo: {
146
+ contentBasedDeduplication: true,
147
+ throughputLimit: 'perMessageGroupId',
148
+ deduplicationScope: 'messageGroup',
149
+ },
150
+ } : {
151
+ queueName: queueName,
152
+ };
153
+ const queue = (await deps.createQueue(createQueueRequest)).catch(e => {
154
+ throw new Error(`Failed to create test queue: ${queueName}. Error: ${e.message}`);
155
+ })
156
+ .get();
157
+ const subscription = await deps.subscribeSqs({
158
+ queueUrl: queue.url,
159
+ queueArn: queue.arn,
160
+ }).then(_ => {
161
+ return _.catchAsync(async (e) => {
162
+ await deps.cleanUp({
163
+ url: queue.url,
164
+ arn: queue.arn,
165
+ }, Empty());
166
+ throw new Error(`Failed to subscribe to the bounded context's topic. Error: ${e.message}`);
167
+ })
168
+ .then(_ => _.get());
169
+ });
170
+ await new Promise((resolve) => {
171
+ setTimeout(() => {
172
+ return resolve(true);
173
+ }, configuration.subscriptionAwaitTime);
174
+ });
175
+ return {
176
+ url: queue.url,
177
+ arn: queue.arn,
178
+ subscriptionArn: subscription.subscriptionArn,
179
+ };
180
+ };
181
+ /**
182
+ * Asynchronous function for receiving messages from an SQS queue.
183
+ * @function
184
+ * @param deps - Dependencies for receiving messages.
185
+ * @returns A function that receives messages from a specified queue URL.
186
+ */
187
+ export const receiveMessages = (deps) => async (queueUrl) => {
188
+ const receiveMessagesInternal = deps.receive({
189
+ client: deps.sqsClient,
190
+ configuration: {
191
+ maxNumberOfMessages: 10,
192
+ waitTimeSeconds: 5,
193
+ QueueUrl: queueUrl,
194
+ autoAcknowledgeMessages: true,
195
+ },
196
+ transformMessage: (message) => message,
197
+ });
198
+ const poll = async (accumulated) => {
199
+ const receivedMessages = (await receiveMessagesInternal())
200
+ .catch(e => {
201
+ throw new Error(`Failed to receive messages from test queue: ${queueUrl}. Error: ${e.message}`);
202
+ })
203
+ .get();
204
+ if (receivedMessages.length === 0) {
205
+ return accumulated;
206
+ }
207
+ return poll(accumulated.concat(receivedMessages));
208
+ };
209
+ return poll([]);
210
+ };
211
+ /**
212
+ * Runs a command, validates the results, and performs cleanup operations.
213
+ * @function
214
+ * @param deps - Dependencies for running and validating commands.
215
+ * @returns {RunAndValidate<State>} The run and validate function.
216
+ */
217
+ export const runAndValidate = (deps) => async (command, getCommands, maybeExpectedState, expectedEvents) => {
218
+ const queue = await deps.createQueueAndSubscribe(deps.configuration);
219
+ const results = await TryAsync(async () => {
220
+ const initialCommandResult = await (await deps.sendCommand(command)).catchAsync(async (e) => {
221
+ throw new CommandExecutionError(command, e.message);
222
+ }).then(_ => _.get());
223
+ const consecuentResults = await mapSeries(getCommands, async (getCommand) => {
224
+ return await (await deps.sendCommand(getCommand(Optional(initialCommandResult.events[0].contextId))))
225
+ .catchAsync(async (e) => {
226
+ throw new CommandExecutionError(command, e.message);
227
+ }).then(_ => _.get());
228
+ });
229
+ return [initialCommandResult].concat(consecuentResults);
230
+ })
231
+ .then(_ => _.get())
232
+ .catch(e => {
233
+ deps.cleanUp(queue, Empty());
234
+ throw e;
235
+ });
236
+ return TryAsync(async () => {
237
+ const messages = await deps.receiveMessages(queue.url)
238
+ .catch(e => {
239
+ throw new Error(`Failed to receive messages from test queue: ${queue.url}. Error: ${e.message}`);
240
+ });
241
+ if (messages.length === 0) {
242
+ throw new Error('No events were triggered');
243
+ }
244
+ await maybeExpectedState.mapAsync(async (expectedState) => {
245
+ expect(results[results.length - 1].state).toStrictEqual(expectedState);
246
+ return deps.configuration.loadStateLambdaArn.mapAsync(async (_) => {
247
+ const contextId = results[results.length - 1].events[0].contextId;
248
+ const state = (await deps.loadState({
249
+ contextId: contextId,
250
+ })).catch(e => {
251
+ throw new Error(`Unable to load state for context id ${contextId}. Error: ${e.message}`);
252
+ }).get();
253
+ expect(state).toStrictEqual(expectedState);
254
+ return true;
255
+ }).then(_ => _.orElse(true));
256
+ }).then(_ => _.orElse(true));
257
+ const chiselEvents = messages.map(message => {
258
+ const bodyString = Optional(message.Body).orElse('{}');
259
+ const body = JSON.parse(bodyString);
260
+ const eventState = JSON.parse(body.Message);
261
+ const chiselEvent = {
262
+ messageAttributes: {
263
+ bc: body.MessageAttributes.bc.Value,
264
+ eventType: body.MessageAttributes.eventType.Value,
265
+ },
266
+ event: eventState.event,
267
+ state: eventState.state,
268
+ };
269
+ return chiselEvent;
270
+ });
271
+ expectedEvents.forEach(expectedEvent => {
272
+ const event = Optional(chiselEvents.find(event => event.messageAttributes.eventType === expectedEvent.type && event.messageAttributes.bc === deps.configuration.boundedContext))
273
+ .orThrow(new Error(`Expected event of type: ${expectedEvent.type} for bounded context: ${deps.configuration.boundedContext} was not triggered`));
274
+ expect(event.event.contextId).toEqual(expectedEvent.contextId);
275
+ expect(event.event.body).toStrictEqual(expectedEvent.body);
276
+ });
277
+ return results;
278
+ }).then(maybeAsserted => {
279
+ return maybeAsserted.get();
280
+ }).finally(() => {
281
+ return deps.cleanUp(queue, Optional(results));
282
+ });
283
+ };
284
+ /**
285
+ * Sends a command to a queue and validates the command events and state.
286
+ * @function
287
+ * @param deps - Dependencies for running and validating async commands.
288
+ * @returns {RunAndValidate<State>} The run and validate function.
289
+ */
290
+ export const runAndValidateAsync = (deps) => async (initialCommand, getCommands, maybeExpectedState, expectedEvents) => {
291
+ const queue = await deps.createQueueAndSubscribe(deps.configuration);
292
+ const commandResultHolder = [Empty()];
293
+ const allCommands = [initialCommand];
294
+ await (deps.sendCommand(initialCommand))
295
+ .then(_ => {
296
+ return _.catchAsync(async (e) => {
297
+ await deps.cleanUp(queue, Empty());
298
+ throw new CommandExecutionError(initialCommand, e.message);
299
+ });
300
+ })
301
+ .then(_ => {
302
+ _.get();
303
+ });
304
+ await forEachSeries(getCommands, async (getCommand) => {
305
+ const command = getCommand(Empty());
306
+ allCommands.push(command);
307
+ await (await deps.sendCommand(command)).catchAsync(async (e) => {
308
+ await deps.cleanUp(queue, Empty());
309
+ throw new CommandExecutionError(command, e.message);
310
+ }).then(_ => _.get());
311
+ });
312
+ const errorMessages = await (await deps.receiveErrorMessages())
313
+ .catchAsync(async () => {
314
+ return [];
315
+ }).then(_ => _.get());
316
+ const maybeError = Optional(errorMessages.find(message => {
317
+ return allCommands.find(command => isMatch(command, message.request));
318
+ }));
319
+ (await maybeError.mapAsync(async (error) => {
320
+ await deps.cleanUp(queue, Empty());
321
+ throw new CommandExecutionError(error.request, error.error.message);
322
+ })).orElse(true);
323
+ return TryAsync(async () => {
324
+ const messages = (await deps.receiveMessages(queue.url)
325
+ .catch(e => {
326
+ throw new Error(`Failed to receive messages from test queue: ${queue.url}. Error: ${e.message}`);
327
+ }))
328
+ .sort();
329
+ if (messages.length === 0) {
330
+ throw new Error('No events were triggered');
331
+ }
332
+ const chiselEvents = messages
333
+ .map(message => {
334
+ const bodyString = Optional(message.Body).orElse('{}');
335
+ const body = JSON.parse(bodyString);
336
+ const eventState = JSON.parse(body.Message);
337
+ const chiselEvent = {
338
+ messageAttributes: {
339
+ bc: body.MessageAttributes.bc.Value,
340
+ eventType: body.MessageAttributes.eventType.Value,
341
+ },
342
+ event: eventState.event,
343
+ state: eventState.state,
344
+ };
345
+ return chiselEvent;
346
+ })
347
+ .sort((m1, m2) => {
348
+ return Number(m1.event.eventDate) - Number(m2.event.eventDate);
349
+ });
350
+ expectedEvents.forEach(expectedEvent => {
351
+ const event = Optional(chiselEvents.find(event => event.messageAttributes.eventType === expectedEvent.type && event.messageAttributes.bc === deps.configuration.boundedContext))
352
+ .orThrow(new Error(`Expected event of type: ${expectedEvent.type} for bounded context: ${deps.configuration.boundedContext} was not triggered`));
353
+ expect(event.event.contextId).toEqual(expectedEvent.contextId);
354
+ expect(event.event.body).toStrictEqual(expectedEvent.body);
355
+ });
356
+ commandResultHolder[0] = Optional([{
357
+ state: chiselEvents[chiselEvents.length - 1].state,
358
+ events: chiselEvents.map(_ => _.event),
359
+ }]);
360
+ await maybeExpectedState.mapAsync(async (expectedState) => {
361
+ expect(commandResultHolder[0].get()[0].state).toStrictEqual(expectedState);
362
+ return deps.configuration.loadStateLambdaArn.mapAsync(async (_) => {
363
+ const contextId = commandResultHolder[0].get()[0].events[0].contextId;
364
+ const state = (await deps.loadState({
365
+ contextId: contextId,
366
+ })).catch(e => {
367
+ throw new Error(`Unable to load state for context id ${contextId}. Error: ${e.message}`);
368
+ }).get();
369
+ expect(state).toStrictEqual(expectedState);
370
+ return true;
371
+ }).then(_ => _.orElse(true));
372
+ }).then(_ => _.orElse(true));
373
+ return commandResultHolder[0];
374
+ }).then(maybeAsserted => {
375
+ return maybeAsserted.get();
376
+ }).finally(() => {
377
+ return deps.cleanUp(queue, commandResultHolder[0]);
378
+ });
379
+ };
380
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["/** @module lowLevel */\nimport {expect} from 'vitest'\nimport {Command, CommandResult, InternalTriggeredEvent, ChiselEvent} from '@othree.io/chisel'\nimport {Empty, Optional, TryAsync} from '@othree.io/optional'\nimport {\n sqs, sns, dynamo\n} from '@othree.io/awsome'\nimport {\n Message, SQSClient,\n} from '@aws-sdk/client-sqs'\nimport {forEachSeries, mapSeries} from 'async'\nimport isMatch from 'lodash.ismatch'\n\n/**\n * Custom error class for representing errors that occur during command execution.\n * @class\n * @extends {Error}\n */\nexport class CommandExecutionError extends Error {\n static ERROR = 'CommandExecutionError'\n command: Command\n\n constructor(command: Command, message?: string) {\n super(Optional(message).orElse(`Command ${command.type} failed`))\n // https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, new.target.prototype) // restore prototype chain\n\n this.name = CommandExecutionError.ERROR\n this.command = command\n this.stack = new Error().stack\n this.message = Optional(message).orElse(`Command ${command.type} failed`)\n }\n}\n\n/**\n * Represents an error message related to a command placed in a dead-letter queue (DLQ).\n * @typedef DLQMessage\n */\nexport type DLQMessage = Readonly<{\n request: Command\n error: Readonly<{\n name: string\n message: string\n stack?: string\n }>\n}>\n\n\n/**\n * Represents a query for loading the state using a context identifier.\n * @typedef LoadStateQuery\n */\nexport type LoadStateQuery = Readonly<{\n contextId: string\n}>\n\n\n/**\n * Represents a Chisel test builder that configures expected events and states for a command.\n * @typedef ChiselTest\n * @template State - The type representing the state.\n */\nexport type ChiselTest<State> = Readonly<{\n /**\n * Specifies an expected event that should be produced by the command.\n * @param {ChiselEvent} event - The expected event.\n * @returns {ChiselTest<State>} The ChiselTest instance with the expected event.\n */\n expectEvent: (event: ChiselEvent) => ChiselTest<State>\n /**\n * Specifies an expected state that the system should be in after the command execution.\n * @param {State} state - The expected state.\n * @returns {ChiselTest<State>} The ChiselTest instance with the expected state.\n */\n expectState: (state: State) => ChiselTest<State>\n /**\n * Executes the command and validates the expected results.\n * @returns {Promise<CommandResult<State>>} A promise that resolves to the command(s) results.\n */\n toPass: () => Promise<Array<CommandResult<State>>>\n}>\n\nexport type GetCommand = (contextId: Optional<string>) => Command\n\n/**\n * A specialized extension of the `ChiselTest` type tailored for testing command execution.\n * @typedef CommandChiselTest\n * @template State - The type representing the state used in the test.\n */\nexport type CommandChiselTest<State> = Readonly<{\n and: (getCommand: GetCommand) => CommandChiselTest<State>\n toFail: (errorMsg?: string) => Promise<Error>\n}> & ChiselTest<State>\n\n/**\n * Creates a Chisel test builder based on a given run and validate function.\n * @template State - The type representing the state.\n * @param {RunAndValidate<State>} runAndValidate - The function to run and validate a command.\n * @returns A function that accepts a command and returns a ChiselTest instance.\n */\nexport const when = <State>(runAndValidate: RunAndValidate<State>) =>\n /**\n * Builds a Chisel test for a specific command.\n * @param {Command} command - The command to be tested.\n * @returns {ChiselTest<State>} The ChiselTest instance configured for the given command.\n */\n (command: Command): CommandChiselTest<State> => {\n const expectedEvents: Array<ChiselEvent> = []\n const getCommands: Array<GetCommand> = []\n const expectedStateHolder: [Optional<State>] = [Empty()]\n\n const chiselTest: CommandChiselTest<State> = Object.freeze({\n and: (getCommand: GetCommand) => {\n getCommands.push(getCommand)\n\n return chiselTest\n },\n expectEvent: (event: ChiselEvent) => {\n expectedEvents.push(event)\n\n const narrowed: ChiselTest<State> = Object.freeze({\n toPass: chiselTest.toPass,\n expectState: chiselTest.expectState,\n expectEvent: chiselTest.expectEvent,\n })\n return narrowed\n },\n expectState: (state: State) => {\n expectedStateHolder[0] = Optional(state)\n\n const narrowed: ChiselTest<State> = Object.freeze({\n toPass: chiselTest.toPass,\n expectState: chiselTest.expectState,\n expectEvent: chiselTest.expectEvent,\n })\n return narrowed\n },\n toPass: async (): Promise<Array<CommandResult<State>>> => {\n return await runAndValidate(command, getCommands, expectedStateHolder[0], expectedEvents)\n },\n toFail: async (errorMsg?: string): Promise<Error> => {\n const result = await TryAsync(async () => {\n return await runAndValidate(command, getCommands, expectedStateHolder[0], expectedEvents)\n })\n\n if (result.isPresent) {\n throw new Error('Command expected to fail but succeeded')\n }\n\n if (!(result.getError() instanceof CommandExecutionError)) {\n throw result.getError()\n }\n\n const errorMsgOpt = Optional(errorMsg)\n if (errorMsgOpt.isPresent) {\n expect(result.getError().message).toEqual(errorMsgOpt.get())\n }\n return result.getError()\n\n },\n })\n\n return chiselTest\n }\n\n/**\n * Represents the configuration for all types of tests\n * @typedef BaseTestConfiguration\n */\nexport type BaseTestConfiguration = Readonly<{\n boundedContext: string\n topicArn: string\n eventsTable: string\n subscriptionAwaitTime: number\n testName: string\n loadStateLambdaArn: Optional<string>\n}>\n\n/**\n * Configuration options for a Chisel test.\n * @typedef ChiselTestConfiguration\n */\nexport type ChiselTestConfiguration = Readonly<{\n commandHandlerArn: string\n}> & BaseTestConfiguration\n\n/**\n * Configuration options for an Async SQS Chisel test.\n * @typedef AsyncSqsChiselTestConfiguration\n */\nexport type AsyncSqsChiselTestConfiguration = Readonly<{\n commandQueueUrl: string\n extractMessageGroupId?: (command: Command) => string\n extractMessageDeduplicationId?: (command: Command) => string\n commandDLQUrl: string\n}> & BaseTestConfiguration\n\n/**\n * Configuration options for an Async SNS Chisel test.\n * @typedef AsyncSnsChiselTestConfiguration\n */\nexport type AsyncSnsChiselTestConfiguration = Readonly<{\n commandTopicArn: string\n extractMessageGroupId?: (command: Command) => string\n extractMessageDeduplicationId?: (command: Command) => string\n commandDLQUrl: string\n}> & BaseTestConfiguration\n\n/**\n * Represents the possible configurations for different types of tests.\n * @typedef {ChiselTestConfiguration | AsyncSqsChiselTestConfiguration | AsyncSnsChiselTestConfiguration} TestConfiguration\n */\nexport type TestConfiguration =\n ChiselTestConfiguration\n | AsyncSqsChiselTestConfiguration\n | AsyncSnsChiselTestConfiguration\n\n/**\n * Determines if the configuration is an ChiselTestConfiguration\n * @param configuration Test configuration\n * @returns {boolean} True is configuration is of type ChiselTestConfiguration\n */\nexport const isChiselTestConfiguration = (configuration: TestConfiguration): configuration is ChiselTestConfiguration => {\n return 'commandHandlerArn' in configuration\n}\n\n/**\n * Determines if the configuration is an AsyncSqsChiselTestConfiguration\n * @param configuration Test configuration\n * @returns {boolean} True is configuration is of type AsyncSqsChiselTestConfiguration\n */\nexport const isAsyncSqsChiselTestConfiguration = (configuration: TestConfiguration): configuration is AsyncSqsChiselTestConfiguration => {\n return 'commandQueueUrl' in configuration\n}\n\n/**\n * Represents a Chisel event message containing attributes, an event, and a state.\n * @typedef ChiselEventMessage\n * @template State - The type representing the state.\n */\nexport type ChiselEventMessage<State> = Readonly<{\n messageAttributes: Readonly<{\n bc: string\n eventType: string\n }>\n event: InternalTriggeredEvent\n state: State\n}>\n\n/**\n * Function type for cleanup operations including deleting a queue, events, and data.\n * @typedef {Function} CleanUp\n */\nexport type CleanUp = <State>(queue: SubscribedSqsQueue | sqs.SqsQueue, maybeCommandResults: Optional<Array<CommandResult<State>>>) => Promise<true>\n\n/**\n * Performs cleanup operations including deleting a queue, events, and data.\n * @function\n * @param deps - Dependencies for cleanup operations.\n * @returns {CleanUp} A function that performs cleanup operations.\n */\nexport const cleanUp = (deps: {\n getBy: (keys: dynamo.Keys) => Promise<Optional<Array<InternalTriggeredEvent>>>\n deleteBy: (keys: dynamo.Keys) => Promise<Optional<boolean>>\n deleteQueue: (request: sqs.DeleteQueueRequest) => Promise<Optional<boolean>>\n unsubscribe: (subscription: sns.Subscription) => Promise<Optional<true>>\n}): CleanUp =>\n async <State>(queue: SubscribedSqsQueue | sqs.SqsQueue, maybeCommandResults: Optional<Array<CommandResult<State>>>): Promise<true> => {\n const maybeDeleted = (await deps.deleteQueue({\n queueUrl: queue.url,\n })).map<true>(_ => true)\n .catch(e => {\n throw new Error(`CleanUp::Failed to delete queue: ${queue.url}. Error: ${e.message}`)\n })\n\n await Optional((queue as any).subscriptionArn).mapAsync(async subscription => {\n return await deps.unsubscribe({\n subscriptionArn: subscription,\n })\n })\n\n const dataCleaned = (await maybeCommandResults.mapAsync<true>(async result => {\n const events = (await deps.getBy({\n contextId: result[0].events[0].contextId,\n })).catch(e => {\n throw new Error(`CleanUp::Failed to get events from events table. Error: ${e.message}`)\n }).get()\n\n await Promise.all(events.map(async event => {\n return (await deps.deleteBy({\n contextId: event.contextId,\n eventDate: event.eventDate,\n })).get()\n }))\n\n return true\n })).orElse(true)\n\n return maybeDeleted.get() && dataCleaned\n }\n\n/**\n * Representation of a queue subscribed to a topic\n * @typedef SubscribedSqsQueue\n */\nexport type SubscribedSqsQueue = Readonly<{\n subscriptionArn: string\n}> & sqs.SqsQueue\n\n/**\n * Creates and subscribes to an SQS queue.\n * @typedef {Function} CreateQueueAndSubscribe\n */\nexport type CreateQueueAndSubscribe = (configuration: TestConfiguration) => Promise<SubscribedSqsQueue | sqs.SqsQueue>\n\n/**\n * Creates and subscribes to an SQS queue.\n * @function\n * @param deps - Dependencies for queue creation and subscription.\n * @returns {CreateQueueAndSubscribe} A function that creates and subscribes to an SQS queue.\n */\nexport const createQueueAndSubscribe = (deps: {\n now: () => number\n createQueue: (request: sqs.CreateQueueRequest) => Promise<Optional<sqs.SqsQueue>>\n cleanUp: CleanUp\n subscribeSqs: (request: sns.SubscribeQueueRequest) => Promise<Optional<sns.Subscription>>\n}): CreateQueueAndSubscribe =>\n async (configuration: TestConfiguration): Promise<SubscribedSqsQueue | sqs.SqsQueue> => {\n const queueName = `${configuration.testName}-${deps.now()}`\n\n const createQueueRequest: sqs.CreateQueueRequest = configuration.topicArn.endsWith('fifo') ? {\n queueName: `${queueName}.fifo`,\n fifo: {\n contentBasedDeduplication: true,\n throughputLimit: 'perMessageGroupId',\n deduplicationScope: 'messageGroup',\n },\n } as sqs.CreateFifoQueueRequest : {\n queueName: queueName,\n }\n const queue = (await deps.createQueue(createQueueRequest)).catch<sqs.SqsQueue>(e => {\n throw new Error(`Failed to create test queue: ${queueName}. Error: ${e.message}`)\n })\n .get()\n\n\n const subscription = await deps.subscribeSqs({\n queueUrl: queue.url,\n queueArn: queue.arn,\n }).then(_ => {\n return _.catchAsync(async e => {\n await deps.cleanUp<any>({\n url: queue.url,\n arn: queue.arn,\n }, Empty())\n throw new Error(`Failed to subscribe to the bounded context's topic. Error: ${e.message}`)\n })\n .then(_ => _.get())\n })\n\n await new Promise((resolve) => {\n setTimeout(() => {\n return resolve(true)\n }, configuration.subscriptionAwaitTime)\n })\n\n return {\n url: queue.url,\n arn: queue.arn,\n subscriptionArn: subscription.subscriptionArn,\n }\n }\n\n/**\n * Asynchronous function for receiving messages from an SQS queue.\n * @function\n * @param deps - Dependencies for receiving messages.\n * @returns A function that receives messages from a specified queue URL.\n */\nexport const receiveMessages = (deps: {\n sqsClient: SQSClient\n receive: <T>(deps: Readonly<{\n client: SQSClient\n configuration: sqs.ReceiveMessagesConfiguration\n transformMessage: (message: Message) => T\n }>) => () => Promise<Optional<Array<T>>>\n}) =>\n async (queueUrl: string): Promise<Array<Message>> => {\n const receiveMessagesInternal = deps.receive<Message>({\n client: deps.sqsClient,\n configuration: {\n maxNumberOfMessages: 10,\n waitTimeSeconds: 5,\n QueueUrl: queueUrl,\n autoAcknowledgeMessages: true,\n },\n transformMessage: (message) => message,\n })\n\n const poll = async (accumulated: Array<Message>): Promise<Array<Message>> => {\n const receivedMessages = (await receiveMessagesInternal())\n .catch<Array<Message>>(e => {\n throw new Error(`Failed to receive messages from test queue: ${queueUrl}. Error: ${e.message}`)\n })\n .get()\n\n if (receivedMessages.length === 0) {\n return accumulated\n }\n\n return poll(accumulated.concat(receivedMessages))\n }\n\n return poll([])\n }\n\n/**\n * A function that runs a command, validates the results, and performs cleanup operations.\n * @typedef {Function} RunAndValidate\n */\nexport type RunAndValidate<State> = (command: Command, getCommands: Array<GetCommand>, maybeExpectedState: Optional<State>, expectedEvents: Array<ChiselEvent>) => Promise<Array<CommandResult<State>>>\n\n/**\n * Runs a command, validates the results, and performs cleanup operations.\n * @function\n * @param deps - Dependencies for running and validating commands.\n * @returns {RunAndValidate<State>} The run and validate function.\n */\nexport const runAndValidate = <State>(deps: {\n sendCommand: (command: Command) => Promise<Optional<CommandResult<State>>>\n createQueueAndSubscribe: CreateQueueAndSubscribe\n receiveMessages: (queueUrl: string) => Promise<Array<Message>>\n cleanUp: CleanUp\n configuration: ChiselTestConfiguration\n loadState: (query: LoadStateQuery) => Promise<Optional<State>>\n}): RunAndValidate<State> =>\n async (command: Command, getCommands: Array<GetCommand>, maybeExpectedState: Optional<State>, expectedEvents: Array<ChiselEvent>): Promise<Array<CommandResult<State>>> => {\n\n const queue = await deps.createQueueAndSubscribe(deps.configuration)\n\n const results = await TryAsync(async () => {\n const initialCommandResult = await (await deps.sendCommand(command)).catchAsync<CommandResult<State>>(async e => {\n throw new CommandExecutionError(command, e.message)\n }).then(_ => _.get())\n\n const consecuentResults = await mapSeries(getCommands, async (getCommand: GetCommand) => {\n return await (await deps.sendCommand(getCommand(Optional(initialCommandResult.events[0].contextId))))\n .catchAsync<CommandResult<State>>(async e => {\n throw new CommandExecutionError(command, e.message)\n }).then(_ => _.get())\n })\n\n return [initialCommandResult].concat(consecuentResults)\n })\n .then(_ => _.get())\n .catch(e => {\n deps.cleanUp<State>(queue, Empty())\n throw e\n })\n\n\n return TryAsync(async () => {\n const messages = await deps.receiveMessages(queue.url)\n .catch(e => {\n throw new Error(`Failed to receive messages from test queue: ${queue.url}. Error: ${e.message}`)\n })\n\n if (messages.length === 0) {\n throw new Error('No events were triggered')\n }\n\n await maybeExpectedState.mapAsync<boolean>(async expectedState => {\n expect(results[results.length - 1].state).toStrictEqual(expectedState)\n\n return deps.configuration.loadStateLambdaArn.mapAsync<boolean>(async _ => {\n const contextId = results[results.length - 1].events[0].contextId\n const state = (await deps.loadState({\n contextId: contextId,\n })).catch(e => {\n throw new Error(`Unable to load state for context id ${contextId}. Error: ${e.message}`)\n }).get()\n\n expect(state).toStrictEqual(expectedState)\n\n return true\n }).then(_ => _.orElse(true))\n }).then(_ => _.orElse(true))\n\n const chiselEvents = messages.map(message => {\n const bodyString = Optional(message.Body).orElse('{}')\n const body = JSON.parse(bodyString)\n const eventState = JSON.parse(body.Message)\n const chiselEvent: ChiselEventMessage<State> = {\n messageAttributes: {\n bc: body.MessageAttributes.bc.Value,\n eventType: body.MessageAttributes.eventType.Value,\n },\n event: eventState.event,\n state: eventState.state,\n }\n\n return chiselEvent\n })\n\n expectedEvents.forEach(expectedEvent => {\n const event = Optional(chiselEvents.find(event => event.messageAttributes.eventType === expectedEvent.type && event.messageAttributes.bc === deps.configuration.boundedContext))\n .orThrow(new Error(`Expected event of type: ${expectedEvent.type} for bounded context: ${deps.configuration.boundedContext} was not triggered`))\n\n expect(event.event.contextId).toEqual(expectedEvent.contextId)\n expect(event.event.body).toStrictEqual(expectedEvent.body)\n })\n\n return results\n }).then(maybeAsserted => {\n return maybeAsserted.get()\n }).finally(() => {\n return deps.cleanUp<State>(queue, Optional(results))\n })\n\n }\n\n\n/**\n * Sends a command to a queue and validates the command events and state.\n * @function\n * @param deps - Dependencies for running and validating async commands.\n * @returns {RunAndValidate<State>} The run and validate function.\n */\nexport const runAndValidateAsync = <State>(deps: {\n sendCommand: (command: Command) => Promise<Optional<boolean>>\n createQueueAndSubscribe: CreateQueueAndSubscribe\n receiveMessages: (queueUrl: string) => Promise<Array<Message>>\n cleanUp: CleanUp\n receiveErrorMessages: () => Promise<Optional<Array<DLQMessage>>>\n configuration: AsyncSqsChiselTestConfiguration | AsyncSnsChiselTestConfiguration\n loadState: (query: LoadStateQuery) => Promise<Optional<State>>\n}): RunAndValidate<State> =>\n async (initialCommand: Command, getCommands: Array<GetCommand>, maybeExpectedState: Optional<State>, expectedEvents: Array<ChiselEvent>): Promise<Array<CommandResult<State>>> => {\n\n const queue = await deps.createQueueAndSubscribe(deps.configuration)\n const commandResultHolder: [Optional<Array<CommandResult<State>>>] = [Empty()]\n const allCommands = [initialCommand]\n\n await (deps.sendCommand(initialCommand))\n .then(_ => {\n return _.catchAsync<boolean>(async e => {\n await deps.cleanUp<State>(queue, Empty())\n throw new CommandExecutionError(initialCommand, e.message)\n })\n })\n .then(_ => {\n _.get()\n })\n\n await forEachSeries(getCommands, async (getCommand: GetCommand) => {\n const command = getCommand(Empty<string>())\n allCommands.push(command)\n await (await deps.sendCommand(command)).catchAsync<boolean>(async e => {\n await deps.cleanUp<State>(queue, Empty())\n throw new CommandExecutionError(command, e.message)\n }).then(_ => _.get())\n })\n\n const errorMessages = await (await deps.receiveErrorMessages())\n .catchAsync<Array<DLQMessage>>(async () => {\n return []\n }).then(_ => _.get())\n\n const maybeError = Optional(errorMessages.find(message => {\n return allCommands.find(command => isMatch(command, message.request))\n }))\n\n ;(await maybeError.mapAsync<boolean>(async error => {\n await deps.cleanUp<State>(queue, Empty())\n throw new CommandExecutionError(error.request, error.error.message)\n })).orElse(true)\n\n return TryAsync(async () => {\n const messages = (await deps.receiveMessages(queue.url)\n .catch(e => {\n throw new Error(`Failed to receive messages from test queue: ${queue.url}. Error: ${e.message}`)\n }))\n .sort()\n\n if (messages.length === 0) {\n throw new Error('No events were triggered')\n }\n\n const chiselEvents = messages\n .map(message => {\n const bodyString = Optional(message.Body).orElse('{}')\n const body = JSON.parse(bodyString)\n const eventState = JSON.parse(body.Message)\n const chiselEvent: ChiselEventMessage<State> = {\n messageAttributes: {\n bc: body.MessageAttributes.bc.Value,\n eventType: body.MessageAttributes.eventType.Value,\n },\n event: eventState.event,\n state: eventState.state,\n }\n\n return chiselEvent\n })\n .sort((m1, m2) => {\n return Number(m1.event.eventDate) - Number(m2.event.eventDate)\n })\n\n expectedEvents.forEach(expectedEvent => {\n const event = Optional(chiselEvents.find(event => event.messageAttributes.eventType === expectedEvent.type && event.messageAttributes.bc === deps.configuration.boundedContext))\n .orThrow(new Error(`Expected event of type: ${expectedEvent.type} for bounded context: ${deps.configuration.boundedContext} was not triggered`))\n\n expect(event.event.contextId).toEqual(expectedEvent.contextId)\n expect(event.event.body).toStrictEqual(expectedEvent.body)\n })\n\n commandResultHolder[0] = Optional([{\n state: chiselEvents[chiselEvents.length - 1].state,\n events: chiselEvents.map(_ => _.event),\n }])\n\n await maybeExpectedState.mapAsync<boolean>(async expectedState => {\n expect(commandResultHolder[0].get()[0].state).toStrictEqual(expectedState)\n\n return deps.configuration.loadStateLambdaArn.mapAsync<boolean>(async _ => {\n const contextId = commandResultHolder[0].get()[0].events[0].contextId\n const state = (await deps.loadState({\n contextId: contextId,\n })).catch(e => {\n throw new Error(`Unable to load state for context id ${contextId}. Error: ${e.message}`)\n }).get()\n\n expect(state).toStrictEqual(expectedState)\n\n return true\n }).then(_ => _.orElse(true))\n }).then(_ => _.orElse(true))\n\n return commandResultHolder[0]\n }).then(maybeAsserted => {\n return maybeAsserted.get()\n }).finally(() => {\n return deps.cleanUp<State>(queue, commandResultHolder[0])\n })\n\n }\n"]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@othree.io/chisel-forge",
3
+ "version": "1.0.1",
4
+ "description": "Chisel Test Utils",
5
+ "main": "lib/cjs/index.js",
6
+ "module": "lib/esm/index.js",
7
+ "types": "lib/cjs/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/cjs/index.d.ts",
11
+ "import": "./lib/esm/index.js",
12
+ "require": "./lib/cjs/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "lib"
17
+ ],
18
+ "scripts": {
19
+ "test": "vitest run --coverage",
20
+ "build": "tsc && tsc -p tsconfig.esm.json",
21
+ "semantic-release": "semantic-release",
22
+ "docs": "tsc && jsdoc -d docs --configure jsdoc.config.json"
23
+ },
24
+ "keywords": [
25
+ "chisel",
26
+ "test",
27
+ "utils"
28
+ ],
29
+ "author": "othree",
30
+ "license": "ISC",
31
+ "peerDependencies": {
32
+ "@othree.io/awsome": "^4.0.0",
33
+ "@othree.io/chisel": "^5.0.0",
34
+ "@othree.io/optional": "^2.3.3",
35
+ "async": "^3.2.6",
36
+ "lodash.ismatch": "^4.4.0",
37
+ "vitest": "^4.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "@othree.io/awsome": "^4.0.0",
41
+ "@othree.io/chisel": "^5.0.0",
42
+ "@othree.io/optional": "^2.3.3",
43
+ "async": "^3.2.6",
44
+ "lodash.ismatch": "^4.4.0",
45
+ "@types/async": "^3.2.25",
46
+ "@types/lodash.ismatch": "^4.4.9",
47
+ "@vitest/coverage-v8": "^4.0.0",
48
+ "docdash": "^2.0.2",
49
+ "jsdoc": "^4.0.5",
50
+ "jsdoc-plugin-typescript": "^3.2.0",
51
+ "semantic-release": "^25.0.3",
52
+ "typescript": "^5.9.3"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ }
57
+ }