@onlineapps/service-wrapper 2.0.20 → 2.0.22
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.
- package/API.md +0 -0
- package/README.md +0 -0
- package/docs/CONFIGURATION_GUIDE.md +0 -0
- package/docs/FINAL_ARCHITECTURE.md +0 -0
- package/docs/MIGRATION_FROM_OPENAPI.md +0 -0
- package/docs/STANDARDS_OVERVIEW.md +0 -0
- package/docs/archived-2025-09-29/API_STRUCTURE_STANDARD.md +0 -0
- package/docs/archived-2025-09-29/ARCHITECTURE_DECISION.md +0 -0
- package/docs/archived-2025-09-29/HANDLER_VS_HTTP_COMPARISON.md +0 -0
- package/docs/archived-2025-09-29/INSTALLATION_GUIDE.md +0 -0
- package/docs/archived-2025-09-29/OPERATIONS_SCHEMA.md +0 -0
- package/docs/archived-2025-09-29/PERFORMANCE.md +0 -0
- package/docs/archived-2025-09-29/PROCESS_FLOWS.md +0 -0
- package/docs/archived-2025-09-29/SERVICE_TESTING_STANDARD.md +0 -0
- package/docs/archived-2025-09-29/WRAPPER_ARCHITECTURE.md +0 -0
- package/examples/README.md +0 -0
- package/examples/cookbook-file-output.json +0 -0
- package/examples/cookbook-multi-step.json +0 -0
- package/examples/cookbook-single-operation.json +0 -0
- package/jest.config.js +0 -0
- package/jsdoc.json +0 -0
- package/package.json +1 -1
- package/src/ServiceWrapper.js +120 -25
- package/src/index.js +0 -0
- package/tests/component/ServiceWrapper.component.test.js +0 -407
- package/tests/component/connector-integration.test.js +0 -293
- package/tests/e2e/full-flow.test.js +0 -293
- package/tests/integration/orchestrator-integration.test.js +0 -170
- package/tests/mocks/connectors.js +0 -304
- package/tests/monitoring-integration.test.js +0 -150
- package/tests/run-tests.js +0 -135
- package/tests/setup.js +0 -31
- package/tests/unit/ServiceWrapper.test.js +0 -372
|
@@ -1,372 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const ServiceWrapper = require('../../src/ServiceWrapper');
|
|
4
|
-
const {
|
|
5
|
-
MockMQConnector,
|
|
6
|
-
MockServiceRegistryClient,
|
|
7
|
-
MockLogger,
|
|
8
|
-
MockOrchestratorConnector,
|
|
9
|
-
MockApiMapperConnector,
|
|
10
|
-
MockCookbookConnector
|
|
11
|
-
} = require('../mocks/connectors');
|
|
12
|
-
|
|
13
|
-
// Mock all connector dependencies
|
|
14
|
-
jest.mock('@onlineapps/conn-infra-mq', () => MockMQConnector);
|
|
15
|
-
jest.mock('@onlineapps/conn-orch-registry', () => ({
|
|
16
|
-
ServiceRegistryClient: MockServiceRegistryClient
|
|
17
|
-
}));
|
|
18
|
-
jest.mock('@onlineapps/conn-base-logger', () => MockLogger);
|
|
19
|
-
jest.mock('@onlineapps/conn-orch-orchestrator', () => MockOrchestratorConnector);
|
|
20
|
-
jest.mock('@onlineapps/conn-orch-api-mapper', () => MockApiMapperConnector);
|
|
21
|
-
jest.mock('@onlineapps/conn-orch-cookbook', () => MockCookbookConnector);
|
|
22
|
-
|
|
23
|
-
describe('ServiceWrapper Unit Tests @unit', () => {
|
|
24
|
-
let wrapper;
|
|
25
|
-
let mockExpressApp;
|
|
26
|
-
let mockOpenApiSpec;
|
|
27
|
-
|
|
28
|
-
beforeEach(() => {
|
|
29
|
-
// Mock Express app
|
|
30
|
-
mockExpressApp = jest.fn();
|
|
31
|
-
|
|
32
|
-
// Mock OpenAPI spec
|
|
33
|
-
mockOpenApiSpec = {
|
|
34
|
-
openapi: '3.0.0',
|
|
35
|
-
info: {
|
|
36
|
-
title: 'Test Service',
|
|
37
|
-
version: '1.0.0',
|
|
38
|
-
description: 'Test service for unit tests'
|
|
39
|
-
},
|
|
40
|
-
paths: {
|
|
41
|
-
'/test': {
|
|
42
|
-
get: {
|
|
43
|
-
operationId: 'getTest',
|
|
44
|
-
responses: { '200': { description: 'OK' } }
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// Clear all mocks
|
|
51
|
-
jest.clearAllMocks();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
afterEach(async () => {
|
|
55
|
-
if (wrapper && wrapper.isRunning) {
|
|
56
|
-
await wrapper.stop();
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe('Constructor', () => {
|
|
61
|
-
test('should create instance with valid options', () => {
|
|
62
|
-
wrapper = new ServiceWrapper({
|
|
63
|
-
service: mockExpressApp,
|
|
64
|
-
serviceName: 'test-service',
|
|
65
|
-
openApiSpec: mockOpenApiSpec
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
expect(wrapper).toBeInstanceOf(ServiceWrapper);
|
|
69
|
-
expect(wrapper.serviceName).toBe('test-service');
|
|
70
|
-
expect(wrapper.service).toBe(mockExpressApp);
|
|
71
|
-
expect(wrapper.openApiSpec).toBe(mockOpenApiSpec);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test('should throw error without service', () => {
|
|
75
|
-
expect(() => {
|
|
76
|
-
new ServiceWrapper({
|
|
77
|
-
serviceName: 'test-service',
|
|
78
|
-
openApiSpec: mockOpenApiSpec
|
|
79
|
-
});
|
|
80
|
-
}).toThrow('Service (Express app) is required');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('should throw error without serviceName', () => {
|
|
84
|
-
expect(() => {
|
|
85
|
-
new ServiceWrapper({
|
|
86
|
-
service: mockExpressApp,
|
|
87
|
-
openApiSpec: mockOpenApiSpec
|
|
88
|
-
});
|
|
89
|
-
}).toThrow('Service name is required');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('should throw error without openApiSpec', () => {
|
|
93
|
-
expect(() => {
|
|
94
|
-
new ServiceWrapper({
|
|
95
|
-
service: mockExpressApp,
|
|
96
|
-
serviceName: 'test-service'
|
|
97
|
-
});
|
|
98
|
-
}).toThrow('OpenAPI specification is required');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('should accept optional config', () => {
|
|
102
|
-
wrapper = new ServiceWrapper({
|
|
103
|
-
service: mockExpressApp,
|
|
104
|
-
serviceName: 'test-service',
|
|
105
|
-
openApiSpec: mockOpenApiSpec,
|
|
106
|
-
config: {
|
|
107
|
-
port: 3001,
|
|
108
|
-
prefetch: 5,
|
|
109
|
-
heartbeatInterval: 10000
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
expect(wrapper.config.port).toBe(3001);
|
|
114
|
-
expect(wrapper.config.prefetch).toBe(5);
|
|
115
|
-
expect(wrapper.config.heartbeatInterval).toBe(10000);
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
describe('Start', () => {
|
|
120
|
-
beforeEach(() => {
|
|
121
|
-
wrapper = new ServiceWrapper({
|
|
122
|
-
service: mockExpressApp,
|
|
123
|
-
serviceName: 'test-service',
|
|
124
|
-
openApiSpec: mockOpenApiSpec
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('should start successfully', async () => {
|
|
129
|
-
await wrapper.start();
|
|
130
|
-
|
|
131
|
-
expect(wrapper.isRunning).toBe(true);
|
|
132
|
-
expect(wrapper.mqClient).toBeDefined();
|
|
133
|
-
expect(wrapper.mqClient.isConnected()).toBe(true);
|
|
134
|
-
expect(wrapper.registryClient).toBeDefined();
|
|
135
|
-
expect(wrapper.orchestrator).toBeDefined();
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test('should register service with registry', async () => {
|
|
139
|
-
await wrapper.start();
|
|
140
|
-
|
|
141
|
-
const registeredServices = wrapper.registryClient.registeredServices;
|
|
142
|
-
expect(registeredServices.has('test-service')).toBe(true);
|
|
143
|
-
|
|
144
|
-
const serviceInfo = registeredServices.get('test-service');
|
|
145
|
-
expect(serviceInfo.name).toBe('test-service');
|
|
146
|
-
expect(serviceInfo.url).toBe('http://localhost:3000');
|
|
147
|
-
expect(serviceInfo.openapi).toBe(mockOpenApiSpec);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test('should subscribe to workflow queue', async () => {
|
|
151
|
-
await wrapper.start();
|
|
152
|
-
|
|
153
|
-
const consumers = wrapper.mqClient.consumers;
|
|
154
|
-
expect(consumers.has('test-service.workflow')).toBe(true);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test('should not start twice', async () => {
|
|
158
|
-
await wrapper.start();
|
|
159
|
-
const firstMqClient = wrapper.mqClient;
|
|
160
|
-
|
|
161
|
-
await wrapper.start(); // Second call
|
|
162
|
-
|
|
163
|
-
expect(wrapper.mqClient).toBe(firstMqClient); // Same instance
|
|
164
|
-
expect(wrapper.logger.logs.some(log =>
|
|
165
|
-
log.level === 'warn' && log.message.includes('already running')
|
|
166
|
-
)).toBe(true);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test('should handle start failure gracefully', async () => {
|
|
170
|
-
// Force connection to fail
|
|
171
|
-
MockMQConnector.prototype.connect = jest.fn()
|
|
172
|
-
.mockRejectedValue(new Error('Connection failed'));
|
|
173
|
-
|
|
174
|
-
wrapper = new ServiceWrapper({
|
|
175
|
-
service: mockExpressApp,
|
|
176
|
-
serviceName: 'test-service',
|
|
177
|
-
openApiSpec: mockOpenApiSpec
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
await expect(wrapper.start()).rejects.toThrow('Connection failed');
|
|
181
|
-
expect(wrapper.isRunning).toBe(false);
|
|
182
|
-
|
|
183
|
-
// Restore original
|
|
184
|
-
MockMQConnector.prototype.connect = jest.fn().mockResolvedValue();
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe('Stop', () => {
|
|
189
|
-
beforeEach(async () => {
|
|
190
|
-
wrapper = new ServiceWrapper({
|
|
191
|
-
service: mockExpressApp,
|
|
192
|
-
serviceName: 'test-service',
|
|
193
|
-
openApiSpec: mockOpenApiSpec
|
|
194
|
-
});
|
|
195
|
-
await wrapper.start();
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test('should stop successfully', async () => {
|
|
199
|
-
expect(wrapper.isRunning).toBe(true);
|
|
200
|
-
|
|
201
|
-
await wrapper.stop();
|
|
202
|
-
|
|
203
|
-
expect(wrapper.isRunning).toBe(false);
|
|
204
|
-
expect(wrapper.mqClient.isConnected()).toBe(false);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
test('should unregister from registry', async () => {
|
|
208
|
-
expect(wrapper.registryClient.registeredServices.has('test-service')).toBe(true);
|
|
209
|
-
|
|
210
|
-
await wrapper.stop();
|
|
211
|
-
|
|
212
|
-
expect(wrapper.registryClient.registeredServices.has('test-service')).toBe(false);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test('should stop heartbeat', async () => {
|
|
216
|
-
const intervalSpy = jest.spyOn(global, 'clearInterval');
|
|
217
|
-
|
|
218
|
-
await wrapper.stop();
|
|
219
|
-
|
|
220
|
-
expect(intervalSpy).toHaveBeenCalled();
|
|
221
|
-
intervalSpy.mockRestore();
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test('should not stop if not running', async () => {
|
|
225
|
-
await wrapper.stop();
|
|
226
|
-
const logCount = wrapper.logger.logs.length;
|
|
227
|
-
|
|
228
|
-
await wrapper.stop(); // Second call
|
|
229
|
-
|
|
230
|
-
expect(wrapper.logger.logs.some((log, index) =>
|
|
231
|
-
index >= logCount &&
|
|
232
|
-
log.level === 'warn' &&
|
|
233
|
-
log.message.includes('not running')
|
|
234
|
-
)).toBe(true);
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
describe('Message Processing', () => {
|
|
239
|
-
beforeEach(async () => {
|
|
240
|
-
wrapper = new ServiceWrapper({
|
|
241
|
-
service: mockExpressApp,
|
|
242
|
-
serviceName: 'test-service',
|
|
243
|
-
openApiSpec: mockOpenApiSpec
|
|
244
|
-
});
|
|
245
|
-
await wrapper.start();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
test('should process workflow message successfully', async () => {
|
|
249
|
-
const testMessage = {
|
|
250
|
-
workflow_id: 'wf-123',
|
|
251
|
-
cookbook: {
|
|
252
|
-
steps: [
|
|
253
|
-
{ id: 'step1', type: 'task', service: 'test-service' }
|
|
254
|
-
]
|
|
255
|
-
},
|
|
256
|
-
current_step: 'step1',
|
|
257
|
-
context: { test: true }
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
// Simulate message arrival
|
|
261
|
-
await wrapper.mqClient.simulateMessage('test-service.workflow', testMessage);
|
|
262
|
-
|
|
263
|
-
// Check orchestrator received the message
|
|
264
|
-
const processedMessages = wrapper.orchestrator.processedMessages;
|
|
265
|
-
expect(processedMessages).toHaveLength(1);
|
|
266
|
-
expect(processedMessages[0].message).toEqual(testMessage);
|
|
267
|
-
expect(processedMessages[0].serviceName).toBe('test-service');
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
test('should handle message processing error', async () => {
|
|
271
|
-
// Make orchestrator fail
|
|
272
|
-
wrapper.orchestrator.setNextProcessingToFail(true);
|
|
273
|
-
|
|
274
|
-
const testMessage = {
|
|
275
|
-
workflow_id: 'wf-123',
|
|
276
|
-
cookbook: { steps: [] },
|
|
277
|
-
current_step: 'step1',
|
|
278
|
-
context: {}
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
// Simulate message arrival
|
|
282
|
-
await wrapper.mqClient.simulateMessage('test-service.workflow', testMessage);
|
|
283
|
-
|
|
284
|
-
// Check error was logged
|
|
285
|
-
expect(wrapper.logger.logs.some(log =>
|
|
286
|
-
log.level === 'error' &&
|
|
287
|
-
log.message.includes('Message processing failed')
|
|
288
|
-
)).toBe(true);
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
describe('GetStatus', () => {
|
|
293
|
-
test('should return status when running', async () => {
|
|
294
|
-
wrapper = new ServiceWrapper({
|
|
295
|
-
service: mockExpressApp,
|
|
296
|
-
serviceName: 'test-service',
|
|
297
|
-
openApiSpec: mockOpenApiSpec,
|
|
298
|
-
config: { port: 3001 }
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
await wrapper.start();
|
|
302
|
-
const status = wrapper.getStatus();
|
|
303
|
-
|
|
304
|
-
expect(status).toEqual({
|
|
305
|
-
serviceName: 'test-service',
|
|
306
|
-
isRunning: true,
|
|
307
|
-
mqConnected: true,
|
|
308
|
-
registryConnected: true,
|
|
309
|
-
config: {
|
|
310
|
-
port: 3001,
|
|
311
|
-
prefetch: 10,
|
|
312
|
-
heartbeatInterval: 30000
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
test('should return status when not running', () => {
|
|
318
|
-
wrapper = new ServiceWrapper({
|
|
319
|
-
service: mockExpressApp,
|
|
320
|
-
serviceName: 'test-service',
|
|
321
|
-
openApiSpec: mockOpenApiSpec
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
const status = wrapper.getStatus();
|
|
325
|
-
|
|
326
|
-
expect(status).toEqual({
|
|
327
|
-
serviceName: 'test-service',
|
|
328
|
-
isRunning: false,
|
|
329
|
-
mqConnected: false,
|
|
330
|
-
registryConnected: false,
|
|
331
|
-
config: {
|
|
332
|
-
port: 3000,
|
|
333
|
-
prefetch: 10,
|
|
334
|
-
heartbeatInterval: 30000
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
describe('Heartbeat', () => {
|
|
341
|
-
beforeEach(() => {
|
|
342
|
-
jest.useFakeTimers();
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
afterEach(() => {
|
|
346
|
-
jest.useRealTimers();
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
test('should send heartbeats periodically', async () => {
|
|
350
|
-
wrapper = new ServiceWrapper({
|
|
351
|
-
service: mockExpressApp,
|
|
352
|
-
serviceName: 'test-service',
|
|
353
|
-
openApiSpec: mockOpenApiSpec,
|
|
354
|
-
config: { heartbeatInterval: 5000 }
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
await wrapper.start();
|
|
358
|
-
|
|
359
|
-
// Initially no heartbeats
|
|
360
|
-
expect(wrapper.registryClient.heartbeats).toHaveLength(0);
|
|
361
|
-
|
|
362
|
-
// Advance time
|
|
363
|
-
jest.advanceTimersByTime(5000);
|
|
364
|
-
|
|
365
|
-
// Wait for async heartbeat
|
|
366
|
-
await Promise.resolve();
|
|
367
|
-
|
|
368
|
-
expect(wrapper.registryClient.heartbeats.length).toBeGreaterThan(0);
|
|
369
|
-
expect(wrapper.registryClient.heartbeats[0].service).toBe('test-service');
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
});
|