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,357 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { ConfigurationManager } from '../ConfigurationManager';
4
+ import { ConfigurationError } from '../../interfaces/IErrors';
5
+ import { IConfiguration } from '../../interfaces/IConfiguration';
6
+
7
+ // Mock fs module
8
+ jest.mock('fs', () => ({
9
+ readFileSync: jest.fn(),
10
+ existsSync: jest.fn(),
11
+ }));
12
+ const mockedFs = fs as jest.Mocked<typeof fs>;
13
+
14
+ describe('ConfigurationManager', () => {
15
+ let configManager: ConfigurationManager;
16
+ const mockConfigPath = path.join(process.cwd(), 'config.json');
17
+
18
+ beforeEach(() => {
19
+ // Reset singleton instance before each test
20
+ (ConfigurationManager as any).instance = null;
21
+ configManager = ConfigurationManager.getInstance();
22
+ configManager.clearCache();
23
+
24
+ // Reset all mocks
25
+ jest.clearAllMocks();
26
+ });
27
+
28
+ describe('Singleton Behavior', () => {
29
+ it('should return the same instance when called multiple times', () => {
30
+ const instance1 = ConfigurationManager.getInstance();
31
+ const instance2 = ConfigurationManager.getInstance();
32
+ const instance3 = ConfigurationManager.getInstance();
33
+
34
+ expect(instance1).toBe(instance2);
35
+ expect(instance2).toBe(instance3);
36
+ expect(instance1).toBe(instance3);
37
+ });
38
+ });
39
+
40
+ describe('Valid Configuration Loading', () => {
41
+ it('should load valid configuration successfully', () => {
42
+ const validConfig: IConfiguration = {
43
+ url: 'amqp://localhost:5672',
44
+ dlq: {
45
+ active: true,
46
+ ttl: 300000,
47
+ maxRetries: 3,
48
+ retryDelay: 5000,
49
+ },
50
+ logging: {
51
+ level: 'info',
52
+ format: 'json',
53
+ },
54
+ };
55
+
56
+ mockedFs.existsSync.mockReturnValue(true);
57
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(validConfig));
58
+
59
+ const result = configManager.loadConfig();
60
+
61
+ expect(mockedFs.existsSync).toHaveBeenCalledWith(mockConfigPath);
62
+ expect(mockedFs.readFileSync).toHaveBeenCalledWith(mockConfigPath, 'utf-8');
63
+ expect(result).toEqual(validConfig);
64
+ });
65
+
66
+ it('should load minimal valid configuration', () => {
67
+ const minimalConfig: IConfiguration = {
68
+ url: 'amqp://localhost:5672',
69
+ dlq: {
70
+ active: false,
71
+ },
72
+ };
73
+
74
+ mockedFs.existsSync.mockReturnValue(true);
75
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(minimalConfig));
76
+
77
+ const result = configManager.loadConfig();
78
+
79
+ expect(result).toEqual(minimalConfig);
80
+ });
81
+
82
+ it('should return cached configuration on subsequent calls', () => {
83
+ const validConfig: IConfiguration = {
84
+ url: 'amqp://localhost:5672',
85
+ dlq: {
86
+ active: true,
87
+ },
88
+ };
89
+
90
+ mockedFs.existsSync.mockReturnValue(true);
91
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(validConfig));
92
+
93
+ // First call
94
+ const result1 = configManager.loadConfig();
95
+ // Second call
96
+ const result2 = configManager.loadConfig();
97
+
98
+ expect(mockedFs.readFileSync).toHaveBeenCalledTimes(1);
99
+ expect(result1).toEqual(result2);
100
+ expect(result1).toBe(result2); // Should be the same object reference
101
+ });
102
+
103
+ it('should load configuration with optional logging properties', () => {
104
+ const configWithLogging: IConfiguration = {
105
+ url: 'amqp://test:5672',
106
+ dlq: {
107
+ active: true,
108
+ ttl: 600000,
109
+ maxRetries: 5,
110
+ retryDelay: 10000,
111
+ },
112
+ logging: {
113
+ level: 'debug',
114
+ format: 'text',
115
+ },
116
+ };
117
+
118
+ mockedFs.existsSync.mockReturnValue(true);
119
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(configWithLogging));
120
+
121
+ const result = configManager.loadConfig();
122
+
123
+ expect(result).toEqual(configWithLogging);
124
+ });
125
+ });
126
+
127
+ describe('Missing File Error Handling', () => {
128
+ it('should throw ConfigurationError when config file does not exist', () => {
129
+ mockedFs.existsSync.mockReturnValue(false);
130
+
131
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
132
+ expect(() => configManager.loadConfig()).toThrow(
133
+ `Configuration file not found at ${mockConfigPath}. Please create a config.json file in your project root.`
134
+ );
135
+ });
136
+
137
+ it('should include config path in error context when file is missing', () => {
138
+ mockedFs.existsSync.mockReturnValue(false);
139
+
140
+ try {
141
+ configManager.loadConfig();
142
+ throw new Error('Expected ConfigurationError to be thrown');
143
+ } catch (error) {
144
+ expect(error).toBeInstanceOf(ConfigurationError);
145
+ expect((error as ConfigurationError).context).toEqual({
146
+ configPath: mockConfigPath,
147
+ });
148
+ }
149
+ });
150
+
151
+ it('should throw ConfigurationError when file read fails', () => {
152
+ mockedFs.existsSync.mockReturnValue(true);
153
+ mockedFs.readFileSync.mockImplementation(() => {
154
+ throw new Error('Permission denied');
155
+ });
156
+
157
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
158
+ expect(() => configManager.loadConfig()).toThrow('Failed to load configuration: Permission denied');
159
+ });
160
+
161
+ it('should throw ConfigurationError when JSON parsing fails', () => {
162
+ mockedFs.existsSync.mockReturnValue(true);
163
+ mockedFs.readFileSync.mockReturnValue('{ invalid json }');
164
+
165
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
166
+ expect(() => configManager.loadConfig()).toThrow(/Invalid JSON in configuration file/);
167
+ });
168
+ });
169
+
170
+ describe('Invalid Schema Validation', () => {
171
+ beforeEach(() => {
172
+ mockedFs.existsSync.mockReturnValue(true);
173
+ });
174
+
175
+ it('should throw ConfigurationError when config is not an object', () => {
176
+ mockedFs.readFileSync.mockReturnValue('"not an object"');
177
+
178
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
179
+ expect(() => configManager.loadConfig()).toThrow('Configuration must be a valid object');
180
+ });
181
+
182
+ it('should throw ConfigurationError when url is missing', () => {
183
+ const invalidConfig = {
184
+ dlq: { active: true },
185
+ };
186
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
187
+
188
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
189
+ expect(() => configManager.loadConfig()).toThrow('Configuration must include a valid "url" string property');
190
+ });
191
+
192
+ it('should throw ConfigurationError when url is not a string', () => {
193
+ const invalidConfig = {
194
+ url: 12345,
195
+ dlq: { active: true },
196
+ };
197
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
198
+
199
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
200
+ expect(() => configManager.loadConfig()).toThrow('Configuration must include a valid "url" string property');
201
+ });
202
+
203
+ it('should throw ConfigurationError when dlq is missing', () => {
204
+ const invalidConfig = {
205
+ url: 'amqp://localhost:5672',
206
+ };
207
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
208
+
209
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
210
+ expect(() => configManager.loadConfig()).toThrow('Configuration must include a valid "dlq" object property');
211
+ });
212
+
213
+ it('should throw ConfigurationError when dlq is not an object', () => {
214
+ const invalidConfig = {
215
+ url: 'amqp://localhost:5672',
216
+ dlq: 'not an object',
217
+ };
218
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
219
+
220
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
221
+ expect(() => configManager.loadConfig()).toThrow('Configuration must include a valid "dlq" object property');
222
+ });
223
+
224
+ it('should throw ConfigurationError when dlq.active is missing', () => {
225
+ const invalidConfig = {
226
+ url: 'amqp://localhost:5672',
227
+ dlq: {},
228
+ };
229
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
230
+
231
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
232
+ expect(() => configManager.loadConfig()).toThrow('Configuration dlq.active must be a boolean value');
233
+ });
234
+
235
+ it('should throw ConfigurationError when dlq.active is not a boolean', () => {
236
+ const invalidConfig = {
237
+ url: 'amqp://localhost:5672',
238
+ dlq: { active: 'yes' },
239
+ };
240
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
241
+
242
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
243
+ expect(() => configManager.loadConfig()).toThrow('Configuration dlq.active must be a boolean value');
244
+ });
245
+
246
+ it('should throw ConfigurationError when dlq.ttl is negative', () => {
247
+ const invalidConfig = {
248
+ url: 'amqp://localhost:5672',
249
+ dlq: { active: true, ttl: -1000 },
250
+ };
251
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
252
+
253
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
254
+ expect(() => configManager.loadConfig()).toThrow('Configuration dlq.ttl must be a positive number');
255
+ });
256
+
257
+ it('should throw ConfigurationError when dlq.maxRetries is negative', () => {
258
+ const invalidConfig = {
259
+ url: 'amqp://localhost:5672',
260
+ dlq: { active: true, maxRetries: -5 },
261
+ };
262
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
263
+
264
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
265
+ expect(() => configManager.loadConfig()).toThrow('Configuration dlq.maxRetries must be a positive number');
266
+ });
267
+
268
+ it('should throw ConfigurationError when dlq.retryDelay is negative', () => {
269
+ const invalidConfig = {
270
+ url: 'amqp://localhost:5672',
271
+ dlq: { active: true, retryDelay: -2000 },
272
+ };
273
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
274
+
275
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
276
+ expect(() => configManager.loadConfig()).toThrow('Configuration dlq.retryDelay must be a positive number');
277
+ });
278
+
279
+ it('should throw ConfigurationError when logging is not an object', () => {
280
+ const invalidConfig = {
281
+ url: 'amqp://localhost:5672',
282
+ dlq: { active: true },
283
+ logging: 'invalid',
284
+ };
285
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
286
+
287
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
288
+ expect(() => configManager.loadConfig()).toThrow('Configuration logging must be an object');
289
+ });
290
+
291
+ it('should throw ConfigurationError when logging.level is invalid', () => {
292
+ const invalidConfig = {
293
+ url: 'amqp://localhost:5672',
294
+ dlq: { active: true },
295
+ logging: { level: 'invalid' },
296
+ };
297
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
298
+
299
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
300
+ expect(() => configManager.loadConfig()).toThrow('Configuration logging.level must be one of: error, warn, info, debug');
301
+ });
302
+
303
+ it('should throw ConfigurationError when logging.format is invalid', () => {
304
+ const invalidConfig = {
305
+ url: 'amqp://localhost:5672',
306
+ dlq: { active: true },
307
+ logging: { format: 'invalid' },
308
+ };
309
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(invalidConfig));
310
+
311
+ expect(() => configManager.loadConfig()).toThrow(ConfigurationError);
312
+ expect(() => configManager.loadConfig()).toThrow('Configuration logging.format must be one of: json, text');
313
+ });
314
+ });
315
+
316
+ describe('Utility Methods', () => {
317
+ it('should return correct config path', () => {
318
+ const configPath = configManager.getConfigPath();
319
+ expect(configPath).toBe(mockConfigPath);
320
+ });
321
+
322
+ it('should clear cache properly', () => {
323
+ const validConfig: IConfiguration = {
324
+ url: 'amqp://localhost:5672',
325
+ dlq: { active: true },
326
+ };
327
+
328
+ mockedFs.existsSync.mockReturnValue(true);
329
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify(validConfig));
330
+
331
+ // Load config to cache it
332
+ configManager.loadConfig();
333
+ expect(mockedFs.readFileSync).toHaveBeenCalledTimes(1);
334
+
335
+ // Clear cache and load again
336
+ configManager.clearCache();
337
+ configManager.loadConfig();
338
+ expect(mockedFs.readFileSync).toHaveBeenCalledTimes(2);
339
+ });
340
+
341
+ it('should validate config directly without loading from file', () => {
342
+ const validConfig: IConfiguration = {
343
+ url: 'amqp://localhost:5672',
344
+ dlq: { active: true },
345
+ };
346
+
347
+ expect(() => configManager.validateConfig(validConfig)).not.toThrow();
348
+
349
+ const invalidConfig = {
350
+ url: 'amqp://localhost:5672',
351
+ // missing dlq
352
+ };
353
+
354
+ expect(() => configManager.validateConfig(invalidConfig)).toThrow(ConfigurationError);
355
+ });
356
+ });
357
+ });