@onlineapps/conn-orch-validator 3.3.1 โ 4.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 +389 -0
- package/README.md +46 -6
- package/TESTING_STRATEGY.md +1 -1
- package/docs/DESIGN.md +18 -11
- package/package.json +2 -3
- package/src/CookbookTestRunner.js +155 -26
- package/src/CookbookTestUtils.js +12 -4
- package/src/ServiceReadinessValidator.js +35 -18
- package/src/ValidationOrchestrator.js +223 -126
- package/src/cli/biz-ci-gate.js +356 -7
- package/src/helpers/README.md +3 -55
- package/src/helpers/createServiceReadinessTests.js +5 -1
- package/src/index.js +5 -2
- package/src/mocks/MockMQClient.js +100 -15
- package/src/utils/bizCiGateContract.js +110 -21
- package/src/utils/connectorContract.js +96 -0
- package/src/utils/cookbookFormat.js +95 -0
- package/src/utils/deployContract.js +488 -0
- package/src/utils/envContract.js +417 -0
- package/src/utils/installContract.js +142 -0
- package/src/utils/integrationRun.js +297 -0
- package/src/utils/libCompat.js +158 -0
- package/src/utils/preValidation.js +137 -0
- package/src/utils/setupDatabase.js +154 -0
- package/src/utils/stepFailure.js +73 -0
- package/src/utils/testNamespace.js +104 -0
- package/src/validators/ServiceStructureValidator.js +252 -45
- package/src/config.js +0 -32
- package/src/defaults.js +0 -11
- package/src/helpers/createPreValidationTests.js +0 -326
- package/test-mq-flow.js +0 -72
- package/test-orchestrator.js +0 -95
package/src/cli/biz-ci-gate.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
6
7
|
const {
|
|
7
8
|
DEFAULT_CONTRACT_RELATIVE_PATH,
|
|
8
9
|
loadAndValidateIntegrationContract,
|
|
@@ -14,6 +15,20 @@ const {
|
|
|
14
15
|
runConnectorSetup,
|
|
15
16
|
writeJsonFile,
|
|
16
17
|
} = require('../utils/bizCiGateContract');
|
|
18
|
+
const {
|
|
19
|
+
DEFAULT_REPORT_DIR_RELATIVE_PATH,
|
|
20
|
+
runIntegrationSuite,
|
|
21
|
+
} = require('../utils/integrationRun');
|
|
22
|
+
const { verifyDeployContract } = require('../utils/deployContract');
|
|
23
|
+
const { verifyInstallContract } = require('../utils/installContract');
|
|
24
|
+
const {
|
|
25
|
+
verifyEnvCompleteness,
|
|
26
|
+
ENV_SCAN_SCOPE,
|
|
27
|
+
ENV_SCAN_BLIND_SPOTS,
|
|
28
|
+
} = require('../utils/envContract');
|
|
29
|
+
const { runPreValidation } = require('../utils/preValidation');
|
|
30
|
+
const { checkLibCompat, loadLibrarySet } = require('../utils/libCompat');
|
|
31
|
+
const { buildSchema } = require('../utils/setupDatabase');
|
|
17
32
|
|
|
18
33
|
function parseArgs(argv) {
|
|
19
34
|
const parsed = {
|
|
@@ -47,10 +62,31 @@ Usage:
|
|
|
47
62
|
oa-biz-ci-gate <command> [--service-root <path>] [--contract <path>] [options]
|
|
48
63
|
|
|
49
64
|
Commands:
|
|
50
|
-
verify-contract Validate integration-contract.json schema
|
|
65
|
+
verify-contract Validate integration-contract.json schema, connector flags,
|
|
66
|
+
the deploy contract (R1-R7), the installation contract AND
|
|
67
|
+
the env contract.
|
|
68
|
+
verify-env-contract Env contract only: every environment variable the service
|
|
69
|
+
repository visibly reads is declared in the contract's env
|
|
70
|
+
block or covered by a config placeholder / required connector.
|
|
71
|
+
Prints its measurement boundary on every run.
|
|
72
|
+
verify-deploy-contract Deploy contract only (R1-R7): image pin, deploy sequence,
|
|
73
|
+
Node major agreement, no published ports, full commit SHA,
|
|
74
|
+
CI database engine.
|
|
75
|
+
verify-install-contract Installation contract only: the docs/setup package every repo
|
|
76
|
+
carries, plus the BASELINE/SEED SQL tree required of every repo
|
|
77
|
+
whose contract declares a database.
|
|
78
|
+
run-prevalidation Run the service's cookbooks offline against mocked infrastructure
|
|
79
|
+
and write conn-runtime/validation-proof.json. Replaces the
|
|
80
|
+
per-service scripts/run-pre-validation.js.
|
|
81
|
+
verify-lib-compat Compare @onlineapps/* pins against the platform library SSOT (R6).
|
|
51
82
|
verify-integration-minimum Validate package scripts + fail on empty integration suite.
|
|
83
|
+
run-integration Run the integration suite (one runner process per test file)
|
|
84
|
+
and fail unless the tests actually EXECUTED and passed.
|
|
85
|
+
Counting files on disk cannot see a suite that was
|
|
86
|
+
skipped; this reads the runner's own JSON report.
|
|
52
87
|
emit-ci-env Emit connector-aware CI environment exports.
|
|
53
88
|
wait-connectors Wait for required connector TCP endpoints.
|
|
89
|
+
setup-db Build the schema from the declared migrations (contract database block).
|
|
54
90
|
run-setup Run optional connector setup commands from contract.
|
|
55
91
|
write-summary Write normalized integration signal artifact JSON.
|
|
56
92
|
|
|
@@ -59,6 +95,10 @@ Common options:
|
|
|
59
95
|
--contract <path> Contract path (absolute or relative to service root).
|
|
60
96
|
Default: ${DEFAULT_CONTRACT_RELATIVE_PATH}
|
|
61
97
|
|
|
98
|
+
verify-lib-compat options:
|
|
99
|
+
--libraries <path|url> Library SSOT source. Default: $LIBRARIES_SSOT_URL
|
|
100
|
+
Auth for an https source: $LIBRARIES_SSOT_TOKEN
|
|
101
|
+
|
|
62
102
|
emit-ci-env options:
|
|
63
103
|
--format <shell|dotenv|json> Output format. Default: shell
|
|
64
104
|
--output <path> Optional output file path
|
|
@@ -71,8 +111,10 @@ write-summary options:
|
|
|
71
111
|
--output <path> Summary artifact path. Default: <service-root>/ci/integration-signal.json
|
|
72
112
|
--gate-verdict <pass|fail> Override gate verdict
|
|
73
113
|
--gate-reason <text> Override gate reason
|
|
74
|
-
|
|
75
|
-
|
|
114
|
+
|
|
115
|
+
Executed test counts are read from ci/integration-run.json, written by
|
|
116
|
+
run-integration. Without that artifact the summary reports them as unknown โ
|
|
117
|
+
it never substitutes a count of files on disk.
|
|
76
118
|
`);
|
|
77
119
|
}
|
|
78
120
|
|
|
@@ -84,11 +126,167 @@ function resolveContractOptions(options) {
|
|
|
84
126
|
return {
|
|
85
127
|
serviceRoot: options['service-root'] || process.cwd(),
|
|
86
128
|
contractPath: options.contract || undefined,
|
|
129
|
+
// Undefined here means loadLibrarySet falls back to LIBRARIES_SSOT_URL and
|
|
130
|
+
// fails fast if that is unset too โ never a silent skip.
|
|
131
|
+
libraries: options.libraries || undefined,
|
|
87
132
|
};
|
|
88
133
|
}
|
|
89
134
|
|
|
90
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Render a deploy-contract result and signal whether it passed.
|
|
137
|
+
* The rules live in utils/deployContract; this only presents them.
|
|
138
|
+
*/
|
|
139
|
+
function reportDeployContract(result) {
|
|
140
|
+
if (result.ok) {
|
|
141
|
+
process.stdout.write(`[BizCiGate] OK deploy-contract (${result.service}) โ R1-R7 satisfied\n`);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
for (const violation of result.violations) {
|
|
145
|
+
process.stderr.write(`[BizCiGate] FAIL ${result.service} โ ${violation.requirement} โ ${violation.message}\n`);
|
|
146
|
+
}
|
|
147
|
+
process.stderr.write(`[BizCiGate] FAIL ${result.service} โ ${result.violations.length} deploy-contract violation(s)\n`);
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Render the env-contract completeness result and signal whether it passed.
|
|
153
|
+
*
|
|
154
|
+
* The scope line is printed on every run, pass or fail. A gate that reports
|
|
155
|
+
* "OK" without saying what it looked at invites the reader to assume it looked
|
|
156
|
+
* at everything; this one measures literal reads in the service repository and
|
|
157
|
+
* says so out loud, including how many dynamic accesses it could not resolve.
|
|
158
|
+
*/
|
|
159
|
+
function reportEnvContract(serviceName, contract, result) {
|
|
160
|
+
process.stdout.write(`[BizCiGate] env-contract scope: ${ENV_SCAN_SCOPE}\n`);
|
|
161
|
+
process.stdout.write(`[BizCiGate] env-contract NOT measured: ${ENV_SCAN_BLIND_SPOTS}`
|
|
162
|
+
+ ` โ ${result.dynamicSites.length} dynamic access site(s) in this repo\n`);
|
|
163
|
+
|
|
164
|
+
if (result.ok) {
|
|
165
|
+
const adoption = contract.env ? '' : ' โ no "env" block declared yet (F16 adoption pending)';
|
|
166
|
+
process.stdout.write(`[BizCiGate] OK env-contract (${serviceName}) โ ${result.readCount} name(s) read, `
|
|
167
|
+
+ `${result.declaredCount} declared, ${result.coveredCount} covered${adoption}\n`);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const violation of result.violations) {
|
|
172
|
+
process.stderr.write(`[BizCiGate] FAIL ${serviceName} โ ENV_COMPLETENESS โ `
|
|
173
|
+
+ `"${violation.name}" is read in ${violation.sources.join(', ')} but is neither declared in `
|
|
174
|
+
+ 'config/service/integration-contract.json (env.required / env.optional) nor covered by a '
|
|
175
|
+
+ '${...} config placeholder or a required connector.\n'
|
|
176
|
+
+ ` Fix: add { "name": "${violation.name}", "why": "<what the service does with it>" } `
|
|
177
|
+
+ 'to env.optional (or env.required if the service cannot start without it), or delete the read.\n');
|
|
178
|
+
}
|
|
179
|
+
process.stderr.write(`[BizCiGate] FAIL ${serviceName} โ ${result.violations.length} env-contract violation(s)\n`);
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function reportInstallContract(result) {
|
|
184
|
+
if (result.ok) {
|
|
185
|
+
const half = result.databaseChecked ? 'docs + SQL package' : 'docs (no database declared)';
|
|
186
|
+
process.stdout.write(`[BizCiGate] OK install-contract (${result.service}) โ ${half}\n`);
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
for (const violation of result.violations) {
|
|
190
|
+
process.stderr.write(`[BizCiGate] FAIL ${result.service} โ ${violation.requirement} โ ${violation.message}\n`);
|
|
191
|
+
}
|
|
192
|
+
process.stderr.write(`[BizCiGate] FAIL ${result.service} โ ${result.violations.length} install-contract violation(s)\n`);
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Build the schema the contract declares. One implementation for every service:
|
|
198
|
+
* the repo declares WHAT, this decides HOW (docs/biz/00-model/uniformity-principle.md).
|
|
199
|
+
*/
|
|
200
|
+
function runSetupDb(options) {
|
|
91
201
|
const contractInfo = loadAndValidateIntegrationContract(options.serviceRoot, options.contractPath);
|
|
202
|
+
const database = contractInfo.contract.database;
|
|
203
|
+
|
|
204
|
+
if (!database) {
|
|
205
|
+
// Skipping is explicit and visible โ a service with no database says so by
|
|
206
|
+
// omitting the block, and that is not the same as a failure to find one.
|
|
207
|
+
process.stdout.write('[BizCiGate] OK setup-db โ no database declared, nothing to build\n');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const result = buildSchema({
|
|
212
|
+
serviceRoot: contractInfo.serviceRoot,
|
|
213
|
+
database,
|
|
214
|
+
connection: {
|
|
215
|
+
host: process.env.DB_HOST,
|
|
216
|
+
port: Number.parseInt(process.env.DB_PORT || '3306', 10),
|
|
217
|
+
user: process.env.DB_USER,
|
|
218
|
+
password: process.env.DB_PASSWORD ?? '',
|
|
219
|
+
requireTls: process.env.DB_REQUIRE_TLS === '1'
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
process.stdout.write(`[BizCiGate] OK setup-db โ ${result.schema}: `
|
|
224
|
+
+ `${result.migrationsApplied} migration(s), ${result.seedsApplied} seed(s)\n`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function runVerifyLibCompat(options) {
|
|
228
|
+
const pkgPath = path.join(options.serviceRoot, 'package.json');
|
|
229
|
+
if (!fs.existsSync(pkgPath)) {
|
|
230
|
+
process.stderr.write(`[BizCiGate] FAIL verify-lib-compat โ package.json not found at ${pkgPath}\n`);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
235
|
+
const librarySet = await loadLibrarySet(options.libraries);
|
|
236
|
+
const result = checkLibCompat(pkg, librarySet);
|
|
237
|
+
|
|
238
|
+
if (result.notGated.length > 0) {
|
|
239
|
+
process.stdout.write(`[BizCiGate] not gated (no infra service installs these): ${result.notGated.join(', ')}\n`);
|
|
240
|
+
}
|
|
241
|
+
if (!result.ok) {
|
|
242
|
+
for (const violation of result.violations) {
|
|
243
|
+
process.stderr.write(`[BizCiGate] FAIL verify-lib-compat โ ${violation.message}\n`);
|
|
244
|
+
}
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
process.stdout.write(`[BizCiGate] OK verify-lib-compat โ ${result.gated.length} pin(s) match the platform library set\n`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function runVerifyContract(options) {
|
|
251
|
+
const contractInfo = loadAndValidateIntegrationContract(options.serviceRoot, options.contractPath);
|
|
252
|
+
|
|
253
|
+
// The deploy contract (R1-R7) is part of the same mandatory gate: a service
|
|
254
|
+
// whose compose, deploy sequence, Node major or database engine breaks the
|
|
255
|
+
// platform contract must not reach the build stage. Folding it in here means
|
|
256
|
+
// a version bump activates it โ no per-repo CI edit, nothing to forget.
|
|
257
|
+
if (!reportDeployContract(verifyDeployContract(contractInfo.serviceRoot))) {
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// The installation contract joins the same gate on the same argument: a repo
|
|
262
|
+
// whose setup docs or SQL package are missing cannot be installed from its own
|
|
263
|
+
// instructions, and that must not reach the build stage. Folding it in also
|
|
264
|
+
// retires the eight drifting shell copies โ and with them the hand-maintained
|
|
265
|
+
// REPO_HAS_DB literal, since the database block already says whether the SQL
|
|
266
|
+
// half applies.
|
|
267
|
+
if (!reportInstallContract(
|
|
268
|
+
verifyInstallContract(contractInfo.serviceRoot, contractInfo.contract.database)
|
|
269
|
+
)) {
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// The env contract joins on the same argument once more: a repo that reads a
|
|
274
|
+
// variable nobody declared has no way to tell a missing value from a dead one,
|
|
275
|
+
// and finds out at runtime โ biz-emailer read EMAIL_PROVIDER_LIVE_MODE that no
|
|
276
|
+
// template ever set, so live mail was silently off (defect D1).
|
|
277
|
+
if (!reportEnvContract(
|
|
278
|
+
contractInfo.contract.serviceName || path.basename(contractInfo.serviceRoot),
|
|
279
|
+
contractInfo.contract,
|
|
280
|
+
verifyEnvCompleteness({ serviceRoot: contractInfo.serviceRoot, contract: contractInfo.contract })
|
|
281
|
+
)) {
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// R6 belongs to the same gate for the same reason. It needs LIBRARIES_SSOT_URL;
|
|
286
|
+
// an unconfigured gate fails loudly rather than passing, so the CI variable
|
|
287
|
+
// must exist before any repo pins a version carrying this check.
|
|
288
|
+
await runVerifyLibCompat({ ...options, serviceRoot: contractInfo.serviceRoot });
|
|
289
|
+
|
|
92
290
|
process.stdout.write(`[BizCiGate] OK verify-contract\n`);
|
|
93
291
|
process.stdout.write(`${JSON.stringify({
|
|
94
292
|
serviceRoot: contractInfo.serviceRoot,
|
|
@@ -99,6 +297,87 @@ function runVerifyContract(options) {
|
|
|
99
297
|
}, null, 2)}\n`);
|
|
100
298
|
}
|
|
101
299
|
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Resolve the service URL from the service's OWN config.
|
|
303
|
+
*
|
|
304
|
+
* ConfigLoader lives in @onlineapps/service-wrapper (L4) and this package is L3,
|
|
305
|
+
* so it is never a dependency here โ it is resolved from the service being
|
|
306
|
+
* validated, which is where it legitimately exists.
|
|
307
|
+
*/
|
|
308
|
+
function resolveServiceUrl(serviceRoot) {
|
|
309
|
+
let wrapperPath;
|
|
310
|
+
try {
|
|
311
|
+
wrapperPath = require.resolve('@onlineapps/service-wrapper', { paths: [serviceRoot] });
|
|
312
|
+
} catch {
|
|
313
|
+
throw new Error('[PreValidation] Missing dependency - @onlineapps/service-wrapper is not '
|
|
314
|
+
+ `installed in ${serviceRoot}. Fix: run npm install in the service repository.`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const { ConfigLoader } = require(wrapperPath);
|
|
318
|
+
const config = ConfigLoader.loadAll({ basePath: serviceRoot, env: process.env });
|
|
319
|
+
return config.service.url;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function runRunPreValidation(options) {
|
|
323
|
+
const outcome = await runPreValidation({
|
|
324
|
+
serviceRoot: path.resolve(options.serviceRoot),
|
|
325
|
+
serviceUrl: resolveServiceUrl(path.resolve(options.serviceRoot)),
|
|
326
|
+
RunnerClass: require('../CookbookTestRunner'),
|
|
327
|
+
ProofGeneratorClass: require('../validators/ValidationProofGenerator'),
|
|
328
|
+
logger: console
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const { results } = outcome;
|
|
332
|
+
process.stdout.write(`[BizCiGate] cookbooks โ total ${results.total}, `
|
|
333
|
+
+ `passed ${results.passed}, failed ${results.failed} (${results.duration}ms)\n`);
|
|
334
|
+
|
|
335
|
+
if (!outcome.ok) {
|
|
336
|
+
for (const step of outcome.failedSteps) {
|
|
337
|
+
process.stderr.write(`[BizCiGate] FAIL run-prevalidation โ ${step.id}: ${step.error}\n`);
|
|
338
|
+
for (const validationError of step.validationErrors) {
|
|
339
|
+
process.stderr.write(`[BizCiGate] * ${validationError}\n`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
process.stderr.write(`[BizCiGate] FAIL run-prevalidation โ ${results.failed} failed step(s); `
|
|
343
|
+
+ 'no validation proof written\n');
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
process.stdout.write(`[BizCiGate] OK run-prevalidation โ proof ${outcome.proof.validationProof.slice(0, 16)}... `
|
|
348
|
+
+ `-> ${outcome.proofPath}\n`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** Same checks, runnable on their own โ for the cross-repo audit and local use. */
|
|
352
|
+
function runVerifyDeployContract(options) {
|
|
353
|
+
if (!reportDeployContract(verifyDeployContract(options.serviceRoot))) {
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Same checks, runnable on their own. Loads the contract because the database
|
|
359
|
+
* block is what decides whether the SQL half of the contract applies. */
|
|
360
|
+
function runVerifyInstallContract(options) {
|
|
361
|
+
const contractInfo = loadAndValidateIntegrationContract(options.serviceRoot, options.contractPath);
|
|
362
|
+
if (!reportInstallContract(
|
|
363
|
+
verifyInstallContract(contractInfo.serviceRoot, contractInfo.contract.database)
|
|
364
|
+
)) {
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Same check, runnable on its own โ for the cross-repo audit and local use. */
|
|
370
|
+
function runVerifyEnvContract(options) {
|
|
371
|
+
const contractInfo = loadAndValidateIntegrationContract(options.serviceRoot, options.contractPath);
|
|
372
|
+
if (!reportEnvContract(
|
|
373
|
+
contractInfo.contract.serviceName || path.basename(contractInfo.serviceRoot),
|
|
374
|
+
contractInfo.contract,
|
|
375
|
+
verifyEnvCompleteness({ serviceRoot: contractInfo.serviceRoot, contract: contractInfo.contract })
|
|
376
|
+
)) {
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
102
381
|
function runVerifyIntegrationMinimum(options) {
|
|
103
382
|
const result = verifyIntegrationMinimum(options.serviceRoot, options.contractPath);
|
|
104
383
|
process.stdout.write(`[BizCiGate] OK verify-integration-minimum\n`);
|
|
@@ -111,6 +390,44 @@ function runVerifyIntegrationMinimum(options) {
|
|
|
111
390
|
}, null, 2)}\n`);
|
|
112
391
|
}
|
|
113
392
|
|
|
393
|
+
/**
|
|
394
|
+
* The gate's only honest observation point for "did the suite run?".
|
|
395
|
+
*
|
|
396
|
+
* The file-count check runs first as a cheap precondition, then every discovered
|
|
397
|
+
* file gets its own runner process and its own JSON report. Per-file processes
|
|
398
|
+
* are the library's HOW, not a per-repo declaration: biz-invoicing already needed
|
|
399
|
+
* them (its suite was OOM-killed in a single process), and they bound one leaking
|
|
400
|
+
* suite's blast radius for everyone else.
|
|
401
|
+
*/
|
|
402
|
+
function runRunIntegration(options) {
|
|
403
|
+
const verified = verifyIntegrationMinimum(options.serviceRoot, options.contractPath);
|
|
404
|
+
|
|
405
|
+
const result = runIntegrationSuite({
|
|
406
|
+
serviceRoot: verified.serviceRoot,
|
|
407
|
+
testFiles: verified.integrationTestFiles,
|
|
408
|
+
jestBin: path.join(verified.serviceRoot, 'node_modules', '.bin', 'jest'),
|
|
409
|
+
jestConfig: path.join(verified.serviceRoot, 'jest.config.js'),
|
|
410
|
+
reportDir: path.join(verified.serviceRoot, DEFAULT_REPORT_DIR_RELATIVE_PATH),
|
|
411
|
+
spawn: spawnSync,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const { aggregate, evaluation } = result;
|
|
415
|
+
|
|
416
|
+
if (!evaluation.ok) {
|
|
417
|
+
for (const violation of evaluation.violations) {
|
|
418
|
+
process.stderr.write(`${violation.message}\n`);
|
|
419
|
+
}
|
|
420
|
+
process.stderr.write(`[BizCiGate] FAIL run-integration โ ${aggregate.executedTests} test(s) executed, `
|
|
421
|
+
+ `${aggregate.notExecutedTests} skipped, ${aggregate.failedTests} failed, `
|
|
422
|
+
+ `across ${aggregate.reportCount} runner process(es). Report: ${result.artefactPath}\n`);
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
process.stdout.write(`[BizCiGate] OK run-integration โ ${aggregate.executedTests} test(s) executed and passed `
|
|
427
|
+
+ `in ${aggregate.totalSuites} suite(s), across ${aggregate.reportCount} runner process(es)\n`);
|
|
428
|
+
process.stdout.write(`[BizCiGate] Wrote integration run artefact to ${result.artefactPath}\n`);
|
|
429
|
+
}
|
|
430
|
+
|
|
114
431
|
function runEmitCiEnv(options) {
|
|
115
432
|
const contractInfo = loadAndValidateIntegrationContract(options.serviceRoot, options.contractPath);
|
|
116
433
|
const env = buildCiEnvironment(contractInfo);
|
|
@@ -161,8 +478,6 @@ function runWriteSummary(options) {
|
|
|
161
478
|
contractPath: options.contractPath,
|
|
162
479
|
gateVerdict: options['gate-verdict'],
|
|
163
480
|
gateReason: options['gate-reason'],
|
|
164
|
-
unitExecuted: options['unit-executed'],
|
|
165
|
-
integrationExecuted: options['integration-executed'],
|
|
166
481
|
});
|
|
167
482
|
|
|
168
483
|
const outputPath = options.output
|
|
@@ -187,13 +502,42 @@ async function main() {
|
|
|
187
502
|
|
|
188
503
|
try {
|
|
189
504
|
if (command === 'verify-contract') {
|
|
190
|
-
runVerifyContract(options);
|
|
505
|
+
await runVerifyContract(options);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (command === 'verify-deploy-contract') {
|
|
510
|
+
runVerifyDeployContract(options);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (command === 'run-prevalidation') {
|
|
515
|
+
await runRunPreValidation(options);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (command === 'verify-install-contract') {
|
|
520
|
+
runVerifyInstallContract(options);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (command === 'verify-env-contract') {
|
|
525
|
+
runVerifyEnvContract(options);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (command === 'verify-lib-compat') {
|
|
530
|
+
await runVerifyLibCompat(options);
|
|
191
531
|
return;
|
|
192
532
|
}
|
|
193
533
|
if (command === 'verify-integration-minimum') {
|
|
194
534
|
runVerifyIntegrationMinimum(options);
|
|
195
535
|
return;
|
|
196
536
|
}
|
|
537
|
+
if (command === 'run-integration') {
|
|
538
|
+
runRunIntegration(options);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
197
541
|
if (command === 'emit-ci-env') {
|
|
198
542
|
runEmitCiEnv(options);
|
|
199
543
|
return;
|
|
@@ -202,6 +546,11 @@ async function main() {
|
|
|
202
546
|
await runWaitConnectors(options);
|
|
203
547
|
return;
|
|
204
548
|
}
|
|
549
|
+
if (command === 'setup-db') {
|
|
550
|
+
runSetupDb(options);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
|
|
205
554
|
if (command === 'run-setup') {
|
|
206
555
|
runSetup(options);
|
|
207
556
|
return;
|
package/src/helpers/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Instead of **copying test code** between services, we provide **reusable test he
|
|
|
13
13
|
|
|
14
14
|
## Available Helpers
|
|
15
15
|
|
|
16
|
-
###
|
|
16
|
+
### createServiceReadinessTests
|
|
17
17
|
|
|
18
18
|
**Purpose:** Integration test for service HTTP API readiness
|
|
19
19
|
|
|
@@ -55,63 +55,11 @@ createServiceReadinessTests(__dirname, {
|
|
|
55
55
|
|
|
56
56
|
---
|
|
57
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
58
|
## How It Works
|
|
111
59
|
|
|
112
60
|
### Automatic Structure Validation
|
|
113
61
|
|
|
114
|
-
|
|
62
|
+
The helper uses `ServiceStructureValidator` to validate service structure BEFORE running tests:
|
|
115
63
|
|
|
116
64
|
```
|
|
117
65
|
๐ Validating service structure...
|
|
@@ -156,7 +104,7 @@ If validation fails, clear error messages are shown:
|
|
|
156
104
|
|
|
157
105
|
### Zero Configuration
|
|
158
106
|
|
|
159
|
-
|
|
107
|
+
The helper automatically detects and loads:
|
|
160
108
|
|
|
161
109
|
```javascript
|
|
162
110
|
// Service root (2 levels up from tests/integration/)
|
|
@@ -31,6 +31,7 @@ const fs = require('fs');
|
|
|
31
31
|
const ServiceReadinessValidator = require('../ServiceReadinessValidator');
|
|
32
32
|
const MockRegistry = require('../mocks/MockRegistry');
|
|
33
33
|
const { ServiceStructureValidator } = require('../validators/ServiceStructureValidator');
|
|
34
|
+
const { MIN_COOKBOOK_FORMAT_VERSION } = require('../utils/cookbookFormat');
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Create service readiness integration test suite
|
|
@@ -97,7 +98,10 @@ function createServiceReadinessTests(testsDir, options = {}) {
|
|
|
97
98
|
if (includeOptionalChecks) {
|
|
98
99
|
mockRegistry = new MockRegistry();
|
|
99
100
|
testCookbook = {
|
|
100
|
-
version
|
|
101
|
+
// Cookbook FORMAT version โ the synthesised cookbook must satisfy the
|
|
102
|
+
// same rule the Tier-1 gate enforces (api/docs/biz/40-cookbooks/format.md
|
|
103
|
+
// ยง Required fields), otherwise this helper rejects its own output.
|
|
104
|
+
version: MIN_COOKBOOK_FORMAT_VERSION,
|
|
101
105
|
steps: Object.entries(operationsFlat).map(([name, op]) => ({
|
|
102
106
|
id: `test-${name}`,
|
|
103
107
|
type: 'task',
|
package/src/index.js
CHANGED
|
@@ -33,7 +33,6 @@ const ServiceReadinessValidator = require('./ServiceReadinessValidator');
|
|
|
33
33
|
const ValidationOrchestrator = require('./ValidationOrchestrator');
|
|
34
34
|
|
|
35
35
|
const { createServiceReadinessTests } = require('./helpers/createServiceReadinessTests');
|
|
36
|
-
const { createPreValidationTests } = require('./helpers/createPreValidationTests');
|
|
37
36
|
|
|
38
37
|
module.exports = {
|
|
39
38
|
get MockMQClient() { return MockMQClient; },
|
|
@@ -48,9 +47,13 @@ module.exports = {
|
|
|
48
47
|
get ServiceStructureValidator() { return ServiceStructureValidator; },
|
|
49
48
|
get ValidationOrchestrator() { return ValidationOrchestrator; },
|
|
50
49
|
|
|
50
|
+
// The namespace an integration test may write into โ read from the platform
|
|
51
|
+
// env, never chosen per test. Enforced by deploy-contract R8.
|
|
52
|
+
get getTestNamespace() { return require('./utils/testNamespace').getTestNamespace; },
|
|
53
|
+
|
|
51
54
|
get createServiceReadinessTests() { return createServiceReadinessTests; },
|
|
52
|
-
get createPreValidationTests() { return createPreValidationTests; },
|
|
53
55
|
get BizCiGateContract() { return BizCiGateContract; },
|
|
56
|
+
get IntegrationRun() { return require('./utils/integrationRun'); },
|
|
54
57
|
|
|
55
58
|
createMockMQ: () => new MockMQClient(),
|
|
56
59
|
createMockRegistry: () => new MockRegistry(),
|