@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,396 @@
|
|
|
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
|
+
console.log(`Executing task: ${service}.${operation}`, resolvedInput);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Get service from registry
|
|
157
|
+
const serviceInfo = await this.registry.getService(service);
|
|
158
|
+
if (!serviceInfo) {
|
|
159
|
+
throw new Error(`Service not found: ${service}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Simulate API call or send to queue
|
|
163
|
+
if (this.mqClient) {
|
|
164
|
+
// Send to service queue
|
|
165
|
+
const queueName = `service.${service}.request`;
|
|
166
|
+
await this.mqClient.publish(queueName, {
|
|
167
|
+
workflow_id: workflow.id,
|
|
168
|
+
step_id: step.id,
|
|
169
|
+
operation,
|
|
170
|
+
input: resolvedInput
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Wait for response (with timeout)
|
|
174
|
+
const response = await this.waitForResponse(
|
|
175
|
+
workflow.id,
|
|
176
|
+
step.id,
|
|
177
|
+
this.timeout
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
return response;
|
|
181
|
+
} else {
|
|
182
|
+
// Simulate response
|
|
183
|
+
return {
|
|
184
|
+
processed: true,
|
|
185
|
+
operation,
|
|
186
|
+
input: resolvedInput,
|
|
187
|
+
output: { simulated: true }
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Execute foreach step
|
|
194
|
+
*/
|
|
195
|
+
async executeForeachStep(step, workflow) {
|
|
196
|
+
const { items, body } = step;
|
|
197
|
+
|
|
198
|
+
// Resolve items array
|
|
199
|
+
const itemsArray = this.resolveInput(items, workflow.context);
|
|
200
|
+
if (!Array.isArray(itemsArray)) {
|
|
201
|
+
throw new Error('Foreach items must resolve to an array');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const results = [];
|
|
205
|
+
for (const item of itemsArray) {
|
|
206
|
+
// Create context with current item
|
|
207
|
+
const itemContext = {
|
|
208
|
+
...workflow.context,
|
|
209
|
+
current_item: item
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Execute body step for each item
|
|
213
|
+
const itemWorkflow = { ...workflow, context: itemContext };
|
|
214
|
+
const result = await this.executeStep(body, itemWorkflow);
|
|
215
|
+
results.push(result);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { items: itemsArray.length, results };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Execute switch step
|
|
223
|
+
*/
|
|
224
|
+
async executeSwitchStep(step, workflow) {
|
|
225
|
+
const { condition, cases, default: defaultCase } = step;
|
|
226
|
+
|
|
227
|
+
// Resolve condition value
|
|
228
|
+
const conditionValue = this.resolveInput(condition, workflow.context);
|
|
229
|
+
|
|
230
|
+
// Find matching case
|
|
231
|
+
const matchingCase = cases.find(c => c.value === conditionValue);
|
|
232
|
+
const stepToExecute = matchingCase ? matchingCase.step : defaultCase;
|
|
233
|
+
|
|
234
|
+
if (!stepToExecute) {
|
|
235
|
+
throw new Error(`No matching case for condition value: ${conditionValue}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Execute the selected step
|
|
239
|
+
const result = await this.executeStep(stepToExecute, workflow);
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
condition: conditionValue,
|
|
243
|
+
executed: stepToExecute.id,
|
|
244
|
+
result
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Execute fork-join step
|
|
250
|
+
*/
|
|
251
|
+
async executeForkJoinStep(step, workflow) {
|
|
252
|
+
const { branches, join } = step;
|
|
253
|
+
|
|
254
|
+
// Execute all branches in parallel
|
|
255
|
+
const branchPromises = branches.map(branch =>
|
|
256
|
+
this.executeStep(branch, workflow)
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const results = await Promise.all(branchPromises);
|
|
260
|
+
|
|
261
|
+
// Apply join strategy
|
|
262
|
+
let joinedResult;
|
|
263
|
+
switch (join.strategy) {
|
|
264
|
+
case 'merge':
|
|
265
|
+
joinedResult = this.mergeResults(results);
|
|
266
|
+
break;
|
|
267
|
+
case 'first':
|
|
268
|
+
joinedResult = results[0];
|
|
269
|
+
break;
|
|
270
|
+
case 'all':
|
|
271
|
+
joinedResult = results;
|
|
272
|
+
break;
|
|
273
|
+
default:
|
|
274
|
+
joinedResult = results;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
branches: branches.length,
|
|
279
|
+
strategy: join.strategy,
|
|
280
|
+
result: joinedResult
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Resolve input using JSONPath or direct value
|
|
286
|
+
*/
|
|
287
|
+
resolveInput(input, context) {
|
|
288
|
+
if (!input) return null;
|
|
289
|
+
|
|
290
|
+
if (typeof input === 'string' && input.startsWith('$')) {
|
|
291
|
+
// JSONPath resolution
|
|
292
|
+
return this.resolveJsonPath(input, context);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (typeof input === 'object' && input !== null) {
|
|
296
|
+
// Recursively resolve object properties
|
|
297
|
+
const resolved = {};
|
|
298
|
+
for (const [key, value] of Object.entries(input)) {
|
|
299
|
+
resolved[key] = this.resolveInput(value, context);
|
|
300
|
+
}
|
|
301
|
+
return resolved;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return input;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Simple JSONPath resolution
|
|
309
|
+
*/
|
|
310
|
+
resolveJsonPath(path, context) {
|
|
311
|
+
// Remove leading $
|
|
312
|
+
const pathParts = path.substring(1).split('.');
|
|
313
|
+
|
|
314
|
+
let current = context;
|
|
315
|
+
for (const part of pathParts) {
|
|
316
|
+
if (current === null || current === undefined) {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
current = current[part];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return current;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Wait for step response from queue
|
|
327
|
+
*/
|
|
328
|
+
async waitForResponse(workflowId, stepId, timeout) {
|
|
329
|
+
return new Promise((resolve, reject) => {
|
|
330
|
+
const timeoutId = setTimeout(() => {
|
|
331
|
+
reject(new Error(`Timeout waiting for response: ${stepId}`));
|
|
332
|
+
}, timeout);
|
|
333
|
+
|
|
334
|
+
// Listen for response on response queue
|
|
335
|
+
const responseQueue = `workflow.${workflowId}.response`;
|
|
336
|
+
|
|
337
|
+
this.mqClient.consume(responseQueue, (msg) => {
|
|
338
|
+
const message = JSON.parse(msg.content.toString());
|
|
339
|
+
|
|
340
|
+
if (message.step_id === stepId) {
|
|
341
|
+
clearTimeout(timeoutId);
|
|
342
|
+
this.mqClient.ack(msg);
|
|
343
|
+
resolve(message.result);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Merge results from parallel branches
|
|
351
|
+
*/
|
|
352
|
+
mergeResults(results) {
|
|
353
|
+
const merged = {};
|
|
354
|
+
|
|
355
|
+
results.forEach((result, index) => {
|
|
356
|
+
if (result.data && typeof result.data === 'object') {
|
|
357
|
+
Object.assign(merged, result.data);
|
|
358
|
+
} else {
|
|
359
|
+
merged[`branch_${index}`] = result.data;
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
return merged;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Get workflow status
|
|
368
|
+
*/
|
|
369
|
+
getWorkflowStatus(workflowId) {
|
|
370
|
+
return this.runningWorkflows.get(workflowId);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Stop workflow
|
|
375
|
+
*/
|
|
376
|
+
stopWorkflow(workflowId) {
|
|
377
|
+
const workflow = this.runningWorkflows.get(workflowId);
|
|
378
|
+
if (workflow && workflow.status === 'running') {
|
|
379
|
+
workflow.status = 'stopped';
|
|
380
|
+
this.emit('workflow:stopped', { workflowId });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Clear completed workflows
|
|
386
|
+
*/
|
|
387
|
+
clearCompleted() {
|
|
388
|
+
for (const [id, workflow] of this.runningWorkflows.entries()) {
|
|
389
|
+
if (workflow.status === 'completed' || workflow.status === 'failed') {
|
|
390
|
+
this.runningWorkflows.delete(id);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = WorkflowTestRunner;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Test Helpers - Generic Test Suite Creators
|
|
2
|
+
|
|
3
|
+
This directory contains **generic test helpers** that create complete test suites for business services.
|
|
4
|
+
|
|
5
|
+
## Philosophy
|
|
6
|
+
|
|
7
|
+
Instead of **copying test code** between services, we provide **reusable test helpers** that:
|
|
8
|
+
- ✅ Work for **ALL business services** without modification
|
|
9
|
+
- ✅ Automatically validate service structure
|
|
10
|
+
- ✅ Provide clear error messages when something is wrong
|
|
11
|
+
- ✅ Follow SPOT (Single Point Of Truth) principles
|
|
12
|
+
- ✅ Stay up-to-date with latest testing standards
|
|
13
|
+
|
|
14
|
+
## Available Helpers
|
|
15
|
+
|
|
16
|
+
### 1. createServiceReadinessTests
|
|
17
|
+
|
|
18
|
+
**Purpose:** Integration test for service HTTP API readiness
|
|
19
|
+
|
|
20
|
+
**File:** `createServiceReadinessTests.js`
|
|
21
|
+
|
|
22
|
+
**What it does:**
|
|
23
|
+
- Validates service structure (conn-config/, src/app.js, package.json)
|
|
24
|
+
- Validates configuration files (config.json, operations.json)
|
|
25
|
+
- Tests all operations endpoints
|
|
26
|
+
- Validates health endpoint
|
|
27
|
+
- Auto-generates cookbook tests (with mocks)
|
|
28
|
+
- Tests registry compatibility (with mocks)
|
|
29
|
+
- Scores 100/100 with all checks
|
|
30
|
+
|
|
31
|
+
**Usage:**
|
|
32
|
+
```javascript
|
|
33
|
+
// services/my-service/tests/integration/service-readiness.test.js
|
|
34
|
+
const { createServiceReadinessTests } = require('@onlineapps/conn-orch-validator');
|
|
35
|
+
|
|
36
|
+
createServiceReadinessTests(__dirname);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Options:**
|
|
40
|
+
```javascript
|
|
41
|
+
createServiceReadinessTests(__dirname, {
|
|
42
|
+
testPort: 5556, // Test server port (default: 5556)
|
|
43
|
+
includeOptionalChecks: true, // Cookbook & registry checks (default: true)
|
|
44
|
+
timeout: 15000 // Test timeout in ms (default: 15000)
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Score Breakdown:**
|
|
49
|
+
- operations: 30 points (required)
|
|
50
|
+
- endpoints: 30 points (required)
|
|
51
|
+
- health: 20 points (required)
|
|
52
|
+
- cookbook: 15 points (optional, with mocks)
|
|
53
|
+
- registry: 5 points (optional, with mocks)
|
|
54
|
+
- **Total: 100/100**
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### 2. createPreValidationTests
|
|
59
|
+
|
|
60
|
+
**Purpose:** Tier 1 pre-validation process tests
|
|
61
|
+
|
|
62
|
+
**File:** `createPreValidationTests.js`
|
|
63
|
+
|
|
64
|
+
**What it does:**
|
|
65
|
+
- Validates pre-requisites (cookbooks/, operations.json, scripts)
|
|
66
|
+
- Runs cookbook tests with mocked infrastructure
|
|
67
|
+
- Validates ValidationProof generation
|
|
68
|
+
- Tests proof structure and integrity
|
|
69
|
+
- Verifies SHA256 hash correctness
|
|
70
|
+
- Tests tamper detection
|
|
71
|
+
- Validates dependencies tracking
|
|
72
|
+
|
|
73
|
+
**Usage:**
|
|
74
|
+
```javascript
|
|
75
|
+
// services/my-service/tests/integration/pre-validation-process.test.js
|
|
76
|
+
const { createPreValidationTests } = require('@onlineapps/conn-orch-validator');
|
|
77
|
+
|
|
78
|
+
createPreValidationTests(__dirname);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Options:**
|
|
82
|
+
```javascript
|
|
83
|
+
createPreValidationTests(__dirname, {
|
|
84
|
+
timeout: 60000 // Test timeout in ms (default: 60000)
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Test Structure:**
|
|
89
|
+
1. Pre-requisites Check
|
|
90
|
+
- cookbooks/ directory exists
|
|
91
|
+
- operations.json exists
|
|
92
|
+
- Pre-validation script configured
|
|
93
|
+
2. Cookbook Test Execution
|
|
94
|
+
- Runs npm run test:cookbooks
|
|
95
|
+
- Verifies infrastructure mocking
|
|
96
|
+
3. ValidationProof Generation
|
|
97
|
+
- Creates .validation-proof.json
|
|
98
|
+
- Validates proof structure
|
|
99
|
+
- Checks SHA256 hash
|
|
100
|
+
4. Proof Integrity Verification
|
|
101
|
+
- ValidationProofCodec decode
|
|
102
|
+
- Hash matching
|
|
103
|
+
- Age verification
|
|
104
|
+
- Tamper detection
|
|
105
|
+
5. Dependencies Tracking
|
|
106
|
+
- Proof includes @onlineapps/* versions
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## How It Works
|
|
111
|
+
|
|
112
|
+
### Automatic Structure Validation
|
|
113
|
+
|
|
114
|
+
Both helpers use `ServiceStructureValidator` to validate service structure BEFORE running tests:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
🔍 Validating service structure...
|
|
118
|
+
|
|
119
|
+
═══════════════════════════════════════════════════════════════
|
|
120
|
+
SERVICE STRUCTURE VALIDATION
|
|
121
|
+
═══════════════════════════════════════════════════════════════
|
|
122
|
+
|
|
123
|
+
✅ ALL CHECKS PASSED
|
|
124
|
+
|
|
125
|
+
✓ Found Configuration directory: conn-config
|
|
126
|
+
✓ Found Source code directory: src
|
|
127
|
+
✓ Found Tests directory: tests
|
|
128
|
+
✓ Found valid config.json
|
|
129
|
+
✓ Found valid operations.json
|
|
130
|
+
✓ Found src/app.js
|
|
131
|
+
✓ Found 3 cookbook test(s)
|
|
132
|
+
|
|
133
|
+
═══════════════════════════════════════════════════════════════
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
If validation fails, clear error messages are shown:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
❌ ERRORS (2):
|
|
140
|
+
|
|
141
|
+
✗ Required directory missing: conn-config
|
|
142
|
+
Type: MISSING_DIRECTORY
|
|
143
|
+
File: conn-config
|
|
144
|
+
Fix: Create directory: mkdir -p conn-config
|
|
145
|
+
|
|
146
|
+
✗ Service configuration missing: conn-config/config.json
|
|
147
|
+
Type: MISSING_CONFIG
|
|
148
|
+
File: conn-config/config.json
|
|
149
|
+
Fix: Create config.json with service metadata. See: /docs/standards/SERVICE_TEMPLATE.md
|
|
150
|
+
|
|
151
|
+
⚠️ WARNINGS (1):
|
|
152
|
+
|
|
153
|
+
⚠ Recommended npm script missing: test:cookbooks
|
|
154
|
+
Suggestion: Add to package.json scripts: "test:cookbooks": "..."
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Zero Configuration
|
|
158
|
+
|
|
159
|
+
Helpers automatically detect and load:
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
// Service root (2 levels up from tests/integration/)
|
|
163
|
+
const serviceRoot = path.resolve(__dirname, '../..');
|
|
164
|
+
|
|
165
|
+
// Standard file locations
|
|
166
|
+
const config = require(path.join(serviceRoot, 'conn-config/config.json'));
|
|
167
|
+
const operations = require(path.join(serviceRoot, 'conn-config/operations.json'));
|
|
168
|
+
const app = require(path.join(serviceRoot, 'src/app.js'));
|
|
169
|
+
|
|
170
|
+
// Extract metadata
|
|
171
|
+
const serviceName = config.service.name;
|
|
172
|
+
const serviceVersion = config.service.version;
|
|
173
|
+
const healthEndpoint = config.wrapper?.health?.endpoint || '/health';
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
No service-specific code needed!
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Adding New Helpers
|
|
181
|
+
|
|
182
|
+
When creating new generic test helpers:
|
|
183
|
+
|
|
184
|
+
1. **Follow naming convention:** `create[Purpose]Tests.js`
|
|
185
|
+
2. **Accept testsDir as first parameter:** `function create...(testsDir, options)`
|
|
186
|
+
3. **Calculate service root:** `const serviceRoot = path.resolve(testsDir, '../..');`
|
|
187
|
+
4. **Validate structure first:** Use `ServiceStructureValidator`
|
|
188
|
+
5. **Load standard files:** config.json, operations.json, app.js
|
|
189
|
+
6. **Provide clear output:** Log validation results, test progress
|
|
190
|
+
7. **Export single function:** `module.exports = { create...Tests };`
|
|
191
|
+
8. **Update index.js:** Add to exports
|
|
192
|
+
9. **Document in README.md:** Add to "Available Helpers" section
|
|
193
|
+
|
|
194
|
+
**Example skeleton:**
|
|
195
|
+
```javascript
|
|
196
|
+
const path = require('path');
|
|
197
|
+
const { ServiceStructureValidator } = require('../validators/ServiceStructureValidator');
|
|
198
|
+
|
|
199
|
+
function createMyTests(testsDir, options = {}) {
|
|
200
|
+
const serviceRoot = path.resolve(testsDir, '../..');
|
|
201
|
+
|
|
202
|
+
// 1. Validate structure
|
|
203
|
+
const validator = new ServiceStructureValidator(serviceRoot);
|
|
204
|
+
const result = validator.validate();
|
|
205
|
+
console.log(ServiceStructureValidator.formatResult(result));
|
|
206
|
+
|
|
207
|
+
if (!result.valid) {
|
|
208
|
+
throw new Error('Structure validation failed');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 2. Load config
|
|
212
|
+
const config = require(path.join(serviceRoot, 'conn-config/config.json'));
|
|
213
|
+
|
|
214
|
+
// 3. Create test suite
|
|
215
|
+
describe('My Test Suite @integration', () => {
|
|
216
|
+
// tests...
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = { createMyTests };
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Related Documentation
|
|
226
|
+
|
|
227
|
+
- [/docs/standards/TESTING.md](/docs/standards/TESTING.md) - Testing standards
|
|
228
|
+
- [/tests/TESTING.md](/tests/TESTING.md) - SPOT principles
|
|
229
|
+
- [/shared/connector/conn-orch-validator/README.md](/shared/connector/conn-orch-validator/README.md) - Package documentation
|
|
230
|
+
- [/shared/connector/conn-orch-validator/docs/DESIGN.md](/shared/connector/conn-orch-validator/docs/DESIGN.md) - Design principles
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
*Last updated: 2025-10-21*
|
|
235
|
+
*Maintained by: OA Drive Core Team*
|