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,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
|
+
});
|