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