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,387 @@
1
+ import * as amqp from 'amqplib';
2
+ import { ConnectionManager, ConnectionState } from '../ConnectionManager';
3
+ import { IConfiguration } from '../../interfaces/IConfiguration';
4
+
5
+ // Mock amqplib
6
+ jest.mock('amqplib', () => ({
7
+ connect: jest.fn(),
8
+ }));
9
+ const mockedAmqp = amqp as jest.Mocked<typeof amqp>;
10
+
11
+ // Mock Logger
12
+ jest.mock('../Logger', () => ({
13
+ Logger: {
14
+ createComponentLogger: jest.fn().mockReturnValue({
15
+ logConnectionState: jest.fn(),
16
+ logError: jest.fn(),
17
+ logOperation: jest.fn(),
18
+ warn: jest.fn(),
19
+ info: jest.fn(),
20
+ error: jest.fn(),
21
+ }),
22
+ },
23
+ }));
24
+
25
+ describe('ConnectionManager', () => {
26
+ let mockConnection: any;
27
+ let config: IConfiguration;
28
+
29
+ beforeEach(() => {
30
+ // Reset singleton instance before each test
31
+ ConnectionManager.resetInstance();
32
+
33
+ // Setup mock configuration
34
+ config = {
35
+ url: 'amqp://localhost:5672',
36
+ dlq: {
37
+ active: true,
38
+ ttl: 300000,
39
+ maxRetries: 3,
40
+ retryDelay: 5000,
41
+ },
42
+ logging: {
43
+ level: 'info',
44
+ format: 'json',
45
+ },
46
+ };
47
+
48
+ // Setup mock connection
49
+ mockConnection = {
50
+ on: jest.fn(),
51
+ close: jest.fn().mockResolvedValue(undefined),
52
+ };
53
+
54
+ // Reset all mocks
55
+ jest.clearAllMocks();
56
+ jest.clearAllTimers();
57
+ jest.useFakeTimers();
58
+ });
59
+
60
+ afterEach(() => {
61
+ jest.useRealTimers();
62
+ ConnectionManager.resetInstance();
63
+ });
64
+
65
+ describe('Singleton Behavior', () => {
66
+ it('should return the same instance when called multiple times', () => {
67
+ const instance1 = ConnectionManager.getInstance(config);
68
+ const instance2 = ConnectionManager.getInstance(config);
69
+ const instance3 = ConnectionManager.getInstance(config);
70
+
71
+ expect(instance1).toBe(instance2);
72
+ expect(instance2).toBe(instance3);
73
+ expect(instance1).toBe(instance3);
74
+ });
75
+
76
+ it('should maintain singleton behavior across different configurations', () => {
77
+ const config1 = { ...config, url: 'amqp://localhost:5672' };
78
+ const config2 = { ...config, url: 'amqp://localhost:5673' };
79
+
80
+ const instance1 = ConnectionManager.getInstance(config1);
81
+ const instance2 = ConnectionManager.getInstance(config2);
82
+
83
+ expect(instance1).toBe(instance2);
84
+ });
85
+
86
+ it('should create new instance after reset', () => {
87
+ const instance1 = ConnectionManager.getInstance(config);
88
+ ConnectionManager.resetInstance();
89
+ const instance2 = ConnectionManager.getInstance(config);
90
+
91
+ expect(instance1).not.toBe(instance2);
92
+ });
93
+ });
94
+
95
+ describe('Connection Management', () => {
96
+ let connectionManager: ConnectionManager;
97
+
98
+ beforeEach(() => {
99
+ connectionManager = ConnectionManager.getInstance(config);
100
+ });
101
+
102
+ it('should establish connection successfully', async () => {
103
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
104
+
105
+ const connection = await connectionManager.connect();
106
+
107
+ expect(mockedAmqp.connect).toHaveBeenCalledWith(config.url);
108
+ expect(connection).toBe(mockConnection);
109
+ expect(connectionManager.isConnected()).toBe(true);
110
+ expect(connectionManager.getConnection()).toBe(mockConnection);
111
+ });
112
+
113
+ it('should return existing connection if already connected', async () => {
114
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
115
+
116
+ const connection1 = await connectionManager.connect();
117
+ const connection2 = await connectionManager.connect();
118
+
119
+ expect(mockedAmqp.connect).toHaveBeenCalledTimes(1);
120
+ expect(connection1).toBe(connection2);
121
+ });
122
+
123
+ it('should handle connection failure', async () => {
124
+ const error = new Error('Connection failed');
125
+ mockedAmqp.connect.mockRejectedValue(error);
126
+
127
+ await expect(connectionManager.connect()).rejects.toThrow('Failed to connect to RabbitMQ: Connection failed');
128
+ expect(connectionManager.isConnected()).toBe(false);
129
+ expect(connectionManager.getConnection()).toBe(null);
130
+ });
131
+
132
+ it('should disconnect properly', async () => {
133
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
134
+ await connectionManager.connect();
135
+
136
+ await connectionManager.disconnect();
137
+
138
+ expect(mockConnection.close).toHaveBeenCalled();
139
+ expect(connectionManager.isConnected()).toBe(false);
140
+ expect(connectionManager.getConnection()).toBe(null);
141
+ });
142
+ });
143
+
144
+ describe('Reconnection Logic', () => {
145
+ let connectionManager: ConnectionManager;
146
+ let connectionLostCallback: jest.Mock;
147
+
148
+ beforeEach(() => {
149
+ connectionManager = ConnectionManager.getInstance(config);
150
+ connectionLostCallback = jest.fn();
151
+ connectionManager.onConnectionLost(connectionLostCallback);
152
+ });
153
+
154
+ it('should handle connection loss and trigger reconnection', async () => {
155
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
156
+ await connectionManager.connect();
157
+
158
+ // Simulate connection loss
159
+ const errorHandler = mockConnection.on.mock.calls.find((call: any) => call[0] === 'error')?.[1];
160
+ expect(errorHandler).toBeDefined();
161
+
162
+ // Trigger connection error
163
+ errorHandler(new Error('Connection lost'));
164
+
165
+ expect(connectionLostCallback).toHaveBeenCalled();
166
+ expect(connectionManager.getConnectionState()).toBe(ConnectionState.RECONNECTING);
167
+ });
168
+
169
+ it('should implement exponential backoff for reconnection', async () => {
170
+ mockedAmqp.connect
171
+ .mockResolvedValueOnce(mockConnection) // Initial connection
172
+ .mockRejectedValueOnce(new Error('Reconnect failed 1'))
173
+ .mockRejectedValueOnce(new Error('Reconnect failed 2'))
174
+ .mockResolvedValueOnce(mockConnection); // Successful reconnection
175
+
176
+ await connectionManager.connect();
177
+
178
+ // Simulate connection loss
179
+ const errorHandler = mockConnection.on.mock.calls.find((call: any) => call[0] === 'error')?.[1];
180
+ errorHandler(new Error('Connection lost'));
181
+
182
+ // Wait for connection loss to be processed
183
+ await new Promise(resolve => setImmediate(resolve));
184
+
185
+ // First reconnection attempt (1s delay)
186
+ jest.advanceTimersByTime(1000);
187
+ await new Promise(resolve => setImmediate(resolve));
188
+
189
+ // Second reconnection attempt (2s delay)
190
+ jest.advanceTimersByTime(2000);
191
+ await new Promise(resolve => setImmediate(resolve));
192
+
193
+ // Third reconnection attempt (4s delay) - should succeed
194
+ jest.advanceTimersByTime(4000);
195
+ await new Promise(resolve => setImmediate(resolve));
196
+
197
+ expect(mockedAmqp.connect).toHaveBeenCalledTimes(4); // Initial + 3 reconnection attempts
198
+ }, 15000);
199
+
200
+ it('should stop reconnection after max attempts', async () => {
201
+ mockedAmqp.connect
202
+ .mockResolvedValueOnce(mockConnection) // Initial connection
203
+ .mockRejectedValue(new Error('Connection failed')); // All reconnection attempts fail
204
+
205
+ await connectionManager.connect();
206
+
207
+ // Simulate connection loss
208
+ const errorHandler = mockConnection.on.mock.calls.find((call: any) => call[0] === 'error')?.[1];
209
+ errorHandler(new Error('Connection lost'));
210
+
211
+ // Wait for connection loss to be processed
212
+ await Promise.resolve();
213
+
214
+ // Advance through all reconnection attempts (10 attempts with exponential backoff)
215
+ for (let i = 0; i < 10; i++) {
216
+ const delay = Math.min(1000 * Math.pow(2, i), 30000);
217
+ jest.advanceTimersByTime(delay);
218
+ await new Promise(resolve => setImmediate(resolve));
219
+ }
220
+
221
+ expect(connectionManager.getConnectionState()).toBe(ConnectionState.FAILED);
222
+ expect(mockedAmqp.connect).toHaveBeenCalledTimes(11); // Initial + 10 reconnection attempts
223
+ });
224
+ });
225
+
226
+ describe('Operation Queuing', () => {
227
+ let connectionManager: ConnectionManager;
228
+
229
+ beforeEach(() => {
230
+ connectionManager = ConnectionManager.getInstance(config);
231
+ });
232
+
233
+ it('should queue operations during reconnection', async () => {
234
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
235
+ await connectionManager.connect();
236
+
237
+ // Simulate connection loss
238
+ const errorHandler = mockConnection.on.mock.calls.find((call: any) => call[0] === 'error')?.[1];
239
+ errorHandler(new Error('Connection lost'));
240
+
241
+ // Wait for connection loss to be processed
242
+ await Promise.resolve();
243
+
244
+ const operation1 = jest.fn().mockResolvedValue('result1');
245
+ const operation2 = jest.fn().mockResolvedValue('result2');
246
+
247
+ // Queue operations during reconnection
248
+ connectionManager.queueOperation(operation1);
249
+ connectionManager.queueOperation(operation2);
250
+
251
+ // Operations should not be executed yet
252
+ expect(operation1).not.toHaveBeenCalled();
253
+ expect(operation2).not.toHaveBeenCalled();
254
+
255
+ // Simulate successful reconnection
256
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
257
+ jest.advanceTimersByTime(1000);
258
+ await new Promise(resolve => setImmediate(resolve));
259
+
260
+ // Operations should be executed after reconnection
261
+ expect(operation1).toHaveBeenCalled();
262
+ expect(operation2).toHaveBeenCalled();
263
+ });
264
+
265
+ it('should execute operations immediately when connected', async () => {
266
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
267
+ await connectionManager.connect();
268
+
269
+ const operation = jest.fn().mockResolvedValue('result');
270
+ connectionManager.queueOperation(operation);
271
+
272
+ // Operation should be executed immediately
273
+ await Promise.resolve();
274
+ expect(operation).toHaveBeenCalled();
275
+ });
276
+
277
+ it('should handle operation execution with executeOperation method', async () => {
278
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
279
+ await connectionManager.connect();
280
+
281
+ const operation = jest.fn().mockResolvedValue('test-result');
282
+ const result = await connectionManager.executeOperation(operation);
283
+
284
+ expect(operation).toHaveBeenCalled();
285
+ expect(result).toBe('test-result');
286
+ });
287
+
288
+ it('should queue executeOperation calls during reconnection', async () => {
289
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
290
+ await connectionManager.connect();
291
+
292
+ // Simulate connection loss
293
+ const errorHandler = mockConnection.on.mock.calls.find((call: any) => call[0] === 'error')?.[1];
294
+ errorHandler(new Error('Connection lost'));
295
+
296
+ // Wait for connection loss to be processed
297
+ await Promise.resolve();
298
+
299
+ const operation = jest.fn().mockResolvedValue('queued-result');
300
+ const resultPromise = connectionManager.executeOperation(operation);
301
+
302
+ // Operation should not be executed yet
303
+ expect(operation).not.toHaveBeenCalled();
304
+
305
+ // Simulate successful reconnection
306
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
307
+ jest.advanceTimersByTime(1000);
308
+ await new Promise(resolve => setImmediate(resolve));
309
+
310
+ const result = await resultPromise;
311
+ expect(operation).toHaveBeenCalled();
312
+ expect(result).toBe('queued-result');
313
+ }, 10000);
314
+
315
+ it('should handle errors in queued operations', async () => {
316
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
317
+ await connectionManager.connect();
318
+
319
+ // Simulate connection loss
320
+ const errorHandler = mockConnection.on.mock.calls.find((call: any) => call[0] === 'error')?.[1];
321
+ errorHandler(new Error('Connection lost'));
322
+
323
+ // Wait for connection loss to be processed
324
+ await new Promise(resolve => setImmediate(resolve));
325
+
326
+ const failingOperation = jest.fn().mockRejectedValue(new Error('Operation failed'));
327
+ const successfulOperation = jest.fn().mockResolvedValue('success');
328
+
329
+ connectionManager.queueOperation(failingOperation);
330
+ connectionManager.queueOperation(successfulOperation);
331
+
332
+ // Simulate successful reconnection
333
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
334
+ jest.advanceTimersByTime(1000);
335
+ await new Promise(resolve => setImmediate(resolve));
336
+
337
+ // Both operations should be attempted
338
+ expect(failingOperation).toHaveBeenCalled();
339
+ expect(successfulOperation).toHaveBeenCalled();
340
+ }, 15000);
341
+ });
342
+
343
+ describe('Connection Event Handling', () => {
344
+ let connectionManager: ConnectionManager;
345
+
346
+ beforeEach(() => {
347
+ connectionManager = ConnectionManager.getInstance(config);
348
+ });
349
+
350
+ it('should set up event handlers on connection', async () => {
351
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
352
+ await connectionManager.connect();
353
+
354
+ expect(mockConnection.on).toHaveBeenCalledWith('error', expect.any(Function));
355
+ expect(mockConnection.on).toHaveBeenCalledWith('close', expect.any(Function));
356
+ expect(mockConnection.on).toHaveBeenCalledWith('blocked', expect.any(Function));
357
+ expect(mockConnection.on).toHaveBeenCalledWith('unblocked', expect.any(Function));
358
+ });
359
+
360
+ it('should handle connection close event', async () => {
361
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
362
+ await connectionManager.connect();
363
+
364
+ const closeHandler = mockConnection.on.mock.calls.find((call: any) => call[0] === 'close')?.[1];
365
+ expect(closeHandler).toBeDefined();
366
+
367
+ closeHandler();
368
+
369
+ expect(connectionManager.getConnectionState()).toBe(ConnectionState.RECONNECTING);
370
+ });
371
+
372
+ it('should handle blocked and unblocked events', async () => {
373
+ mockedAmqp.connect.mockResolvedValue(mockConnection);
374
+ await connectionManager.connect();
375
+
376
+ const blockedHandler = mockConnection.on.mock.calls.find((call: any) => call[0] === 'blocked')?.[1];
377
+ const unblockedHandler = mockConnection.on.mock.calls.find((call: any) => call[0] === 'unblocked')?.[1];
378
+
379
+ expect(blockedHandler).toBeDefined();
380
+ expect(unblockedHandler).toBeDefined();
381
+
382
+ // These should not throw errors
383
+ blockedHandler('Memory alarm');
384
+ unblockedHandler();
385
+ });
386
+ });
387
+ });