@seidor-cloud-produtos/orbit-backend-lib 2.0.110 → 2.0.111

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 (58) hide show
  1. package/dist/clean-arch/application/cache/cache.spec.d.ts +1 -0
  2. package/dist/clean-arch/application/cache/cache.spec.js +178 -0
  3. package/dist/clean-arch/application/consistency-event-dispatcher/consistency-event-dispatcher.d.ts +1 -1
  4. package/dist/clean-arch/application/logger/logger.spec.d.ts +1 -0
  5. package/dist/clean-arch/application/logger/logger.spec.js +204 -0
  6. package/dist/clean-arch/domain/entities/entity.spec.d.ts +1 -0
  7. package/dist/clean-arch/domain/entities/entity.spec.js +130 -0
  8. package/dist/clean-arch/domain/events/consistency-event/consistency-event.spec.d.ts +1 -0
  9. package/dist/clean-arch/domain/events/consistency-event/consistency-event.spec.js +91 -0
  10. package/dist/clean-arch/domain/value-objects/password.spec.d.ts +1 -0
  11. package/dist/clean-arch/domain/value-objects/password.spec.js +90 -0
  12. package/dist/clean-arch/infra/adapters/http-adapters.spec.d.ts +1 -0
  13. package/dist/clean-arch/infra/adapters/http-adapters.spec.js +318 -0
  14. package/dist/clean-arch/infra/authorizations/authorizer.spec.js +43 -0
  15. package/dist/clean-arch/infra/cache/cache-clients.spec.d.ts +1 -0
  16. package/dist/clean-arch/infra/cache/cache-clients.spec.js +161 -0
  17. package/dist/clean-arch/infra/cache/clients/node-cache.js +3 -2
  18. package/dist/clean-arch/infra/http/controller.spec.js +170 -51
  19. package/dist/clean-arch/infra/http/helpers.spec.d.ts +1 -0
  20. package/dist/clean-arch/infra/http/helpers.spec.js +136 -0
  21. package/dist/clean-arch/infra/http/net.spec.d.ts +1 -0
  22. package/dist/clean-arch/infra/http/net.spec.js +46 -0
  23. package/dist/clean-arch/infra/logger/logger-orbit.d.ts +2 -0
  24. package/dist/clean-arch/infra/logger/logger-orbit.spec.d.ts +1 -0
  25. package/dist/clean-arch/infra/logger/logger-orbit.spec.js +122 -0
  26. package/dist/clean-arch/infra/orbit-http-client/orbit-axios-client/orbit-axios-client.spec.d.ts +1 -0
  27. package/dist/clean-arch/infra/orbit-http-client/orbit-axios-client/orbit-axios-client.spec.js +143 -0
  28. package/dist/clean-arch/infra/orbit-notification-client/discord-client.spec.d.ts +1 -0
  29. package/dist/clean-arch/infra/orbit-notification-client/discord-client.spec.js +58 -0
  30. package/dist/clean-arch/infra/queue/queue-utils.spec.d.ts +1 -0
  31. package/dist/clean-arch/infra/queue/queue-utils.spec.js +170 -0
  32. package/dist/clean-arch/infra/queue/rabbitmq/amqp-lib.js +4 -4
  33. package/dist/clean-arch/infra/queue/rabbitmq/amqp-lib.spec.js +466 -89
  34. package/dist/clean-arch/infra/queue/rabbitmq/types.d.ts +1 -1
  35. package/dist/clean-arch/infra/scaledjob/amqp/runner.d.ts +0 -3
  36. package/dist/clean-arch/infra/scaledjob/amqp/runner.js +5 -6
  37. package/dist/clean-arch/infra/scaledjob/amqp/runner.spec.js +74 -84
  38. package/dist/clean-arch/infra/scaledjob/sqs/runner.spec.d.ts +1 -0
  39. package/dist/clean-arch/infra/scaledjob/sqs/runner.spec.js +150 -0
  40. package/dist/clean-arch/infra/tracing/start-tracing.d.ts +10 -0
  41. package/dist/clean-arch/infra/tracing/start-tracing.js +78 -0
  42. package/dist/clean-arch/infra/validations/zod/validation-zod.spec.js +23 -0
  43. package/dist/clean-arch/shared/pagination/get-take-and-skip.spec.d.ts +1 -0
  44. package/dist/clean-arch/shared/pagination/get-take-and-skip.spec.js +16 -0
  45. package/dist/coverage/block-navigation.d.ts +1 -0
  46. package/dist/coverage/block-navigation.js +72 -0
  47. package/dist/coverage/prettify.d.ts +0 -0
  48. package/dist/coverage/prettify.js +1009 -0
  49. package/dist/coverage/sorter.d.ts +1 -0
  50. package/dist/coverage/sorter.js +178 -0
  51. package/dist/frameworks/express/api-gateway/mapping-model.spec.js +58 -0
  52. package/dist/frameworks/express/authorizations/authorization-express.spec.js +9 -0
  53. package/dist/frameworks/express/middleware-express.spec.d.ts +1 -0
  54. package/dist/frameworks/express/middleware-express.spec.js +51 -0
  55. package/dist/frameworks/nest/nest.spec.d.ts +1 -0
  56. package/dist/frameworks/nest/nest.spec.js +122 -0
  57. package/dist/infra/http/api-gateway/mapping-model.spec.js +42 -0
  58. package/package.json +5 -1
