@onlineapps/conn-orch-validator 7.0.0 → 8.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/CHANGELOG.md +2558 -2
- package/README.md +1038 -4
- package/docs/DESIGN.md +3 -1
- package/manifests/biz-service.manifest.json +658 -0
- package/manifests/library.manifest.json +324 -0
- package/package.json +12 -6
- package/src/CookbookTestRunner.js +408 -101
- package/src/CookbookTestUtils.js +7 -8
- package/src/ServiceReadinessValidator.js +10 -35
- package/src/ValidationOrchestrator.js +219 -71
- package/src/cli/biz-ci-gate.js +176 -33
- package/src/cli/oa-lint-scripts.js +221 -0
- package/src/cli/oa-sync-template.js +1020 -0
- package/src/cli/oa-validate.js +474 -0
- package/src/helpers/README.md +2 -1
- package/src/helpers/createServiceReadinessTests.js +60 -4
- package/src/index.js +33 -3
- package/src/lint/scripts/lintScripts.js +298 -0
- package/src/manifest/checks/composeRunnerBlock.js +222 -0
- package/src/manifest/checks/composeShape.js +165 -0
- package/src/manifest/checks/contractBridge.js +181 -0
- package/src/manifest/checks/discoveryOrphan.js +50 -0
- package/src/manifest/checks/docsLintBridge.js +553 -0
- package/src/manifest/checks/fileAbsent.js +35 -0
- package/src/manifest/checks/gitTracked.js +204 -0
- package/src/manifest/checks/index.js +111 -0
- package/src/manifest/checks/libraryContext.js +226 -0
- package/src/manifest/checks/libraryDocs.js +75 -0
- package/src/manifest/checks/libraryPackage.js +272 -0
- package/src/manifest/checks/librarySource.js +274 -0
- package/src/manifest/checks/libraryTests.js +121 -0
- package/src/manifest/checks/libraryWorkspace.js +293 -0
- package/src/manifest/checks/readmeRegion.js +135 -0
- package/src/manifest/checks/scriptHeaders.js +79 -0
- package/src/manifest/checks/serviceConfig.js +390 -0
- package/src/manifest/checks/serviceConnectors.js +81 -0
- package/src/manifest/checks/serviceDb.js +388 -0
- package/src/manifest/checks/serviceFiles.js +754 -0
- package/src/manifest/checks/serviceIdentityRows.js +351 -0
- package/src/manifest/checks/serviceRuntime.js +295 -0
- package/src/manifest/checks/serviceScripts.js +213 -0
- package/src/manifest/deployabilitySignal.js +121 -0
- package/src/manifest/discovery.js +386 -0
- package/src/manifest/loadManifest.js +62 -0
- package/src/manifest/manifestShape.js +446 -0
- package/src/manifest/report.js +245 -0
- package/src/manifest/runManifest.js +449 -0
- package/src/manifest/serviceIdentity.js +140 -0
- package/src/manifest/walk.js +74 -0
- package/src/manifest/workspaceRoot.js +242 -0
- package/src/mocks/MockMQClient.js +13 -30
- package/src/mocks/MockRegistry.js +4 -2
- package/src/mocks/MockStorage.js +4 -2
- package/src/sync/docsRegion.js +463 -0
- package/src/sync/generatedRegion.js +228 -0
- package/src/sync/readmeLocation.js +182 -0
- package/src/sync/readmePointer.js +477 -0
- package/src/sync/serviceTemplate.js +583 -0
- package/src/sync/sharedEnv.js +162 -0
- package/src/sync/uniformFiles.js +474 -0
- package/src/utils/bizCiGateContract.js +131 -7
- package/src/utils/connectorContract.js +97 -7
- package/src/utils/cookbookFormat.js +81 -40
- package/src/utils/deployContract.js +140 -9
- package/src/utils/envContract.js +57 -1
- package/src/utils/handlerRef.js +181 -0
- package/src/utils/installContract.js +287 -41
- package/src/utils/libCompat.js +29 -7
- package/src/utils/migrationOrder.js +163 -0
- package/src/utils/preValidation.js +20 -7
- package/src/utils/setupDatabase.js +194 -13
- package/src/utils/testCoverageContract.js +539 -0
- package/src/utils/testNamespace.js +247 -23
- package/src/utils/throwawaySchema.js +207 -0
- package/src/validators/ServiceStructureValidator.js +2 -1
- package/templates/business-service/.dockerignore +42 -0
- package/templates/business-service/.gitlab-ci.yml +290 -0
- package/templates/business-service/Dockerfile +27 -0
- package/templates/business-service/README.md +213 -0
- package/templates/business-service/config/biz-docs-lint.tree.json +10 -0
- package/templates/business-service/config/env-templates/__SERVICE_NAME__.env +4 -0
- package/templates/business-service/config/env-templates/shared.env +65 -0
- package/templates/business-service/config/service/config.json +14 -0
- package/templates/business-service/config/service/integration-contract.json +12 -0
- package/templates/business-service/config/service/operations.json +41 -0
- package/templates/business-service/docker-compose.production.yml +60 -0
- package/templates/business-service/docker-compose.yml +93 -0
- package/templates/business-service/docs/80-setup/INSTALL.md +101 -0
- package/templates/business-service/docs/80-setup/PLATFORM_MATRIX.md +65 -0
- package/templates/business-service/docs/80-setup/README.md +18 -0
- package/templates/business-service/docs/80-setup/VALIDATION.md +78 -0
- package/templates/business-service/docs/README.md +18 -0
- package/templates/business-service/gitignore +42 -0
- package/templates/business-service/index.js +10 -0
- package/templates/business-service/init.sh +54 -0
- package/templates/business-service/jest.config.js +6 -0
- package/templates/business-service/package.json.template +31 -0
- package/templates/business-service/scripts/verify-deploy-uniform.sh +180 -0
- package/templates/business-service/src/handlers/v3/echo.js +39 -0
- package/templates/business-service/tests/cookbooks/echo.json +36 -0
- package/templates/business-service/tests/unit/handler.test.js +78 -0
- package/src/WorkflowTestRunner.js +0 -402
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const EventEmitter = require('events');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* WorkflowTestRunner - Executes and tests cookbook workflows
|
|
7
|
-
*/
|
|
8
|
-
class WorkflowTestRunner extends EventEmitter {
|
|
9
|
-
constructor(options = {}) {
|
|
10
|
-
super();
|
|
11
|
-
this.mqClient = options.mqClient;
|
|
12
|
-
this.registry = options.registry;
|
|
13
|
-
this.storage = options.storage;
|
|
14
|
-
this.timeout = options.timeout || 30000;
|
|
15
|
-
this.debug = options.debug || false;
|
|
16
|
-
this.runningWorkflows = new Map();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Run workflow test
|
|
21
|
-
*/
|
|
22
|
-
async runWorkflow(cookbook, initialContext = {}) {
|
|
23
|
-
const workflowId = `test-workflow-${Date.now()}`;
|
|
24
|
-
|
|
25
|
-
const workflow = {
|
|
26
|
-
id: workflowId,
|
|
27
|
-
cookbook,
|
|
28
|
-
context: {
|
|
29
|
-
api_input: initialContext,
|
|
30
|
-
steps: {}
|
|
31
|
-
},
|
|
32
|
-
status: 'running',
|
|
33
|
-
startedAt: Date.now(),
|
|
34
|
-
currentStep: 0,
|
|
35
|
-
results: [],
|
|
36
|
-
errors: []
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
this.runningWorkflows.set(workflowId, workflow);
|
|
40
|
-
this.emit('workflow:started', { workflowId, cookbook });
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
// Execute steps sequentially
|
|
44
|
-
for (let i = 0; i < cookbook.steps.length; i++) {
|
|
45
|
-
const step = cookbook.steps[i];
|
|
46
|
-
workflow.currentStep = i;
|
|
47
|
-
|
|
48
|
-
const stepResult = await this.executeStep(step, workflow);
|
|
49
|
-
workflow.results.push(stepResult);
|
|
50
|
-
|
|
51
|
-
if (!stepResult.success) {
|
|
52
|
-
workflow.status = 'failed';
|
|
53
|
-
workflow.errors.push(stepResult.error);
|
|
54
|
-
this.emit('workflow:failed', { workflowId, error: stepResult.error });
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Update context with step result
|
|
59
|
-
workflow.context.steps[step.id] = stepResult.data;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (workflow.status !== 'failed') {
|
|
63
|
-
workflow.status = 'completed';
|
|
64
|
-
workflow.completedAt = Date.now();
|
|
65
|
-
this.emit('workflow:completed', { workflowId, results: workflow.results });
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
} catch (error) {
|
|
69
|
-
workflow.status = 'error';
|
|
70
|
-
workflow.errors.push(error.message);
|
|
71
|
-
this.emit('workflow:error', { workflowId, error: error.message });
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return workflow;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Execute individual step
|
|
79
|
-
*/
|
|
80
|
-
async executeStep(step, workflow) {
|
|
81
|
-
const startTime = Date.now();
|
|
82
|
-
|
|
83
|
-
this.emit('step:started', {
|
|
84
|
-
workflowId: workflow.id,
|
|
85
|
-
stepId: step.id,
|
|
86
|
-
type: step.type
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const result = {
|
|
90
|
-
stepId: step.id,
|
|
91
|
-
type: step.type,
|
|
92
|
-
success: false,
|
|
93
|
-
data: null,
|
|
94
|
-
error: null,
|
|
95
|
-
duration: 0
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
switch (step.type) {
|
|
100
|
-
case 'task':
|
|
101
|
-
result.data = await this.executeTaskStep(step, workflow);
|
|
102
|
-
break;
|
|
103
|
-
|
|
104
|
-
case 'foreach':
|
|
105
|
-
result.data = await this.executeForeachStep(step, workflow);
|
|
106
|
-
break;
|
|
107
|
-
|
|
108
|
-
case 'switch':
|
|
109
|
-
result.data = await this.executeSwitchStep(step, workflow);
|
|
110
|
-
break;
|
|
111
|
-
|
|
112
|
-
case 'fork_join':
|
|
113
|
-
result.data = await this.executeForkJoinStep(step, workflow);
|
|
114
|
-
break;
|
|
115
|
-
|
|
116
|
-
default:
|
|
117
|
-
throw new Error(`Unknown step type: ${step.type}`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
result.success = true;
|
|
121
|
-
result.duration = Date.now() - startTime;
|
|
122
|
-
|
|
123
|
-
this.emit('step:completed', {
|
|
124
|
-
workflowId: workflow.id,
|
|
125
|
-
stepId: step.id,
|
|
126
|
-
result
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
} catch (error) {
|
|
130
|
-
result.error = error.message;
|
|
131
|
-
result.duration = Date.now() - startTime;
|
|
132
|
-
|
|
133
|
-
this.emit('step:failed', {
|
|
134
|
-
workflowId: workflow.id,
|
|
135
|
-
stepId: step.id,
|
|
136
|
-
error: error.message
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return result;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Execute task step
|
|
145
|
-
*/
|
|
146
|
-
async executeTaskStep(step, workflow) {
|
|
147
|
-
const { service, operation, input } = step;
|
|
148
|
-
|
|
149
|
-
// Resolve input using JSONPath
|
|
150
|
-
const resolvedInput = this.resolveInput(input, workflow.context);
|
|
151
|
-
|
|
152
|
-
if (this.debug) {
|
|
153
|
-
// WHICH operation ran, never WHAT it carried: the resolved input is the
|
|
154
|
-
// workflow's payload, and printing it to stdout put whatever the cookbook
|
|
155
|
-
// held — identifiers, amounts, anything — into the terminal and every log
|
|
156
|
-
// collector behind it. This class takes no logger (constructor above), so
|
|
157
|
-
// the trace stays on console; giving it one is an API change and the
|
|
158
|
-
// owner's call, not a side effect of this fix.
|
|
159
|
-
console.log(`Executing task: ${service}.${operation}`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Get service from registry
|
|
163
|
-
const serviceInfo = await this.registry.getService(service);
|
|
164
|
-
if (!serviceInfo) {
|
|
165
|
-
throw new Error(`Service not found: ${service}`);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Simulate API call or send to queue
|
|
169
|
-
if (this.mqClient) {
|
|
170
|
-
// Send to service queue
|
|
171
|
-
const queueName = `service.${service}.request`;
|
|
172
|
-
await this.mqClient.publish(queueName, {
|
|
173
|
-
workflow_id: workflow.id,
|
|
174
|
-
step_id: step.id,
|
|
175
|
-
operation,
|
|
176
|
-
input: resolvedInput
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// Wait for response (with timeout)
|
|
180
|
-
const response = await this.waitForResponse(
|
|
181
|
-
workflow.id,
|
|
182
|
-
step.id,
|
|
183
|
-
this.timeout
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
return response;
|
|
187
|
-
} else {
|
|
188
|
-
// Simulate response
|
|
189
|
-
return {
|
|
190
|
-
processed: true,
|
|
191
|
-
operation,
|
|
192
|
-
input: resolvedInput,
|
|
193
|
-
output: { simulated: true }
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Execute foreach step
|
|
200
|
-
*/
|
|
201
|
-
async executeForeachStep(step, workflow) {
|
|
202
|
-
const { items, body } = step;
|
|
203
|
-
|
|
204
|
-
// Resolve items array
|
|
205
|
-
const itemsArray = this.resolveInput(items, workflow.context);
|
|
206
|
-
if (!Array.isArray(itemsArray)) {
|
|
207
|
-
throw new Error('Foreach items must resolve to an array');
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const results = [];
|
|
211
|
-
for (const item of itemsArray) {
|
|
212
|
-
// Create context with current item
|
|
213
|
-
const itemContext = {
|
|
214
|
-
...workflow.context,
|
|
215
|
-
current_item: item
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
// Execute body step for each item
|
|
219
|
-
const itemWorkflow = { ...workflow, context: itemContext };
|
|
220
|
-
const result = await this.executeStep(body, itemWorkflow);
|
|
221
|
-
results.push(result);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return { items: itemsArray.length, results };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Execute switch step
|
|
229
|
-
*/
|
|
230
|
-
async executeSwitchStep(step, workflow) {
|
|
231
|
-
const { condition, cases, default: defaultCase } = step;
|
|
232
|
-
|
|
233
|
-
// Resolve condition value
|
|
234
|
-
const conditionValue = this.resolveInput(condition, workflow.context);
|
|
235
|
-
|
|
236
|
-
// Find matching case
|
|
237
|
-
const matchingCase = cases.find(c => c.value === conditionValue);
|
|
238
|
-
const stepToExecute = matchingCase ? matchingCase.step : defaultCase;
|
|
239
|
-
|
|
240
|
-
if (!stepToExecute) {
|
|
241
|
-
throw new Error(`No matching case for condition value: ${conditionValue}`);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Execute the selected step
|
|
245
|
-
const result = await this.executeStep(stepToExecute, workflow);
|
|
246
|
-
|
|
247
|
-
return {
|
|
248
|
-
condition: conditionValue,
|
|
249
|
-
executed: stepToExecute.id,
|
|
250
|
-
result
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Execute fork-join step
|
|
256
|
-
*/
|
|
257
|
-
async executeForkJoinStep(step, workflow) {
|
|
258
|
-
const { branches, join } = step;
|
|
259
|
-
|
|
260
|
-
// Execute all branches in parallel
|
|
261
|
-
const branchPromises = branches.map(branch =>
|
|
262
|
-
this.executeStep(branch, workflow)
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
const results = await Promise.all(branchPromises);
|
|
266
|
-
|
|
267
|
-
// Apply join strategy
|
|
268
|
-
let joinedResult;
|
|
269
|
-
switch (join.strategy) {
|
|
270
|
-
case 'merge':
|
|
271
|
-
joinedResult = this.mergeResults(results);
|
|
272
|
-
break;
|
|
273
|
-
case 'first':
|
|
274
|
-
joinedResult = results[0];
|
|
275
|
-
break;
|
|
276
|
-
case 'all':
|
|
277
|
-
joinedResult = results;
|
|
278
|
-
break;
|
|
279
|
-
default:
|
|
280
|
-
joinedResult = results;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return {
|
|
284
|
-
branches: branches.length,
|
|
285
|
-
strategy: join.strategy,
|
|
286
|
-
result: joinedResult
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Resolve input using JSONPath or direct value
|
|
292
|
-
*/
|
|
293
|
-
resolveInput(input, context) {
|
|
294
|
-
if (!input) return null;
|
|
295
|
-
|
|
296
|
-
if (typeof input === 'string' && input.startsWith('$')) {
|
|
297
|
-
// JSONPath resolution
|
|
298
|
-
return this.resolveJsonPath(input, context);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (typeof input === 'object' && input !== null) {
|
|
302
|
-
// Recursively resolve object properties
|
|
303
|
-
const resolved = {};
|
|
304
|
-
for (const [key, value] of Object.entries(input)) {
|
|
305
|
-
resolved[key] = this.resolveInput(value, context);
|
|
306
|
-
}
|
|
307
|
-
return resolved;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return input;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Simple JSONPath resolution
|
|
315
|
-
*/
|
|
316
|
-
resolveJsonPath(path, context) {
|
|
317
|
-
// Remove leading $
|
|
318
|
-
const pathParts = path.substring(1).split('.');
|
|
319
|
-
|
|
320
|
-
let current = context;
|
|
321
|
-
for (const part of pathParts) {
|
|
322
|
-
if (current === null || current === undefined) {
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
current = current[part];
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
return current;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Wait for step response from queue
|
|
333
|
-
*/
|
|
334
|
-
async waitForResponse(workflowId, stepId, timeout) {
|
|
335
|
-
return new Promise((resolve, reject) => {
|
|
336
|
-
const timeoutId = setTimeout(() => {
|
|
337
|
-
reject(new Error(`Timeout waiting for response: ${stepId}`));
|
|
338
|
-
}, timeout);
|
|
339
|
-
|
|
340
|
-
// Listen for response on response queue
|
|
341
|
-
const responseQueue = `workflow.${workflowId}.response`;
|
|
342
|
-
|
|
343
|
-
this.mqClient.consume(responseQueue, (msg) => {
|
|
344
|
-
const message = JSON.parse(msg.content.toString());
|
|
345
|
-
|
|
346
|
-
if (message.step_id === stepId) {
|
|
347
|
-
clearTimeout(timeoutId);
|
|
348
|
-
this.mqClient.ack(msg);
|
|
349
|
-
resolve(message.result);
|
|
350
|
-
}
|
|
351
|
-
});
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Merge results from parallel branches
|
|
357
|
-
*/
|
|
358
|
-
mergeResults(results) {
|
|
359
|
-
const merged = {};
|
|
360
|
-
|
|
361
|
-
results.forEach((result, index) => {
|
|
362
|
-
if (result.data && typeof result.data === 'object') {
|
|
363
|
-
Object.assign(merged, result.data);
|
|
364
|
-
} else {
|
|
365
|
-
merged[`branch_${index}`] = result.data;
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
return merged;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Get workflow status
|
|
374
|
-
*/
|
|
375
|
-
getWorkflowStatus(workflowId) {
|
|
376
|
-
return this.runningWorkflows.get(workflowId);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Stop workflow
|
|
381
|
-
*/
|
|
382
|
-
stopWorkflow(workflowId) {
|
|
383
|
-
const workflow = this.runningWorkflows.get(workflowId);
|
|
384
|
-
if (workflow && workflow.status === 'running') {
|
|
385
|
-
workflow.status = 'stopped';
|
|
386
|
-
this.emit('workflow:stopped', { workflowId });
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Clear completed workflows
|
|
392
|
-
*/
|
|
393
|
-
clearCompleted() {
|
|
394
|
-
for (const [id, workflow] of this.runningWorkflows.entries()) {
|
|
395
|
-
if (workflow.status === 'completed' || workflow.status === 'failed') {
|
|
396
|
-
this.runningWorkflows.delete(id);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
module.exports = WorkflowTestRunner;
|