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.

Files changed (140) hide show
  1. package/.eslintrc.js +23 -0
  2. package/.kiro/specs/sdk-rabbitmq/design.md +369 -0
  3. package/.kiro/specs/sdk-rabbitmq/requirements.md +97 -0
  4. package/.kiro/specs/sdk-rabbitmq/tasks.md +248 -0
  5. package/README.md +273 -5
  6. package/bun.lock +790 -0
  7. package/config.example.json +13 -0
  8. package/dist/components/ConfigurationManager.d.ts +35 -0
  9. package/dist/components/ConfigurationManager.d.ts.map +1 -0
  10. package/dist/components/ConfigurationManager.js +118 -0
  11. package/dist/components/ConfigurationManager.js.map +1 -0
  12. package/dist/components/ConnectionManager.d.ts +93 -0
  13. package/dist/components/ConnectionManager.d.ts.map +1 -0
  14. package/dist/components/ConnectionManager.js +349 -0
  15. package/dist/components/ConnectionManager.js.map +1 -0
  16. package/dist/components/DLQHandler.d.ts +81 -0
  17. package/dist/components/DLQHandler.d.ts.map +1 -0
  18. package/dist/components/DLQHandler.js +228 -0
  19. package/dist/components/DLQHandler.js.map +1 -0
  20. package/dist/components/Logger.d.ts +77 -0
  21. package/dist/components/Logger.d.ts.map +1 -0
  22. package/dist/components/Logger.js +193 -0
  23. package/dist/components/Logger.js.map +1 -0
  24. package/dist/components/MessagePublisher.d.ts +49 -0
  25. package/dist/components/MessagePublisher.d.ts.map +1 -0
  26. package/dist/components/MessagePublisher.js +158 -0
  27. package/dist/components/MessagePublisher.js.map +1 -0
  28. package/dist/components/MessageSubscriber.d.ts +108 -0
  29. package/dist/components/MessageSubscriber.d.ts.map +1 -0
  30. package/dist/components/MessageSubscriber.js +503 -0
  31. package/dist/components/MessageSubscriber.js.map +1 -0
  32. package/dist/components/ResourceCreator.d.ts +89 -0
  33. package/dist/components/ResourceCreator.d.ts.map +1 -0
  34. package/dist/components/ResourceCreator.js +352 -0
  35. package/dist/components/ResourceCreator.js.map +1 -0
  36. package/dist/components/SdkRabbitmq.d.ts +103 -0
  37. package/dist/components/SdkRabbitmq.d.ts.map +1 -0
  38. package/dist/components/SdkRabbitmq.js +364 -0
  39. package/dist/components/SdkRabbitmq.js.map +1 -0
  40. package/dist/components/index.d.ts +9 -0
  41. package/dist/components/index.d.ts.map +1 -0
  42. package/dist/components/index.js +20 -0
  43. package/dist/components/index.js.map +1 -0
  44. package/dist/index.d.ts +5 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +27 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/interfaces/IConfiguration.d.ts +35 -0
  49. package/dist/interfaces/IConfiguration.d.ts.map +1 -0
  50. package/dist/interfaces/IConfiguration.js +3 -0
  51. package/dist/interfaces/IConfiguration.js.map +1 -0
  52. package/dist/interfaces/IConnection.d.ts +21 -0
  53. package/dist/interfaces/IConnection.d.ts.map +1 -0
  54. package/dist/interfaces/IConnection.js +3 -0
  55. package/dist/interfaces/IConnection.js.map +1 -0
  56. package/dist/interfaces/IDLQ.d.ts +12 -0
  57. package/dist/interfaces/IDLQ.d.ts.map +1 -0
  58. package/dist/interfaces/IDLQ.js +3 -0
  59. package/dist/interfaces/IDLQ.js.map +1 -0
  60. package/dist/interfaces/IErrors.d.ts +33 -0
  61. package/dist/interfaces/IErrors.d.ts.map +1 -0
  62. package/dist/interfaces/IErrors.js +56 -0
  63. package/dist/interfaces/IErrors.js.map +1 -0
  64. package/dist/interfaces/ILogger.d.ts +14 -0
  65. package/dist/interfaces/ILogger.d.ts.map +1 -0
  66. package/dist/interfaces/ILogger.js +3 -0
  67. package/dist/interfaces/ILogger.js.map +1 -0
  68. package/dist/interfaces/IMessage.d.ts +52 -0
  69. package/dist/interfaces/IMessage.d.ts.map +1 -0
  70. package/dist/interfaces/IMessage.js +3 -0
  71. package/dist/interfaces/IMessage.js.map +1 -0
  72. package/dist/interfaces/IResource.d.ts +31 -0
  73. package/dist/interfaces/IResource.d.ts.map +1 -0
  74. package/dist/interfaces/IResource.js +3 -0
  75. package/dist/interfaces/IResource.js.map +1 -0
  76. package/dist/interfaces/ISdkRabbitmq.d.ts +17 -0
  77. package/dist/interfaces/ISdkRabbitmq.d.ts.map +1 -0
  78. package/dist/interfaces/ISdkRabbitmq.js +3 -0
  79. package/dist/interfaces/ISdkRabbitmq.js.map +1 -0
  80. package/dist/interfaces/index.d.ts +9 -0
  81. package/dist/interfaces/index.d.ts.map +1 -0
  82. package/dist/interfaces/index.js +33 -0
  83. package/dist/interfaces/index.js.map +1 -0
  84. package/dist/utils/configSchema.d.ts +8 -0
  85. package/dist/utils/configSchema.d.ts.map +1 -0
  86. package/dist/utils/configSchema.js +51 -0
  87. package/dist/utils/configSchema.js.map +1 -0
  88. package/docker-compose.yml +24 -0
  89. package/example.ts +65 -0
  90. package/examples/README-dynamic-routing.md +155 -0
  91. package/examples/bind-unbind-example.js +56 -0
  92. package/examples/test-chatbot-exchange.ts +83 -0
  93. package/examples/test-dynamic-routing-flow.js +299 -0
  94. package/examples/test-dynamic-routing-flow.ts +355 -0
  95. package/examples/test-no-disconnect.ts +0 -0
  96. package/examples/test-raw-rabbitmq.js +68 -0
  97. package/examples/test-same-channel.ts +81 -0
  98. package/examples/test-schedule-flow.ts +713 -0
  99. package/examples/test-simple-greeting.ts +66 -0
  100. package/examples/test-simple-schedule.ts +76 -0
  101. package/examples/test-wildcard.ts +364 -0
  102. package/jest.config.js +17 -0
  103. package/package.json +42 -4
  104. package/preinstall.js +1 -0
  105. package/prompts/test-dynamic-routing-flow.md +46 -0
  106. package/run.js +4 -0
  107. package/scripts/run-dynamic-routing-test.ts +31 -0
  108. package/src/.gitkeep +1 -0
  109. package/src/components/.gitkeep +1 -0
  110. package/src/components/ConfigurationManager.ts +104 -0
  111. package/src/components/ConnectionManager.ts +357 -0
  112. package/src/components/DLQHandler.ts +271 -0
  113. package/src/components/Logger.ts +224 -0
  114. package/src/components/MessagePublisher.ts +180 -0
  115. package/src/components/MessageSubscriber.ts +597 -0
  116. package/src/components/ResourceCreator.ts +411 -0
  117. package/src/components/SdkRabbitmq.ts +443 -0
  118. package/src/components/__tests__/ConfigurationManager.test.ts +357 -0
  119. package/src/components/__tests__/ConnectionManager.test.ts +387 -0
  120. package/src/components/__tests__/DLQHandler.test.ts +399 -0
  121. package/src/components/__tests__/Logger.test.ts +354 -0
  122. package/src/components/__tests__/MessagePublisher.test.ts +337 -0
  123. package/src/components/__tests__/MessageSubscriber.test.ts +542 -0
  124. package/src/components/__tests__/ResourceCreator.test.ts +465 -0
  125. package/src/components/__tests__/SdkRabbitmq.integration.test.ts +433 -0
  126. package/src/components/index.ts +8 -0
  127. package/src/index.ts +11 -0
  128. package/src/interfaces/.gitkeep +1 -0
  129. package/src/interfaces/IConfiguration.ts +38 -0
  130. package/src/interfaces/IConnection.ts +27 -0
  131. package/src/interfaces/IDLQ.ts +13 -0
  132. package/src/interfaces/IErrors.ts +53 -0
  133. package/src/interfaces/ILogger.ts +16 -0
  134. package/src/interfaces/IMessage.ts +65 -0
  135. package/src/interfaces/IResource.ts +35 -0
  136. package/src/interfaces/ISdkRabbitmq.ts +26 -0
  137. package/src/interfaces/index.ts +23 -0
  138. package/src/utils/.gitkeep +1 -0
  139. package/src/utils/configSchema.ts +58 -0
  140. 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
+ });