@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.
- package/dist/clean-arch/application/cache/cache.spec.d.ts +1 -0
- package/dist/clean-arch/application/cache/cache.spec.js +178 -0
- package/dist/clean-arch/application/consistency-event-dispatcher/consistency-event-dispatcher.d.ts +1 -1
- package/dist/clean-arch/application/logger/logger.spec.d.ts +1 -0
- package/dist/clean-arch/application/logger/logger.spec.js +204 -0
- package/dist/clean-arch/domain/entities/entity.spec.d.ts +1 -0
- package/dist/clean-arch/domain/entities/entity.spec.js +130 -0
- package/dist/clean-arch/domain/events/consistency-event/consistency-event.spec.d.ts +1 -0
- package/dist/clean-arch/domain/events/consistency-event/consistency-event.spec.js +91 -0
- package/dist/clean-arch/domain/value-objects/password.spec.d.ts +1 -0
- package/dist/clean-arch/domain/value-objects/password.spec.js +90 -0
- package/dist/clean-arch/infra/adapters/http-adapters.spec.d.ts +1 -0
- package/dist/clean-arch/infra/adapters/http-adapters.spec.js +318 -0
- package/dist/clean-arch/infra/authorizations/authorizer.spec.js +43 -0
- package/dist/clean-arch/infra/cache/cache-clients.spec.d.ts +1 -0
- package/dist/clean-arch/infra/cache/cache-clients.spec.js +161 -0
- package/dist/clean-arch/infra/cache/clients/node-cache.js +3 -2
- package/dist/clean-arch/infra/http/controller.spec.js +170 -51
- package/dist/clean-arch/infra/http/helpers.spec.d.ts +1 -0
- package/dist/clean-arch/infra/http/helpers.spec.js +136 -0
- package/dist/clean-arch/infra/http/net.spec.d.ts +1 -0
- package/dist/clean-arch/infra/http/net.spec.js +46 -0
- package/dist/clean-arch/infra/logger/logger-orbit.d.ts +2 -0
- package/dist/clean-arch/infra/logger/logger-orbit.spec.d.ts +1 -0
- package/dist/clean-arch/infra/logger/logger-orbit.spec.js +122 -0
- package/dist/clean-arch/infra/orbit-http-client/orbit-axios-client/orbit-axios-client.spec.d.ts +1 -0
- package/dist/clean-arch/infra/orbit-http-client/orbit-axios-client/orbit-axios-client.spec.js +143 -0
- package/dist/clean-arch/infra/orbit-notification-client/discord-client.spec.d.ts +1 -0
- package/dist/clean-arch/infra/orbit-notification-client/discord-client.spec.js +58 -0
- package/dist/clean-arch/infra/queue/queue-utils.spec.d.ts +1 -0
- package/dist/clean-arch/infra/queue/queue-utils.spec.js +170 -0
- package/dist/clean-arch/infra/queue/rabbitmq/amqp-lib.js +4 -4
- package/dist/clean-arch/infra/queue/rabbitmq/amqp-lib.spec.js +466 -89
- package/dist/clean-arch/infra/queue/rabbitmq/types.d.ts +1 -1
- package/dist/clean-arch/infra/scaledjob/amqp/runner.d.ts +0 -3
- package/dist/clean-arch/infra/scaledjob/amqp/runner.js +5 -6
- package/dist/clean-arch/infra/scaledjob/amqp/runner.spec.js +74 -84
- package/dist/clean-arch/infra/scaledjob/sqs/runner.spec.d.ts +1 -0
- package/dist/clean-arch/infra/scaledjob/sqs/runner.spec.js +150 -0
- package/dist/clean-arch/infra/tracing/start-tracing.d.ts +10 -0
- package/dist/clean-arch/infra/tracing/start-tracing.js +78 -0
- package/dist/clean-arch/infra/validations/zod/validation-zod.spec.js +23 -0
- package/dist/clean-arch/shared/pagination/get-take-and-skip.spec.d.ts +1 -0
- package/dist/clean-arch/shared/pagination/get-take-and-skip.spec.js +16 -0
- package/dist/coverage/block-navigation.d.ts +1 -0
- package/dist/coverage/block-navigation.js +72 -0
- package/dist/coverage/prettify.d.ts +0 -0
- package/dist/coverage/prettify.js +1009 -0
- package/dist/coverage/sorter.d.ts +1 -0
- package/dist/coverage/sorter.js +178 -0
- package/dist/frameworks/express/api-gateway/mapping-model.spec.js +58 -0
- package/dist/frameworks/express/authorizations/authorization-express.spec.js +9 -0
- package/dist/frameworks/express/middleware-express.spec.d.ts +1 -0
- package/dist/frameworks/express/middleware-express.spec.js +51 -0
- package/dist/frameworks/nest/nest.spec.d.ts +1 -0
- package/dist/frameworks/nest/nest.spec.js +122 -0
- package/dist/infra/http/api-gateway/mapping-model.spec.js +42 -0
- 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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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: '
|
|
23
|
-
domainEvent: '
|
|
24
|
-
configs:
|
|
115
|
+
exchangeName: 'exchange',
|
|
116
|
+
domainEvent: { name: 'domain.event' },
|
|
117
|
+
configs: { persistent: false },
|
|
25
118
|
},
|
|
26
119
|
];
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
});
|
|
@@ -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
|
|
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
|
-
|
|
89
|
-
|
|
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, {
|