test: add m3 source contract dry-run plan
This commit is contained in:
@@ -85,8 +85,11 @@ const requiredDevM3ValidationCommands = [
|
||||
"node --check scripts/dev-m3-hardware-loop-smoke.mjs",
|
||||
"node --check scripts/validate-dev-m3-cardinality.mjs",
|
||||
"node scripts/validate-dev-m3-cardinality.mjs",
|
||||
"node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production"
|
||||
"node scripts/dev-m3-hardware-loop-smoke.mjs --dry-run",
|
||||
"node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --expect-non-prod"
|
||||
];
|
||||
const legacyDevM3LiveCommand =
|
||||
"node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production";
|
||||
const requiredDevM3Docs = [
|
||||
"docs/dev-acceptance-matrix.md",
|
||||
"docs/m3-hardware-loop.md"
|
||||
@@ -1334,6 +1337,10 @@ async function validateDevM3Report(report, label) {
|
||||
`${label}.validationCommands missing ${requiredCommand}`
|
||||
);
|
||||
}
|
||||
assert.ok(
|
||||
report.validationCommands.includes(legacyDevM3LiveCommand),
|
||||
`${label}.validationCommands missing legacy compatibility command ${legacyDevM3LiveCommand}`
|
||||
);
|
||||
|
||||
assertObject(report.runtimeTarget, `${label}.runtimeTarget`);
|
||||
assert.equal(report.runtimeTarget.endpoint, "http://74.48.78.17:16667", `${label}.runtimeTarget.endpoint`);
|
||||
@@ -1348,6 +1355,16 @@ async function validateDevM3Report(report, label) {
|
||||
assert.equal(report.runtimeTarget.environment, "dev", `${label}.runtimeTarget.environment`);
|
||||
assert.equal(report.runtimeTarget.requiredBoxSimulators, 2, `${label}.runtimeTarget.requiredBoxSimulators`);
|
||||
assert.equal(report.runtimeTarget.requiredGatewaySimulators, 2, `${label}.runtimeTarget.requiredGatewaySimulators`);
|
||||
if (Object.hasOwn(report.runtimeTarget, "requiredPatchPanels")) {
|
||||
assert.equal(report.runtimeTarget.requiredPatchPanels, 1, `${label}.runtimeTarget.requiredPatchPanels`);
|
||||
}
|
||||
if (Object.hasOwn(report.runtimeTarget, "requiredRoute")) {
|
||||
assert.equal(
|
||||
report.runtimeTarget.requiredRoute,
|
||||
"res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1",
|
||||
`${label}.runtimeTarget.requiredRoute`
|
||||
);
|
||||
}
|
||||
assert.equal(report.runtimeTarget.realHardwareAllowed, false, `${label}.runtimeTarget.realHardwareAllowed`);
|
||||
assert.equal(report.runtimeTarget.prodAllowed, false, `${label}.runtimeTarget.prodAllowed`);
|
||||
|
||||
@@ -1355,7 +1372,9 @@ async function validateDevM3Report(report, label) {
|
||||
for (const field of [
|
||||
"liveFlagRequired",
|
||||
"confirmDevRequired",
|
||||
"expectNonProdRequired",
|
||||
"confirmedNonProductionRequired",
|
||||
"dryRunPlanSupported",
|
||||
"prodForbidden",
|
||||
"realHardwareForbidden",
|
||||
"secretReadForbidden",
|
||||
@@ -1364,6 +1383,9 @@ async function validateDevM3Report(report, label) {
|
||||
]) {
|
||||
assert.equal(report.safetyGates[field], true, `${label}.safetyGates.${field}`);
|
||||
}
|
||||
if (Object.hasOwn(report.safetyGates, "dryRunCallsLiveEndpoints")) {
|
||||
assert.equal(report.safetyGates.dryRunCallsLiveEndpoints, false, `${label}.safetyGates.dryRunCallsLiveEndpoints`);
|
||||
}
|
||||
|
||||
assertArray(report.liveChecks, `${label}.liveChecks`);
|
||||
assert.ok(report.liveChecks.length >= 8, `${label}.liveChecks must include DEV M3 checks`);
|
||||
@@ -1449,7 +1471,14 @@ async function validateDevM3Report(report, label) {
|
||||
["pass", "gap", "source-ready", "manifest-ready"].includes(evidence.status),
|
||||
`${evidenceLabel}.status must be pass, gap, source-ready, or manifest-ready`
|
||||
);
|
||||
assertRepoRelativePath(evidence.source, `${evidenceLabel}.source`);
|
||||
if (Array.isArray(evidence.source)) {
|
||||
assertStringArray(evidence.source, `${evidenceLabel}.source`, { minLength: 1 });
|
||||
for (const [sourceIndex, sourcePath] of evidence.source.entries()) {
|
||||
assertRepoRelativePath(sourcePath, `${evidenceLabel}.source[${sourceIndex}]`);
|
||||
}
|
||||
} else {
|
||||
assertRepoRelativePath(evidence.source, `${evidenceLabel}.source`);
|
||||
}
|
||||
assertString(evidence.summary, `${evidenceLabel}.summary`);
|
||||
assertObject(evidence.evidence, `${evidenceLabel}.evidence`);
|
||||
assertString(evidence.requiredFollowUp, `${evidenceLabel}.requiredFollowUp`);
|
||||
@@ -1462,6 +1491,42 @@ async function validateDevM3Report(report, label) {
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.hasOwn(report, "dryRunPlan")) {
|
||||
const plan = report.dryRunPlan;
|
||||
assertObject(plan, `${label}.dryRunPlan`);
|
||||
assert.equal(plan.mode, "plan-only", `${label}.dryRunPlan.mode`);
|
||||
assert.equal(plan.evidenceLevel, "DRY-RUN", `${label}.dryRunPlan.evidenceLevel`);
|
||||
assert.equal(plan.route, "res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1", `${label}.dryRunPlan.route`);
|
||||
assert.equal(plan.dryRunCallsLiveEndpoints, false, `${label}.dryRunPlan.dryRunCallsLiveEndpoints`);
|
||||
assert.equal(plan.liveWriteWillRun, false, `${label}.dryRunPlan.liveWriteWillRun`);
|
||||
assertStringArray(plan.liveWritePreconditions, `${label}.dryRunPlan.liveWritePreconditions`, { minLength: 4 });
|
||||
assertStringArray(plan.evidenceFields, `${label}.dryRunPlan.evidenceFields`, { minLength: 4 });
|
||||
assertArray(plan.endpointPlan, `${label}.dryRunPlan.endpointPlan`);
|
||||
assert.ok(plan.endpointPlan.length >= 8, `${label}.dryRunPlan.endpointPlan must enumerate precondition and live endpoints`);
|
||||
assert.ok(
|
||||
plan.endpointPlan.some((endpoint) => endpoint.serviceId === "hwlab-patch-panel" && endpoint.path === "/signals/route" && endpoint.mutatesDevState === true),
|
||||
`${label}.dryRunPlan.endpointPlan must include the patch-panel route write endpoint`
|
||||
);
|
||||
assert.ok(
|
||||
plan.endpointPlan.some((endpoint) => endpoint.serviceId === "hwlab-box-simu" && endpoint.path === "/ports/write" && endpoint.mutatesDevState === true),
|
||||
`${label}.dryRunPlan.endpointPlan must include the source box DO1 write endpoint`
|
||||
);
|
||||
assert.ok(
|
||||
plan.endpointPlan.every((endpoint) => endpoint.calledInDryRun === false),
|
||||
`${label}.dryRunPlan.endpointPlan endpoints must not be called in dry-run`
|
||||
);
|
||||
for (const requiredField of [
|
||||
"operationId",
|
||||
"traceId",
|
||||
"auditId",
|
||||
"evidenceId",
|
||||
"routeResponse.propagatedBy=hwlab-patch-panel",
|
||||
"targetState.ports.DI1.propagatedBy=hwlab-patch-panel"
|
||||
]) {
|
||||
assert.ok(plan.evidenceFields.includes(requiredField), `${label}.dryRunPlan.evidenceFields missing ${requiredField}`);
|
||||
}
|
||||
}
|
||||
|
||||
assertObject(report.liveOperation, `${label}.liveOperation`);
|
||||
assertStatus(report.liveOperation.status, `${label}.liveOperation.status`);
|
||||
for (const field of ["operationId", "traceId", "auditId", "evidenceId", "summary"]) {
|
||||
|
||||
Reference in New Issue
Block a user