@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.
package/src/index.js CHANGED
@@ -1,17 +1,25 @@
1
1
  'use strict';
2
2
 
3
- // Mock infrastructure components (load first - no dependencies)
3
+ /**
4
+ * @module @onlineapps/conn-orch-validator
5
+ * @description Service validation framework using the operations.json contract.
6
+ *
7
+ * Production entry point: {@link ValidationOrchestrator} (6-step pre-validation
8
+ * driven by operations.json — OpenAPI path iteration is NOT supported).
9
+ *
10
+ * @see /api/docs/standards/operations-registry-contract.md §3 (operations.json)
11
+ * @see /api/docs/standards/validation-probe-contract.md (validation probes)
12
+ * @see /api/docs/architecture/validator.md (validator architecture)
13
+ */
14
+
4
15
  const MockMQClient = require('./mocks/MockMQClient');
5
16
  const MockRegistry = require('./mocks/MockRegistry');
6
17
  const MockStorage = require('./mocks/MockStorage');
7
18
 
8
- // Base test utilities (no circular dependencies)
9
- const ServiceValidator = require('./ServiceValidator');
10
19
  const CookbookTestUtils = require('./CookbookTestUtils');
11
20
  const { ServiceStructureValidator } = require('./validators/ServiceStructureValidator');
12
21
  const ValidationProofGenerator = require('./validators/ValidationProofGenerator');
13
22
 
14
- // Test runners (depend on mocks)
15
23
  let CookbookTestRunner;
16
24
  try {
17
25
  CookbookTestRunner = require('./CookbookTestRunner');
@@ -20,43 +28,29 @@ try {
20
28
  }
21
29
  const WorkflowTestRunner = require('./WorkflowTestRunner');
22
30
 
23
- // Orchestrators (depend on validators and runners)
24
31
  const ServiceReadinessValidator = require('./ServiceReadinessValidator');
25
- const TestOrchestrator = require('./TestOrchestrator');
26
- const ServiceTestHarness = require('./ServiceTestHarness');
27
32
  const ValidationOrchestrator = require('./ValidationOrchestrator');
28
33
 
29
- // Test helpers (generic test suite creators)
30
34
  const { createServiceReadinessTests } = require('./helpers/createServiceReadinessTests');
31
35
  const { createPreValidationTests } = require('./helpers/createPreValidationTests');
32
36
 
33
- // Export all components with getters to avoid circular dependency issues
34
37
  module.exports = {
35
- // Mocks
36
38
  get MockMQClient() { return MockMQClient; },
37
39
  get MockRegistry() { return MockRegistry; },
38
40
  get MockStorage() { return MockStorage; },
39
41
 
40
- // Test utilities
41
- get ServiceTestHarness() { return ServiceTestHarness; },
42
- get ServiceValidator() { return ServiceValidator; },
43
42
  get WorkflowTestRunner() { return WorkflowTestRunner; },
44
43
  get CookbookTestUtils() { return CookbookTestUtils; },
45
44
  get ServiceReadinessValidator() { return ServiceReadinessValidator; },
46
- get TestOrchestrator() { return TestOrchestrator; },
47
45
  get CookbookTestRunner() { return CookbookTestRunner; },
48
46
  get ValidationProofGenerator() { return ValidationProofGenerator; },
49
47
  get ServiceStructureValidator() { return ServiceStructureValidator; },
50
48
  get ValidationOrchestrator() { return ValidationOrchestrator; },
51
49
 
52
- // Test helpers (NEW - generic test suite creators)
53
50
  get createServiceReadinessTests() { return createServiceReadinessTests; },
54
51
  get createPreValidationTests() { return createPreValidationTests; },
55
52
 
56
- // Convenience factory functions
57
- createTestHarness: (options) => new ServiceTestHarness(options),
58
- createValidator: (options) => new ServiceValidator(options),
59
53
  createMockMQ: () => new MockMQClient(),
60
54
  createMockRegistry: () => new MockRegistry(),
61
55
  createMockStorage: () => new MockStorage()
62
- };
56
+ };
@@ -410,7 +410,8 @@ class ServiceStructureValidator {
410
410
  }
