@onlineapps/conn-orch-validator 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/README.md +78 -0
- package/TESTING_STRATEGY.md +92 -0
- package/docs/DESIGN.md +134 -0
- package/examples/service-wrapper-usage.js +250 -0
- package/examples/three-tier-testing.js +144 -0
- package/jest.config.js +23 -0
- package/onlineapps-conn-e2e-testing-1.0.0.tgz +0 -0
- package/package.json +43 -0
- package/src/CookbookTestRunner.js +434 -0
- package/src/CookbookTestUtils.js +237 -0
- package/src/ServiceReadinessValidator.js +430 -0
- package/src/ServiceTestHarness.js +256 -0
- package/src/ServiceValidator.js +387 -0
- package/src/TestOrchestrator.js +727 -0
- package/src/ValidationOrchestrator.js +506 -0
- package/src/WorkflowTestRunner.js +396 -0
- package/src/helpers/README.md +235 -0
- package/src/helpers/createPreValidationTests.js +321 -0
- package/src/helpers/createServiceReadinessTests.js +245 -0
- package/src/index.js +62 -0
- package/src/mocks/MockMQClient.js +176 -0
- package/src/mocks/MockRegistry.js +164 -0
- package/src/mocks/MockStorage.js +186 -0
- package/src/validators/ServiceStructureValidator.js +487 -0
- package/src/validators/ValidationProofGenerator.js +79 -0
- package/test-mq-flow.js +72 -0
- package/test-orchestrator.js +95 -0
- package/tests/component/testing-framework-integration.test.js +313 -0
- package/tests/integration/ServiceReadiness.test.js +265 -0
- package/tests/monitoring-e2e.test.js +315 -0
- package/tests/run-example.js +257 -0
- package/tests/unit/CookbookTestRunner.test.js +353 -0
- package/tests/unit/MockMQClient.test.js +190 -0
- package/tests/unit/MockRegistry.test.js +233 -0
- package/tests/unit/MockStorage.test.js +257 -0
- package/tests/unit/ServiceValidator.test.js +429 -0
- package/tests/unit/WorkflowTestRunner.test.js +546 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ServiceValidator = require('./ServiceValidator');
|
|
4
|
+
const WorkflowTestRunner = require('./WorkflowTestRunner');
|
|
5
|
+
const ServiceTestHarness = require('./ServiceTestHarness');
|
|
6
|
+
const ServiceReadinessValidator = require('./ServiceReadinessValidator');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* TestOrchestrator - Three-tier testing strategy
|
|
10
|
+
* Level 1: Unit tests with full mocks
|
|
11
|
+
* Level 2: Component tests with partial mocks
|
|
12
|
+
* Level 3: Integration tests with real services
|
|
13
|
+
*/
|
|
14
|
+
class TestOrchestrator {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.service = options.service;
|
|
17
|
+
this.serviceName = options.serviceName;
|
|
18
|
+
this.openApiSpec = options.openApiSpec;
|
|
19
|
+
this.logger = options.logger || console;
|
|
20
|
+
|
|
21
|
+
// Test configuration
|
|
22
|
+
this.testLevels = {
|
|
23
|
+
unit: {
|
|
24
|
+
useMocks: true,
|
|
25
|
+
useTestQueues: false,
|
|
26
|
+
useRealServices: false
|
|
27
|
+
},
|
|
28
|
+
component: {
|
|
29
|
+
useMocks: true,
|
|
30
|
+
useTestQueues: true,
|
|
31
|
+
useRealServices: false
|
|
32
|
+
},
|
|
33
|
+
integration: {
|
|
34
|
+
useMocks: false,
|
|
35
|
+
useTestQueues: true,
|
|
36
|
+
useRealServices: true
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Test suites
|
|
41
|
+
this.testSuites = {
|
|
42
|
+
unit: [],
|
|
43
|
+
component: [],
|
|
44
|
+
integration: []
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Results
|
|
48
|
+
this.results = {
|
|
49
|
+
unit: null,
|
|
50
|
+
component: null,
|
|
51
|
+
integration: null
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run all test levels
|
|
57
|
+
*/
|
|
58
|
+
async runAllTests() {
|
|
59
|
+
const results = {
|
|
60
|
+
timestamp: Date.now(),
|
|
61
|
+
service: this.serviceName,
|
|
62
|
+
levels: {}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Level 1: Unit Tests
|
|
66
|
+
this.logger.info('Starting Level 1: Unit Tests (full mocks)...');
|
|
67
|
+
results.levels.unit = await this.runUnitTests();
|
|
68
|
+
|
|
69
|
+
if (!results.levels.unit.passed) {
|
|
70
|
+
results.recommendation = 'Failed at unit test level - fix basic functionality';
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Level 2: Component Tests
|
|
75
|
+
this.logger.info('Starting Level 2: Component Tests (partial mocks + test queues)...');
|
|
76
|
+
results.levels.component = await this.runComponentTests();
|
|
77
|
+
|
|
78
|
+
if (!results.levels.component.passed) {
|
|
79
|
+
results.recommendation = 'Failed at component test level - fix integration issues';
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Level 3: Integration Tests
|
|
84
|
+
this.logger.info('Starting Level 3: Integration Tests (real services + test queues)...');
|
|
85
|
+
results.levels.integration = await this.runIntegrationTests();
|
|
86
|
+
|
|
87
|
+
if (!results.levels.integration.passed) {
|
|
88
|
+
results.recommendation = 'Failed at integration level - service not production ready';
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
results.passed = true;
|
|
93
|
+
results.recommendation = 'All test levels passed - service is production ready';
|
|
94
|
+
return results;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Level 1: Unit Tests with full mocks
|
|
99
|
+
*/
|
|
100
|
+
async runUnitTests() {
|
|
101
|
+
const results = {
|
|
102
|
+
level: 'unit',
|
|
103
|
+
passed: true,
|
|
104
|
+
tests: [],
|
|
105
|
+
coverage: 0
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// Create test harness with full mocks
|
|
110
|
+
const harness = new ServiceTestHarness({
|
|
111
|
+
service: this.service,
|
|
112
|
+
serviceName: this.serviceName,
|
|
113
|
+
openApiSpec: this.openApiSpec,
|
|
114
|
+
mockInfrastructure: true
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await harness.start();
|
|
118
|
+
|
|
119
|
+
// Test 1: Service starts and responds
|
|
120
|
+
const startTest = await this.testServiceStart(harness);
|
|
121
|
+
results.tests.push(startTest);
|
|
122
|
+
if (!startTest.passed) results.passed = false;
|
|
123
|
+
|
|
124
|
+
// Test 2: API endpoints exist
|
|
125
|
+
const endpointTest = await this.testEndpointsExist(harness);
|
|
126
|
+
results.tests.push(endpointTest);
|
|
127
|
+
if (!endpointTest.passed) results.passed = false;
|
|
128
|
+
|
|
129
|
+
// Test 3: Health check works
|
|
130
|
+
const healthTest = await this.testHealthCheck(harness);
|
|
131
|
+
results.tests.push(healthTest);
|
|
132
|
+
if (!healthTest.passed) results.passed = false;
|
|
133
|
+
|
|
134
|
+
// Test 4: Can handle mock messages
|
|
135
|
+
const messageTest = await this.testMockMessageHandling(harness);
|
|
136
|
+
results.tests.push(messageTest);
|
|
137
|
+
if (!messageTest.passed) results.passed = false;
|
|
138
|
+
|
|
139
|
+
await harness.stop();
|
|
140
|
+
|
|
141
|
+
// Calculate coverage
|
|
142
|
+
const passedTests = results.tests.filter(t => t.passed).length;
|
|
143
|
+
results.coverage = (passedTests / results.tests.length) * 100;
|
|
144
|
+
|
|
145
|
+
} catch (error) {
|
|
146
|
+
results.passed = false;
|
|
147
|
+
results.error = error.message;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return results;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Level 2: Component Tests with test queues
|
|
155
|
+
*/
|
|
156
|
+
async runComponentTests() {
|
|
157
|
+
const results = {
|
|
158
|
+
level: 'component',
|
|
159
|
+
passed: true,
|
|
160
|
+
tests: [],
|
|
161
|
+
coverage: 0
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// Create test harness with test queues
|
|
166
|
+
const harness = new ServiceTestHarness({
|
|
167
|
+
service: this.service,
|
|
168
|
+
serviceName: this.serviceName,
|
|
169
|
+
openApiSpec: this.openApiSpec,
|
|
170
|
+
mockInfrastructure: true // Still use mocks but with test queue names
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Configure test queues
|
|
174
|
+
this.configureTestQueues(harness);
|
|
175
|
+
|
|
176
|
+
await harness.start();
|
|
177
|
+
|
|
178
|
+
// Test 1: OpenAPI compliance
|
|
179
|
+
const openApiTest = await this.testOpenApiCompliance(harness);
|
|
180
|
+
results.tests.push(openApiTest);
|
|
181
|
+
if (!openApiTest.passed) results.passed = false;
|
|
182
|
+
|
|
183
|
+
// Test 2: Workflow execution
|
|
184
|
+
const workflowTest = await this.testWorkflowExecution(harness);
|
|
185
|
+
results.tests.push(workflowTest);
|
|
186
|
+
if (!workflowTest.passed) results.passed = false;
|
|
187
|
+
|
|
188
|
+
// Test 3: Message routing
|
|
189
|
+
const routingTest = await this.testMessageRouting(harness);
|
|
190
|
+
results.tests.push(routingTest);
|
|
191
|
+
if (!routingTest.passed) results.passed = false;
|
|
192
|
+
|
|
193
|
+
// Test 4: Error handling
|
|
194
|
+
const errorTest = await this.testErrorHandling(harness);
|
|
195
|
+
results.tests.push(errorTest);
|
|
196
|
+
if (!errorTest.passed) results.passed = false;
|
|
197
|
+
|
|
198
|
+
await harness.stop();
|
|
199
|
+
|
|
200
|
+
// Calculate coverage
|
|
201
|
+
const passedTests = results.tests.filter(t => t.passed).length;
|
|
202
|
+
results.coverage = (passedTests / results.tests.length) * 100;
|
|
203
|
+
|
|
204
|
+
} catch (error) {
|
|
205
|
+
results.passed = false;
|
|
206
|
+
results.error = error.message;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return results;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Level 3: Integration Tests with real services
|
|
214
|
+
*/
|
|
215
|
+
async runIntegrationTests() {
|
|
216
|
+
const results = {
|
|
217
|
+
level: 'integration',
|
|
218
|
+
passed: true,
|
|
219
|
+
tests: [],
|
|
220
|
+
coverage: 0
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
// Use real infrastructure with test configuration
|
|
225
|
+
const config = this.getIntegrationTestConfig();
|
|
226
|
+
|
|
227
|
+
// Run readiness validator (uses real endpoints)
|
|
228
|
+
const validator = new ServiceReadinessValidator();
|
|
229
|
+
const readinessTest = await validator.validateReadiness({
|
|
230
|
+
name: this.serviceName,
|
|
231
|
+
url: config.serviceUrl,
|
|
232
|
+
openApiSpec: this.openApiSpec,
|
|
233
|
+
registry: config.registry,
|
|
234
|
+
testCookbook: this.getTestCookbook(),
|
|
235
|
+
healthEndpoint: '/health'
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
results.tests.push({
|
|
239
|
+
name: 'Service Readiness',
|
|
240
|
+
passed: readinessTest.ready,
|
|
241
|
+
score: readinessTest.score,
|
|
242
|
+
details: readinessTest
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (!readinessTest.ready) {
|
|
246
|
+
results.passed = false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Test with real message queue (test exchange)
|
|
250
|
+
if (config.mqUrl) {
|
|
251
|
+
const mqTest = await this.testRealMessageQueue(config);
|
|
252
|
+
results.tests.push(mqTest);
|
|
253
|
+
if (!mqTest.passed) results.passed = false;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Test with real registry (test environment)
|
|
257
|
+
if (config.registryUrl) {
|
|
258
|
+
const registryTest = await this.testRealRegistry(config);
|
|
259
|
+
results.tests.push(registryTest);
|
|
260
|
+
if (!registryTest.passed) results.passed = false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Test end-to-end workflow
|
|
264
|
+
const e2eTest = await this.testEndToEndWorkflow(config);
|
|
265
|
+
results.tests.push(e2eTest);
|
|
266
|
+
if (!e2eTest.passed) results.passed = false;
|
|
267
|
+
|
|
268
|
+
// Calculate coverage
|
|
269
|
+
const passedTests = results.tests.filter(t => t.passed).length;
|
|
270
|
+
results.coverage = (passedTests / results.tests.length) * 100;
|
|
271
|
+
|
|
272
|
+
} catch (error) {
|
|
273
|
+
results.passed = false;
|
|
274
|
+
results.error = error.message;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return results;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// === Unit Test Methods ===
|
|
281
|
+
|
|
282
|
+
async testServiceStart(harness) {
|
|
283
|
+
try {
|
|
284
|
+
const isRunning = harness.isRunning;
|
|
285
|
+
return {
|
|
286
|
+
name: 'Service Start',
|
|
287
|
+
passed: isRunning,
|
|
288
|
+
message: isRunning ? 'Service started successfully' : 'Service failed to start'
|
|
289
|
+
};
|
|
290
|
+
} catch (error) {
|
|
291
|
+
return {
|
|
292
|
+
name: 'Service Start',
|
|
293
|
+
passed: false,
|
|
294
|
+
error: error.message
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async testEndpointsExist(harness) {
|
|
300
|
+
try {
|
|
301
|
+
const endpoints = this.extractEndpoints(this.openApiSpec);
|
|
302
|
+
const tested = [];
|
|
303
|
+
|
|
304
|
+
for (const endpoint of endpoints.slice(0, 3)) { // Test first 3
|
|
305
|
+
try {
|
|
306
|
+
await harness.callApi(endpoint.method, endpoint.path);
|
|
307
|
+
tested.push({ path: endpoint.path, exists: true });
|
|
308
|
+
} catch {
|
|
309
|
+
tested.push({ path: endpoint.path, exists: false });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const passed = tested.every(t => t.exists);
|
|
314
|
+
return {
|
|
315
|
+
name: 'Endpoints Exist',
|
|
316
|
+
passed,
|
|
317
|
+
tested,
|
|
318
|
+
message: `${tested.filter(t => t.exists).length}/${tested.length} endpoints exist`
|
|
319
|
+
};
|
|
320
|
+
} catch (error) {
|
|
321
|
+
return {
|
|
322
|
+
name: 'Endpoints Exist',
|
|
323
|
+
passed: false,
|
|
324
|
+
error: error.message
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async testHealthCheck(harness) {
|
|
330
|
+
try {
|
|
331
|
+
const response = await harness.callApi('GET', '/health');
|
|
332
|
+
const passed = response && response.status === 'healthy';
|
|
333
|
+
return {
|
|
334
|
+
name: 'Health Check',
|
|
335
|
+
passed,
|
|
336
|
+
response,
|
|
337
|
+
message: passed ? 'Health check passed' : 'Health check failed'
|
|
338
|
+
};
|
|
339
|
+
} catch (error) {
|
|
340
|
+
return {
|
|
341
|
+
name: 'Health Check',
|
|
342
|
+
passed: false,
|
|
343
|
+
error: error.message
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async testMockMessageHandling(harness) {
|
|
349
|
+
try {
|
|
350
|
+
const message = {
|
|
351
|
+
workflow_id: 'test-123',
|
|
352
|
+
step: 'test-step',
|
|
353
|
+
data: { test: true }
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
await harness.mqClient.publish('test-queue', message);
|
|
357
|
+
const published = harness.getPublishedMessages('test-queue');
|
|
358
|
+
|
|
359
|
+
const passed = published.length > 0;
|
|
360
|
+
return {
|
|
361
|
+
name: 'Mock Message Handling',
|
|
362
|
+
passed,
|
|
363
|
+
messagesHandled: published.length,
|
|
364
|
+
message: `Handled ${published.length} messages`
|
|
365
|
+
};
|
|
366
|
+
} catch (error) {
|
|
367
|
+
return {
|
|
368
|
+
name: 'Mock Message Handling',
|
|
369
|
+
passed: false,
|
|
370
|
+
error: error.message
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// === Component Test Methods ===
|
|
376
|
+
|
|
377
|
+
async testOpenApiCompliance(harness) {
|
|
378
|
+
try {
|
|
379
|
+
const validator = new ServiceValidator();
|
|
380
|
+
const result = await validator.validateService(
|
|
381
|
+
harness.baseUrl,
|
|
382
|
+
this.openApiSpec
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
name: 'OpenAPI Compliance',
|
|
387
|
+
passed: result.valid,
|
|
388
|
+
coverage: result.coverage,
|
|
389
|
+
errors: result.errors,
|
|
390
|
+
message: `${result.coverage}% endpoint coverage`
|
|
391
|
+
};
|
|
392
|
+
} catch (error) {
|
|
393
|
+
return {
|
|
394
|
+
name: 'OpenAPI Compliance',
|
|
395
|
+
passed: false,
|
|
396
|
+
error: error.message
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async testWorkflowExecution(harness) {
|
|
402
|
+
try {
|
|
403
|
+
const cookbook = this.getTestCookbook();
|
|
404
|
+
const result = await harness.simulateWorkflow(cookbook);
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
name: 'Workflow Execution',
|
|
408
|
+
passed: result.completed,
|
|
409
|
+
stepsExecuted: result.results.length,
|
|
410
|
+
message: `Executed ${result.results.length} workflow steps`
|
|
411
|
+
};
|
|
412
|
+
} catch (error) {
|
|
413
|
+
return {
|
|
414
|
+
name: 'Workflow Execution',
|
|
415
|
+
passed: false,
|
|
416
|
+
error: error.message
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async testMessageRouting(harness) {
|
|
422
|
+
try {
|
|
423
|
+
// Publish to different queues
|
|
424
|
+
await harness.mqClient.publish('test.service.request', { type: 'request' });
|
|
425
|
+
await harness.mqClient.publish('test.service.response', { type: 'response' });
|
|
426
|
+
await harness.mqClient.publish('test.workflow.event', { type: 'event' });
|
|
427
|
+
|
|
428
|
+
const requests = harness.getPublishedMessages('test.service.request');
|
|
429
|
+
const responses = harness.getPublishedMessages('test.service.response');
|
|
430
|
+
const events = harness.getPublishedMessages('test.workflow.event');
|
|
431
|
+
|
|
432
|
+
const passed = requests.length > 0 && responses.length > 0 && events.length > 0;
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
name: 'Message Routing',
|
|
436
|
+
passed,
|
|
437
|
+
queues: {
|
|
438
|
+
requests: requests.length,
|
|
439
|
+
responses: responses.length,
|
|
440
|
+
events: events.length
|
|
441
|
+
},
|
|
442
|
+
message: 'Message routing working correctly'
|
|
443
|
+
};
|
|
444
|
+
} catch (error) {
|
|
445
|
+
return {
|
|
446
|
+
name: 'Message Routing',
|
|
447
|
+
passed: false,
|
|
448
|
+
error: error.message
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async testErrorHandling(harness) {
|
|
454
|
+
try {
|
|
455
|
+
// Test various error scenarios
|
|
456
|
+
const errors = [];
|
|
457
|
+
|
|
458
|
+
// Invalid endpoint
|
|
459
|
+
try {
|
|
460
|
+
await harness.callApi('GET', '/non-existent');
|
|
461
|
+
} catch (e) {
|
|
462
|
+
errors.push('Handled 404 correctly');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Invalid payload
|
|
466
|
+
try {
|
|
467
|
+
await harness.callApi('POST', '/api/endpoint', 'invalid-json');
|
|
468
|
+
} catch (e) {
|
|
469
|
+
errors.push('Handled invalid JSON');
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const passed = errors.length >= 1;
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
name: 'Error Handling',
|
|
476
|
+
passed,
|
|
477
|
+
errorsHandled: errors.length,
|
|
478
|
+
details: errors,
|
|
479
|
+
message: `Handled ${errors.length} error scenarios`
|
|
480
|
+
};
|
|
481
|
+
} catch (error) {
|
|
482
|
+
return {
|
|
483
|
+
name: 'Error Handling',
|
|
484
|
+
passed: false,
|
|
485
|
+
error: error.message
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// === Integration Test Methods ===
|
|
491
|
+
|
|
492
|
+
async testRealMessageQueue(config) {
|
|
493
|
+
try {
|
|
494
|
+
const MQClient = require('@onlineapps/connector-mq-client');
|
|
495
|
+
const mqClient = new MQClient({
|
|
496
|
+
url: config.mqUrl,
|
|
497
|
+
queue: `test.${this.serviceName}`,
|
|
498
|
+
exchange: 'test.exchange'
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
await mqClient.connect();
|
|
502
|
+
|
|
503
|
+
const testMessage = {
|
|
504
|
+
test: true,
|
|
505
|
+
timestamp: Date.now(),
|
|
506
|
+
service: this.serviceName
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
await mqClient.publish(`test.${this.serviceName}`, testMessage);
|
|
510
|
+
|
|
511
|
+
// Set up consumer to verify
|
|
512
|
+
let received = false;
|
|
513
|
+
await mqClient.consume(`test.${this.serviceName}`, (msg) => {
|
|
514
|
+
const content = JSON.parse(msg.content.toString());
|
|
515
|
+
if (content.test && content.service === this.serviceName) {
|
|
516
|
+
received = true;
|
|
517
|
+
}
|
|
518
|
+
mqClient.ack(msg);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// Wait for message
|
|
522
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
523
|
+
|
|
524
|
+
await mqClient.disconnect();
|
|
525
|
+
|
|
526
|
+
return {
|
|
527
|
+
name: 'Real Message Queue',
|
|
528
|
+
passed: received,
|
|
529
|
+
message: received ? 'Successfully sent and received test message' : 'Message not received'
|
|
530
|
+
};
|
|
531
|
+
} catch (error) {
|
|
532
|
+
return {
|
|
533
|
+
name: 'Real Message Queue',
|
|
534
|
+
passed: false,
|
|
535
|
+
error: error.message
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async testRealRegistry(config) {
|
|
541
|
+
try {
|
|
542
|
+
// Test registration with real registry
|
|
543
|
+
const response = await require('axios').post(
|
|
544
|
+
`${config.registryUrl}/register`,
|
|
545
|
+
{
|
|
546
|
+
name: `test-${this.serviceName}`,
|
|
547
|
+
version: '1.0.0-test',
|
|
548
|
+
url: config.serviceUrl,
|
|
549
|
+
openapi: this.openApiSpec
|
|
550
|
+
}
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
const passed = response.status === 200;
|
|
554
|
+
|
|
555
|
+
// Clean up test registration
|
|
556
|
+
if (passed) {
|
|
557
|
+
await require('axios').delete(
|
|
558
|
+
`${config.registryUrl}/unregister/test-${this.serviceName}`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return {
|
|
563
|
+
name: 'Real Registry',
|
|
564
|
+
passed,
|
|
565
|
+
message: 'Successfully registered and unregistered with real registry'
|
|
566
|
+
};
|
|
567
|
+
} catch (error) {
|
|
568
|
+
return {
|
|
569
|
+
name: 'Real Registry',
|
|
570
|
+
passed: false,
|
|
571
|
+
error: error.message
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
async testEndToEndWorkflow(config) {
|
|
577
|
+
try {
|
|
578
|
+
// Create real workflow runner
|
|
579
|
+
const runner = new WorkflowTestRunner({
|
|
580
|
+
mqClient: config.mqClient,
|
|
581
|
+
registry: config.registry,
|
|
582
|
+
timeout: 30000
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
const cookbook = this.getTestCookbook();
|
|
586
|
+
const workflow = await runner.runWorkflow(cookbook, {
|
|
587
|
+
test: true,
|
|
588
|
+
service: this.serviceName
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
const passed = workflow.status === 'completed';
|
|
592
|
+
|
|
593
|
+
return {
|
|
594
|
+
name: 'End-to-End Workflow',
|
|
595
|
+
passed,
|
|
596
|
+
workflowId: workflow.id,
|
|
597
|
+
status: workflow.status,
|
|
598
|
+
steps: workflow.results.length,
|
|
599
|
+
message: `Workflow ${workflow.status} with ${workflow.results.length} steps`
|
|
600
|
+
};
|
|
601
|
+
} catch (error) {
|
|
602
|
+
return {
|
|
603
|
+
name: 'End-to-End Workflow',
|
|
604
|
+
passed: false,
|
|
605
|
+
error: error.message
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// === Helper Methods ===
|
|
611
|
+
|
|
612
|
+
configureTestQueues(harness) {
|
|
613
|
+
// Configure test queue names
|
|
614
|
+
const testQueues = {
|
|
615
|
+
request: `test.${this.serviceName}.request`,
|
|
616
|
+
response: `test.${this.serviceName}.response`,
|
|
617
|
+
workflow: `test.workflow.${this.serviceName}`,
|
|
618
|
+
dlq: `test.${this.serviceName}.dlq`
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// Apply to mock MQ client
|
|
622
|
+
if (harness.mqClient) {
|
|
623
|
+
harness.mqClient.testQueues = testQueues;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return testQueues;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
getIntegrationTestConfig() {
|
|
630
|
+
// Get configuration for integration tests
|
|
631
|
+
return {
|
|
632
|
+
serviceUrl: process.env.TEST_SERVICE_URL || `http://localhost:${process.env.PORT || 3000}`,
|
|
633
|
+
mqUrl: process.env.TEST_MQ_URL || process.env.RABBITMQ_URL,
|
|
634
|
+
registryUrl: process.env.TEST_REGISTRY_URL || 'http://localhost:8080',
|
|
635
|
+
storageUrl: process.env.TEST_STORAGE_URL || process.env.MINIO_URL,
|
|
636
|
+
registry: null, // Would be real registry instance
|
|
637
|
+
mqClient: null // Would be real MQ client instance
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
getTestCookbook() {
|
|
642
|
+
// Generate test cookbook based on service OpenAPI
|
|
643
|
+
const steps = [];
|
|
644
|
+
|
|
645
|
+
if (this.openApiSpec?.paths) {
|
|
646
|
+
const endpoints = this.extractEndpoints(this.openApiSpec);
|
|
647
|
+
|
|
648
|
+
// Add first few endpoints as test steps
|
|
649
|
+
endpoints.slice(0, 3).forEach((endpoint, index) => {
|
|
650
|
+
steps.push({
|
|
651
|
+
id: `test-step-${index}`,
|
|
652
|
+
type: 'task',
|
|
653
|
+
service: this.serviceName,
|
|
654
|
+
operation: endpoint.operation?.operationId || endpoint.path,
|
|
655
|
+
input: {}
|
|
656
|
+
});
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return {
|
|
661
|
+
version: '1.0.0',
|
|
662
|
+
steps: steps.length > 0 ? steps : [
|
|
663
|
+
{
|
|
664
|
+
id: 'default-step',
|
|
665
|
+
type: 'task',
|
|
666
|
+
service: this.serviceName,
|
|
667
|
+
operation: 'health',
|
|
668
|
+
input: {}
|
|
669
|
+
}
|
|
670
|
+
]
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
extractEndpoints(openApiSpec) {
|
|
675
|
+
const endpoints = [];
|
|
676
|
+
if (!openApiSpec?.paths) return endpoints;
|
|
677
|
+
|
|
678
|
+
for (const [path, pathItem] of Object.entries(openApiSpec.paths)) {
|
|
679
|
+
for (const [method, operation] of Object.entries(pathItem)) {
|
|
680
|
+
if (method !== 'parameters' && !method.startsWith('x-')) {
|
|
681
|
+
endpoints.push({ path, method, operation });
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return endpoints;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Generate test report
|
|
691
|
+
*/
|
|
692
|
+
generateReport(results) {
|
|
693
|
+
const report = [];
|
|
694
|
+
|
|
695
|
+
report.push('=== Test Orchestration Report ===');
|
|
696
|
+
report.push(`Service: ${results.service}`);
|
|
697
|
+
report.push(`Timestamp: ${new Date(results.timestamp).toISOString()}`);
|
|
698
|
+
report.push('');
|
|
699
|
+
|
|
700
|
+
// Level results
|
|
701
|
+
for (const [level, levelResults] of Object.entries(results.levels)) {
|
|
702
|
+
report.push(`Level: ${level.toUpperCase()}`);
|
|
703
|
+
report.push(` Passed: ${levelResults.passed ? 'YES' : 'NO'}`);
|
|
704
|
+
report.push(` Coverage: ${levelResults.coverage}%`);
|
|
705
|
+
report.push(` Tests: ${levelResults.tests.length}`);
|
|
706
|
+
|
|
707
|
+
if (levelResults.tests) {
|
|
708
|
+
levelResults.tests.forEach(test => {
|
|
709
|
+
const status = test.passed ? '✓' : '✗';
|
|
710
|
+
report.push(` ${status} ${test.name}`);
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (levelResults.error) {
|
|
715
|
+
report.push(` Error: ${levelResults.error}`);
|
|
716
|
+
}
|
|
717
|
+
report.push('');
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
report.push(`Overall: ${results.passed ? 'PASSED' : 'FAILED'}`);
|
|
721
|
+
report.push(`Recommendation: ${results.recommendation}`);
|
|
722
|
+
|
|
723
|
+
return report.join('\n');
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
module.exports = TestOrchestrator;
|