@zdavison/matador 4.0.3 → 4.2.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.
Files changed (71) hide show
  1. package/dist/core/matador.d.ts.map +1 -1
  2. package/dist/core/matador.js +4 -1
  3. package/dist/index.cjs +156 -16
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/pipeline/pipeline.d.ts +30 -2
  7. package/dist/pipeline/pipeline.d.ts.map +1 -1
  8. package/dist/pipeline/pipeline.js +65 -9
  9. package/dist/retry/index.d.ts +1 -1
  10. package/dist/retry/index.d.ts.map +1 -1
  11. package/dist/retry/policy.d.ts +27 -0
  12. package/dist/retry/policy.d.ts.map +1 -1
  13. package/dist/retry/standard-policy.d.ts +33 -2
  14. package/dist/retry/standard-policy.d.ts.map +1 -1
  15. package/dist/retry/standard-policy.js +73 -9
  16. package/dist/topology/types.d.ts +14 -0
  17. package/dist/topology/types.d.ts.map +1 -1
  18. package/dist/transport/rabbitmq/rabbitmq-transport.d.ts.map +1 -1
  19. package/dist/transport/rabbitmq/rabbitmq-transport.js +3 -0
  20. package/package.json +1 -1
  21. package/dist/checkpoint/context.test.d.ts +0 -2
  22. package/dist/checkpoint/context.test.d.ts.map +0 -1
  23. package/dist/checkpoint/context.test.js +0 -428
  24. package/dist/checkpoint/stores/stores.test.d.ts +0 -2
  25. package/dist/checkpoint/stores/stores.test.d.ts.map +0 -1
  26. package/dist/checkpoint/stores/stores.test.js +0 -146
  27. package/dist/codec/rabbitmq-codec.test.d.ts +0 -2
  28. package/dist/codec/rabbitmq-codec.test.d.ts.map +0 -1
  29. package/dist/codec/rabbitmq-codec.test.js +0 -428
  30. package/dist/core/fanout.test.d.ts +0 -2
  31. package/dist/core/fanout.test.d.ts.map +0 -1
  32. package/dist/core/fanout.test.js +0 -1970
  33. package/dist/core/matador.test.d.ts +0 -2
  34. package/dist/core/matador.test.d.ts.map +0 -1
  35. package/dist/core/matador.test.js +0 -813
  36. package/dist/core/shutdown.test.d.ts +0 -2
  37. package/dist/core/shutdown.test.d.ts.map +0 -1
  38. package/dist/core/shutdown.test.js +0 -599
  39. package/dist/errors/retry-errors.test.d.ts +0 -2
  40. package/dist/errors/retry-errors.test.d.ts.map +0 -1
  41. package/dist/errors/retry-errors.test.js +0 -136
  42. package/dist/pipeline/pipeline.test.d.ts +0 -2
  43. package/dist/pipeline/pipeline.test.d.ts.map +0 -1
  44. package/dist/pipeline/pipeline.test.js +0 -1124
  45. package/dist/retry/standard-policy.test.d.ts +0 -2
  46. package/dist/retry/standard-policy.test.d.ts.map +0 -1
  47. package/dist/retry/standard-policy.test.js +0 -196
  48. package/dist/schema/registry.test.d.ts +0 -2
  49. package/dist/schema/registry.test.d.ts.map +0 -1
  50. package/dist/schema/registry.test.js +0 -278
  51. package/dist/schema/types.test.d.ts +0 -2
  52. package/dist/schema/types.test.d.ts.map +0 -1
  53. package/dist/schema/types.test.js +0 -246
  54. package/dist/topology/builder.test.d.ts +0 -2
  55. package/dist/topology/builder.test.d.ts.map +0 -1
  56. package/dist/topology/builder.test.js +0 -547
  57. package/dist/transport/local/local-transport.test.d.ts +0 -2
  58. package/dist/transport/local/local-transport.test.d.ts.map +0 -1
  59. package/dist/transport/local/local-transport.test.js +0 -232
  60. package/dist/transport/multi/multi-transport.test.d.ts +0 -2
  61. package/dist/transport/multi/multi-transport.test.d.ts.map +0 -1
  62. package/dist/transport/multi/multi-transport.test.js +0 -322
  63. package/dist/transport/rabbitmq/rabbitmq-transport-reconnection.test.d.ts +0 -2
  64. package/dist/transport/rabbitmq/rabbitmq-transport-reconnection.test.d.ts.map +0 -1
  65. package/dist/transport/rabbitmq/rabbitmq-transport-reconnection.test.js +0 -149
  66. package/dist/transport/rabbitmq/rabbitmq-transport.test.d.ts +0 -2
  67. package/dist/transport/rabbitmq/rabbitmq-transport.test.d.ts.map +0 -1
  68. package/dist/transport/rabbitmq/rabbitmq-transport.test.js +0 -230
  69. package/dist/types/event.test.d.ts +0 -2
  70. package/dist/types/event.test.d.ts.map +0 -1
  71. package/dist/types/event.test.js +0 -130