@@ -1,105 +1,482 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
+ const vitest_1 = require("vitest");
5
+ vitest_1.vi.mock('amqplib', () => ({
6
+ default: {
7
+ connect: vitest_1.vi.fn(),
8
+ },
9
+ }));
10
+ const amqplib_1 = tslib_1.__importDefault(require("amqplib"));
11
+ const infra_error_1 = tslib_1.__importDefault(require("../../errors/infra-error"));
4
12
  const amqp_lib_1 = tslib_1.__importDefault(require("./amqp-lib"));
5
- describe('AmqpQueue', () => {
6
- it('should not publish if empty memory', async () => {
7
- const mq = new amqp_lib_1.default();
8
- mq['messagesInMemory'] = [];
9
- const mockPublish = (mq['publish'] = vi.fn());
10
- await mq['publishMessagesOfMemory']();
11
- expect(mockPublish.mock.calls.length).toBe(0);
12
- });
13
- it('should publish if has messages with correct params', async () => {
14
- const mq = new amqp_lib_1.default();
15
- mq['messagesInMemory'] = [
16
- {
17
- exchangeName: 'exchangeName_1',
18
- domainEvent: 'domainEvent_1',
19
- configs: 'my_cfg_1',
20
- },
13
+ const FOURTEEN_DAYS_IN_MS = 14 * 24 * 60 * 60 * 1000;
14
+ const SEVEN_DAYS_IN_MS = 7 * 24 * 60 * 60 * 1000;
15
+ const createChannel = () => ({
16
+ ack: vitest_1.vi.fn(),
17
+ assertExchange: vitest_1.vi.fn().mockResolvedValue(undefined),
18
+ assertQueue: vitest_1.vi.fn().mockResolvedValue(undefined),
19
+ bindQueue: vitest_1.vi.fn().mockResolvedValue(undefined),
20
+ close: vitest_1.vi.fn().mockResolvedValue(undefined),
21
+ consume: vitest_1.vi.fn().mockResolvedValue(undefined),
22
+ get: vitest_1.vi.fn().mockResolvedValue(null),
23
+ nack: vitest_1.vi.fn(),
24
+ prefetch: vitest_1.vi.fn().mockResolvedValue(undefined),
25
+ publish: vitest_1.vi.fn(),
26
+ reject: vitest_1.vi.fn(),
27
+ });
28
+ const createConnection = (channel = createChannel()) => ({
29
+ close: vitest_1.vi.fn().mockResolvedValue(undefined),
30
+ createChannel: vitest_1.vi.fn().mockResolvedValue(channel),
31
+ on: vitest_1.vi.fn(),
32
+ removeAllListeners: vitest_1.vi.fn(),
33
+ });
34
+ const createLogger = () => ({
35
+ debug: vitest_1.vi.fn(),
36
+ error: vitest_1.vi.fn(),
37
+ info: vitest_1.vi.fn(),
38
+ warn: vitest_1.vi.fn(),
39
+ });
40
+ (0, vitest_1.describe)('AmqpQueue', () => {
41
+ const connectMock = vitest_1.vi.mocked(amqplib_1.default.connect);
42
+ const originalNodeEnv = process.env.NODE_ENV;
43
+ (0, vitest_1.beforeEach)(() => {
44
+ vitest_1.vi.clearAllMocks();
45
+ amqp_lib_1.default.instance = undefined;
46
+ process.env.NODE_ENV = 'test';
47
+ });
48
+ (0, vitest_1.afterEach)(() => {
49
+ vitest_1.vi.useRealTimers();
50
+ process.env.NODE_ENV = originalNodeEnv;
51
+ });
52
+ (0, vitest_1.it)('should reconnect listeners and log on successful connect', async () => {
53
+ const logger = createLogger();
54
+ const connection = createConnection();
55
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', logger);
56
+ const reconnectSpy = vitest_1.vi
57
+ .spyOn(queue, 'treatReconnection')
58
+ .mockResolvedValue(undefined);
59
+ connectMock.mockResolvedValue(connection);
60
+ process.env.NODE_ENV = 'development';
61
+ await (0, vitest_1.expect)(queue.connect('jobs')).resolves.toBe(queue);
62
+ (0, vitest_1.expect)(connectMock).toHaveBeenCalledWith('amqp://broker/jobs', {
63
+ maxStoredMessagesOnError: 500,
64
+ });
65
+ (0, vitest_1.expect)(connection.on).toHaveBeenCalledWith('error', vitest_1.expect.any(Function));
66
+ (0, vitest_1.expect)(connection.on).toHaveBeenCalledWith('close', vitest_1.expect.any(Function));
67
+ (0, vitest_1.expect)(logger.info).toHaveBeenCalledWith('⚙️ PROCESS jobs PID: ', process.pid);
68
+ (0, vitest_1.expect)(logger.info).toHaveBeenCalledWith('🐝 Queue connected on vHost: jobs');
69
+ const onError = connection.on.mock.calls.find(([event]) => event === 'error')?.[1];
70
+ const onClose = connection.on.mock.calls.find(([event]) => event === 'close')?.[1];
71
+ await onError?.(new Error('fail'));
72
+ await onClose?.();
73
+ (0, vitest_1.expect)(reconnectSpy).toHaveBeenCalledTimes(2);
74
+ (0, vitest_1.expect)(reconnectSpy).toHaveBeenNthCalledWith(1, 'jobs');
75
+ (0, vitest_1.expect)(reconnectSpy).toHaveBeenNthCalledWith(2, 'jobs');
76
+ });
77
+ (0, vitest_1.it)('should retry the connection before succeeding', async () => {
78
+ vitest_1.vi.useFakeTimers();
79
+ const logger = createLogger();
80
+ const connection = createConnection();
81
+ const queue = new amqp_lib_1.default({ retry: { maxCount: 1, intervalMs: 10 } }, 'amqp://broker', logger);
82
+ connectMock
83
+ .mockRejectedValueOnce(new Error('fail'))
84
+ .mockResolvedValueOnce(connection);
85
+ const promise = queue.connect('jobs');
86
+ await vitest_1.vi.advanceTimersByTimeAsync(10);
87
+ await (0, vitest_1.expect)(promise).resolves.toBe(queue);
88
+ (0, vitest_1.expect)(logger.info).toHaveBeenCalledWith('Retrying connection in... 10ms');
89
+ (0, vitest_1.expect)(connectMock).toHaveBeenCalledTimes(2);
90
+ });
91
+ (0, vitest_1.it)('should throw when the retry limit is reached', async () => {
92
+ const queue = new amqp_lib_1.default({ retry: { maxCount: 1, intervalMs: 10 } }, 'amqp://broker', createLogger());
93
+ connectMock.mockRejectedValue(new Error('fail'));
94
+ await (0, vitest_1.expect)(queue.connect('jobs', 1)).rejects.toThrow('fail');
95
+ });
96
+ (0, vitest_1.it)('should close connection and remove listeners', async () => {
97
+ const logger = createLogger();
98
+ const connection = createConnection();
99
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', logger);
100
+ process.env.NODE_ENV = 'development';
101
+ queue['connection'] = connection;
102
+ queue['vHost'] = 'jobs';
103
+ await queue.close();
104
+ (0, vitest_1.expect)(connection.removeAllListeners).toHaveBeenCalledWith('close');
105
+ (0, vitest_1.expect)(connection.close).toHaveBeenCalledTimes(1);
106
+ (0, vitest_1.expect)(logger.info).toHaveBeenCalledWith('⛔ Queue closed on vHost jobs');
107
+ });
108
+ (0, vitest_1.it)('should resubscribe channels and republish buffered messages on reconnection', async () => {
109
+ const logger = createLogger();
110
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', logger);
111
+ const handler = { handle: vitest_1.vi.fn() };
112
+ queue['channels'].set('queueName', handler);
113
+ queue['messagesInMemory'] = [
21
114
  {
22
- exchangeName: 'exchangeName_2',
23
- domainEvent: 'domainEvent_2',
24
- configs: 'my_cfg_2',
115
+ exchangeName: 'exchange',
116
+ domainEvent: { name: 'domain.event' },
117
+ configs: { persistent: false },
25
118
  },
26
119
  ];
27
- const mockPublish = (mq['publish'] = vi.fn());
28
- await mq['publishMessagesOfMemory']();
29
- expect(mockPublish.mock.calls).toEqual([
30
- ['exchangeName_1', 'domainEvent_1', 'my_cfg_1'],
31
- ['exchangeName_2', 'domainEvent_2', 'my_cfg_2'],
32
- ]);
33
- });
34
- describe('get', () => {
35
- it('should return null if no message is found', async () => {
36
- const mq = new amqp_lib_1.default();
37
- mq['connection'] = {
38
- createChannel: vi.fn().mockResolvedValue({
39
- get: vi.fn().mockResolvedValue(null),
40
- close: vi.fn(),
41
- }),
120
+ const connectSpy = vitest_1.vi.spyOn(queue, 'connect').mockResolvedValue(queue);
121
+ const onSpy = vitest_1.vi.spyOn(queue, 'on').mockResolvedValue(undefined);
122
+ const publishSpy = vitest_1.vi.spyOn(queue, 'publish').mockResolvedValue(undefined);
123
+ await queue['treatReconnection']('jobs');
124
+ (0, vitest_1.expect)(logger.info).toHaveBeenCalledWith(' Connection Error!!');
125
+ (0, vitest_1.expect)(logger.info).toHaveBeenCalledWith('🔄 Try connection to the vHost jobs');
126
+ (0, vitest_1.expect)(connectSpy).toHaveBeenCalledWith('jobs');
127
+ (0, vitest_1.expect)(onSpy).toHaveBeenCalledWith('queueName', handler);
128
+ (0, vitest_1.expect)(publishSpy).toHaveBeenCalledWith('exchange', { name: 'domain.event' }, { persistent: false });
129
+ (0, vitest_1.expect)(queue['messagesInMemory']).toHaveLength(0);
130
+ (0, vitest_1.expect)(logger.info).toHaveBeenCalledWith(' Publishing memory message to: [domain.event]');
131
+ (0, vitest_1.expect)(logger.info).toHaveBeenCalledWith('✅ Published memory messages!');
132
+ });
133
+ (0, vitest_1.it)('should not publish buffered messages when there are none', async () => {
134
+ const queue = new amqp_lib_1.default();
135
+ queue['messagesInMemory'] = [];
136
+ queue['publish'] = vitest_1.vi.fn();
137
+ await queue['publishMessagesOfMemory']();
138
+ (0, vitest_1.expect)(queue['publish']).not.toHaveBeenCalled();
139
+ });
140
+ (0, vitest_1.describe)('get', () => {
141
+ (0, vitest_1.it)('should return null if no message is found', async () => {
142
+ const channel = createChannel();
143
+ const queue = new amqp_lib_1.default();
144
+ queue['connection'] = {
145
+ createChannel: vitest_1.vi.fn().mockResolvedValue(channel),
42
146
  };
43
- const result = await mq.get('queueName');
44
- expect(result).toBeNull();
147
+ const result = await queue.get('queueName');
148
+ (0, vitest_1.expect)(result).toBeNull();
149
+ (0, vitest_1.expect)(channel.close).toHaveBeenCalledTimes(1);
45
150
  });
46
- it('should return rawMessage and channel if message is found', async () => {
151
+ (0, vitest_1.it)('should return rawMessage and channel if message is found', async () => {
47
152
  const fakeMsg = { content: Buffer.from('msg') };
48
- const fakeChannel = {
49
- get: vi.fn().mockResolvedValue(fakeMsg),
50
- close: vi.fn(),
153
+ const channel = createChannel();
154
+ const queue = new amqp_lib_1.default();
155
+ channel.get.mockResolvedValue(fakeMsg);
156
+ queue['connection'] = {
157
+ createChannel: vitest_1.vi.fn().mockResolvedValue(channel),
158
+ };
159
+ const result = await queue.get('queueName');
160
+ (0, vitest_1.expect)(result).toEqual({ rawMessage: fakeMsg, channel });
161
+ (0, vitest_1.expect)(channel.get).toHaveBeenCalledWith('queueName', undefined);
162
+ });
163
+ (0, vitest_1.it)('should handle errors and return null', async () => {
164
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
165
+ queue['connection'] = {
166
+ createChannel: vitest_1.vi.fn().mockRejectedValue(new Error('fail')),
167
+ };
168
+ await (0, vitest_1.expect)(queue.get('queueName')).resolves.toBeNull();
169
+ });
170
+ });
171
+ (0, vitest_1.describe)('ack', () => {
172
+ (0, vitest_1.it)('should call channel.ack and close', async () => {
173
+ const channel = createChannel();
174
+ const queue = new amqp_lib_1.default();
175
+ await queue.ack(channel, 'msg');
176
+ (0, vitest_1.expect)(channel.ack).toHaveBeenCalledWith('msg');
177
+ (0, vitest_1.expect)(channel.close).toHaveBeenCalledTimes(1);
178
+ });
179
+ (0, vitest_1.it)('should handle errors gracefully', async () => {
180
+ const channel = createChannel();
181
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
182
+ channel.ack.mockImplementation(() => {
183
+ throw new Error('fail');
184
+ });
185
+ await (0, vitest_1.expect)(queue.ack(channel, 'msg')).resolves.toBeUndefined();
186
+ });
187
+ });
188
+ (0, vitest_1.describe)('nack', () => {
189
+ (0, vitest_1.it)('should call channel.nack and close', async () => {
190
+ const channel = createChannel();
191
+ const queue = new amqp_lib_1.default();
192
+ await queue.nack(channel, 'msg', true);
193
+ (0, vitest_1.expect)(channel.nack).toHaveBeenCalledWith('msg', false, true);
194
+ (0, vitest_1.expect)(channel.close).toHaveBeenCalledTimes(1);
195
+ });
196
+ (0, vitest_1.it)('should handle errors gracefully', async () => {
197
+ const channel = createChannel();
198
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
199
+ channel.nack.mockImplementation(() => {
200
+ throw new Error('fail');
201
+ });
202
+ await (0, vitest_1.expect)(queue.nack(channel, 'msg')).resolves.toBeUndefined();
203
+ });
204
+ });
205
+ (0, vitest_1.describe)('on', () => {
206
+ (0, vitest_1.it)('should consume messages, ack them and keep the callback registered', async () => {
207
+ const logger = createLogger();
208
+ const channel = createChannel();
209
+ const connection = createConnection(channel);
210
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', logger);
211
+ const message = { content: Buffer.from(JSON.stringify({ id: '1' })) };
212
+ const handler = {
213
+ getSimultaneity: vitest_1.vi.fn().mockReturnValue(2),
214
+ handle: vitest_1.vi.fn().mockResolvedValue(undefined),
51
215
  };
52
- const mq = new amqp_lib_1.default();
53
- mq['connection'] = {
54
- createChannel: vi.fn().mockResolvedValue(fakeChannel),
216
+ channel.consume.mockImplementation(async (_queueName, callback) => {
217
+ await callback(message);
218
+ });
219
+ queue['connection'] = connection;
220
+ queue['vHost'] = 'jobs';
221
+ process.env.NODE_ENV = 'development';
222
+ await queue.on('queueName', handler);
223
+ (0, vitest_1.expect)(channel.prefetch).toHaveBeenCalledWith(2);
224
+ (0, vitest_1.expect)(handler.handle).toHaveBeenCalledWith({ id: '1' });
225
+ (0, vitest_1.expect)(channel.ack).toHaveBeenCalledWith(message);
226
+ (0, vitest_1.expect)(queue['channels'].get('queueName')).toBe(handler);
227
+ (0, vitest_1.expect)(logger.info).toHaveBeenCalledWith('🍯 Consume on queue: [queueName] on vHost jobs');
228
+ });
229
+ (0, vitest_1.it)('should reject messages when the consumer fails outside tests', async () => {
230
+ const channel = createChannel();
231
+ const connection = createConnection(channel);
232
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
233
+ const message = { content: Buffer.from(JSON.stringify({ id: '1' })) };
234
+ const handler = {
235
+ getSimultaneity: vitest_1.vi.fn().mockReturnValue(1),
236
+ handle: vitest_1.vi.fn().mockRejectedValue(new Error('fail')),
237
+ };
238
+ channel.consume.mockImplementation(async (_queueName, callback) => {
239
+ await callback(message);
240
+ });
241
+ queue['connection'] = connection;
242
+ process.env.NODE_ENV = 'development';
243
+ await queue.on('queueName', handler);
244
+ (0, vitest_1.expect)(channel.reject).toHaveBeenCalledWith(message, false);
245
+ });
246
+ (0, vitest_1.it)('should not reject messages when the consumer fails in tests', async () => {
247
+ const channel = createChannel();
248
+ const connection = createConnection(channel);
249
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
250
+ const message = { content: Buffer.from(JSON.stringify({ id: '1' })) };
251
+ const handler = {
252
+ getSimultaneity: vitest_1.vi.fn().mockReturnValue(1),
253
+ handle: vitest_1.vi.fn().mockRejectedValue(new Error('fail')),
55
254
  };
56
- const result = await mq.get('queueName');
57
- expect(result).toEqual({ rawMessage: fakeMsg, channel: fakeChannel });
58
- expect(fakeChannel.get).toHaveBeenCalledWith('queueName', undefined);
59
- });
60
- it('should handle errors and return null', async () => {
61
- const mq = new amqp_lib_1.default();
62
- mq['connection'] = {
63
- createChannel: vi.fn().mockRejectedValue(new Error('fail')),
255
+ channel.consume.mockImplementation(async (_queueName, callback) => {
256
+ await callback(message);
257
+ });
258
+ queue['connection'] = connection;
259
+ await queue.on('queueName', handler);
260
+ (0, vitest_1.expect)(channel.reject).not.toHaveBeenCalled();
261
+ });
262
+ (0, vitest_1.it)('should log consumption errors', async () => {
263
+ const logger = createLogger();
264
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', logger);
265
+ queue['connection'] = {
266
+ createChannel: vitest_1.vi.fn().mockRejectedValue(new Error('fail')),
64
267
  };
65
- const result = await mq.get('queueName');
66
- expect(result).toBeNull();
67
- });
68
- });
69
- describe('ack', () => {
70
- it('should call channel.ack and close', async () => {
71
- const ack = vi.fn();
72
- const close = vi.fn();
73
- const channel = { ack, close };
74
- const mq = new amqp_lib_1.default();
75
- await mq.ack(channel, 'msg');
76
- expect(ack).toHaveBeenCalledWith('msg');
77
- expect(close).toHaveBeenCalled();
78
- });
79
- it('should handle errors gracefully', async () => {
80
- const ack = vi.fn(() => { throw new Error('fail'); });
81
- const close = vi.fn();
82
- const channel = { ack, close };
83
- const mq = new amqp_lib_1.default();
84
- await expect(mq.ack(channel, 'msg')).resolves.toBeUndefined();
85
- });
86
- });
87
- describe('nack', () => {
88
- it('should call channel.nack and close', async () => {
89
- const nack = vi.fn();
90
- const close = vi.fn();
91
- const channel = { nack, close };
92
- const mq = new amqp_lib_1.default();
93
- await mq.nack(channel, 'msg', true);
94
- expect(nack).toHaveBeenCalledWith('msg', false, true);
95
- expect(close).toHaveBeenCalled();
96
- });
97
- it('should handle errors gracefully', async () => {
98
- const nack = vi.fn(() => { throw new Error('fail'); });
99
- const close = vi.fn();
100
- const channel = { nack, close };
101
- const mq = new amqp_lib_1.default();
102
- await expect(mq.nack(channel, 'msg')).resolves.toBeUndefined();
268
+ await queue.on('queueName', {
269
+ getSimultaneity: vitest_1.vi.fn().mockReturnValue(1),
270
+ handle: vitest_1.vi.fn(),
271
+ });
272
+ (0, vitest_1.expect)(logger.info).toHaveBeenCalledWith({
273
+ message: ' Error to consume messages: [queueName] on vHost undefined',
274
+ error: vitest_1.expect.any(Error),
275
+ });
276
+ });
277
+ });
278
+ (0, vitest_1.describe)('publish', () => {
279
+ (0, vitest_1.it)('should publish with default expiration when it is invalid', async () => {
280
+ const channel = createChannel();
281
+ const connection = createConnection(channel);
282
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
283
+ const event = { name: 'domain.event', payload: { id: '1' } };
284
+ queue['connection'] = connection;
285
+ await queue.publish('exchange', event, {
286
+ expiration: 'invalid',
287
+ headers: { source: 'spec' },
288
+ });
289
+ (0, vitest_1.expect)(channel.publish).toHaveBeenCalledWith('exchange', 'domain.event', Buffer.from(JSON.stringify(event)), {
290
+ persistent: true,
291
+ expiration: FOURTEEN_DAYS_IN_MS,
292
+ headers: { source: 'spec' },
293
+ });
294
+ (0, vitest_1.expect)(channel.close).toHaveBeenCalledTimes(1);
295
+ });
296
+ (0, vitest_1.it)('should keep null expiration when it is explicitly valid', async () => {
297
+ const channel = createChannel();
298
+ const connection = createConnection(channel);
299
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
300
+ const event = { name: 'domain.event' };
301
+ queue['connection'] = connection;
302
+ await queue.publish('exchange', event, {
303
+ expiration: null,
304
+ });
305
+ (0, vitest_1.expect)(channel.publish.mock.calls[0][3]).toEqual({
306
+ persistent: true,
307
+ expiration: null,
308
+ });
103
309
  });
310
+ (0, vitest_1.it)('should store messages in memory when publish times out', async () => {
311
+ vitest_1.vi.useFakeTimers();
312
+ const logger = createLogger();
313
+ const queue = new amqp_lib_1.default({ timeoutSeconds: 1 }, 'amqp://broker', logger);
314
+ const event = { name: 'domain.event' };
315
+ queue['publishToServer'] = vitest_1.vi.fn(() => new Promise(resolve => resolve(undefined)));
316
+ queue['publishToServer'].mockImplementation(() => new Promise(() => undefined));
317
+ const publishPromise = queue.publish('exchange', event);
318
+ await vitest_1.vi.advanceTimersByTimeAsync(1000);
319
+ await publishPromise;
320
+ (0, vitest_1.expect)(logger.info).toHaveBeenCalledWith({
321
+ message: '⛔ Error to publish messages to: domain.event.',
322
+ error: vitest_1.expect.any(infra_error_1.default),
323
+ });
324
+ (0, vitest_1.expect)(queue['messagesInMemory']).toEqual([
325
+ {
326
+ exchangeName: 'exchange',
327
+ domainEvent: event,
328
+ configs: {
329
+ expiration: FOURTEEN_DAYS_IN_MS,
330
+ },
331
+ },
332
+ ]);
333
+ });
334
+ (0, vitest_1.it)('should not store messages when storeMessageOnError is false', async () => {
335
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
336
+ queue['publishToServer'] = vitest_1.vi.fn().mockRejectedValue(new Error('fail'));
337
+ await queue.publish('exchange', { name: 'domain.event' }, {
338
+ storeMessageOnError: false,
339
+ });
340
+ (0, vitest_1.expect)(queue['messagesInMemory']).toHaveLength(0);
341
+ });
342
+ (0, vitest_1.it)('should not exceed the configured in-memory error buffer', async () => {
343
+ const queue = new amqp_lib_1.default({ maxStoredMessagesOnError: 1 }, 'amqp://broker', createLogger());
344
+ queue['messagesInMemory'] = [
345
+ {
346
+ exchangeName: 'existing',
347
+ domainEvent: { name: 'existing.event' },
348
+ configs: {},
349
+ },
350
+ ];
351
+ queue['publishToServer'] = vitest_1.vi.fn().mockRejectedValue(new Error('fail'));
352
+ await queue.publish('exchange', { name: 'domain.event' });
353
+ (0, vitest_1.expect)(queue['messagesInMemory']).toHaveLength(1);
354
+ });
355
+ });
356
+ (0, vitest_1.describe)('createConsumers', () => {
357
+ (0, vitest_1.it)('should create exchanges, queues and bindings', async () => {
358
+ const channel = createChannel();
359
+ const connection = createConnection(channel);
360
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
361
+ queue['connection'] = connection;
362
+ queue['vHost'] = 'jobs';
363
+ await queue.createConsumers('queueName', {
364
+ exchangeName: 'exchange',
365
+ exchangeType: 'direct',
366
+ queueType: 'quorum',
367
+ dlq: {
368
+ TTLms: 5000,
369
+ exchangeName: 'exchange.dlq',
370
+ },
371
+ });
372
+ (0, vitest_1.expect)(channel.assertExchange).toHaveBeenNthCalledWith(1, 'exchange', 'direct', { durable: true });
373
+ (0, vitest_1.expect)(channel.assertQueue).toHaveBeenNthCalledWith(1, 'queueName', {
374
+ arguments: {
375
+ 'x-dead-letter-exchange': 'exchange.dlq.DLX',
376
+ 'x-dead-letter-routing-key': 'queueName.DLK',
377
+ 'x-queue-type': 'quorum',
378
+ },
379
+ durable: true,
380
+ deadLetterExchange: 'exchange.dlq.DLX',
381
+ deadLetterRoutingKey: 'queueName.DLK',
382
+ });
383
+ (0, vitest_1.expect)(channel.bindQueue).toHaveBeenNthCalledWith(1, 'queueName', 'exchange', 'queueName');
384
+ (0, vitest_1.expect)(channel.assertExchange).toHaveBeenNthCalledWith(2, 'exchange.dlq.DLX', 'direct', { durable: true });
385
+ (0, vitest_1.expect)(channel.assertQueue).toHaveBeenNthCalledWith(2, 'queueName.DLQ', {
386
+ durable: true,
387
+ arguments: {
388
+ 'x-message-ttl': 5000,
389
+ 'x-queue-type': 'classic',
390
+ },
391
+ });
392
+ (0, vitest_1.expect)(channel.bindQueue).toHaveBeenNthCalledWith(2, 'queueName.DLQ', 'exchange.dlq.DLX', 'queueName.DLK');
393
+ (0, vitest_1.expect)(channel.close).toHaveBeenCalledTimes(1);
394
+ });
395
+ (0, vitest_1.it)('should log errors when queue creation fails', async () => {
396
+ const logger = createLogger();
397
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', logger);
398
+ queue['connection'] = {
399
+ createChannel: vitest_1.vi.fn().mockRejectedValue(new Error('fail')),
400
+ };
401
+ queue['vHost'] = 'jobs';
402
+ await queue.createConsumers('queueName', {
403
+ exchangeName: 'exchange',
404
+ exchangeType: 'direct',
405
+ });
406
+ (0, vitest_1.expect)(logger.info).toHaveBeenCalledWith({
407
+ message: '⛔ Error to create queue: queueName on vHost jobs',
408
+ error: vitest_1.expect.any(Error),
409
+ });
410
+ });
411
+ (0, vitest_1.it)('should apply default DLQ exchange, type and omit TTL when it is absent', async () => {
412
+ const channel = createChannel();
413
+ const connection = createConnection(channel);
414
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
415
+ queue['connection'] = connection;
416
+ await queue.createConsumers('queueName', {
417
+ exchangeName: 'exchange',
418
+ exchangeType: '',
419
+ });
420
+ (0, vitest_1.expect)(channel.assertExchange).toHaveBeenNthCalledWith(2, 'exchange.DLX', 'direct', { durable: true });
421
+ (0, vitest_1.expect)(channel.assertQueue).toHaveBeenNthCalledWith(2, 'queueName.DLQ', {
422
+ durable: true,
423
+ arguments: {
424
+ 'x-queue-type': 'classic',
425
+ },
426
+ });
427
+ (0, vitest_1.expect)(channel.bindQueue).toHaveBeenNthCalledWith(2, 'queueName.DLQ', 'exchange.DLX', 'queueName.DLK');
428
+ });
429
+ });
430
+ (0, vitest_1.it)('should add default TTL when creating queues', async () => {
431
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
432
+ const createConsumersSpy = vitest_1.vi
433
+ .spyOn(queue, 'createConsumers')
434
+ .mockResolvedValue(undefined);
435
+ await queue.createQueues('queueName', {
436
+ exchangeName: 'exchange',
437
+ exchangeType: 'direct',
438
+ });
439
+ (0, vitest_1.expect)(createConsumersSpy).toHaveBeenCalledWith('queueName', {
440
+ exchangeName: 'exchange',
441
+ exchangeType: 'direct',
442
+ dlq: {
443
+ TTLms: SEVEN_DAYS_IN_MS,
444
+ },
445
+ });
446
+ });
447
+ (0, vitest_1.it)('should preserve provided DLQ TTL when creating queues', async () => {
448
+ const queue = new amqp_lib_1.default(undefined, 'amqp://broker', createLogger());
449
+ const createConsumersSpy = vitest_1.vi
450
+ .spyOn(queue, 'createConsumers')
451
+ .mockResolvedValue(undefined);
452
+ await queue.createQueues('queueName', {
453
+ exchangeName: 'exchange',
454
+ exchangeType: 'fanout',
455
+ dlq: {
456
+ TTLms: 5000,
457
+ exchangeName: 'exchange.dlx',
458
+ },
459
+ });
460
+ (0, vitest_1.expect)(createConsumersSpy).toHaveBeenCalledWith('queueName', {
461
+ exchangeName: 'exchange',
462
+ exchangeType: 'fanout',
463
+ dlq: {
464
+ TTLms: 5000,
465
+ exchangeName: 'exchange.dlx',
466
+ },
467
+ });
468
+ });
469
+ (0, vitest_1.it)('should reuse the singleton for the same vhost and recreate it for another one', async () => {
470
+ const sameVhostInstance = new amqp_lib_1.default();
471
+ sameVhostInstance['vHost'] = 'jobs';
472
+ amqp_lib_1.default.instance = sameVhostInstance;
473
+ await (0, vitest_1.expect)(amqp_lib_1.default.getInstance('jobs')).resolves.toBe(sameVhostInstance);
474
+ const nextInstance = new amqp_lib_1.default();
475
+ const connectSpy = vitest_1.vi
476
+ .spyOn(amqp_lib_1.default.prototype, 'connect')
477
+ .mockResolvedValue(nextInstance);
478
+ amqp_lib_1.default.instance = sameVhostInstance;
479
+ await (0, vitest_1.expect)(amqp_lib_1.default.getInstance('other')).resolves.toBe(nextInstance);
480
+ (0, vitest_1.expect)(connectSpy).toHaveBeenCalledWith('other');
104
481
  });
105
482
  });
@@ -1,4 +1,4 @@
1
- import amqp from "amqplib";
1
+ import amqp from 'amqplib';
2
2
  /**
3
3
  * Resultado do polling via `channel.get(...)`.
4
4
  *
@@ -25,9 +25,6 @@ export declare class RabbitMQScaledJobRunner {
25
25
  constructor(amqpQueue: AmqpQueue, queueName: string, handler: Handler, options?: IRunnerOptions, logger?: Logger);
26
26
  /**
27
27
  * Normaliza opções com defaults seguros.
28
- *
29
- * @remarks Atualmente `manualAck` é normalizado com `|| true`, então
30
- * o valor `false` pode ser convertido para `true`.
31
28
  */
32
29
  private setOptions;
33
30
  /**
@@ -27,14 +27,11 @@ class RabbitMQScaledJobRunner {
27
27
  }
28
28
  /**
29
29
  * Normaliza opções com defaults seguros.
30
- *
31
- * @remarks Atualmente `manualAck` é normalizado com `|| true`, então
32
- * o valor `false` pode ser convertido para `true`.
33
30
  */
34
31
  setOptions(options) {
35
32
  this.options = {
36
33
  errorStrategy: options?.errorStrategy || 'nack-dlq',
37
- manualAck: options?.manualAck || true,
34
+ manualAck: options?.manualAck ?? true,
38
35
  };
39
36
  }
40
37
  /**
@@ -85,9 +82,11 @@ class RabbitMQScaledJobRunner {
85
82
  async run() {
86
83
  let getResult = null;
87
84
  let consumedMessages = 0;
88
- const timeoutPerMessageSeconds = this.handler.getTimeoutPerMessageSeconds();
89
- const messagesToGet = this.handler.getSimultaneity();
85
+ let timeoutPerMessageSeconds = undefined;
86
+ let messagesToGet = 1;
90
87
  try {
88
+ timeoutPerMessageSeconds = this.handler.getTimeoutPerMessageSeconds();
89
+ messagesToGet = this.handler.getSimultaneity();
91
90
  while (consumedMessages < messagesToGet) {
92
91
  try {
93
92
  getResult = await this.amqpQueue.get(this.queueName, {