@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.
Files changed (102) hide show
  1. package/CHANGELOG.md +2558 -2
  2. package/README.md +1038 -4
  3. package/docs/DESIGN.md +3 -1
  4. package/manifests/biz-service.manifest.json +658 -0
  5. package/manifests/library.manifest.json +324 -0
  6. package/package.json +12 -6
  7. package/src/CookbookTestRunner.js +408 -101
  8. package/src/CookbookTestUtils.js +7 -8
  9. package/src/ServiceReadinessValidator.js +10 -35
  10. package/src/ValidationOrchestrator.js +219 -71
  11. package/src/cli/biz-ci-gate.js +176 -33
  12. package/src/cli/oa-lint-scripts.js +221 -0
  13. package/src/cli/oa-sync-template.js +1020 -0
  14. package/src/cli/oa-validate.js +474 -0
  15. package/src/helpers/README.md +2 -1
  16. package/src/helpers/createServiceReadinessTests.js +60 -4
  17. package/src/index.js +33 -3
  18. package/src/lint/scripts/lintScripts.js +298 -0
  19. package/src/manifest/checks/composeRunnerBlock.js +222 -0
  20. package/src/manifest/checks/composeShape.js +165 -0
  21. package/src/manifest/checks/contractBridge.js +181 -0
  22. package/src/manifest/checks/discoveryOrphan.js +50 -0
  23. package/src/manifest/checks/docsLintBridge.js +553 -0
  24. package/src/manifest/checks/fileAbsent.js +35 -0
  25. package/src/manifest/checks/gitTracked.js +204 -0
  26. package/src/manifest/checks/index.js +111 -0
  27. package/src/manifest/checks/libraryContext.js +226 -0
  28. package/src/manifest/checks/libraryDocs.js +75 -0
  29. package/src/manifest/checks/libraryPackage.js +272 -0
  30. package/src/manifest/checks/librarySource.js +274 -0
  31. package/src/manifest/checks/libraryTests.js +121 -0
  32. package/src/manifest/checks/libraryWorkspace.js +293 -0
  33. package/src/manifest/checks/readmeRegion.js +135 -0
  34. package/src/manifest/checks/scriptHeaders.js +79 -0
  35. package/src/manifest/checks/serviceConfig.js +390 -0
  36. package/src/manifest/checks/serviceConnectors.js +81 -0
  37. package/src/manifest/checks/serviceDb.js +388 -0
  38. package/src/manifest/checks/serviceFiles.js +754 -0
  39. package/src/manifest/checks/serviceIdentityRows.js +351 -0
  40. package/src/manifest/checks/serviceRuntime.js +295 -0
  41. package/src/manifest/checks/serviceScripts.js +213 -0
  42. package/src/manifest/deployabilitySignal.js +121 -0
  43. package/src/manifest/discovery.js +386 -0
  44. package/src/manifest/loadManifest.js +62 -0
  45. package/src/manifest/manifestShape.js +446 -0
  46. package/src/manifest/report.js +245 -0
  47. package/src/manifest/runManifest.js +449 -0
  48. package/src/manifest/serviceIdentity.js +140 -0
  49. package/src/manifest/walk.js +74 -0
  50. package/src/manifest/workspaceRoot.js +242 -0
  51. package/src/mocks/MockMQClient.js +13 -30
  52. package/src/mocks/MockRegistry.js +4 -2
  53. package/src/mocks/MockStorage.js +4 -2
  54. package/src/sync/docsRegion.js +463 -0
  55. package/src/sync/generatedRegion.js +228 -0
  56. package/src/sync/readmeLocation.js +182 -0
  57. package/src/sync/readmePointer.js +477 -0
  58. package/src/sync/serviceTemplate.js +583 -0
  59. package/src/sync/sharedEnv.js +162 -0
  60. package/src/sync/uniformFiles.js +474 -0
  61. package/src/utils/bizCiGateContract.js +131 -7
  62. package/src/utils/connectorContract.js +97 -7
  63. package/src/utils/cookbookFormat.js +81 -40
  64. package/src/utils/deployContract.js +140 -9
  65. package/src/utils/envContract.js +57 -1
  66. package/src/utils/handlerRef.js +181 -0
  67. package/src/utils/installContract.js +287 -41
  68. package/src/utils/libCompat.js +29 -7
  69. package/src/utils/migrationOrder.js +163 -0
  70. package/src/utils/preValidation.js +20 -7
  71. package/src/utils/setupDatabase.js +194 -13
  72. package/src/utils/testCoverageContract.js +539 -0
  73. package/src/utils/testNamespace.js +247 -23
  74. package/src/utils/throwawaySchema.js +207 -0
  75. package/src/validators/ServiceStructureValidator.js +2 -1
  76. package/templates/business-service/.dockerignore +42 -0
  77. package/templates/business-service/.gitlab-ci.yml +290 -0
  78. package/templates/business-service/Dockerfile +27 -0
  79. package/templates/business-service/README.md +213 -0
  80. package/templates/business-service/config/biz-docs-lint.tree.json +10 -0
  81. package/templates/business-service/config/env-templates/__SERVICE_NAME__.env +4 -0
  82. package/templates/business-service/config/env-templates/shared.env +65 -0
  83. package/templates/business-service/config/service/config.json +14 -0
  84. package/templates/business-service/config/service/integration-contract.json +12 -0
  85. package/templates/business-service/config/service/operations.json +41 -0
  86. package/templates/business-service/docker-compose.production.yml +60 -0
  87. package/templates/business-service/docker-compose.yml +93 -0
  88. package/templates/business-service/docs/80-setup/INSTALL.md +101 -0
  89. package/templates/business-service/docs/80-setup/PLATFORM_MATRIX.md +65 -0
  90. package/templates/business-service/docs/80-setup/README.md +18 -0
  91. package/templates/business-service/docs/80-setup/VALIDATION.md +78 -0
  92. package/templates/business-service/docs/README.md +18 -0
  93. package/templates/business-service/gitignore +42 -0
  94. package/templates/business-service/index.js +10 -0
  95. package/templates/business-service/init.sh +54 -0
  96. package/templates/business-service/jest.config.js +6 -0
  97. package/templates/business-service/package.json.template +31 -0
  98. package/templates/business-service/scripts/verify-deploy-uniform.sh +180 -0
  99. package/templates/business-service/src/handlers/v3/echo.js +39 -0
  100. package/templates/business-service/tests/cookbooks/echo.json +36 -0
  101. package/templates/business-service/tests/unit/handler.test.js +78 -0
  102. 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;