@@ -1,149 +0,0 @@
1
- import { beforeEach, describe, expect, it, mock } from 'bun:test';
2
- import { TopologyBuilder } from '../../topology/index.js';
3
- // Hoisted by Bun before static imports are resolved.
4
- // Keeps amqplib out of the other rabbitmq-transport.test.ts which relies on
5
- // real connection failures for its logging assertions.
6
- mock.module('amqplib', () => {
7
- return { default: { connect: mockConnect } };
8
- });
9
- // ─── Shared mock state ────────────────────────────────────────────────────────
10
- // Handlers registered by doConnect() for the 'close' event on the connection.
11
- // Index 0 = first (initial) connection, index 1 = second (post-reconnect), …
12
- const closeHandlers = [];
13
- // Total number of times channel.consume() has been called across all connections.
14
- let consumeCallCount = 0;
15
- // close() call count per connection, indexed by creation order.
16
- const connectionCloseCounts = [];
17
- // Set to a connection index to make that connection's createConfirmChannel throw.
18
- let failConfirmChannelOnConnection = -1;
19
- function resetMockState() {
20
- closeHandlers.length = 0;
21
- consumeCallCount = 0;
22
- connectionCloseCounts.length = 0;
23
- failConfirmChannelOnConnection = -1;
24
- }
25
- function makeMockChannel() {
26
- return {
27
- on: () => { },
28
- assertExchange: async () => { },
29
- assertQueue: async (_name) => ({ queue: _name }),
30
- bindQueue: async () => { },
31
- prefetch: async () => { },
32
- consume: async (_queue, _handler) => {
33
- consumeCallCount++;
34
- return { consumerTag: `consumer-${consumeCallCount}` };
35
- },
36
- cancel: async () => { },
37
- close: async () => { },
38
- // ConfirmChannel publish — immediately acks
39
- publish: (_exchange, _routingKey, _buffer, _options, cb) => {
40
- cb(null);
41
- return true;
42
- },
43
- };
44
- }
45
- function mockConnect() {
46
- const index = connectionCloseCounts.length;
47
- connectionCloseCounts.push(0);
48
- const channel = makeMockChannel();
49
- const connectionCloseListeners = [];
50
- const connection = {
51
- on: (event, handler) => {
52
- if (event === 'close') {
53
- connectionCloseListeners.push(handler);
54
- closeHandlers.push(handler);
55
- }
56
- },
57
- createChannel: async () => channel,
58
- createConfirmChannel: async () => {
59
- if (failConfirmChannelOnConnection === index) {
60
- throw new Error('simulated channel creation failure');
61
- }
62
- return channel;
63
- },
64
- close: async () => {
65
- if (connectionCloseCounts[index] !== undefined) {
66
- connectionCloseCounts[index]++;
67
- }
68
- },
69
- };
70
- return Promise.resolve(connection);
71
- }
72
- // ─── Tests ────────────────────────────────────────────────────────────────────
73
- import { RabbitMQTransport } from './rabbitmq-transport.js';
74
- const topology = TopologyBuilder.create()
75
- .withNamespace('test')
76
- .addQueue('events')
77
- .build();
78
- describe('RabbitMQTransport – consumer recreation on reconnect', () => {
79
- let transport;
80
- beforeEach(async () => {
81
- resetMockState();
82
- transport = new RabbitMQTransport({
83
- url: 'amqp://localhost:5672',
84
- connectionName: 'test',
85
- connection: {
86
- initialReconnectDelay: 10,
87
- maxReconnectDelay: 10,
88
- },
89
- });
90
- await transport.connect();
91
- await transport.applyTopology(topology);
92
- });
93
- it('registers one consumer per queue on initial connect', async () => {
94
- await transport.subscribe('test.events', async () => { });
95
- expect(consumeCallCount).toBe(1);
96
- });
97
- it('recreates consumers after the connection drops and reconnects', async () => {
98
- await transport.subscribe('test.events', async () => { });
99
- expect(consumeCallCount).toBe(1);
100
- // Simulate an unexpected connection close.
101
- // doConnect() registered a 'close' listener that calls
102
- // connectionManager.handleConnectionLost(), which triggers reconnection.
103
- const triggerClose = closeHandlers[0];
104
- if (!triggerClose)
105
- throw new Error('no close handler captured from mock connection');
106
- triggerClose();
107
- // Give the ConnectionManager time to reconnect (initialReconnectDelay = 10 ms).
108
- await new Promise((resolve) => setTimeout(resolve, 100));
109
- expect(consumeCallCount).toBe(2);
110
- });
111
- it('delivers messages to the handler after reconnect', async () => {
112
- const received = [];
113
- await transport.subscribe('test.events', async (envelope) => {
114
- received.push(envelope);
115
- });
116
- const triggerClose = closeHandlers[0];
117
- if (!triggerClose)
118
- throw new Error('no close handler captured from mock connection');
119
- triggerClose();
120
- await new Promise((resolve) => setTimeout(resolve, 100));
121
- expect(consumeCallCount).toBe(2);
122
- });
123
- it('closes the previous (dead) connection before opening a new one', async () => {
124
- await transport.subscribe('test.events', async () => { });
125
- // Trigger a new connect (private method)
126
- // biome-ignore lint/complexity/useLiteralKeys: private method
127
- await transport['doConnect']();
128
- // doConnect() must call close() on the stale first connection before
129
- // creating a new one, so broker resources are not leaked.
130
- expect(connectionCloseCounts[0]).toBe(1);
131
- });
132
- it('closes the new connection when post-connect setup fails during reconnect', async () => {
133
- await transport.subscribe('test.events', async () => { });
134
- // Make the second connection's confirm channel creation fail.
135
- // ConnectionManager will retry; the third connection succeeds.
136
- failConfirmChannelOnConnection = 1;
137
- const triggerClose = closeHandlers[0];
138
- if (!triggerClose)
139
- throw new Error('no close handler captured from mock connection');
140
- triggerClose();
141
- // Wait for the failed attempt (~10 ms) + successful retry (~10 ms) + margin.
142
- await new Promise((resolve) => setTimeout(resolve, 150));
143
- // The failed connection (index 1) must be closed by the catch block so it
144
- // does not leak as an open broker connection while the retry loop continues.
145
- expect(connectionCloseCounts[1]).toBe(1);
146
- // The successful retry (connection index 2) recreates the consumer.
147
- expect(consumeCallCount).toBe(2);
148
- });
149
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=rabbitmq-transport.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rabbitmq-transport.test.d.ts","sourceRoot":"","sources":["../../../src/transport/rabbitmq/rabbitmq-transport.test.ts"],"names":[],"mappings":""}
@@ -1,230 +0,0 @@
1
- import { describe, expect, it, mock } from 'bun:test';
2
- import { TopologyBuilder } from '../../topology/index.js';
3
- import { RabbitMQTransport, redactAmqpUrl } from './rabbitmq-transport.js';
4
- describe('redactAmqpUrl', () => {
5
- it('should redact username and password with 4 asterisks', () => {
6
- const url = 'amqp://myuser:mypassword@localhost:5672';
7
- const redacted = redactAmqpUrl(url);
8
- expect(redacted).toBe('amqp://****:****@localhost:5672');
9
- });
10
- it('should redact credentials in amqps URLs', () => {
11
- const url = 'amqps://admin:secret123@rabbitmq.example.com:5671';
12
- const redacted = redactAmqpUrl(url);
13
- expect(redacted).toBe('amqps://****:****@rabbitmq.example.com:5671');
14
- });
15
- it('should redact long credentials to exactly 4 asterisks', () => {
16
- const url = 'amqp://verylongusername:verylongpassword@rabbitmq-cluster.svc.local:5672';
17
- const redacted = redactAmqpUrl(url);
18
- expect(redacted).toBe('amqp://****:****@rabbitmq-cluster.svc.local:5672');
19
- });
20
- it('should redact short credentials to exactly 4 asterisks', () => {
21
- const url = 'amqp://a:b@host:5672';
22
- const redacted = redactAmqpUrl(url);
23
- expect(redacted).toBe('amqp://****:****@host:5672');
24
- });
25
- it('should preserve vhost in URL', () => {
26
- const url = 'amqp://user:pass@host:5672/myvhost';
27
- const redacted = redactAmqpUrl(url);
28
- expect(redacted).toBe('amqp://****:****@host:5672/myvhost');
29
- });
30
- it('should not modify URL without credentials', () => {
31
- const url = 'amqp://localhost:5672';
32
- const redacted = redactAmqpUrl(url);
33
- expect(redacted).toBe('amqp://localhost:5672');
34
- });
35
- it('should not modify URL with only username (no password)', () => {
36
- const url = 'amqp://guest@localhost:5672';
37
- const redacted = redactAmqpUrl(url);
38
- expect(redacted).toBe('amqp://guest@localhost:5672');
39
- });
40
- it('should handle URL-encoded credentials', () => {
41
- // URL-encoded @ in password: p%40ssword
42
- const url = 'amqp://user:p%40ssword@host:5672';
43
- const redacted = redactAmqpUrl(url);
44
- expect(redacted).toBe('amqp://****:****@host:5672');
45
- });
46
- });
47
- describe('RabbitMQTransport', () => {
48
- describe('connection logging', () => {
49
- it('should log redacted connection URL when connecting', async () => {
50
- const mockLogger = {
51
- debug: mock(() => { }),
52
- info: mock(() => { }),
53
- warn: mock(() => { }),
54
- error: mock(() => { }),
55
- };
56
- const transport = new RabbitMQTransport({
57
- url: 'amqp://testuser:testpass@localhost:5672',
58
- connectionName: 'test-connection',
59
- logger: mockLogger,
60
- connection: {
61
- maxReconnectAttempts: 1, // Only try once to avoid long retries
62
- initialReconnectDelay: 10,
63
- },
64
- });
65
- // Attempt to connect - it will fail since there's no RabbitMQ server
66
- // but the log should still be emitted before the connection attempt
67
- try {
68
- await transport.connect();
69
- }
70
- catch {
71
- // Expected to fail - no RabbitMQ server running
72
- }
73
- // Verify the log was called with the redacted URL
74
- expect(mockLogger.info).toHaveBeenCalledWith("[Matador] \u23F3 Connecting to RabbitMQ at 'amqp://****:****@localhost:5672'.");
75
- });
76
- it('should log connection URL as-is when no credentials provided', async () => {
77
- const mockLogger = {
78
- debug: mock(() => { }),
79
- info: mock(() => { }),
80
- warn: mock(() => { }),
81
- error: mock(() => { }),
82
- };
83
- const transport = new RabbitMQTransport({
84
- url: 'amqp://localhost:5672',
85
- connectionName: 'test-connection',
86
- logger: mockLogger,
87
- connection: {
88
- maxReconnectAttempts: 1,
89
- initialReconnectDelay: 10,
90
- },
91
- });
92
- try {
93
- await transport.connect();
94
- }
95
- catch {
96
- // Expected to fail
97
- }
98
- expect(mockLogger.info).toHaveBeenCalledWith("[Matador] \u23F3 Connecting to RabbitMQ at 'amqp://localhost:5672'.");
99
- });
100
- });
101
- });
102
- describe('dead-letter and retry queue naming under withNaming', () => {
103
- // A v1-style naming strategy: a migrating deployment keeps Matador v1's
104
- // `matador.{ns}.{queue}` names so DLQ/retry resources stay identifiable.
105
- const NS = 'myapp';
106
- const v1Naming = {
107
- queue: (ns, q) => `matador.${ns}.${q}`,
108
- mainExchange: (ns) => `matador.${ns}`,
109
- dlxExchange: (ns) => `matador.${ns}.dlx-undeliverable`,
110
- dlxExchangeType: 'topic',
111
- };
112
- function buildTopology() {
113
- return TopologyBuilder.create()
114
- .withNamespace(NS)
115
- .withNaming(v1Naming)
116
- .addQueue('events')
117
- .build();
118
- }
119
- function createRecordingChannel() {
120
- const assertQueueCalls = [];
121
- const bindQueueCalls = [];
122
- const channel = {
123
- assertQueue: (name, options) => {
124
- assertQueueCalls.push({ name, options });
125
- return Promise.resolve({ queue: name });
126
- },
127
- bindQueue: (queue, exchange, routingKey) => {
128
- bindQueueCalls.push({ queue, exchange, routingKey });
129
- return Promise.resolve({});
130
- },
131
- };
132
- return { channel, assertQueueCalls, bindQueueCalls };
133
- }
134
- function createTransport() {
135
- return new RabbitMQTransport({
136
- url: 'amqp://localhost:5672',
137
- connectionName: 'test',
138
- });
139
- }
140
- it('derives the retry queue name through the naming strategy', async () => {
141
- const transport = createTransport();
142
- const topology = buildTopology();
143
- const queueDef = topology.queues[0];
144
- const { channel, assertQueueCalls, bindQueueCalls } = createRecordingChannel();
145
- await transport.assertRetryQueue(channel, topology, queueDef);
146
- expect(assertQueueCalls.map((c) => c.name)).toEqual([
147
- 'matador.myapp.events.retry',
148
- ]);
149
- // Retry queue dead-letters back to the (overridden) main exchange using the
150
- // resolved work-queue name as the routing key.
151
- const retryOptions = assertQueueCalls[0]?.options;
152
- expect(retryOptions.arguments['x-dead-letter-exchange']).toBe('matador.myapp');
153
- expect(retryOptions.arguments['x-dead-letter-routing-key']).toBe('matador.myapp.events');
154
- expect(bindQueueCalls).toEqual([
155
- {
156
- queue: 'matador.myapp.events.retry',
157
- exchange: 'matador.myapp',
158
- routingKey: 'matador.myapp.events.retry',
159
- },
160
- ]);
161
- });
162
- it('derives DLQ names through the naming strategy and binds them to the overridden DLX', async () => {
163
- const transport = createTransport();
164
- const topology = buildTopology();
165
- const { channel, assertQueueCalls, bindQueueCalls } = createRecordingChannel();
166
- const assertDeadLetterQueues = transport.assertDeadLetterQueues.bind(transport);
167
- await assertDeadLetterQueues(channel, topology, 'unhandled');
168
- await assertDeadLetterQueues(channel, topology, 'undeliverable');
169
- expect(assertQueueCalls.map((c) => c.name)).toEqual([
170
- 'matador.myapp.events.unhandled',
171
- 'matador.myapp.events.undeliverable',
172
- ]);
173
- expect(bindQueueCalls).toEqual([
174
- {
175
- queue: 'matador.myapp.events.unhandled',
176
- exchange: 'matador.myapp.dlx-undeliverable',
177
- routingKey: 'matador.myapp.events.unhandled',
178
- },
179
- {
180
- queue: 'matador.myapp.events.undeliverable',
181
- exchange: 'matador.myapp.dlx-undeliverable',
182
- routingKey: 'matador.myapp.events.undeliverable',
183
- },
184
- ]);
185
- });
186
- });
187
- describe('confirmPublish', () => {
188
- function createTransport(publishTimeoutMs) {
189
- return new RabbitMQTransport({
190
- url: 'amqp://localhost:5672',
191
- connectionName: 'test',
192
- ...(publishTimeoutMs !== undefined ? { publishTimeoutMs } : {}),
193
- });
194
- }
195
- function callConfirmPublish(transport, publish) {
196
- const fakeChannel = {
197
- publish: (_exchange, _routingKey, _buffer, _options, cb) => {
198
- publish(cb);
199
- return true;
200
- },
201
- };
202
- return transport.confirmPublish(fakeChannel, 'ex', 'queue', Buffer.from('x'), {});
203
- }
204
- it('resolves when the broker confirms the publish', async () => {
205
- const transport = createTransport();
206
- await callConfirmPublish(transport, (cb) => cb(null));
207
- });
208
- it('rejects with TransportSendError when the broker nacks', async () => {
209
- const transport = createTransport();
210
- await expect(callConfirmPublish(transport, (cb) => cb(new Error('nacked')))).rejects.toMatchObject({
211
- name: 'TransportSendError',
212
- queue: 'queue',
213
- });
214
- });
215
- it('rejects when the broker never confirms within the timeout', async () => {
216
- const transport = createTransport(50);
217
- await expect(callConfirmPublish(transport, () => {
218
- // Broker never calls back.
219
- })).rejects.toMatchObject({ name: 'TransportSendError' });
220
- });
221
- it('ignores a late confirm after the timeout already rejected', async () => {
222
- const transport = createTransport(50);
223
- let lateCallback;
224
- await expect(callConfirmPublish(transport, (cb) => {
225
- lateCallback = cb;
226
- })).rejects.toMatchObject({ name: 'TransportSendError' });
227
- // Late confirm must not throw or double-settle.
228
- lateCallback?.(null);
229
- });
230
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=event.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"event.test.d.ts","sourceRoot":"","sources":["../../src/types/event.test.ts"],"names":[],"mappings":""}
@@ -1,130 +0,0 @@
1
- import { describe, expect, it } from 'bun:test';
2
- import { createEnvelope } from './envelope.js';
3
- import { MatadorEvent } from './event.js';
4
- class UserCreatedEvent extends MatadorEvent {
5
- data;
6
- static key = 'user.created';
7
- static description = 'Fired when a new user is created';
8
- constructor(data) {
9
- super();
10
- this.data = data;
11
- }
12
- }
13
- class OrderPlacedEvent extends MatadorEvent {
14
- data;
15
- static key = 'order.placed';
16
- static description = 'Fired when an order is placed';
17
- static aliases = ['order.created'];
18
- constructor(data) {
19
- super();
20
- this.data = data;
21
- }
22
- }
23
- class MinimalEvent extends MatadorEvent {
24
- data;
25
- static key = 'minimal.event';
26
- constructor(data) {
27
- super();
28
- this.data = data;
29
- }
30
- }
31
- describe('Event', () => {
32
- describe('static fields', () => {
33
- it('should have static key field on class', () => {
34
- expect(UserCreatedEvent.key).toBe('user.created');
35
- expect(OrderPlacedEvent.key).toBe('order.placed');
36
- expect(MinimalEvent.key).toBe('minimal.event');
37
- });
38
- it('should have static description field on class when defined', () => {
39
- expect(UserCreatedEvent.description).toBe('Fired when a new user is created');
40
- expect(OrderPlacedEvent.description).toBe('Fired when an order is placed');
41
- expect(MinimalEvent.description).toBeUndefined();
42
- });
43
- it('should have static aliases field on class when defined', () => {
44
- expect(UserCreatedEvent.aliases).toBeUndefined();
45
- expect(OrderPlacedEvent.aliases).toEqual(['order.created']);
46
- expect(MinimalEvent.aliases).toBeUndefined();
47
- });
48
- });
49
- describe('instance data', () => {
50
- it('should have instance data field', () => {
51
- const event = new UserCreatedEvent({
52
- userId: 'usr_001',
53
- email: 'test@example.com',
54
- });
55
- expect(event.data).toEqual({
56
- userId: 'usr_001',
57
- email: 'test@example.com',
58
- });
59
- });
60
- });
61
- });
62
- describe('Envelope with eventDescription', () => {
63
- it('should include eventDescription in docket when provided', () => {
64
- const event = new UserCreatedEvent({
65
- userId: 'usr_123',
66
- email: 'test@example.com',
67
- });
68
- const envelope = createEnvelope({
69
- eventKey: UserCreatedEvent.key,
70
- eventDescription: UserCreatedEvent.description,
71
- targetSubscriber: 'test-subscriber',
72
- data: event.data,
73
- importance: 'should-investigate',
74
- });
75
- expect(envelope.docket.eventKey).toBe('user.created');
76
- expect(envelope.docket.eventDescription).toBe('Fired when a new user is created');
77
- });
78
- it('should not include eventDescription in docket when undefined', () => {
79
- const event = new MinimalEvent({ id: 'min_123' });
80
- const envelope = createEnvelope({
81
- eventKey: MinimalEvent.key,
82
- eventDescription: MinimalEvent.description,
83
- targetSubscriber: 'test-subscriber',
84
- data: event.data,
85
- importance: 'should-investigate',
86
- });
87
- expect(envelope.docket.eventKey).toBe('minimal.event');
88
- expect(envelope.docket.eventDescription).toBeUndefined();
89
- });
90
- it('should serialize envelope with eventDescription for logging', () => {
91
- const event = new OrderPlacedEvent({
92
- orderId: 'ord_456',
93
- amount: 99.99,
94
- });
95
- const envelope = createEnvelope({
96
- eventKey: OrderPlacedEvent.key,
97
- eventDescription: OrderPlacedEvent.description,
98
- targetSubscriber: 'order-processor',
99
- data: event.data,
100
- importance: 'must-investigate',
101
- });
102
- const serialized = JSON.stringify(envelope);
103
- const parsed = JSON.parse(serialized);
104
- expect(parsed.docket.eventKey).toBe('order.placed');
105
- expect(parsed.docket.eventDescription).toBe('Fired when an order is placed');
106
- expect(parsed.data.orderId).toBe('ord_456');
107
- });
108
- it('should include eventDescription in hook logging context', () => {
109
- const event = new UserCreatedEvent({
110
- userId: 'usr_error',
111
- email: 'error@example.com',
112
- });
113
- const envelope = createEnvelope({
114
- eventKey: UserCreatedEvent.key,
115
- eventDescription: UserCreatedEvent.description,
116
- targetSubscriber: 'user-handler',
117
- data: event.data,
118
- importance: 'should-investigate',
119
- });
120
- // Simulating what would be logged in onEnqueueError hook
121
- const errorLog = {
122
- message: 'Failed to enqueue event',
123
- eventKey: envelope.docket.eventKey,
124
- eventDescription: envelope.docket.eventDescription,
125
- data: envelope.data,
126
- };
127
- expect(errorLog.eventKey).toBe('user.created');
128
- expect(errorLog.eventDescription).toBe('Fired when a new user is created');
129
- });
130
- });