@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,506 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { ValidationProofCodec, FingerprintUtils } = require('@onlineapps/service-validator-core');
|
|
6
|
+
const { ServiceStructureValidator } = require('./validators/ServiceStructureValidator');
|
|
7
|
+
const ServiceReadinessValidator = require('./ServiceReadinessValidator');
|
|
8
|
+
const CookbookTestRunner = require('./CookbookTestRunner');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ValidationOrchestrator
|
|
12
|
+
*
|
|
13
|
+
* Orchestrates complete service validation (Tier 1 Pre-Validation)
|
|
14
|
+
* Runs 6-step validation process and generates validation proof.
|
|
15
|
+
*
|
|
16
|
+
* Called automatically by ServiceWrapper during initialization.
|
|
17
|
+
*/
|
|
18
|
+
class ValidationOrchestrator {
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.serviceRoot = options.serviceRoot;
|
|
21
|
+
this.serviceName = options.serviceName;
|
|
22
|
+
this.serviceVersion = options.serviceVersion;
|
|
23
|
+
this.logger = options.logger || console;
|
|
24
|
+
|
|
25
|
+
// Paths
|
|
26
|
+
this.configPath = path.join(this.serviceRoot, 'conn-config');
|
|
27
|
+
this.runtimePath = path.join(this.serviceRoot, 'conn-runtime');
|
|
28
|
+
this.proofPath = path.join(this.runtimePath, 'validation-proof.json');
|
|
29
|
+
|
|
30
|
+
// Validators
|
|
31
|
+
this.structureValidator = new ServiceStructureValidator(this.serviceRoot);
|
|
32
|
+
this.readinessValidator = new ServiceReadinessValidator();
|
|
33
|
+
this.cookbookRunner = new CookbookTestRunner({
|
|
34
|
+
servicePath: this.serviceRoot,
|
|
35
|
+
serviceName: this.serviceName
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Main validation entry point
|
|
41
|
+
* Checks if proof exists and is valid, or runs full validation
|
|
42
|
+
*/
|
|
43
|
+
async validate() {
|
|
44
|
+
this.logger.info('[ValidationOrchestrator] Starting validation...');
|
|
45
|
+
|
|
46
|
+
// Check if proof exists and is valid
|
|
47
|
+
const existingProof = await this.loadExistingProof();
|
|
48
|
+
if (existingProof && await this.isProofValid(existingProof)) {
|
|
49
|
+
this.logger.info('[ValidationOrchestrator] ✓ Valid proof found, skipping validation');
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
proofExists: true,
|
|
53
|
+
proof: existingProof,
|
|
54
|
+
skipped: true
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Run full validation
|
|
59
|
+
this.logger.info('[ValidationOrchestrator] No valid proof, running validation...');
|
|
60
|
+
return await this.runFullValidation();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Load existing validation proof from conn-runtime/
|
|
65
|
+
*/
|
|
66
|
+
async loadExistingProof() {
|
|
67
|
+
try {
|
|
68
|
+
if (!fs.existsSync(this.proofPath)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const content = fs.readFileSync(this.proofPath, 'utf8');
|
|
73
|
+
const proof = JSON.parse(content);
|
|
74
|
+
|
|
75
|
+
return proof;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
this.logger.warn(`[ValidationOrchestrator] Failed to load proof: ${error.message}`);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if proof is still valid
|
|
84
|
+
* Valid = correct fingerprint + not expired
|
|
85
|
+
*/
|
|
86
|
+
async isProofValid(proof) {
|
|
87
|
+
try {
|
|
88
|
+
// Calculate current fingerprint
|
|
89
|
+
const currentFingerprint = await this.calculateFingerprint();
|
|
90
|
+
|
|
91
|
+
// Check if fingerprint matches
|
|
92
|
+
if (proof.fingerprint !== currentFingerprint) {
|
|
93
|
+
this.logger.info('[ValidationOrchestrator] Fingerprint mismatch (service changed)');
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Check expiration (proof valid for 30 days)
|
|
98
|
+
const proofDate = new Date(proof.validatedAt);
|
|
99
|
+
const now = new Date();
|
|
100
|
+
const daysSinceValidation = (now - proofDate) / (1000 * 60 * 60 * 24);
|
|
101
|
+
|
|
102
|
+
if (daysSinceValidation > 30) {
|
|
103
|
+
this.logger.info('[ValidationOrchestrator] Proof expired (>30 days old)');
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Verify proof signature
|
|
108
|
+
const isValid = ValidationProofCodec.verify(proof);
|
|
109
|
+
if (!isValid) {
|
|
110
|
+
this.logger.warn('[ValidationOrchestrator] Proof signature invalid');
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return true;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
this.logger.error(`[ValidationOrchestrator] Proof validation error: ${error.message}`);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Calculate service fingerprint
|
|
123
|
+
* Based on: version + operations.json + dependencies + config
|
|
124
|
+
*/
|
|
125
|
+
async calculateFingerprint() {
|
|
126
|
+
try {
|
|
127
|
+
const configFile = path.join(this.configPath, 'config.json');
|
|
128
|
+
const operationsFile = path.join(this.configPath, 'operations.json');
|
|
129
|
+
const packageFile = path.join(this.serviceRoot, 'package.json');
|
|
130
|
+
|
|
131
|
+
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
132
|
+
const operations = JSON.parse(fs.readFileSync(operationsFile, 'utf8'));
|
|
133
|
+
const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
|
|
134
|
+
|
|
135
|
+
// Extract @onlineapps/* dependencies
|
|
136
|
+
const deps = {};
|
|
137
|
+
if (pkg.dependencies) {
|
|
138
|
+
Object.keys(pkg.dependencies).forEach(name => {
|
|
139
|
+
if (name.startsWith('@onlineapps/')) {
|
|
140
|
+
deps[name] = pkg.dependencies[name];
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return FingerprintUtils.calculate({
|
|
146
|
+
serviceVersion: config.service?.version || pkg.version,
|
|
147
|
+
operations: operations,
|
|
148
|
+
dependencies: deps,
|
|
149
|
+
configHash: FingerprintUtils.hashObject(config)
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
throw new Error(`Failed to calculate fingerprint: ${error.message}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Run full 6-step validation process
|
|
158
|
+
*/
|
|
159
|
+
async runFullValidation() {
|
|
160
|
+
const startTime = Date.now();
|
|
161
|
+
const results = {
|
|
162
|
+
success: true,
|
|
163
|
+
steps: {},
|
|
164
|
+
errors: [],
|
|
165
|
+
warnings: [],
|
|
166
|
+
totalTests: 0,
|
|
167
|
+
passedTests: 0,
|
|
168
|
+
failedTests: 0
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
// Step 1: Service Structure
|
|
173
|
+
this.logger.info('[ValidationOrchestrator] Step 1/6: Service Structure');
|
|
174
|
+
results.steps.structure = await this.validateStructure();
|
|
175
|
+
if (!results.steps.structure.valid) {
|
|
176
|
+
results.success = false;
|
|
177
|
+
results.errors.push(...results.steps.structure.errors);
|
|
178
|
+
// Fail fast - can't continue without proper structure
|
|
179
|
+
return this.finalizeResults(results, startTime);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Step 2: Config Files
|
|
183
|
+
this.logger.info('[ValidationOrchestrator] Step 2/6: Config Files');
|
|
184
|
+
results.steps.config = await this.validateConfig();
|
|
185
|
+
if (!results.steps.config.valid) {
|
|
186
|
+
results.success = false;
|
|
187
|
+
results.errors.push(...results.steps.config.errors);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Step 3: Operations Compliance
|
|
191
|
+
this.logger.info('[ValidationOrchestrator] Step 3/6: Operations Compliance');
|
|
192
|
+
results.steps.operations = await this.validateOperations();
|
|
193
|
+
if (!results.steps.operations.valid) {
|
|
194
|
+
results.success = false;
|
|
195
|
+
results.errors.push(...results.steps.operations.errors);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Step 4: Cookbook Tests
|
|
199
|
+
this.logger.info('[ValidationOrchestrator] Step 4/6: Cookbook Tests');
|
|
200
|
+
results.steps.cookbooks = await this.runCookbookTests();
|
|
201
|
+
results.totalTests += results.steps.cookbooks.total || 0;
|
|
202
|
+
results.passedTests += results.steps.cookbooks.passed || 0;
|
|
203
|
+
results.failedTests += results.steps.cookbooks.failed || 0;
|
|
204
|
+
if (!results.steps.cookbooks.success) {
|
|
205
|
+
results.success = false;
|
|
206
|
+
results.errors.push(...(results.steps.cookbooks.errors || []));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Step 5: Service Readiness
|
|
210
|
+
this.logger.info('[ValidationOrchestrator] Step 5/6: Service Readiness');
|
|
211
|
+
results.steps.readiness = await this.validateReadiness();
|
|
212
|
+
if (!results.steps.readiness.valid) {
|
|
213
|
+
results.success = false;
|
|
214
|
+
results.errors.push(...results.steps.readiness.errors);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Step 6: Connector Integration
|
|
218
|
+
this.logger.info('[ValidationOrchestrator] Step 6/6: Connector Integration');
|
|
219
|
+
results.steps.connectors = this.validateConnectors();
|
|
220
|
+
if (!results.steps.connectors.valid) {
|
|
221
|
+
// Non-critical - just warnings
|
|
222
|
+
results.warnings.push(...results.steps.connectors.warnings);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Finalize and generate proof if successful
|
|
226
|
+
return await this.finalizeResults(results, startTime);
|
|
227
|
+
|
|
228
|
+
} catch (error) {
|
|
229
|
+
this.logger.error(`[ValidationOrchestrator] Validation failed: ${error.message}`);
|
|
230
|
+
results.success = false;
|
|
231
|
+
results.errors.push(`Validation error: ${error.message}`);
|
|
232
|
+
return this.finalizeResults(results, startTime);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Step 1: Validate service structure
|
|
238
|
+
*/
|
|
239
|
+
async validateStructure() {
|
|
240
|
+
try {
|
|
241
|
+
const result = this.structureValidator.validate();
|
|
242
|
+
|
|
243
|
+
this.logger.info(`[ValidationOrchestrator] ✓ Service structure: ${result.valid ? 'PASS' : 'FAIL'}`);
|
|
244
|
+
return result;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
return {
|
|
247
|
+
valid: false,
|
|
248
|
+
errors: [`Structure validation failed: ${error.message}`]
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Step 2: Validate config files
|
|
255
|
+
*/
|
|
256
|
+
async validateConfig() {
|
|
257
|
+
const errors = [];
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
// Validate config.json
|
|
261
|
+
const configFile = path.join(this.configPath, 'config.json');
|
|
262
|
+
if (!fs.existsSync(configFile)) {
|
|
263
|
+
errors.push('config.json not found');
|
|
264
|
+
} else {
|
|
265
|
+
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
266
|
+
if (!config.service?.name) errors.push('config.json missing service.name');
|
|
267
|
+
if (!config.service?.version) errors.push('config.json missing service.version');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Validate operations.json
|
|
271
|
+
const operationsFile = path.join(this.configPath, 'operations.json');
|
|
272
|
+
if (!fs.existsSync(operationsFile)) {
|
|
273
|
+
errors.push('operations.json not found');
|
|
274
|
+
} else {
|
|
275
|
+
const operations = JSON.parse(fs.readFileSync(operationsFile, 'utf8'));
|
|
276
|
+
if (!operations.operations || Object.keys(operations.operations).length === 0) {
|
|
277
|
+
errors.push('operations.json has no operations defined');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this.logger.info(`[ValidationOrchestrator] ✓ Config files: ${errors.length === 0 ? 'PASS' : 'FAIL'}`);
|
|
282
|
+
return {
|
|
283
|
+
valid: errors.length === 0,
|
|
284
|
+
errors: errors
|
|
285
|
+
};
|
|
286
|
+
} catch (error) {
|
|
287
|
+
return {
|
|
288
|
+
valid: false,
|
|
289
|
+
errors: [`Config validation failed: ${error.message}`]
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Step 3: Validate operations compliance
|
|
296
|
+
*/
|
|
297
|
+
async validateOperations() {
|
|
298
|
+
try {
|
|
299
|
+
const operationsFile = path.join(this.configPath, 'operations.json');
|
|
300
|
+
const operations = JSON.parse(fs.readFileSync(operationsFile, 'utf8'));
|
|
301
|
+
const errors = [];
|
|
302
|
+
|
|
303
|
+
// Validate each operation
|
|
304
|
+
for (const [opName, opDef] of Object.entries(operations.operations || {})) {
|
|
305
|
+
if (!opDef.endpoint) {
|
|
306
|
+
errors.push(`Operation ${opName}: missing endpoint`);
|
|
307
|
+
} else if (!opDef.endpoint.startsWith('/')) {
|
|
308
|
+
errors.push(`Operation ${opName}: endpoint must start with /`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (!opDef.method) {
|
|
312
|
+
errors.push(`Operation ${opName}: missing method`);
|
|
313
|
+
} else if (!['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(opDef.method)) {
|
|
314
|
+
errors.push(`Operation ${opName}: invalid HTTP method`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (!opDef.input) {
|
|
318
|
+
errors.push(`Operation ${opName}: missing input schema`);
|
|
319
|
+
}
|
|
320
|
+
if (!opDef.output) {
|
|
321
|
+
errors.push(`Operation ${opName}: missing output schema`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
this.logger.info(`[ValidationOrchestrator] ✓ Operations compliance: ${errors.length === 0 ? 'PASS' : 'FAIL'}`);
|
|
326
|
+
return {
|
|
327
|
+
valid: errors.length === 0,
|
|
328
|
+
errors: errors
|
|
329
|
+
};
|
|
330
|
+
} catch (error) {
|
|
331
|
+
return {
|
|
332
|
+
valid: false,
|
|
333
|
+
errors: [`Operations validation failed: ${error.message}`]
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Step 4: Run cookbook tests
|
|
340
|
+
*/
|
|
341
|
+
async runCookbookTests() {
|
|
342
|
+
try {
|
|
343
|
+
const cookbooksPath = path.join(this.serviceRoot, 'tests', 'cookbooks');
|
|
344
|
+
|
|
345
|
+
if (!fs.existsSync(cookbooksPath)) {
|
|
346
|
+
this.logger.warn('[ValidationOrchestrator] No cookbook tests found (tests/cookbooks/ missing)');
|
|
347
|
+
return {
|
|
348
|
+
success: true,
|
|
349
|
+
total: 0,
|
|
350
|
+
passed: 0,
|
|
351
|
+
failed: 0,
|
|
352
|
+
warnings: ['No cookbook tests found']
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const result = await this.cookbookRunner.runCookbooks(cookbooksPath);
|
|
357
|
+
|
|
358
|
+
this.logger.info(`[ValidationOrchestrator] ✓ Cookbook tests: ${result.passed}/${result.total} passed`);
|
|
359
|
+
return {
|
|
360
|
+
success: result.failed === 0,
|
|
361
|
+
total: result.total,
|
|
362
|
+
passed: result.passed,
|
|
363
|
+
failed: result.failed,
|
|
364
|
+
errors: result.failed > 0 ? [`${result.failed} cookbook test(s) failed`] : []
|
|
365
|
+
};
|
|
366
|
+
} catch (error) {
|
|
367
|
+
return {
|
|
368
|
+
success: false,
|
|
369
|
+
total: 0,
|
|
370
|
+
passed: 0,
|
|
371
|
+
failed: 0,
|
|
372
|
+
errors: [`Cookbook tests failed: ${error.message}`]
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Step 5: Validate service readiness
|
|
379
|
+
*/
|
|
380
|
+
async validateReadiness() {
|
|
381
|
+
try {
|
|
382
|
+
// Load operations.json
|
|
383
|
+
const operationsFile = path.join(this.configPath, 'operations.json');
|
|
384
|
+
const operations = JSON.parse(fs.readFileSync(operationsFile, 'utf8'));
|
|
385
|
+
|
|
386
|
+
const result = await this.readinessValidator.validateReadiness({
|
|
387
|
+
serviceRoot: this.serviceRoot,
|
|
388
|
+
serviceName: this.serviceName,
|
|
389
|
+
operations: operations.operations
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
this.logger.info(`[ValidationOrchestrator] ✓ Service readiness: ${result.ready ? 'PASS' : 'FAIL'}`);
|
|
393
|
+
return {
|
|
394
|
+
valid: result.ready,
|
|
395
|
+
errors: result.ready ? [] : result.errors || ['Service not ready']
|
|
396
|
+
};
|
|
397
|
+
} catch (error) {
|
|
398
|
+
return {
|
|
399
|
+
valid: false,
|
|
400
|
+
errors: [`Readiness validation failed: ${error.message}`]
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Step 6: Validate connector integration
|
|
407
|
+
*/
|
|
408
|
+
validateConnectors() {
|
|
409
|
+
try {
|
|
410
|
+
// This is validated implicitly through cookbook tests
|
|
411
|
+
// which test ServiceWrapper + all connectors
|
|
412
|
+
|
|
413
|
+
this.logger.info('[ValidationOrchestrator] ✓ Connector integration: PASS (via cookbook tests)');
|
|
414
|
+
return {
|
|
415
|
+
valid: true,
|
|
416
|
+
warnings: []
|
|
417
|
+
};
|
|
418
|
+
} catch (error) {
|
|
419
|
+
return {
|
|
420
|
+
valid: false,
|
|
421
|
+
warnings: [`Connector validation warning: ${error.message}`]
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Finalize validation results and generate proof if successful
|
|
428
|
+
*/
|
|
429
|
+
async finalizeResults(results, startTime) {
|
|
430
|
+
const duration = Date.now() - startTime;
|
|
431
|
+
results.durationMs = duration;
|
|
432
|
+
|
|
433
|
+
if (results.success) {
|
|
434
|
+
try {
|
|
435
|
+
// Generate validation proof
|
|
436
|
+
const fingerprint = await this.calculateFingerprint();
|
|
437
|
+
const proof = {
|
|
438
|
+
serviceName: this.serviceName,
|
|
439
|
+
serviceVersion: this.serviceVersion,
|
|
440
|
+
fingerprint: fingerprint,
|
|
441
|
+
validator: '@onlineapps/conn-orch-validator',
|
|
442
|
+
validatorVersion: require('../package.json').version,
|
|
443
|
+
validatedAt: new Date().toISOString(),
|
|
444
|
+
testsRun: results.totalTests,
|
|
445
|
+
testsPassed: results.passedTests,
|
|
446
|
+
testsFailed: results.failedTests,
|
|
447
|
+
durationMs: duration,
|
|
448
|
+
steps: {
|
|
449
|
+
structure: results.steps.structure?.valid || false,
|
|
450
|
+
config: results.steps.config?.valid || false,
|
|
451
|
+
operations: results.steps.operations?.valid || false,
|
|
452
|
+
cookbooks: results.steps.cookbooks?.success || false,
|
|
453
|
+
readiness: results.steps.readiness?.valid || false,
|
|
454
|
+
connectors: results.steps.connectors?.valid || false
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// Encode proof using ValidationProofCodec
|
|
459
|
+
const encodedProof = ValidationProofCodec.encode(proof);
|
|
460
|
+
|
|
461
|
+
// Save proof to conn-runtime/
|
|
462
|
+
await this.saveProof(encodedProof);
|
|
463
|
+
|
|
464
|
+
results.proof = encodedProof;
|
|
465
|
+
results.fingerprint = fingerprint;
|
|
466
|
+
|
|
467
|
+
this.logger.info(`[ValidationOrchestrator] ✅ Validation PASSED (${duration}ms)`);
|
|
468
|
+
this.logger.info(`[ValidationOrchestrator] Proof saved to: ${this.proofPath}`);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
this.logger.error(`[ValidationOrchestrator] Failed to generate proof: ${error.message}`);
|
|
471
|
+
results.success = false;
|
|
472
|
+
results.errors.push(`Proof generation failed: ${error.message}`);
|
|
473
|
+
}
|
|
474
|
+
} else {
|
|
475
|
+
this.logger.error(`[ValidationOrchestrator] ❌ Validation FAILED (${duration}ms)`);
|
|
476
|
+
this.logger.error(`[ValidationOrchestrator] Errors: ${results.errors.join(', ')}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return results;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Save validation proof to conn-runtime/validation-proof.json
|
|
484
|
+
*/
|
|
485
|
+
async saveProof(proof) {
|
|
486
|
+
try {
|
|
487
|
+
// Ensure conn-runtime/ directory exists
|
|
488
|
+
if (!fs.existsSync(this.runtimePath)) {
|
|
489
|
+
fs.mkdirSync(this.runtimePath, { recursive: true });
|
|
490
|
+
|
|
491
|
+
// Create .gitkeep
|
|
492
|
+
const gitkeepPath = path.join(this.runtimePath, '.gitkeep');
|
|
493
|
+
fs.writeFileSync(gitkeepPath, '# Runtime data generated by connectors\n');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Save proof
|
|
497
|
+
fs.writeFileSync(this.proofPath, JSON.stringify(proof, null, 2));
|
|
498
|
+
|
|
499
|
+
this.logger.info(`[ValidationOrchestrator] Proof saved: ${this.proofPath}`);
|
|
500
|
+
} catch (error) {
|
|
501
|
+
throw new Error(`Failed to save proof: ${error.message}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
module.exports = ValidationOrchestrator;
|