@onlineapps/service-wrapper 2.0.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.
- package/API.md +336 -0
- package/README.md +185 -0
- package/build-docs.js +54 -0
- package/docs/PERFORMANCE.md +453 -0
- package/docs/PROCESS_FLOWS.md +389 -0
- package/jest.config.js +34 -0
- package/jsdoc.json +22 -0
- package/package.json +44 -0
- package/src/ServiceWrapper.js +343 -0
- package/src/index.js +23 -0
- package/test/component/ServiceWrapper.component.test.js +407 -0
- package/test/component/connector-integration.test.js +293 -0
- package/test/integration/orchestrator-integration.test.js +170 -0
- package/test/mocks/connectors.js +304 -0
- package/test/run-tests.js +135 -0
- package/test/setup.js +31 -0
- package/test/unit/ServiceWrapper.test.js +372 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Integration test for ServiceWrapper with OrchestratorConnector
|
|
6
|
+
* Tests that the thin orchestration layer properly delegates to orchestrator
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
// Mock setup
|
|
12
|
+
const Module = require('module');
|
|
13
|
+
const originalRequire = Module.prototype.require;
|
|
14
|
+
|
|
15
|
+
// Create test doubles
|
|
16
|
+
const testDoubles = {
|
|
17
|
+
processWorkflowMessage: null,
|
|
18
|
+
lastMessage: null,
|
|
19
|
+
lastServiceName: null
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
Module.prototype.require = function(id) {
|
|
23
|
+
switch(id) {
|
|
24
|
+
case '@onlineapps/conn-infra-mq':
|
|
25
|
+
return class MockMQ {
|
|
26
|
+
constructor() { this.connected = false; }
|
|
27
|
+
async connect() { this.connected = true; }
|
|
28
|
+
async disconnect() { this.connected = false; }
|
|
29
|
+
async consume(handler) { this.handler = handler; }
|
|
30
|
+
isConnected() { return this.connected; }
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
case '@onlineapps/conn-orch-registry':
|
|
34
|
+
return {
|
|
35
|
+
ServiceRegistryClient: class {
|
|
36
|
+
async register() { return true; }
|
|
37
|
+
async unregister() { return true; }
|
|
38
|
+
async sendHeartbeat() { return true; }
|
|
39
|
+
isConnected() { return true; }
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
case '@onlineapps/conn-base-logger':
|
|
44
|
+
return {
|
|
45
|
+
create: () => ({
|
|
46
|
+
info: () => {},
|
|
47
|
+
error: () => {},
|
|
48
|
+
warn: () => {},
|
|
49
|
+
debug: () => {}
|
|
50
|
+
})
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
case '@onlineapps/conn-orch-orchestrator':
|
|
54
|
+
return {
|
|
55
|
+
create: (options) => ({
|
|
56
|
+
processWorkflowMessage: async (message, serviceName) => {
|
|
57
|
+
testDoubles.lastMessage = message;
|
|
58
|
+
testDoubles.lastServiceName = serviceName;
|
|
59
|
+
testDoubles.processWorkflowMessage = true;
|
|
60
|
+
return { processed: true };
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
case '@onlineapps/conn-orch-api-mapper':
|
|
66
|
+
return { create: () => ({ map: () => {} }) };
|
|
67
|
+
|
|
68
|
+
case '@onlineapps/conn-orch-cookbook':
|
|
69
|
+
return { validateCookbook: () => true };
|
|
70
|
+
|
|
71
|
+
case '@onlineapps/conn-base-cache':
|
|
72
|
+
return class {
|
|
73
|
+
async connect() {}
|
|
74
|
+
async disconnect() {}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
case '@onlineapps/conn-infra-error-handler':
|
|
78
|
+
return class {
|
|
79
|
+
constructor() {}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
default:
|
|
83
|
+
return originalRequire.apply(this, arguments);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Run test
|
|
88
|
+
async function testOrchestratorIntegration() {
|
|
89
|
+
console.log('=== Orchestrator Integration Test ===\n');
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
// Load ServiceWrapper with mocked dependencies
|
|
93
|
+
const ServiceWrapper = require('../../src/ServiceWrapper');
|
|
94
|
+
|
|
95
|
+
// Create instance
|
|
96
|
+
const wrapper = new ServiceWrapper({
|
|
97
|
+
service: function expressApp() {},
|
|
98
|
+
serviceName: 'test-service',
|
|
99
|
+
openApiSpec: {
|
|
100
|
+
openapi: '3.0.0',
|
|
101
|
+
info: { version: '1.0.0' },
|
|
102
|
+
paths: {}
|
|
103
|
+
},
|
|
104
|
+
config: {}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Start wrapper
|
|
108
|
+
await wrapper.start();
|
|
109
|
+
console.log('✓ ServiceWrapper started');
|
|
110
|
+
|
|
111
|
+
// Get MQ client handler
|
|
112
|
+
const mqClient = wrapper.mqClient;
|
|
113
|
+
if (!mqClient.handler) {
|
|
114
|
+
throw new Error('No message handler registered');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Simulate workflow message
|
|
118
|
+
const testMessage = {
|
|
119
|
+
workflow_id: 'wf-123',
|
|
120
|
+
current_step: {
|
|
121
|
+
id: 'step-1',
|
|
122
|
+
service: 'test-service',
|
|
123
|
+
operation: 'testOp',
|
|
124
|
+
input: { data: 'test' }
|
|
125
|
+
},
|
|
126
|
+
context: {
|
|
127
|
+
variables: {},
|
|
128
|
+
results: {}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Process message through the handler
|
|
133
|
+
console.log('Sending test message to handler...');
|
|
134
|
+
await mqClient.handler(testMessage);
|
|
135
|
+
|
|
136
|
+
// Verify orchestrator was called
|
|
137
|
+
if (testDoubles.processWorkflowMessage) {
|
|
138
|
+
console.log('✓ Orchestrator.processWorkflowMessage was called');
|
|
139
|
+
console.log('✓ Message passed:', JSON.stringify(testDoubles.lastMessage, null, 2));
|
|
140
|
+
console.log('✓ Service name passed:', testDoubles.lastServiceName);
|
|
141
|
+
|
|
142
|
+
// Verify correct delegation
|
|
143
|
+
if (testDoubles.lastMessage === testMessage &&
|
|
144
|
+
testDoubles.lastServiceName === 'test-service') {
|
|
145
|
+
console.log('✓ Message and service name correctly delegated');
|
|
146
|
+
} else {
|
|
147
|
+
console.log('✗ Incorrect delegation parameters');
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
console.log('✗ Orchestrator.processWorkflowMessage was NOT called');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Stop wrapper
|
|
154
|
+
await wrapper.stop();
|
|
155
|
+
console.log('✓ ServiceWrapper stopped');
|
|
156
|
+
|
|
157
|
+
console.log('\n=== Test Result ===');
|
|
158
|
+
console.log('✅ Orchestrator integration working correctly!');
|
|
159
|
+
console.log('ServiceWrapper properly delegates workflow messages to orchestrator.');
|
|
160
|
+
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error('\n=== Test Failed ===');
|
|
163
|
+
console.error('✗ Error:', error.message);
|
|
164
|
+
console.error('Stack:', error.stack);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Run the test
|
|
170
|
+
testOrchestratorIntegration();
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mock connectors for unit testing
|
|
5
|
+
* These mocks allow us to test ServiceWrapper in isolation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Mock MQ Connector
|
|
9
|
+
class MockMQConnector {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.connected = false;
|
|
13
|
+
this.consumers = new Map();
|
|
14
|
+
this.publishedMessages = [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async connect() {
|
|
18
|
+
this.connected = true;
|
|
19
|
+
return Promise.resolve();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async disconnect() {
|
|
23
|
+
this.connected = false;
|
|
24
|
+
return Promise.resolve();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
isConnected() {
|
|
28
|
+
return this.connected;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async consume(handler, options) {
|
|
32
|
+
this.consumers.set(options.queue, handler);
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async publish(queue, message) {
|
|
37
|
+
this.publishedMessages.push({ queue, message });
|
|
38
|
+
return Promise.resolve();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Test helper - simulate message arrival
|
|
42
|
+
async simulateMessage(queue, message) {
|
|
43
|
+
const handler = this.consumers.get(queue);
|
|
44
|
+
if (handler) {
|
|
45
|
+
await handler(message);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Mock Registry Connector
|
|
51
|
+
class MockServiceRegistryClient {
|
|
52
|
+
constructor(config) {
|
|
53
|
+
this.config = config;
|
|
54
|
+
this.registeredServices = new Map();
|
|
55
|
+
this.heartbeats = [];
|
|
56
|
+
this.connected = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async register(serviceInfo) {
|
|
60
|
+
this.registeredServices.set(serviceInfo.name, serviceInfo);
|
|
61
|
+
return Promise.resolve();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async unregister(serviceName) {
|
|
65
|
+
this.registeredServices.delete(serviceName);
|
|
66
|
+
return Promise.resolve();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async sendHeartbeat(serviceName) {
|
|
70
|
+
this.heartbeats.push({ service: serviceName, timestamp: Date.now() });
|
|
71
|
+
return Promise.resolve();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
isConnected() {
|
|
75
|
+
return this.connected;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Mock Logger Connector
|
|
80
|
+
class MockLogger {
|
|
81
|
+
constructor(serviceName) {
|
|
82
|
+
this.serviceName = serviceName;
|
|
83
|
+
this.logs = [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_log(level, message, meta) {
|
|
87
|
+
this.logs.push({ level, message, meta, timestamp: Date.now() });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
info(message, meta) {
|
|
91
|
+
this._log('info', message, meta);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
warn(message, meta) {
|
|
95
|
+
this._log('warn', message, meta);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
error(message, meta) {
|
|
99
|
+
this._log('error', message, meta);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
debug(message, meta) {
|
|
103
|
+
this._log('debug', message, meta);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Test helper
|
|
107
|
+
getLogsByLevel(level) {
|
|
108
|
+
return this.logs.filter(log => log.level === level);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Add static create method to the class itself and as a property
|
|
113
|
+
MockLogger.create = function(serviceName) {
|
|
114
|
+
return new MockLogger(serviceName);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Mock Orchestrator Connector
|
|
118
|
+
class MockWorkflowOrchestrator {
|
|
119
|
+
constructor(config) {
|
|
120
|
+
this.config = config;
|
|
121
|
+
this.processedMessages = [];
|
|
122
|
+
this.shouldFail = false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async processWorkflowMessage(message, serviceName) {
|
|
126
|
+
this.processedMessages.push({ message, serviceName });
|
|
127
|
+
|
|
128
|
+
if (this.shouldFail) {
|
|
129
|
+
throw new Error('Mock processing failure');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
workflow_id: message.workflow_id,
|
|
135
|
+
step_id: message.current_step,
|
|
136
|
+
result: { mocked: true }
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Test helper - make next processing fail
|
|
141
|
+
setNextProcessingToFail(shouldFail = true) {
|
|
142
|
+
this.shouldFail = shouldFail;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const MockOrchestratorConnector = {
|
|
147
|
+
create: (config) => new MockWorkflowOrchestrator(config)
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Mock API Mapper Connector
|
|
151
|
+
class MockApiMapper {
|
|
152
|
+
constructor(config) {
|
|
153
|
+
this.config = config;
|
|
154
|
+
this.operations = new Map();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async callOperation(operationId, input, context) {
|
|
158
|
+
return {
|
|
159
|
+
operationId,
|
|
160
|
+
input,
|
|
161
|
+
context,
|
|
162
|
+
result: 'mocked'
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const MockApiMapperConnector = {
|
|
168
|
+
create: (config) => new MockApiMapper(config)
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Mock Cookbook Connector
|
|
172
|
+
const MockCookbookConnector = {
|
|
173
|
+
validateCookbook: (cookbook) => {
|
|
174
|
+
if (!cookbook || !cookbook.steps) {
|
|
175
|
+
throw new Error('Invalid cookbook');
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
createRouter: (mqClient, registryClient, options) => {
|
|
181
|
+
return {
|
|
182
|
+
routeToService: async (service, message) => {
|
|
183
|
+
return Promise.resolve();
|
|
184
|
+
},
|
|
185
|
+
routeToDLQ: async (cookbook, context, error, service) => {
|
|
186
|
+
return Promise.resolve();
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
CookbookExecutor: class {
|
|
192
|
+
constructor(cookbook, options) {
|
|
193
|
+
this.cookbook = cookbook;
|
|
194
|
+
this.options = options;
|
|
195
|
+
this.context = {
|
|
196
|
+
setInput: (input) => {},
|
|
197
|
+
data: { steps: {} }
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async executeStep(step) {
|
|
202
|
+
return { executed: step.id };
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
CookbookGenerator: class {
|
|
207
|
+
constructor(options) {
|
|
208
|
+
this.options = options;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
generate(openApiSpec) {
|
|
212
|
+
return { generated: true };
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
ResponseMapper: class {
|
|
217
|
+
mapResponse(response, mapping) {
|
|
218
|
+
return response;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* MockCacheConnector - Simulates cache connector
|
|
225
|
+
*/
|
|
226
|
+
class MockCacheConnector {
|
|
227
|
+
constructor(options = {}) {
|
|
228
|
+
this.options = options;
|
|
229
|
+
this.connected = false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async connect() {
|
|
233
|
+
this.connected = true;
|
|
234
|
+
return Promise.resolve();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async disconnect() {
|
|
238
|
+
this.connected = false;
|
|
239
|
+
return Promise.resolve();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async get(key) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async set(key, value, ttl) {
|
|
247
|
+
return 'OK';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async del(key) {
|
|
251
|
+
return 1;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
isConnected() {
|
|
255
|
+
return this.connected;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* MockErrorHandlerConnector - Simulates error handler
|
|
261
|
+
*/
|
|
262
|
+
class MockErrorHandlerConnector {
|
|
263
|
+
constructor(options = {}) {
|
|
264
|
+
this.maxRetries = options.maxRetries || 3;
|
|
265
|
+
this.retryDelay = options.retryDelay || 1000;
|
|
266
|
+
this.logger = options.logger;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async executeWithRetry(fn, context = {}) {
|
|
270
|
+
try {
|
|
271
|
+
return await fn();
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (this.logger) {
|
|
274
|
+
this.logger.error('Error in executeWithRetry', { error: error.message });
|
|
275
|
+
}
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
handleError(error, context = {}) {
|
|
281
|
+
if (this.logger) {
|
|
282
|
+
this.logger.error('Handling error', { error: error.message, context });
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
error: error.message,
|
|
286
|
+
retry: false
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
isRetryableError(error) {
|
|
291
|
+
return error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
module.exports = {
|
|
296
|
+
MockMQConnector,
|
|
297
|
+
MockServiceRegistryClient,
|
|
298
|
+
MockLogger,
|
|
299
|
+
MockOrchestratorConnector,
|
|
300
|
+
MockApiMapperConnector,
|
|
301
|
+
MockCookbookConnector,
|
|
302
|
+
MockCacheConnector,
|
|
303
|
+
MockErrorHandlerConnector
|
|
304
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Test runner that uses mocked dependencies
|
|
6
|
+
* Allows us to test service-wrapper without actual connectors
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
// Setup module aliases for mocked connectors
|
|
12
|
+
const mocks = require('./mocks/connectors');
|
|
13
|
+
|
|
14
|
+
const Module = require('module');
|
|
15
|
+
const originalRequire = Module.prototype.require;
|
|
16
|
+
|
|
17
|
+
Module.prototype.require = function(id) {
|
|
18
|
+
// Intercept connector imports and return appropriate mocks
|
|
19
|
+
switch(id) {
|
|
20
|
+
case '@onlineapps/conn-infra-mq':
|
|
21
|
+
return mocks.MockMQConnector;
|
|
22
|
+
case '@onlineapps/conn-orch-registry':
|
|
23
|
+
return { ServiceRegistryClient: mocks.MockServiceRegistryClient };
|
|
24
|
+
case '@onlineapps/conn-base-logger':
|
|
25
|
+
return mocks.MockLogger;
|
|
26
|
+
case '@onlineapps/conn-orch-orchestrator':
|
|
27
|
+
return mocks.MockOrchestratorConnector;
|
|
28
|
+
case '@onlineapps/conn-orch-api-mapper':
|
|
29
|
+
return mocks.MockApiMapperConnector;
|
|
30
|
+
case '@onlineapps/conn-orch-cookbook':
|
|
31
|
+
return mocks.MockCookbookConnector;
|
|
32
|
+
case '@onlineapps/conn-base-cache':
|
|
33
|
+
return mocks.MockCacheConnector;
|
|
34
|
+
case '@onlineapps/conn-infra-error-handler':
|
|
35
|
+
return mocks.MockErrorHandlerConnector;
|
|
36
|
+
default:
|
|
37
|
+
return originalRequire.apply(this, arguments);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Now we can test the ServiceWrapper with mocked dependencies
|
|
42
|
+
console.log('Testing ServiceWrapper with mocked connectors...\n');
|
|
43
|
+
|
|
44
|
+
// Test 1: Basic instantiation
|
|
45
|
+
try {
|
|
46
|
+
const ServiceWrapper = require('../src/ServiceWrapper');
|
|
47
|
+
const wrapper = new ServiceWrapper({
|
|
48
|
+
service: function() {},
|
|
49
|
+
serviceName: 'test-service',
|
|
50
|
+
openApiSpec: { openapi: '3.0.0', paths: {} }
|
|
51
|
+
});
|
|
52
|
+
console.log('✓ ServiceWrapper instantiation works');
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.log('✗ ServiceWrapper instantiation failed:', error.message);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Test 2: Check what connectors are imported
|
|
58
|
+
try {
|
|
59
|
+
const ServiceWrapper = require('../src/ServiceWrapper');
|
|
60
|
+
console.log('✓ All connector imports resolved');
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.log('✗ Failed to import connectors:', error.message);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Test 3: Test start/stop lifecycle
|
|
66
|
+
async function testLifecycle() {
|
|
67
|
+
try {
|
|
68
|
+
const ServiceWrapper = require('../src/ServiceWrapper');
|
|
69
|
+
const wrapper = new ServiceWrapper({
|
|
70
|
+
service: function() {},
|
|
71
|
+
serviceName: 'test-service',
|
|
72
|
+
openApiSpec: { openapi: '3.0.0', paths: {} }
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await wrapper.start();
|
|
76
|
+
console.log('✓ ServiceWrapper.start() works');
|
|
77
|
+
|
|
78
|
+
const status = wrapper.getStatus();
|
|
79
|
+
console.log('✓ ServiceWrapper.getStatus() returns:', status);
|
|
80
|
+
|
|
81
|
+
await wrapper.stop();
|
|
82
|
+
console.log('✓ ServiceWrapper.stop() works');
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.log('✗ Lifecycle test failed:', error.message);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Test 4: Check for missing functionality
|
|
89
|
+
function checkMissingFunctionality() {
|
|
90
|
+
console.log('\n=== Missing Functionality Check ===');
|
|
91
|
+
|
|
92
|
+
const missing = [];
|
|
93
|
+
|
|
94
|
+
// Check for cache connector
|
|
95
|
+
try {
|
|
96
|
+
require('@onlineapps/conn-base-cache');
|
|
97
|
+
} catch (e) {
|
|
98
|
+
missing.push('conn-base-cache');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check for error handler
|
|
102
|
+
try {
|
|
103
|
+
require('@onlineapps/conn-infra-error-handler');
|
|
104
|
+
} catch (e) {
|
|
105
|
+
missing.push('conn-infra-error-handler');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check cookbook connector exports
|
|
109
|
+
const cookbook = require('./mocks/connectors').MockCookbookConnector;
|
|
110
|
+
const requiredExports = ['validateCookbook', 'createRouter', 'CookbookExecutor', 'CookbookGenerator', 'ResponseMapper'];
|
|
111
|
+
const missingExports = requiredExports.filter(exp => !cookbook[exp]);
|
|
112
|
+
|
|
113
|
+
if (missingExports.length > 0) {
|
|
114
|
+
missing.push(`cookbook missing: ${missingExports.join(', ')}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (missing.length > 0) {
|
|
118
|
+
console.log('Missing connectors/functionality:');
|
|
119
|
+
missing.forEach(m => console.log(` - ${m}`));
|
|
120
|
+
} else {
|
|
121
|
+
console.log('All required functionality is available (mocked)');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Run tests
|
|
126
|
+
testLifecycle().then(() => {
|
|
127
|
+
checkMissingFunctionality();
|
|
128
|
+
console.log('\n=== Test Summary ===');
|
|
129
|
+
console.log('Service-wrapper is working as a thin orchestration layer!');
|
|
130
|
+
console.log('Next steps:');
|
|
131
|
+
console.log('1. Implement conn-base-cache connector');
|
|
132
|
+
console.log('2. Implement conn-infra-error-handler connector');
|
|
133
|
+
console.log('3. Fix cookbook connector exports');
|
|
134
|
+
console.log('4. Test with real connectors');
|
|
135
|
+
});
|
package/test/setup.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Jest setup file
|
|
5
|
+
* Configure test environment
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Set test environment variables
|
|
9
|
+
process.env.NODE_ENV = 'test';
|
|
10
|
+
process.env.LOG_LEVEL = 'error'; // Reduce noise during tests
|
|
11
|
+
|
|
12
|
+
// Global test timeout
|
|
13
|
+
jest.setTimeout(10000);
|
|
14
|
+
|
|
15
|
+
// Mock timers for tests that need them
|
|
16
|
+
global.mockTimers = () => {
|
|
17
|
+
jest.useFakeTimers();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
global.restoreTimers = () => {
|
|
21
|
+
jest.useRealTimers();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Helper to wait for async operations
|
|
25
|
+
global.waitForAsync = () => new Promise(resolve => setImmediate(resolve));
|
|
26
|
+
|
|
27
|
+
// Suppress console errors in tests unless debugging
|
|
28
|
+
if (!process.env.DEBUG_TESTS) {
|
|
29
|
+
global.console.error = jest.fn();
|
|
30
|
+
global.console.warn = jest.fn();
|
|
31
|
+
}
|