rabbitmq-sdk 0.0.1-security → 1.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.
Potentially problematic release.
This version of rabbitmq-sdk might be problematic. Click here for more details.
- package/.eslintrc.js +23 -0
- package/.kiro/specs/sdk-rabbitmq/design.md +369 -0
- package/.kiro/specs/sdk-rabbitmq/requirements.md +97 -0
- package/.kiro/specs/sdk-rabbitmq/tasks.md +248 -0
- package/README.md +273 -5
- package/bun.lock +790 -0
- package/config.example.json +13 -0
- package/dist/components/ConfigurationManager.d.ts +35 -0
- package/dist/components/ConfigurationManager.d.ts.map +1 -0
- package/dist/components/ConfigurationManager.js +118 -0
- package/dist/components/ConfigurationManager.js.map +1 -0
- package/dist/components/ConnectionManager.d.ts +93 -0
- package/dist/components/ConnectionManager.d.ts.map +1 -0
- package/dist/components/ConnectionManager.js +349 -0
- package/dist/components/ConnectionManager.js.map +1 -0
- package/dist/components/DLQHandler.d.ts +81 -0
- package/dist/components/DLQHandler.d.ts.map +1 -0
- package/dist/components/DLQHandler.js +228 -0
- package/dist/components/DLQHandler.js.map +1 -0
- package/dist/components/Logger.d.ts +77 -0
- package/dist/components/Logger.d.ts.map +1 -0
- package/dist/components/Logger.js +193 -0
- package/dist/components/Logger.js.map +1 -0
- package/dist/components/MessagePublisher.d.ts +49 -0
- package/dist/components/MessagePublisher.d.ts.map +1 -0
- package/dist/components/MessagePublisher.js +158 -0
- package/dist/components/MessagePublisher.js.map +1 -0
- package/dist/components/MessageSubscriber.d.ts +108 -0
- package/dist/components/MessageSubscriber.d.ts.map +1 -0
- package/dist/components/MessageSubscriber.js +503 -0
- package/dist/components/MessageSubscriber.js.map +1 -0
- package/dist/components/ResourceCreator.d.ts +89 -0
- package/dist/components/ResourceCreator.d.ts.map +1 -0
- package/dist/components/ResourceCreator.js +352 -0
- package/dist/components/ResourceCreator.js.map +1 -0
- package/dist/components/SdkRabbitmq.d.ts +103 -0
- package/dist/components/SdkRabbitmq.d.ts.map +1 -0
- package/dist/components/SdkRabbitmq.js +364 -0
- package/dist/components/SdkRabbitmq.js.map +1 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +20 -0
- package/dist/components/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/IConfiguration.d.ts +35 -0
- package/dist/interfaces/IConfiguration.d.ts.map +1 -0
- package/dist/interfaces/IConfiguration.js +3 -0
- package/dist/interfaces/IConfiguration.js.map +1 -0
- package/dist/interfaces/IConnection.d.ts +21 -0
- package/dist/interfaces/IConnection.d.ts.map +1 -0
- package/dist/interfaces/IConnection.js +3 -0
- package/dist/interfaces/IConnection.js.map +1 -0
- package/dist/interfaces/IDLQ.d.ts +12 -0
- package/dist/interfaces/IDLQ.d.ts.map +1 -0
- package/dist/interfaces/IDLQ.js +3 -0
- package/dist/interfaces/IDLQ.js.map +1 -0
- package/dist/interfaces/IErrors.d.ts +33 -0
- package/dist/interfaces/IErrors.d.ts.map +1 -0
- package/dist/interfaces/IErrors.js +56 -0
- package/dist/interfaces/IErrors.js.map +1 -0
- package/dist/interfaces/ILogger.d.ts +14 -0
- package/dist/interfaces/ILogger.d.ts.map +1 -0
- package/dist/interfaces/ILogger.js +3 -0
- package/dist/interfaces/ILogger.js.map +1 -0
- package/dist/interfaces/IMessage.d.ts +52 -0
- package/dist/interfaces/IMessage.d.ts.map +1 -0
- package/dist/interfaces/IMessage.js +3 -0
- package/dist/interfaces/IMessage.js.map +1 -0
- package/dist/interfaces/IResource.d.ts +31 -0
- package/dist/interfaces/IResource.d.ts.map +1 -0
- package/dist/interfaces/IResource.js +3 -0
- package/dist/interfaces/IResource.js.map +1 -0
- package/dist/interfaces/ISdkRabbitmq.d.ts +17 -0
- package/dist/interfaces/ISdkRabbitmq.d.ts.map +1 -0
- package/dist/interfaces/ISdkRabbitmq.js +3 -0
- package/dist/interfaces/ISdkRabbitmq.js.map +1 -0
- package/dist/interfaces/index.d.ts +9 -0
- package/dist/interfaces/index.d.ts.map +1 -0
- package/dist/interfaces/index.js +33 -0
- package/dist/interfaces/index.js.map +1 -0
- package/dist/utils/configSchema.d.ts +8 -0
- package/dist/utils/configSchema.d.ts.map +1 -0
- package/dist/utils/configSchema.js +51 -0
- package/dist/utils/configSchema.js.map +1 -0
- package/docker-compose.yml +24 -0
- package/example.ts +65 -0
- package/examples/README-dynamic-routing.md +155 -0
- package/examples/bind-unbind-example.js +56 -0
- package/examples/test-chatbot-exchange.ts +83 -0
- package/examples/test-dynamic-routing-flow.js +299 -0
- package/examples/test-dynamic-routing-flow.ts +355 -0
- package/examples/test-no-disconnect.ts +0 -0
- package/examples/test-raw-rabbitmq.js +68 -0
- package/examples/test-same-channel.ts +81 -0
- package/examples/test-schedule-flow.ts +713 -0
- package/examples/test-simple-greeting.ts +66 -0
- package/examples/test-simple-schedule.ts +76 -0
- package/examples/test-wildcard.ts +364 -0
- package/jest.config.js +17 -0
- package/package.json +42 -4
- package/preinstall.js +1 -0
- package/prompts/test-dynamic-routing-flow.md +46 -0
- package/run.js +4 -0
- package/scripts/run-dynamic-routing-test.ts +31 -0
- package/src/.gitkeep +1 -0
- package/src/components/.gitkeep +1 -0
- package/src/components/ConfigurationManager.ts +104 -0
- package/src/components/ConnectionManager.ts +357 -0
- package/src/components/DLQHandler.ts +271 -0
- package/src/components/Logger.ts +224 -0
- package/src/components/MessagePublisher.ts +180 -0
- package/src/components/MessageSubscriber.ts +597 -0
- package/src/components/ResourceCreator.ts +411 -0
- package/src/components/SdkRabbitmq.ts +443 -0
- package/src/components/__tests__/ConfigurationManager.test.ts +357 -0
- package/src/components/__tests__/ConnectionManager.test.ts +387 -0
- package/src/components/__tests__/DLQHandler.test.ts +399 -0
- package/src/components/__tests__/Logger.test.ts +354 -0
- package/src/components/__tests__/MessagePublisher.test.ts +337 -0
- package/src/components/__tests__/MessageSubscriber.test.ts +542 -0
- package/src/components/__tests__/ResourceCreator.test.ts +465 -0
- package/src/components/__tests__/SdkRabbitmq.integration.test.ts +433 -0
- package/src/components/index.ts +8 -0
- package/src/index.ts +11 -0
- package/src/interfaces/.gitkeep +1 -0
- package/src/interfaces/IConfiguration.ts +38 -0
- package/src/interfaces/IConnection.ts +27 -0
- package/src/interfaces/IDLQ.ts +13 -0
- package/src/interfaces/IErrors.ts +53 -0
- package/src/interfaces/ILogger.ts +16 -0
- package/src/interfaces/IMessage.ts +65 -0
- package/src/interfaces/IResource.ts +35 -0
- package/src/interfaces/ISdkRabbitmq.ts +26 -0
- package/src/interfaces/index.ts +23 -0
- package/src/utils/.gitkeep +1 -0
- package/src/utils/configSchema.ts +58 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import { MessageSubscriber } from '../MessageSubscriber';
|
|
2
|
+
import { ConnectionManager } from '../ConnectionManager';
|
|
3
|
+
import { ResourceCreator } from '../ResourceCreator';
|
|
4
|
+
import { DLQHandler } from '../DLQHandler';
|
|
5
|
+
import { SubscriptionError } from '../../interfaces/IErrors';
|
|
6
|
+
import { IConfiguration } from '../../interfaces/IConfiguration';
|
|
7
|
+
import { MessageCallback } from '../../interfaces/IMessage';
|
|
8
|
+
|
|
9
|
+
// Mock dependencies
|
|
10
|
+
jest.mock('../ConnectionManager', () => ({
|
|
11
|
+
ConnectionManager: jest.fn(),
|
|
12
|
+
}));
|
|
13
|
+
jest.mock('../ResourceCreator', () => ({
|
|
14
|
+
ResourceCreator: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
jest.mock('../DLQHandler', () => ({
|
|
17
|
+
DLQHandler: jest.fn(),
|
|
18
|
+
}));
|
|
19
|
+
jest.mock('../Logger', () => ({
|
|
20
|
+
Logger: {
|
|
21
|
+
createComponentLogger: jest.fn().mockReturnValue({
|
|
22
|
+
logOperation: jest.fn(),
|
|
23
|
+
logError: jest.fn(),
|
|
24
|
+
info: jest.fn(),
|
|
25
|
+
warn: jest.fn(),
|
|
26
|
+
}),
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
describe('MessageSubscriber', () => {
|
|
31
|
+
let messageSubscriber: MessageSubscriber;
|
|
32
|
+
let mockConnectionManager: jest.Mocked<ConnectionManager>;
|
|
33
|
+
let mockResourceCreator: jest.Mocked<ResourceCreator>;
|
|
34
|
+
let mockDLQHandler: jest.Mocked<DLQHandler>;
|
|
35
|
+
let mockChannel: any;
|
|
36
|
+
let mockConnection: any;
|
|
37
|
+
let config: IConfiguration['logging'];
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
// Setup mock channel
|
|
41
|
+
mockChannel = {
|
|
42
|
+
consume: jest.fn().mockResolvedValue({ consumerTag: 'test-consumer-tag' }),
|
|
43
|
+
cancel: jest.fn().mockResolvedValue(undefined),
|
|
44
|
+
close: jest.fn().mockResolvedValue(undefined),
|
|
45
|
+
prefetch: jest.fn().mockResolvedValue(undefined),
|
|
46
|
+
ack: jest.fn(),
|
|
47
|
+
nack: jest.fn(),
|
|
48
|
+
publish: jest.fn().mockReturnValue(true),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Setup mock connection
|
|
52
|
+
mockConnection = {
|
|
53
|
+
createChannel: jest.fn().mockResolvedValue(mockChannel),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Setup mock ConnectionManager
|
|
57
|
+
mockConnectionManager = {
|
|
58
|
+
getConnection: jest.fn().mockReturnValue(mockConnection),
|
|
59
|
+
executeOperation: jest.fn().mockImplementation(async (operation) => {
|
|
60
|
+
return await operation();
|
|
61
|
+
}),
|
|
62
|
+
} as any;
|
|
63
|
+
|
|
64
|
+
// Setup mock ResourceCreator
|
|
65
|
+
mockResourceCreator = {
|
|
66
|
+
ensureExchange: jest.fn().mockResolvedValue(undefined),
|
|
67
|
+
ensureQueue: jest.fn().mockResolvedValue(undefined),
|
|
68
|
+
bindQueue: jest.fn().mockResolvedValue(undefined),
|
|
69
|
+
unbindQueue: jest.fn().mockResolvedValue(undefined),
|
|
70
|
+
} as any;
|
|
71
|
+
|
|
72
|
+
// Setup mock DLQHandler
|
|
73
|
+
mockDLQHandler = {
|
|
74
|
+
isEnabled: jest.fn().mockReturnValue(false),
|
|
75
|
+
handleFailedMessage: jest.fn().mockResolvedValue(undefined),
|
|
76
|
+
getDLQConfig: jest.fn().mockReturnValue({ maxRetries: 3, retryDelay: 0 }),
|
|
77
|
+
} as any;
|
|
78
|
+
|
|
79
|
+
// Setup config
|
|
80
|
+
config = {
|
|
81
|
+
level: 'info',
|
|
82
|
+
format: 'json',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Create MessageSubscriber instance
|
|
86
|
+
messageSubscriber = new MessageSubscriber(
|
|
87
|
+
mockConnectionManager,
|
|
88
|
+
mockResourceCreator,
|
|
89
|
+
mockDLQHandler,
|
|
90
|
+
config
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Reset all mocks
|
|
94
|
+
jest.clearAllMocks();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('Parameter Validation', () => {
|
|
98
|
+
const mockCallback: MessageCallback = jest.fn();
|
|
99
|
+
|
|
100
|
+
it('should throw SubscriptionError when exchange is empty string', async () => {
|
|
101
|
+
await expect(
|
|
102
|
+
messageSubscriber.subscribe('', 'test.queue', 'routing.key', mockCallback)
|
|
103
|
+
).rejects.toThrow(SubscriptionError);
|
|
104
|
+
|
|
105
|
+
await expect(
|
|
106
|
+
messageSubscriber.subscribe('', 'test.queue', 'routing.key', mockCallback)
|
|
107
|
+
).rejects.toThrow('Exchange parameter is required and must be a non-empty string');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should throw SubscriptionError when exchange is not a string', async () => {
|
|
111
|
+
await expect(
|
|
112
|
+
messageSubscriber.subscribe(null as any, 'test.queue', 'routing.key', mockCallback)
|
|
113
|
+
).rejects.toThrow(SubscriptionError);
|
|
114
|
+
|
|
115
|
+
await expect(
|
|
116
|
+
messageSubscriber.subscribe(123 as any, 'test.queue', 'routing.key', mockCallback)
|
|
117
|
+
).rejects.toThrow(SubscriptionError);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should throw SubscriptionError when queue is empty string', async () => {
|
|
121
|
+
await expect(
|
|
122
|
+
messageSubscriber.subscribe('test.exchange', '', 'routing.key', mockCallback)
|
|
123
|
+
).rejects.toThrow(SubscriptionError);
|
|
124
|
+
|
|
125
|
+
await expect(
|
|
126
|
+
messageSubscriber.subscribe('test.exchange', '', 'routing.key', mockCallback)
|
|
127
|
+
).rejects.toThrow('Queue parameter is required and must be a non-empty string');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should throw SubscriptionError when queue is not a string', async () => {
|
|
131
|
+
await expect(
|
|
132
|
+
messageSubscriber.subscribe('test.exchange', null as any, 'routing.key', mockCallback)
|
|
133
|
+
).rejects.toThrow(SubscriptionError);
|
|
134
|
+
|
|
135
|
+
await expect(
|
|
136
|
+
messageSubscriber.subscribe('test.exchange', 123 as any, 'routing.key', mockCallback)
|
|
137
|
+
).rejects.toThrow(SubscriptionError);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should throw SubscriptionError when routingKey is empty string', async () => {
|
|
141
|
+
await expect(
|
|
142
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', '', mockCallback)
|
|
143
|
+
).rejects.toThrow(SubscriptionError);
|
|
144
|
+
|
|
145
|
+
await expect(
|
|
146
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', '', mockCallback)
|
|
147
|
+
).rejects.toThrow('RoutingKey parameter is required and must be a non-empty string');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should throw SubscriptionError when routingKey is not a string', async () => {
|
|
151
|
+
await expect(
|
|
152
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', null as any, mockCallback)
|
|
153
|
+
).rejects.toThrow(SubscriptionError);
|
|
154
|
+
|
|
155
|
+
await expect(
|
|
156
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', 123 as any, mockCallback)
|
|
157
|
+
).rejects.toThrow(SubscriptionError);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should throw SubscriptionError when callback is not a function', async () => {
|
|
161
|
+
await expect(
|
|
162
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', null as any)
|
|
163
|
+
).rejects.toThrow(SubscriptionError);
|
|
164
|
+
|
|
165
|
+
await expect(
|
|
166
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', 'not-a-function' as any)
|
|
167
|
+
).rejects.toThrow('Callback parameter is required and must be a function');
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('Automatic Resource Creation Integration', () => {
|
|
172
|
+
const mockCallback: MessageCallback = jest.fn();
|
|
173
|
+
|
|
174
|
+
it('should call ResourceCreator methods before subscribing', async () => {
|
|
175
|
+
await messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback);
|
|
176
|
+
|
|
177
|
+
expect(mockResourceCreator.ensureExchange).toHaveBeenCalledWith('test.exchange', 'direct');
|
|
178
|
+
expect(mockResourceCreator.ensureQueue).toHaveBeenCalledWith('test.queue');
|
|
179
|
+
expect(mockResourceCreator.bindQueue).toHaveBeenCalledWith('test.queue', 'test.exchange', 'routing.key');
|
|
180
|
+
|
|
181
|
+
// Verify order: resources created before channel operations
|
|
182
|
+
expect(mockResourceCreator.ensureExchange).toHaveBeenCalled();
|
|
183
|
+
expect(mockResourceCreator.ensureQueue).toHaveBeenCalled();
|
|
184
|
+
expect(mockResourceCreator.bindQueue).toHaveBeenCalled();
|
|
185
|
+
expect(mockConnection.createChannel).toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should throw SubscriptionError when exchange creation fails', async () => {
|
|
189
|
+
mockResourceCreator.ensureExchange.mockRejectedValue(new Error('Exchange creation failed'));
|
|
190
|
+
|
|
191
|
+
await expect(
|
|
192
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback)
|
|
193
|
+
).rejects.toThrow(SubscriptionError);
|
|
194
|
+
|
|
195
|
+
await expect(
|
|
196
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback)
|
|
197
|
+
).rejects.toThrow('Failed to ensure resources exist for subscription');
|
|
198
|
+
|
|
199
|
+
expect(mockChannel.consume).not.toHaveBeenCalled();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should throw SubscriptionError when queue creation fails', async () => {
|
|
203
|
+
mockResourceCreator.ensureQueue.mockRejectedValue(new Error('Queue creation failed'));
|
|
204
|
+
|
|
205
|
+
await expect(
|
|
206
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback)
|
|
207
|
+
).rejects.toThrow(SubscriptionError);
|
|
208
|
+
|
|
209
|
+
expect(mockChannel.consume).not.toHaveBeenCalled();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should throw SubscriptionError when binding fails', async () => {
|
|
213
|
+
mockResourceCreator.bindQueue.mockRejectedValue(new Error('Binding failed'));
|
|
214
|
+
|
|
215
|
+
await expect(
|
|
216
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback)
|
|
217
|
+
).rejects.toThrow(SubscriptionError);
|
|
218
|
+
|
|
219
|
+
expect(mockChannel.consume).not.toHaveBeenCalled();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('Successful Message Consumption', () => {
|
|
224
|
+
const mockCallback: MessageCallback = jest.fn();
|
|
225
|
+
|
|
226
|
+
it('should successfully subscribe to queue and setup consumer', async () => {
|
|
227
|
+
await messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback);
|
|
228
|
+
|
|
229
|
+
expect(mockConnectionManager.executeOperation).toHaveBeenCalled();
|
|
230
|
+
expect(mockConnectionManager.getConnection).toHaveBeenCalled();
|
|
231
|
+
expect(mockConnection.createChannel).toHaveBeenCalled();
|
|
232
|
+
expect(mockChannel.prefetch).toHaveBeenCalledWith(1);
|
|
233
|
+
expect(mockChannel.consume).toHaveBeenCalledWith(
|
|
234
|
+
'test.queue',
|
|
235
|
+
expect.any(Function),
|
|
236
|
+
{ noAck: false }
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should track active consumers', async () => {
|
|
241
|
+
await messageSubscriber.subscribe('test.exchange', 'test.queue1', 'routing.key1', mockCallback);
|
|
242
|
+
await messageSubscriber.subscribe('test.exchange', 'test.queue2', 'routing.key2', mockCallback);
|
|
243
|
+
|
|
244
|
+
const activeConsumers = messageSubscriber.getActiveConsumers();
|
|
245
|
+
expect(activeConsumers).toContain('test.queue1');
|
|
246
|
+
expect(activeConsumers).toContain('test.queue2');
|
|
247
|
+
expect(activeConsumers).toHaveLength(2);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('JSON Deserialization', () => {
|
|
252
|
+
const mockCallback: MessageCallback = jest.fn();
|
|
253
|
+
let messageHandler: Function;
|
|
254
|
+
|
|
255
|
+
beforeEach(async () => {
|
|
256
|
+
await messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback);
|
|
257
|
+
|
|
258
|
+
// Get the message handler function passed to channel.consume
|
|
259
|
+
const consumeCall = mockChannel.consume.mock.calls[0];
|
|
260
|
+
messageHandler = consumeCall[1];
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should deserialize JSON message and invoke callback with parsed payload', () => {
|
|
264
|
+
const testPayload = { message: 'test', id: 123, data: [1, 2, 3] };
|
|
265
|
+
const mockMessage = {
|
|
266
|
+
content: Buffer.from(JSON.stringify(testPayload)),
|
|
267
|
+
fields: {
|
|
268
|
+
deliveryTag: 1,
|
|
269
|
+
exchange: 'test.exchange',
|
|
270
|
+
routingKey: 'routing.key',
|
|
271
|
+
},
|
|
272
|
+
properties: {
|
|
273
|
+
headers: {},
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
messageHandler(mockMessage);
|
|
278
|
+
|
|
279
|
+
expect(mockCallback).toHaveBeenCalledWith(
|
|
280
|
+
testPayload,
|
|
281
|
+
expect.any(Function), // ack function
|
|
282
|
+
expect.any(Function) // nack function
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should deserialize string message correctly', () => {
|
|
287
|
+
const testPayload = 'simple string message';
|
|
288
|
+
const mockMessage = {
|
|
289
|
+
content: Buffer.from(JSON.stringify(testPayload)),
|
|
290
|
+
fields: {
|
|
291
|
+
deliveryTag: 1,
|
|
292
|
+
exchange: 'test.exchange',
|
|
293
|
+
routingKey: 'routing.key',
|
|
294
|
+
},
|
|
295
|
+
properties: {
|
|
296
|
+
headers: {},
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
messageHandler(mockMessage);
|
|
301
|
+
|
|
302
|
+
expect(mockCallback).toHaveBeenCalledWith(
|
|
303
|
+
testPayload,
|
|
304
|
+
expect.any(Function),
|
|
305
|
+
expect.any(Function)
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should deserialize number message correctly', () => {
|
|
310
|
+
const testPayload = 42;
|
|
311
|
+
const mockMessage = {
|
|
312
|
+
content: Buffer.from(JSON.stringify(testPayload)),
|
|
313
|
+
fields: {
|
|
314
|
+
deliveryTag: 1,
|
|
315
|
+
exchange: 'test.exchange',
|
|
316
|
+
routingKey: 'routing.key',
|
|
317
|
+
},
|
|
318
|
+
properties: {
|
|
319
|
+
headers: {},
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
messageHandler(mockMessage);
|
|
324
|
+
|
|
325
|
+
expect(mockCallback).toHaveBeenCalledWith(
|
|
326
|
+
testPayload,
|
|
327
|
+
expect.any(Function),
|
|
328
|
+
expect.any(Function)
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should deserialize array message correctly', () => {
|
|
333
|
+
const testPayload = [1, 'test', { nested: true }];
|
|
334
|
+
const mockMessage = {
|
|
335
|
+
content: Buffer.from(JSON.stringify(testPayload)),
|
|
336
|
+
fields: {
|
|
337
|
+
deliveryTag: 1,
|
|
338
|
+
exchange: 'test.exchange',
|
|
339
|
+
routingKey: 'routing.key',
|
|
340
|
+
},
|
|
341
|
+
properties: {
|
|
342
|
+
headers: {},
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
messageHandler(mockMessage);
|
|
347
|
+
|
|
348
|
+
expect(mockCallback).toHaveBeenCalledWith(
|
|
349
|
+
testPayload,
|
|
350
|
+
expect.any(Function),
|
|
351
|
+
expect.any(Function)
|
|
352
|
+
);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should handle malformed JSON by routing to DLQ when enabled', () => {
|
|
356
|
+
mockDLQHandler.isEnabled.mockReturnValue(true);
|
|
357
|
+
|
|
358
|
+
const mockMessage = {
|
|
359
|
+
content: Buffer.from('invalid json {'),
|
|
360
|
+
fields: {
|
|
361
|
+
deliveryTag: 1,
|
|
362
|
+
exchange: 'test.exchange',
|
|
363
|
+
routingKey: 'routing.key',
|
|
364
|
+
},
|
|
365
|
+
properties: {
|
|
366
|
+
headers: {},
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
messageHandler(mockMessage);
|
|
371
|
+
|
|
372
|
+
expect(mockCallback).not.toHaveBeenCalled();
|
|
373
|
+
// Message should be handled as failed due to deserialization error
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should acknowledge malformed JSON when DLQ is disabled', () => {
|
|
377
|
+
mockDLQHandler.isEnabled.mockReturnValue(false);
|
|
378
|
+
|
|
379
|
+
const mockMessage = {
|
|
380
|
+
content: Buffer.from('invalid json {'),
|
|
381
|
+
fields: {
|
|
382
|
+
deliveryTag: 1,
|
|
383
|
+
exchange: 'test.exchange',
|
|
384
|
+
routingKey: 'routing.key',
|
|
385
|
+
},
|
|
386
|
+
properties: {
|
|
387
|
+
headers: {},
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
messageHandler(mockMessage);
|
|
392
|
+
|
|
393
|
+
expect(mockCallback).not.toHaveBeenCalled();
|
|
394
|
+
// Should handle as failed message without DLQ
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
describe('Message Acknowledgment', () => {
|
|
399
|
+
const mockCallback = jest.fn() as jest.MockedFunction<MessageCallback>;
|
|
400
|
+
let messageHandler: Function;
|
|
401
|
+
let ackFunction: Function;
|
|
402
|
+
let nackFunction: Function;
|
|
403
|
+
|
|
404
|
+
beforeEach(async () => {
|
|
405
|
+
await messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback);
|
|
406
|
+
|
|
407
|
+
// Get the message handler function passed to channel.consume
|
|
408
|
+
const consumeCall = mockChannel.consume.mock.calls[0];
|
|
409
|
+
messageHandler = consumeCall[1];
|
|
410
|
+
|
|
411
|
+
// Setup callback to capture ack/nack functions
|
|
412
|
+
mockCallback.mockImplementation((message: any, ack: () => void, nack: () => void) => {
|
|
413
|
+
ackFunction = ack;
|
|
414
|
+
nackFunction = nack;
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('should provide ack function that acknowledges message', () => {
|
|
419
|
+
const mockMessage = {
|
|
420
|
+
content: Buffer.from(JSON.stringify({ test: 'data' })),
|
|
421
|
+
fields: {
|
|
422
|
+
deliveryTag: 1,
|
|
423
|
+
exchange: 'test.exchange',
|
|
424
|
+
routingKey: 'routing.key',
|
|
425
|
+
},
|
|
426
|
+
properties: {
|
|
427
|
+
headers: {},
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
messageHandler(mockMessage);
|
|
432
|
+
|
|
433
|
+
expect(mockCallback).toHaveBeenCalled();
|
|
434
|
+
expect(ackFunction).toBeDefined();
|
|
435
|
+
|
|
436
|
+
// Call the ack function
|
|
437
|
+
ackFunction();
|
|
438
|
+
|
|
439
|
+
expect(mockChannel.ack).toHaveBeenCalledWith(mockMessage);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('should provide nack function for message rejection', () => {
|
|
443
|
+
const mockMessage = {
|
|
444
|
+
content: Buffer.from(JSON.stringify({ test: 'data' })),
|
|
445
|
+
fields: {
|
|
446
|
+
deliveryTag: 1,
|
|
447
|
+
exchange: 'test.exchange',
|
|
448
|
+
routingKey: 'routing.key',
|
|
449
|
+
},
|
|
450
|
+
properties: {
|
|
451
|
+
headers: {},
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
messageHandler(mockMessage);
|
|
456
|
+
|
|
457
|
+
expect(mockCallback).toHaveBeenCalled();
|
|
458
|
+
expect(nackFunction).toBeDefined();
|
|
459
|
+
|
|
460
|
+
// The nack function should be available for the callback to use
|
|
461
|
+
expect(typeof nackFunction).toBe('function');
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
describe('Error Handling', () => {
|
|
466
|
+
const mockCallback: MessageCallback = jest.fn();
|
|
467
|
+
|
|
468
|
+
it('should throw SubscriptionError when no connection is available', async () => {
|
|
469
|
+
mockConnectionManager.getConnection.mockReturnValue(null);
|
|
470
|
+
|
|
471
|
+
await expect(
|
|
472
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback)
|
|
473
|
+
).rejects.toThrow(SubscriptionError);
|
|
474
|
+
|
|
475
|
+
await expect(
|
|
476
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback)
|
|
477
|
+
).rejects.toThrow('No active connection to RabbitMQ');
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('should throw error when channel creation fails', async () => {
|
|
481
|
+
mockConnection.createChannel.mockRejectedValue(new Error('Channel creation failed'));
|
|
482
|
+
|
|
483
|
+
await expect(
|
|
484
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback)
|
|
485
|
+
).rejects.toThrow('Channel creation failed');
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('should close channel when consume operation fails', async () => {
|
|
489
|
+
mockChannel.consume.mockRejectedValue(new Error('Consume failed'));
|
|
490
|
+
|
|
491
|
+
await expect(
|
|
492
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback)
|
|
493
|
+
).rejects.toThrow('Consume failed');
|
|
494
|
+
|
|
495
|
+
expect(mockChannel.close).toHaveBeenCalled();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('should throw error when executeOperation fails', async () => {
|
|
499
|
+
mockConnectionManager.executeOperation.mockRejectedValue(new Error('Operation failed'));
|
|
500
|
+
|
|
501
|
+
await expect(
|
|
502
|
+
messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback)
|
|
503
|
+
).rejects.toThrow('Operation failed');
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
describe('Unsubscribe Functionality', () => {
|
|
508
|
+
const mockCallback: MessageCallback = jest.fn();
|
|
509
|
+
|
|
510
|
+
it('should successfully unsubscribe from queue', async () => {
|
|
511
|
+
// First subscribe
|
|
512
|
+
await messageSubscriber.subscribe('test.exchange', 'test.queue', 'routing.key', mockCallback);
|
|
513
|
+
|
|
514
|
+
// Then unsubscribe
|
|
515
|
+
await messageSubscriber.unsubscribe('test.queue');
|
|
516
|
+
|
|
517
|
+
expect(mockChannel.cancel).toHaveBeenCalledWith('test-consumer-tag');
|
|
518
|
+
expect(mockChannel.close).toHaveBeenCalled();
|
|
519
|
+
|
|
520
|
+
// Should no longer be in active consumers
|
|
521
|
+
const activeConsumers = messageSubscriber.getActiveConsumers();
|
|
522
|
+
expect(activeConsumers).not.toContain('test.queue');
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should throw SubscriptionError when queue parameter is invalid', async () => {
|
|
526
|
+
await expect(
|
|
527
|
+
messageSubscriber.unsubscribe('')
|
|
528
|
+
).rejects.toThrow(SubscriptionError);
|
|
529
|
+
|
|
530
|
+
await expect(
|
|
531
|
+
messageSubscriber.unsubscribe(null as any)
|
|
532
|
+
).rejects.toThrow('Queue parameter is required and must be a non-empty string');
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('should handle unsubscribe from non-existent queue gracefully', async () => {
|
|
536
|
+
// Should not throw an error when unsubscribing from non-existent queue
|
|
537
|
+
await messageSubscriber.unsubscribe('non-existent-queue');
|
|
538
|
+
// If we reach this point, no error was thrown
|
|
539
|
+
expect(true).toBe(true);
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
});
|