@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,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Structure Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates that a business service has correct directory structure,
|
|
5
|
+
* configuration files, and follows OA Drive standards.
|
|
6
|
+
*
|
|
7
|
+
* Used by test helpers to provide clear error messages when something is wrong.
|
|
8
|
+
*
|
|
9
|
+
* @module validators/ServiceStructureValidator
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
class ServiceStructureValidator {
|
|
18
|
+
constructor(serviceRoot) {
|
|
19
|
+
this.serviceRoot = serviceRoot;
|
|
20
|
+
this.errors = [];
|
|
21
|
+
this.warnings = [];
|
|
22
|
+
this.info = [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validate complete service structure
|
|
27
|
+
*
|
|
28
|
+
* @returns {Object} Validation result
|
|
29
|
+
*/
|
|
30
|
+
validate() {
|
|
31
|
+
this.errors = [];
|
|
32
|
+
this.warnings = [];
|
|
33
|
+
this.info = [];
|
|
34
|
+
|
|
35
|
+
this.info.push(`Validating service structure: ${this.serviceRoot}`);
|
|
36
|
+
|
|
37
|
+
// 1. Validate directory structure
|
|
38
|
+
this.validateDirectoryStructure();
|
|
39
|
+
|
|
40
|
+
// 2. Validate configuration files
|
|
41
|
+
this.validateConfigurationFiles();
|
|
42
|
+
|
|
43
|
+
// 3. Validate package.json
|
|
44
|
+
this.validatePackageJson();
|
|
45
|
+
|
|
46
|
+
// 4. Validate source code structure
|
|
47
|
+
this.validateSourceStructure();
|
|
48
|
+
|
|
49
|
+
// 5. Validate test structure
|
|
50
|
+
this.validateTestStructure();
|
|
51
|
+
|
|
52
|
+
const valid = this.errors.length === 0;
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
valid,
|
|
56
|
+
errors: this.errors,
|
|
57
|
+
warnings: this.warnings,
|
|
58
|
+
info: this.info
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validate directory structure exists
|
|
64
|
+
*/
|
|
65
|
+
validateDirectoryStructure() {
|
|
66
|
+
const requiredDirs = [
|
|
67
|
+
{ path: 'conn-config', description: 'Configuration directory' },
|
|
68
|
+
{ path: 'src', description: 'Source code directory' },
|
|
69
|
+
{ path: 'tests', description: 'Tests directory' }
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const recommendedDirs = [
|
|
73
|
+
{ path: 'tests/unit', description: 'Unit tests' },
|
|
74
|
+
{ path: 'tests/integration', description: 'Integration tests' },
|
|
75
|
+
{ path: 'tests/cookbooks', description: 'Cookbook tests' }
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
for (const dir of requiredDirs) {
|
|
79
|
+
const fullPath = path.join(this.serviceRoot, dir.path);
|
|
80
|
+
if (!fs.existsSync(fullPath)) {
|
|
81
|
+
this.errors.push({
|
|
82
|
+
type: 'MISSING_DIRECTORY',
|
|
83
|
+
path: dir.path,
|
|
84
|
+
message: `Required directory missing: ${dir.path}`,
|
|
85
|
+
description: dir.description,
|
|
86
|
+
fix: `Create directory: mkdir -p ${dir.path}`
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
this.info.push(`✓ Found ${dir.description}: ${dir.path}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const dir of recommendedDirs) {
|
|
94
|
+
const fullPath = path.join(this.serviceRoot, dir.path);
|
|
95
|
+
if (!fs.existsSync(fullPath)) {
|
|
96
|
+
this.warnings.push({
|
|
97
|
+
type: 'MISSING_RECOMMENDED_DIRECTORY',
|
|
98
|
+
path: dir.path,
|
|
99
|
+
message: `Recommended directory missing: ${dir.path}`,
|
|
100
|
+
description: dir.description,
|
|
101
|
+
fix: `Create directory: mkdir -p ${dir.path}`
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Validate configuration files
|
|
109
|
+
*/
|
|
110
|
+
validateConfigurationFiles() {
|
|
111
|
+
// 1. Validate conn-config/config.json
|
|
112
|
+
const configPath = path.join(this.serviceRoot, 'conn-config/config.json');
|
|
113
|
+
if (!fs.existsSync(configPath)) {
|
|
114
|
+
this.errors.push({
|
|
115
|
+
type: 'MISSING_CONFIG',
|
|
116
|
+
path: 'conn-config/config.json',
|
|
117
|
+
message: 'Service configuration missing: conn-config/config.json',
|
|
118
|
+
fix: 'Create config.json with service metadata. See: /docs/standards/SERVICE_TEMPLATE.md'
|
|
119
|
+
});
|
|
120
|
+
} else {
|
|
121
|
+
try {
|
|
122
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
123
|
+
this.validateConfigStructure(config);
|
|
124
|
+
this.info.push('✓ Found valid config.json');
|
|
125
|
+
} catch (error) {
|
|
126
|
+
this.errors.push({
|
|
127
|
+
type: 'INVALID_CONFIG',
|
|
128
|
+
path: 'conn-config/config.json',
|
|
129
|
+
message: `Invalid JSON in config.json: ${error.message}`,
|
|
130
|
+
fix: 'Fix JSON syntax errors in config.json'
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 2. Validate conn-config/operations.json
|
|
136
|
+
const operationsPath = path.join(this.serviceRoot, 'conn-config/operations.json');
|
|
137
|
+
if (!fs.existsSync(operationsPath)) {
|
|
138
|
+
this.errors.push({
|
|
139
|
+
type: 'MISSING_OPERATIONS',
|
|
140
|
+
path: 'conn-config/operations.json',
|
|
141
|
+
message: 'Operations specification missing: conn-config/operations.json',
|
|
142
|
+
fix: 'Create operations.json. See: /docs/standards/OPERATIONS.md'
|
|
143
|
+
});
|
|
144
|
+
} else {
|
|
145
|
+
try {
|
|
146
|
+
const operations = JSON.parse(fs.readFileSync(operationsPath, 'utf-8'));
|
|
147
|
+
this.validateOperationsStructure(operations);
|
|
148
|
+
this.info.push('✓ Found valid operations.json');
|
|
149
|
+
} catch (error) {
|
|
150
|
+
this.errors.push({
|
|
151
|
+
type: 'INVALID_OPERATIONS',
|
|
152
|
+
path: 'conn-config/operations.json',
|
|
153
|
+
message: `Invalid JSON in operations.json: ${error.message}`,
|
|
154
|
+
fix: 'Fix JSON syntax errors in operations.json'
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Validate config.json structure
|
|
162
|
+
*/
|
|
163
|
+
validateConfigStructure(config) {
|
|
164
|
+
// Required fields
|
|
165
|
+
const requiredFields = [
|
|
166
|
+
'service.name',
|
|
167
|
+
'service.version',
|
|
168
|
+
'service.port'
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
for (const field of requiredFields) {
|
|
172
|
+
const parts = field.split('.');
|
|
173
|
+
let value = config;
|
|
174
|
+
for (const part of parts) {
|
|
175
|
+
value = value?.[part];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!value) {
|
|
179
|
+
this.errors.push({
|
|
180
|
+
type: 'MISSING_CONFIG_FIELD',
|
|
181
|
+
path: 'conn-config/config.json',
|
|
182
|
+
field: field,
|
|
183
|
+
message: `Required field missing in config.json: ${field}`,
|
|
184
|
+
fix: `Add "${field}" to config.json`
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Validate service name format
|
|
190
|
+
if (config.service?.name) {
|
|
191
|
+
if (!/^[a-z][a-z0-9-]*$/.test(config.service.name)) {
|
|
192
|
+
this.warnings.push({
|
|
193
|
+
type: 'INVALID_SERVICE_NAME',
|
|
194
|
+
path: 'conn-config/config.json',
|
|
195
|
+
field: 'service.name',
|
|
196
|
+
value: config.service.name,
|
|
197
|
+
message: `Service name should be lowercase kebab-case: ${config.service.name}`,
|
|
198
|
+
fix: 'Use lowercase letters, numbers, and hyphens only (e.g., "my-service")'
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Validate version format
|
|
204
|
+
if (config.service?.version) {
|
|
205
|
+
if (!/^\d+\.\d+\.\d+$/.test(config.service.version)) {
|
|
206
|
+
this.warnings.push({
|
|
207
|
+
type: 'INVALID_VERSION',
|
|
208
|
+
path: 'conn-config/config.json',
|
|
209
|
+
field: 'service.version',
|
|
210
|
+
value: config.service.version,
|
|
211
|
+
message: `Version should follow semantic versioning: ${config.service.version}`,
|
|
212
|
+
fix: 'Use format: MAJOR.MINOR.PATCH (e.g., "1.0.0")'
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Validate operations.json structure
|
|
220
|
+
*/
|
|
221
|
+
validateOperationsStructure(operations) {
|
|
222
|
+
if (!operations.operations) {
|
|
223
|
+
this.errors.push({
|
|
224
|
+
type: 'INVALID_OPERATIONS_STRUCTURE',
|
|
225
|
+
path: 'conn-config/operations.json',
|
|
226
|
+
message: 'operations.json must have "operations" key',
|
|
227
|
+
fix: 'Wrap operations in {"operations": {...}}. See: /docs/standards/OPERATIONS.md'
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const ops = operations.operations;
|
|
233
|
+
if (typeof ops !== 'object' || Array.isArray(ops)) {
|
|
234
|
+
this.errors.push({
|
|
235
|
+
type: 'INVALID_OPERATIONS_TYPE',
|
|
236
|
+
path: 'conn-config/operations.json',
|
|
237
|
+
message: 'operations must be an object',
|
|
238
|
+
fix: 'operations should be key-value pairs: {"operation-name": {...}}'
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (Object.keys(ops).length === 0) {
|
|
244
|
+
this.warnings.push({
|
|
245
|
+
type: 'NO_OPERATIONS',
|
|
246
|
+
path: 'conn-config/operations.json',
|
|
247
|
+
message: 'No operations defined',
|
|
248
|
+
fix: 'Add at least one operation to operations.json'
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Validate each operation
|
|
254
|
+
for (const [operationName, operationSpec] of Object.entries(ops)) {
|
|
255
|
+
this.validateOperation(operationName, operationSpec);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Validate single operation structure
|
|
261
|
+
*/
|
|
262
|
+
validateOperation(name, spec) {
|
|
263
|
+
const requiredFields = ['endpoint', 'method'];
|
|
264
|
+
|
|
265
|
+
for (const field of requiredFields) {
|
|
266
|
+
if (!spec[field]) {
|
|
267
|
+
this.errors.push({
|
|
268
|
+
type: 'MISSING_OPERATION_FIELD',
|
|
269
|
+
path: 'conn-config/operations.json',
|
|
270
|
+
operation: name,
|
|
271
|
+
field: field,
|
|
272
|
+
message: `Operation "${name}" missing required field: ${field}`,
|
|
273
|
+
fix: `Add "${field}" to operation "${name}"`
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Validate endpoint format
|
|
279
|
+
if (spec.endpoint && !spec.endpoint.startsWith('/')) {
|
|
280
|
+
this.errors.push({
|
|
281
|
+
type: 'INVALID_ENDPOINT',
|
|
282
|
+
path: 'conn-config/operations.json',
|
|
283
|
+
operation: name,
|
|
284
|
+
field: 'endpoint',
|
|
285
|
+
value: spec.endpoint,
|
|
286
|
+
message: `Endpoint must start with /: ${spec.endpoint}`,
|
|
287
|
+
fix: `Change endpoint to: /${spec.endpoint}`
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Validate HTTP method
|
|
292
|
+
const validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
|
|
293
|
+
if (spec.method && !validMethods.includes(spec.method)) {
|
|
294
|
+
this.errors.push({
|
|
295
|
+
type: 'INVALID_HTTP_METHOD',
|
|
296
|
+
path: 'conn-config/operations.json',
|
|
297
|
+
operation: name,
|
|
298
|
+
field: 'method',
|
|
299
|
+
value: spec.method,
|
|
300
|
+
message: `Invalid HTTP method: ${spec.method}`,
|
|
301
|
+
fix: `Use one of: ${validMethods.join(', ')}`
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Validate package.json
|
|
308
|
+
*/
|
|
309
|
+
validatePackageJson() {
|
|
310
|
+
const packagePath = path.join(this.serviceRoot, 'package.json');
|
|
311
|
+
if (!fs.existsSync(packagePath)) {
|
|
312
|
+
this.errors.push({
|
|
313
|
+
type: 'MISSING_PACKAGE_JSON',
|
|
314
|
+
path: 'package.json',
|
|
315
|
+
message: 'package.json missing',
|
|
316
|
+
fix: 'Create package.json: npm init'
|
|
317
|
+
});
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
|
323
|
+
|
|
324
|
+
// Check for required scripts
|
|
325
|
+
const recommendedScripts = [
|
|
326
|
+
{ name: 'test', description: 'Run tests' },
|
|
327
|
+
{ name: 'test:unit', description: 'Run unit tests' },
|
|
328
|
+
{ name: 'test:integration', description: 'Run integration tests' },
|
|
329
|
+
{ name: 'test:cookbooks', description: 'Run pre-validation cookbook tests' }
|
|
330
|
+
];
|
|
331
|
+
|
|
332
|
+
for (const script of recommendedScripts) {
|
|
333
|
+
if (!pkg.scripts?.[script.name]) {
|
|
334
|
+
this.warnings.push({
|
|
335
|
+
type: 'MISSING_NPM_SCRIPT',
|
|
336
|
+
path: 'package.json',
|
|
337
|
+
script: script.name,
|
|
338
|
+
message: `Recommended npm script missing: ${script.name}`,
|
|
339
|
+
description: script.description,
|
|
340
|
+
fix: `Add to package.json scripts: "${script.name}": "..."`
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Check for @onlineapps dependencies
|
|
346
|
+
const requiredDeps = [
|
|
347
|
+
'@onlineapps/service-wrapper',
|
|
348
|
+
'@onlineapps/conn-orch-validator'
|
|
349
|
+
];
|
|
350
|
+
|
|
351
|
+
for (const dep of requiredDeps) {
|
|
352
|
+
const hasDep = pkg.dependencies?.[dep] || pkg.devDependencies?.[dep];
|
|
353
|
+
if (!hasDep) {
|
|
354
|
+
this.warnings.push({
|
|
355
|
+
type: 'MISSING_DEPENDENCY',
|
|
356
|
+
path: 'package.json',
|
|
357
|
+
dependency: dep,
|
|
358
|
+
message: `Recommended dependency missing: ${dep}`,
|
|
359
|
+
fix: `Install: npm install ${dep}`
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
this.info.push('✓ Found valid package.json');
|
|
365
|
+
} catch (error) {
|
|
366
|
+
this.errors.push({
|
|
367
|
+
type: 'INVALID_PACKAGE_JSON',
|
|
368
|
+
path: 'package.json',
|
|
369
|
+
message: `Invalid JSON in package.json: ${error.message}`,
|
|
370
|
+
fix: 'Fix JSON syntax errors in package.json'
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Validate source code structure
|
|
377
|
+
*/
|
|
378
|
+
validateSourceStructure() {
|
|
379
|
+
const appPath = path.join(this.serviceRoot, 'src/app.js');
|
|
380
|
+
if (!fs.existsSync(appPath)) {
|
|
381
|
+
this.errors.push({
|
|
382
|
+
type: 'MISSING_APP',
|
|
383
|
+
path: 'src/app.js',
|
|
384
|
+
message: 'Express application missing: src/app.js',
|
|
385
|
+
fix: 'Create src/app.js with Express app. See: /docs/standards/SERVICE_TEMPLATE.md'
|
|
386
|
+
});
|
|
387
|
+
} else {
|
|
388
|
+
this.info.push('✓ Found src/app.js');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const indexPath = path.join(this.serviceRoot, 'index.js');
|
|
392
|
+
if (!fs.existsSync(indexPath)) {
|
|
393
|
+
this.warnings.push({
|
|
394
|
+
type: 'MISSING_INDEX',
|
|
395
|
+
path: 'index.js',
|
|
396
|
+
message: 'Entry point missing: index.js',
|
|
397
|
+
fix: 'Create index.js as service entry point'
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Validate test structure
|
|
404
|
+
*/
|
|
405
|
+
validateTestStructure() {
|
|
406
|
+
const cookbooksPath = path.join(this.serviceRoot, 'tests/cookbooks');
|
|
407
|
+
if (fs.existsSync(cookbooksPath)) {
|
|
408
|
+
const cookbookFiles = fs.readdirSync(cookbooksPath).filter(f => f.endsWith('.json'));
|
|
409
|
+
if (cookbookFiles.length === 0) {
|
|
410
|
+
this.warnings.push({
|
|
411
|
+
type: 'NO_COOKBOOK_TESTS',
|
|
412
|
+
path: 'tests/cookbooks',
|
|
413
|
+
message: 'No cookbook tests found in tests/cookbooks/',
|
|
414
|
+
fix: 'Create cookbook tests for pre-validation. See: /docs/standards/TESTING.md'
|
|
415
|
+
});
|
|
416
|
+
} else {
|
|
417
|
+
this.info.push(`✓ Found ${cookbookFiles.length} cookbook test(s)`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Format validation result as readable message
|
|
424
|
+
*/
|
|
425
|
+
static formatResult(result) {
|
|
426
|
+
const lines = [];
|
|
427
|
+
|
|
428
|
+
lines.push('');
|
|
429
|
+
lines.push('═══════════════════════════════════════════════════════════════');
|
|
430
|
+
lines.push(' SERVICE STRUCTURE VALIDATION');
|
|
431
|
+
lines.push('═══════════════════════════════════════════════════════════════');
|
|
432
|
+
lines.push('');
|
|
433
|
+
|
|
434
|
+
if (result.errors.length === 0 && result.warnings.length === 0) {
|
|
435
|
+
lines.push('✅ ALL CHECKS PASSED');
|
|
436
|
+
lines.push('');
|
|
437
|
+
for (const info of result.info) {
|
|
438
|
+
lines.push(` ${info}`);
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
// Errors
|
|
442
|
+
if (result.errors.length > 0) {
|
|
443
|
+
lines.push(`❌ ERRORS (${result.errors.length}):`);
|
|
444
|
+
lines.push('');
|
|
445
|
+
for (const error of result.errors) {
|
|
446
|
+
lines.push(` ✗ ${error.message}`);
|
|
447
|
+
lines.push(` Type: ${error.type}`);
|
|
448
|
+
if (error.path) lines.push(` File: ${error.path}`);
|
|
449
|
+
if (error.field) lines.push(` Field: ${error.field}`);
|
|
450
|
+
if (error.value) lines.push(` Value: ${error.value}`);
|
|
451
|
+
lines.push(` Fix: ${error.fix}`);
|
|
452
|
+
lines.push('');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Warnings
|
|
457
|
+
if (result.warnings.length > 0) {
|
|
458
|
+
lines.push(`⚠️ WARNINGS (${result.warnings.length}):`);
|
|
459
|
+
lines.push('');
|
|
460
|
+
for (const warning of result.warnings) {
|
|
461
|
+
lines.push(` ⚠ ${warning.message}`);
|
|
462
|
+
if (warning.fix) {
|
|
463
|
+
lines.push(` Suggestion: ${warning.fix}`);
|
|
464
|
+
}
|
|
465
|
+
lines.push('');
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Info
|
|
470
|
+
if (result.info.length > 0) {
|
|
471
|
+
lines.push('ℹ️ INFO:');
|
|
472
|
+
lines.push('');
|
|
473
|
+
for (const info of result.info) {
|
|
474
|
+
lines.push(` ${info}`);
|
|
475
|
+
}
|
|
476
|
+
lines.push('');
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
lines.push('═══════════════════════════════════════════════════════════════');
|
|
481
|
+
lines.push('');
|
|
482
|
+
|
|
483
|
+
return lines.join('\n');
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
module.exports = { ServiceStructureValidator };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ValidationProofCodec } = require('@onlineapps/service-validator-core');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ValidationProofGenerator - Generates validation proof from test results
|
|
7
|
+
*
|
|
8
|
+
* This is a THIN WRAPPER for use in testing workflows.
|
|
9
|
+
* All encoding logic is delegated to ValidationProofCodec.
|
|
10
|
+
*
|
|
11
|
+
* Responsibility: Bridge between test results and proof generation
|
|
12
|
+
* - Accept test results in test framework format
|
|
13
|
+
* - Transform to ValidationProofSchema format
|
|
14
|
+
* - Delegate encoding to ValidationProofCodec
|
|
15
|
+
*/
|
|
16
|
+
class ValidationProofGenerator {
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.serviceName = options.serviceName;
|
|
19
|
+
this.serviceVersion = options.serviceVersion;
|
|
20
|
+
this.validatorName = options.validatorName || '@onlineapps/conn-orch-validator';
|
|
21
|
+
this.validatorVersion = options.validatorVersion || '1.0.0';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generate validation proof from test results
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} testResults - Results from test execution
|
|
28
|
+
* @param {Object} dependencies - Service dependencies with versions
|
|
29
|
+
* @returns {Object} Validation proof with metadata
|
|
30
|
+
*/
|
|
31
|
+
generateProof(testResults, dependencies = {}) {
|
|
32
|
+
// Transform test results to ValidationProofSchema format
|
|
33
|
+
const validationData = {
|
|
34
|
+
serviceName: this.serviceName,
|
|
35
|
+
version: this.serviceVersion,
|
|
36
|
+
validator: this.validatorName,
|
|
37
|
+
validatorVersion: this.validatorVersion,
|
|
38
|
+
validatedAt: new Date().toISOString(),
|
|
39
|
+
durationMs: testResults.duration || 0,
|
|
40
|
+
testsRun: testResults.total || 0,
|
|
41
|
+
testsPassed: testResults.passed || 0,
|
|
42
|
+
testsFailed: testResults.failed || 0,
|
|
43
|
+
dependencies: dependencies || {}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Delegate encoding to centralized codec
|
|
47
|
+
// This ensures Generator and Verifier use EXACTLY the same algorithm
|
|
48
|
+
return ValidationProofCodec.encode(validationData);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extract test results summary
|
|
53
|
+
*/
|
|
54
|
+
extractTestSummary(testResults) {
|
|
55
|
+
return {
|
|
56
|
+
total: testResults.total || 0,
|
|
57
|
+
passed: testResults.passed || 0,
|
|
58
|
+
failed: testResults.failed || 0,
|
|
59
|
+
duration: testResults.duration || 0,
|
|
60
|
+
coverage: testResults.coverage || 0
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create validation error
|
|
66
|
+
*/
|
|
67
|
+
createValidationError(code, message, details = {}) {
|
|
68
|
+
return {
|
|
69
|
+
error: {
|
|
70
|
+
code,
|
|
71
|
+
message,
|
|
72
|
+
details,
|
|
73
|
+
timestamp: new Date().toISOString()
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = ValidationProofGenerator;
|
package/test-mq-flow.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Simple test script to verify MQ message flow
|
|
6
|
+
* Usage: node test-mq-flow.js
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const amqp = require('amqplib');
|
|
10
|
+
|
|
11
|
+
async function sendTestMessage() {
|
|
12
|
+
const rabbitUrl = 'amqp://guest:guest@localhost:33023';
|
|
13
|
+
const queueName = 'hello-service.workflow';
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Connect to RabbitMQ
|
|
17
|
+
const connection = await amqp.connect(rabbitUrl);
|
|
18
|
+
const channel = await connection.createChannel();
|
|
19
|
+
|
|
20
|
+
// Ensure queue exists
|
|
21
|
+
await channel.assertQueue(queueName, { durable: true });
|
|
22
|
+
|
|
23
|
+
// Create test message with cookbook
|
|
24
|
+
const testMessage = {
|
|
25
|
+
workflow_id: `test-${Date.now()}`,
|
|
26
|
+
cookbook: {
|
|
27
|
+
name: 'test-workflow',
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
steps: [
|
|
30
|
+
{
|
|
31
|
+
id: 'goodDay', // ID se použije jako operation name
|
|
32
|
+
type: 'task',
|
|
33
|
+
service: 'hello-service',
|
|
34
|
+
input: {
|
|
35
|
+
name: 'E2E Test'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
current_step: 'step1',
|
|
41
|
+
step_index: 0,
|
|
42
|
+
context: {}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
console.log('Sending test message:', JSON.stringify(testMessage, null, 2));
|
|
46
|
+
|
|
47
|
+
// Send message
|
|
48
|
+
channel.sendToQueue(
|
|
49
|
+
queueName,
|
|
50
|
+
Buffer.from(JSON.stringify(testMessage)),
|
|
51
|
+
{ persistent: true }
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
console.log(`Message sent to queue: ${queueName}`);
|
|
55
|
+
|
|
56
|
+
// Wait a bit to ensure message is sent
|
|
57
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
58
|
+
|
|
59
|
+
// Clean up
|
|
60
|
+
await channel.close();
|
|
61
|
+
await connection.close();
|
|
62
|
+
|
|
63
|
+
console.log('Test completed successfully');
|
|
64
|
+
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Test failed:', error.message);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Run the test
|
|
72
|
+
sendTestMessage();
|