411
411
 
412
412
  /**
413
- * Validate operations.json structure
413
+ * Validate operations.json structure (v3 — handler registry dispatch).
414
+ * v3 schema per biz-service-invocation-model.md §5.3.
414
415
  */
415
416
  validateOperationsStructure(operations) {
416
417
  if (!operations.operations) {
@@ -418,7 +419,7 @@ class ServiceStructureValidator {
418
419
  type: 'INVALID_OPERATIONS_STRUCTURE',
419
420
  path: 'config/service/operations.json',
420
421
  message: 'operations.json must have "operations" key',
421
- fix: 'Wrap operations in {"operations": {...}}. See: /docs/standards/OPERATIONS.md'
422
+ fix: 'Wrap operations in {"operations": {...}}. See: biz-service-invocation-model.md §5.3'
422
423
  });
423
424
  return;
424
425
  }
@@ -434,6 +435,17 @@ class ServiceStructureValidator {
434
435
  return;
435
436
  }
436
437
 
438
+ if (operations.schema_version && operations.schema_version !== '3.0') {
439
+ this.warnings.push({
440
+ type: 'SCHEMA_VERSION_MISMATCH',
441
+ path: 'config/service/operations.json',
442
+ field: 'schema_version',
443
+ value: operations.schema_version,
444
+ message: `operations.json schema_version is "${operations.schema_version}" — expected "3.0"`,
445
+ fix: 'Update schema_version to "3.0" (RFC §5.3)'
446
+ });
447
+ }
448
+
437
449
  if (Object.keys(ops).length === 0) {
438
450
  this.warnings.push({
439
451
  type: 'NO_OPERATIONS',
@@ -444,17 +456,17 @@ class ServiceStructureValidator {
444
456
  return;
445
457
  }
446
458
 
447
- // Validate each operation
448
459
  for (const [operationName, operationSpec] of Object.entries(ops)) {
449
460
  this.validateOperation(operationName, operationSpec);
450
461
  }
451
462
  }
452
463
 
453
464
  /**
454
- * Validate single operation structure
465
+ * Validate single operation structure (v3).
466
+ * Required: handler, bundle_scope. Forbidden (v2): endpoint, method, path.
455
467
  */
456
468
  validateOperation(name, spec) {
457
- const requiredFields = ['endpoint', 'method'];
469
+ const requiredFields = ['handler', 'bundle_scope'];
458
470
 
459
471
  for (const field of requiredFields) {
460
472
  if (!spec[field]) {
@@ -462,39 +474,53 @@ class ServiceStructureValidator {
462
474
  type: 'MISSING_OPERATION_FIELD',
463
475
  path: 'config/service/operations.json',
464
476
  operation: name,
465
- field: field,
477
+ field,
466
478
  message: `Operation "${name}" missing required field: ${field}`,
467
- fix: `Add "${field}" to operation "${name}"`
479
+ fix: `Add "${field}" to operation "${name}" (v3 schema — RFC §5.3)`
468
480
  });
469
481
  }
470
482
  }
471
483
 
472
- // Validate endpoint format
473
- if (spec.endpoint && !spec.endpoint.startsWith('/')) {
484
+ if (spec.handler && !/^handlers\/[a-zA-Z0-9_\/-]+#[a-zA-Z_][a-zA-Z0-9_]*$/.test(spec.handler)) {
474
485
  this.errors.push({
475
- type: 'INVALID_ENDPOINT',
486
+ type: 'INVALID_HANDLER_REF',
476
487
  path: 'config/service/operations.json',
477
488
  operation: name,
478
- field: 'endpoint',
479
- value: spec.endpoint,
480
- message: `Endpoint must start with /: ${spec.endpoint}`,
481
- fix: `Change endpoint to: /${spec.endpoint}`
489
+ field: 'handler',
490
+ value: spec.handler,
491
+ message: `Handler must be in form 'handlers/<path>#<exportName>': ${spec.handler}`,
492
+ fix: `Change handler to form 'handlers/v3/<file>#<exportName>'`
482
493
  });
483
494
  }
484
495
 
485
- // Validate HTTP method
486
- const validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
487
- if (spec.method && !validMethods.includes(spec.method)) {
496
+ const validScopes = ['platform', 'tenant', 'workspace'];
497
+ if (spec.bundle_scope && !validScopes.includes(spec.bundle_scope)) {
488
498
  this.errors.push({
489
- type: 'INVALID_HTTP_METHOD',
499
+ type: 'INVALID_BUNDLE_SCOPE',
490
500
  path: 'config/service/operations.json',
491
501
  operation: name,
492
- field: 'method',
493
- value: spec.method,
494
- message: `Invalid HTTP method: ${spec.method}`,
495
- fix: `Use one of: ${validMethods.join(', ')}`
502
+ field: 'bundle_scope',
503
+ value: spec.bundle_scope,
504
+ message: `Invalid bundle_scope: ${spec.bundle_scope}`,
505
+ fix: `Use one of: ${validScopes.join(', ')}`
496
506
  });
497
507
  }
508
+
509
+ // Reject v2 HTTP-routing fields (clean break per ARCHITECTURE_PRINCIPLES.md §11).
510
+ const forbiddenV2Fields = ['endpoint', 'method', 'path'];
511
+ for (const forbidden of forbiddenV2Fields) {
512
+ if (forbidden in spec) {
513
+ this.errors.push({
514
+ type: 'V2_FIELD_PRESENT',
515
+ path: 'config/service/operations.json',
516
+ operation: name,
517
+ field: forbidden,
518
+ value: spec[forbidden],
519
+ message: `Operation "${name}" has retired v2 field "${forbidden}" — not allowed in v3 schema`,
520
+ fix: `Remove "${forbidden}" — v3 dispatches via handler registry (RFC §5.3, §5.9)`
521
+ });
522
+ }
523
+ }
498
524
  }
499
525
 
500
526
  /**
@@ -1,250 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Example: How service-wrapper uses connector-testing
5
- * Shows the integration between service-wrapper and testing framework
6
- */
7
-
8
- const { ServiceReadinessValidator, TestOrchestrator } = require('../src');
9
-
10
- /**
11
- * Enhanced ServiceWrapper with integrated testing
12
- */
13
- class ServiceWrapperWithTesting {
14
- constructor(options) {
15
- this.service = options.service;
16
- this.serviceName = options.serviceName;
17
- this.openApiSpec = options.openApiSpec;
18
- this.config = options.config;
19
-
20
- // Testing configuration
21
- this.testingEnabled = options.testingEnabled !== false;
22
- this.testLevel = options.testLevel || 'component'; // unit, component, or integration
23
- this.validator = new ServiceReadinessValidator();
24
- }
25
-
26
- /**
27
- * Start service with validation
28
- */
29
- async start() {
30
- console.log(`Starting ${this.serviceName}...`);
31
-
32
- // Step 1: Pre-start validation
33
- if (this.testingEnabled) {
34
- const validation = await this.validateBeforeStart();
35
-
36
- if (!validation.ready) {
37
- throw new Error(`Service validation failed: ${validation.recommendation}`);
38
- }
39
-
40
- console.log(`Validation passed with score: ${validation.score}/100`);
41
- }
42
-
43
- // Step 2: Start the actual service
44
- await this.startService();
45
-
46
- // Step 3: Post-start verification
47
- if (this.testingEnabled) {
48
- await this.verifyAfterStart();
49
- }
50
-
51
- console.log(`${this.serviceName} started successfully`);
52
- }
53
-
54
- /**
55
- * Validate service before starting
56
- */
57
- async validateBeforeStart() {
58
- console.log('Running pre-start validation...');
59
-
60
- // Use TestOrchestrator for comprehensive testing
61
- const orchestrator = new TestOrchestrator({
62
- service: this.service,
63
- serviceName: this.serviceName,
64
- openApiSpec: this.openApiSpec
65
- });
66
-
67
- // Run tests based on configured level
68
- let testResults;
69
- switch (this.testLevel) {
70
- case 'unit':
71
- testResults = await orchestrator.runUnitTests();
72
- break;
73
- case 'component':
74
- testResults = await orchestrator.runComponentTests();
75
- break;
76
- case 'integration':
77
- testResults = await orchestrator.runIntegrationTests();
78
- break;
79
- default:
80
- testResults = await orchestrator.runComponentTests();
81
- }
82
-
83
- // Convert test results to readiness format
84
- return {
85
- ready: testResults.passed,
86
- score: testResults.coverage || 0,
87
- recommendation: testResults.passed
88
- ? 'Service passed validation'
89
- : 'Service failed validation tests',
90
- details: testResults
91
- };
92
- }
93
-
94
- /**
95
- * Start the actual service
96
- */
97
- async startService() {
98
- // This would contain the actual service startup logic
99
- // - Start Express server
100
- // - Connect to MQ
101
- // - Register with registry
102
- // - etc.
103
-
104
- return new Promise((resolve) => {
105
- const PORT = this.config.port || 3000;
106
- this.server = this.service.listen(PORT, () => {
107
- console.log(`Service listening on port ${PORT}`);
108
- resolve();
109
- });
110
- });
111
- }
112
-
113
- /**
114
- * Verify service after start
115
- */
116
- async verifyAfterStart() {
117
- console.log('Running post-start verification...');
118
-
119
- const serviceUrl = `http://localhost:${this.config.port || 3000}`;
120
-
121
- // Quick readiness check
122
- const readiness = await this.validator.validateReadiness({
123
- name: this.serviceName,
124
- url: serviceUrl,
125
- openApiSpec: this.openApiSpec,
126
- healthEndpoint: '/health'
127
- });
128
-
129
- if (!readiness.ready) {
130
- console.warn('Post-start verification found issues:', readiness.errors);
131
- }
132
-
133
- return readiness;
134
- }
135
-
136
- /**
137
- * Stop service
138
- */
139
- async stop() {
140
- if (this.server) {
141
- await new Promise((resolve) => {
142
- this.server.close(resolve);
143
- });
144
- }
145
- }
146
- }
147
-
148
- // === Example Usage ===
149
-
150
- async function demonstrateUsage() {
151
- const express = require('express');
152
-
153
- // Create a simple service
154
- const app = express();
155
- app.use(express.json());
156
-
157
- app.get('/health', (req, res) => {
158
- res.json({ status: 'healthy' });
159
- });
160
-
161
- app.post('/api/process', (req, res) => {
162
- res.json({ processed: true, data: req.body });
163
- });
164
-
165
- // OpenAPI spec
166
- const openApiSpec = {
167
- openapi: '3.0.0',
168
- info: { title: 'Demo Service', version: '1.0.0' },
169
- paths: {
170
- '/health': {
171
- get: {
172
- operationId: 'healthCheck',
173
- responses: {
174
- '200': {
175
- content: {
176
- 'application/json': {
177
- schema: {
178
- type: 'object',
179
- properties: {
180
- status: { type: 'string' }
181
- }
182
- }
183
- }
184
- }
185
- }
186
- }
187
- }
188
- },
189
- '/api/process': {
190
- post: {
191
- operationId: 'processData',
192
- requestBody: {
193
- content: {
194
- 'application/json': {
195
- schema: { type: 'object' }
196
- }
197
- }
198
- },
199
- responses: {
200
- '200': {
201
- content: {
202
- 'application/json': {
203
- schema: { type: 'object' }
204
- }
205
- }
206
- }
207
- }
208
- }
209
- }
210
- }
211
- };
212
-
213
- // Create wrapper with testing
214
- const wrapper = new ServiceWrapperWithTesting({
215
- service: app,
216
- serviceName: 'demo-service',
217
- openApiSpec,
218
- config: {
219
- port: 3333
220
- },
221
- testingEnabled: true,
222
- testLevel: 'unit' // Start with unit tests for speed
223
- });
224
-
225
- try {
226
- // Start service (will run validation first)
227
- await wrapper.start();
228
-
229
- console.log('\nService is running with validation!');
230
- console.log('Try: curl http://localhost:3333/health');
231
-
232
- // Keep running for demo
233
- await new Promise(resolve => setTimeout(resolve, 5000));
234
-
235
- // Stop service
236
- await wrapper.stop();
237
- console.log('\nService stopped');
238
-
239
- } catch (error) {
240
- console.error('Failed to start service:', error.message);
241
- process.exit(1);
242
- }
243
- }
244
-
245
- // Run demonstration
246
- if (require.main === module) {
247
- demonstrateUsage().catch(console.error);
248
- }
249
-
250
- module.exports = { ServiceWrapperWithTesting };
@@ -1,144 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Example: Three-tier testing strategy
5
- * Shows how to use connector-testing for comprehensive service validation
6
- */
7
-
8
- const { TestOrchestrator } = require('../src');
9
- const express = require('express');
10
-
11
- // Example service
12
- const app = express();
13
- app.use(express.json());
14
-
15
- app.get('/health', (req, res) => {
16
- res.json({ status: 'healthy', service: 'example-service' });
17
- });
18
-
19
- app.post('/process', (req, res) => {
20
- res.json({
21
- processed: true,
22
- input: req.body,
23
- timestamp: Date.now()
24
- });
25
- });
26
-
27
- app.get('/status', (req, res) => {
28
- res.json({
29
- running: true,
30
- uptime: process.uptime()
31
- });
32
- });
33
-
34
- // OpenAPI specification
35
- const openApiSpec = {
36
- openapi: '3.0.0',
37
- info: {
38
- title: 'Example Service',
39
- version: '1.0.0'
40
- },
41
- paths: {
42
- '/health': {
43
- get: {
44
- operationId: 'healthCheck',
45
- responses: {
46
- '200': {
47
- content: {
48
- 'application/json': {
49
- schema: {
50
- type: 'object',
51
- properties: {
52
- status: { type: 'string' },
53
- service: { type: 'string' }
54
- }
55
- }
56
- }
57
- }
58
- }
59
- }
60
- }
61
- },
62
- '/process': {
63
- post: {
64
- operationId: 'processData',
65
- requestBody: {
66
- content: {
67
- 'application/json': {
68
- schema: { type: 'object' }
69
- }
70
- }
71
- },
72
- responses: {
73
- '200': {
74
- content: {
75
- 'application/json': {
76
- schema: {
77
- type: 'object',
78
- properties: {
79
- processed: { type: 'boolean' },
80
- input: { type: 'object' },
81
- timestamp: { type: 'number' }
82
- }
83
- }
84
- }
85
- }
86
- }
87
- }
88
- }
89
- },
90
- '/status': {
91
- get: {
92
- operationId: 'getStatus',
93
- responses: {
94
- '200': {
95
- content: {
96
- 'application/json': {
97
- schema: {
98
- type: 'object',
99
- properties: {
100
- running: { type: 'boolean' },
101
- uptime: { type: 'number' }
102
- }
103
- }
104
- }
105
- }
106
- }
107
- }
108
- }
109
- }
110
- }
111
- };
112
-
113
- async function runExample() {
114
- console.log('=== Three-Tier Testing Example ===\n');
115
-
116
- // Create test orchestrator
117
- const orchestrator = new TestOrchestrator({
118
- service: app,
119
- serviceName: 'example-service',
120
- openApiSpec,
121
- logger: console
122
- });
123
-
124
- // Run all three levels of testing
125
- console.log('Starting comprehensive test suite...\n');
126
- const results = await orchestrator.runAllTests();
127
-
128
- // Generate and display report
129
- const report = orchestrator.generateReport(results);
130
- console.log('\n' + report);
131
-
132
- // Return exit code based on results
133
- process.exit(results.passed ? 0 : 1);
134
- }
135
-
136
- // Run if executed directly
137
- if (require.main === module) {
138
- runExample().catch(error => {
139
- console.error('Test failed:', error);
140
- process.exit(1);
141
- });
142
- }
143
-
144
- module.exports = { app, openApiSpec, runExample };