@onlineapps/conn-orch-validator 2.0.32 → 2.0.34

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