398 lines
12 KiB
JavaScript
398 lines
12 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { execFileSync } from "node:child_process";
|
|
import test from "node:test";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import {
|
|
checkFrontendNoDirectRuntimeCalls,
|
|
classifyM3IoControlReport,
|
|
controlPathReachablePersistenceBlocked,
|
|
expectedM3IoLiveSequence,
|
|
parseM3IoControlE2eArgs,
|
|
requireM3IoControlE2eGates,
|
|
resolveM3IoEndpoint,
|
|
validateExpectedRequestSchemas,
|
|
validateM3IoLiveResponse
|
|
} from "./src/m3-io-control-e2e.mjs";
|
|
|
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
|
test("M3 IO E2E default mode is SOURCE/static and never permits mutation", () => {
|
|
const args = parseM3IoControlE2eArgs([]);
|
|
assert.equal(args.live, false);
|
|
assert.equal(args.target, "frontend");
|
|
assert.equal(args.frontendUrl, "http://74.48.78.17:16666/");
|
|
|
|
assert.deepEqual(requireM3IoControlE2eGates(args), {
|
|
mode: "source-static",
|
|
liveWriteAllowed: false,
|
|
mutationAllowed: false
|
|
});
|
|
});
|
|
|
|
test("M3 IO E2E live mode requires explicit DEV and non-production confirmations", () => {
|
|
assert.throws(
|
|
() => requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--live", "--confirm-dev"])),
|
|
/requires --live --confirm-dev --confirmed-non-production/u
|
|
);
|
|
assert.throws(
|
|
() => requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--live", "--confirm-dev", "--confirmed-non-production", "--dry-run"])),
|
|
/cannot be combined/u
|
|
);
|
|
assert.deepEqual(
|
|
requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--live", "--confirm-dev", "--confirmed-non-production"])),
|
|
{
|
|
mode: "dev-live",
|
|
liveWriteAllowed: true,
|
|
mutationAllowed: true
|
|
}
|
|
);
|
|
assert.deepEqual(
|
|
requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--live", "--confirm-dev", "--expect-non-prod"])),
|
|
{
|
|
mode: "dev-live",
|
|
liveWriteAllowed: true,
|
|
mutationAllowed: true
|
|
}
|
|
);
|
|
});
|
|
|
|
test("M3 IO E2E rejects dangerous flags and non-DEV live endpoints", () => {
|
|
assert.throws(
|
|
() => requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--prod"])),
|
|
/forbidden/u
|
|
);
|
|
assert.throws(
|
|
() => resolveM3IoEndpoint(parseM3IoControlE2eArgs([
|
|
"--live",
|
|
"--confirm-dev",
|
|
"--confirmed-non-production",
|
|
"--url",
|
|
"http://127.0.0.1:16666/"
|
|
])),
|
|
/live endpoint must match target/u
|
|
);
|
|
assert.equal(
|
|
resolveM3IoEndpoint(parseM3IoControlE2eArgs(["--live", "--confirm-dev", "--confirmed-non-production"])).href,
|
|
"http://74.48.78.17:16666/v1/m3/io"
|
|
);
|
|
assert.throws(
|
|
() => resolveM3IoEndpoint(parseM3IoControlE2eArgs([
|
|
"--live",
|
|
"--confirm-dev",
|
|
"--confirmed-non-production",
|
|
"--url",
|
|
"http://74.48.78.17:16667/"
|
|
])),
|
|
/frontend uses DEV/u
|
|
);
|
|
assert.equal(
|
|
resolveM3IoEndpoint(parseM3IoControlE2eArgs([
|
|
"--live",
|
|
"--confirm-dev",
|
|
"--confirmed-non-production",
|
|
"--target",
|
|
"api"
|
|
])).href,
|
|
"http://74.48.78.17:16667/v1/m3/io"
|
|
);
|
|
});
|
|
|
|
test("M3 IO E2E source mode CLI prints non-mutating SOURCE/DRY-RUN report", () => {
|
|
const stdout = execFileSync("node", ["scripts/m3-io-control-e2e.mjs"], {
|
|
cwd: repoRoot,
|
|
encoding: "utf8",
|
|
stdio: ["ignore", "pipe", "pipe"]
|
|
});
|
|
const report = JSON.parse(stdout);
|
|
assert.equal(report.mode, "source-static");
|
|
assert.equal(report.sourceKind, "SOURCE");
|
|
assert.equal(report.liveMutation.attempted, false);
|
|
assert.equal(report.liveMutation.allowed, false);
|
|
assert.equal(report.dryRunPlan.sourceKind, "DRY-RUN");
|
|
assert.equal(report.dryRunPlan.liveWriteWillRun, false);
|
|
assert.equal(report.summary.classification, "source_static_contract_checked");
|
|
assert.equal(report.summary.trustedGreen, false);
|
|
});
|
|
|
|
test("M3 IO E2E request schemas are fixed to DO1 write and DI1 read", () => {
|
|
const result = validateExpectedRequestSchemas(expectedM3IoLiveSequence());
|
|
assert.equal(result.status, "pass");
|
|
assert.deepEqual(
|
|
result.sequence.map((step) => [step.action, step.gatewayId, step.resourceId, step.boxId, step.port, step.value]),
|
|
[
|
|
["do.write", "gwsimu_1", "res_boxsimu_1", "boxsimu_1", "DO1", true],
|
|
["di.read", "gwsimu_2", "res_boxsimu_2", "boxsimu_2", "DI1", undefined],
|
|
["do.write", "gwsimu_1", "res_boxsimu_1", "boxsimu_1", "DO1", false],
|
|
["di.read", "gwsimu_2", "res_boxsimu_2", "boxsimu_2", "DI1", undefined]
|
|
]
|
|
);
|
|
|
|
const invalid = expectedM3IoLiveSequence().map((step) =>
|
|
step.id === "write-do1-true"
|
|
? { ...step, request: { ...step.request, port: "DI1" } }
|
|
: step
|
|
);
|
|
assert.equal(validateExpectedRequestSchemas(invalid).status, "fail");
|
|
});
|
|
|
|
test("M3 IO E2E classifies reachable control with blocked durable persistence correctly", () => {
|
|
const operations = expectedM3IoLiveSequence().map((step) => ({
|
|
id: step.id,
|
|
action: step.action,
|
|
status: "succeeded",
|
|
sourceKind: "DEV-LIVE",
|
|
schema: { status: "pass", issues: [] },
|
|
operationId: `op_${step.id}`,
|
|
traceId: `trc_${step.id}`,
|
|
auditId: `aud_${step.id}`,
|
|
evidenceId: `ev_${step.id}`,
|
|
resultValue: step.expectedValue,
|
|
evidenceState: {
|
|
status: "blocked",
|
|
sourceKind: "BLOCKED",
|
|
durable: false,
|
|
writeStatus: "not_written"
|
|
}
|
|
}));
|
|
const classification = classifyM3IoControlReport({
|
|
checks: [{ id: "source", status: "pass" }],
|
|
liveOperations: operations
|
|
});
|
|
assert.deepEqual(classification, {
|
|
status: "blocked",
|
|
classification: controlPathReachablePersistenceBlocked,
|
|
trustedGreen: false
|
|
});
|
|
});
|
|
|
|
test("M3 IO E2E classifies trusted green only with durable DEV-LIVE evidence", () => {
|
|
const operations = expectedM3IoLiveSequence().map((step) => ({
|
|
id: step.id,
|
|
action: step.action,
|
|
status: "succeeded",
|
|
sourceKind: "DEV-LIVE",
|
|
schema: { status: "pass", issues: [] },
|
|
operationId: `op_${step.id}`,
|
|
traceId: `trc_${step.id}`,
|
|
auditId: `aud_${step.id}`,
|
|
evidenceId: `ev_${step.id}`,
|
|
resultValue: step.expectedValue,
|
|
evidenceState: {
|
|
status: "green",
|
|
sourceKind: "DEV-LIVE",
|
|
durable: true,
|
|
writeStatus: "persisted"
|
|
}
|
|
}));
|
|
assert.deepEqual(
|
|
classifyM3IoControlReport({
|
|
checks: [{ id: "source", status: "pass" }],
|
|
liveOperations: operations
|
|
}),
|
|
{
|
|
status: "pass",
|
|
classification: "trusted_green",
|
|
trustedGreen: true
|
|
}
|
|
);
|
|
});
|
|
|
|
test("M3 IO E2E live durable green evidence overrides stale source check failure", () => {
|
|
const operations = expectedM3IoLiveSequence().map((step) => ({
|
|
id: step.id,
|
|
action: step.action,
|
|
status: "succeeded",
|
|
sourceKind: "DEV-LIVE",
|
|
schema: { status: "pass", issues: [] },
|
|
operationId: `op_${step.id}`,
|
|
traceId: `trc_${step.id}`,
|
|
auditId: `aud_${step.id}`,
|
|
evidenceId: `ev_${step.id}`,
|
|
resultValue: step.expectedValue,
|
|
evidenceState: {
|
|
status: "green",
|
|
sourceKind: "DEV-LIVE",
|
|
durable: true,
|
|
writeStatus: "persisted"
|
|
}
|
|
}));
|
|
assert.deepEqual(
|
|
classifyM3IoControlReport({
|
|
checks: [{ id: "source", status: "fail", summary: "stale source check" }],
|
|
liveOperations: operations
|
|
}),
|
|
{
|
|
status: "pass",
|
|
classification: "trusted_green",
|
|
trustedGreen: true
|
|
}
|
|
);
|
|
});
|
|
|
|
test("M3 IO E2E does not claim trusted green for SOURCE, DRY-RUN, LOCAL, fixture, or BLOCKED backend evidence", () => {
|
|
for (const sourceKind of ["SOURCE", "DRY-RUN", "LOCAL", "fixture", "BLOCKED"]) {
|
|
const operations = expectedM3IoLiveSequence().map((step) => ({
|
|
id: step.id,
|
|
action: step.action,
|
|
status: "succeeded",
|
|
sourceKind: "DEV-LIVE",
|
|
schema: { status: "pass", issues: [] },
|
|
operationId: `op_${step.id}`,
|
|
traceId: `trc_${step.id}`,
|
|
auditId: `aud_${step.id}`,
|
|
evidenceId: `ev_${step.id}`,
|
|
resultValue: step.expectedValue,
|
|
evidenceState: {
|
|
status: "green",
|
|
sourceKind,
|
|
durable: true,
|
|
writeStatus: "persisted"
|
|
}
|
|
}));
|
|
assert.deepEqual(
|
|
classifyM3IoControlReport({
|
|
checks: [{ id: "source", status: "pass" }],
|
|
liveOperations: operations
|
|
}),
|
|
{
|
|
status: "blocked",
|
|
classification: controlPathReachablePersistenceBlocked,
|
|
trustedGreen: false
|
|
}
|
|
);
|
|
}
|
|
});
|
|
|
|
test("M3 IO E2E frontend guardrail requires readiness before controls unlock", () => {
|
|
const appSource = `
|
|
function m3ControlCanOperate(contract) {
|
|
return contract?.status === "available" &&
|
|
contract?.readiness?.status === "ready" &&
|
|
contract?.readiness?.controlReady === true &&
|
|
contract?.readiness?.sourceKind === "DEV-LIVE" &&
|
|
contract?.readiness?.evidenceLevel === "DEV-LIVE";
|
|
}
|
|
async function runM3IoAction() {
|
|
if (!m3ControlCanOperate()) return;
|
|
return fetchJson("/v1/m3/io", { method: "POST" });
|
|
}
|
|
`;
|
|
const pass = checkFrontendNoDirectRuntimeCalls({
|
|
appSource,
|
|
artifactPublisherSource: 'if (url.pathname === "/v1/m3/io" || url.pathname === "/v1/m3/status") proxyCloudApi();'
|
|
});
|
|
assert.equal(pass.status, "pass");
|
|
|
|
const fail = checkFrontendNoDirectRuntimeCalls({
|
|
appSource: `
|
|
function m3ControlCanOperate(contract) {
|
|
return contract?.status === "available";
|
|
}
|
|
async function runM3IoAction() {
|
|
return fetchJson("/v1/m3/io", { method: "POST" });
|
|
}
|
|
`,
|
|
artifactPublisherSource: 'if (url.pathname === "/v1/m3/io" || url.pathname === "/v1/m3/status") proxyCloudApi();'
|
|
});
|
|
assert.equal(fail.status, "fail");
|
|
assert.ok(fail.issues.some((issue) => /readiness/u.test(issue)));
|
|
});
|
|
|
|
test("M3 IO E2E frontend guardrail catches direct gateway or patch-panel calls", () => {
|
|
const appSource = `
|
|
function m3ControlCanOperate(contract) {
|
|
return contract?.status === "available" &&
|
|
contract?.readiness?.status === "ready" &&
|
|
contract?.readiness?.controlReady === true &&
|
|
contract?.readiness?.sourceKind === "DEV-LIVE" &&
|
|
contract?.readiness?.evidenceLevel === "DEV-LIVE";
|
|
}
|
|
async function runM3IoAction() {
|
|
if (!m3ControlCanOperate()) return;
|
|
return fetchJson("/v1/m3/io", { method: "POST" });
|
|
}
|
|
async function other() {
|
|
return fetch("/v1");
|
|
}
|
|
`;
|
|
const pass = checkFrontendNoDirectRuntimeCalls({
|
|
appSource,
|
|
artifactPublisherSource: 'if (url.pathname === "/v1/m3/io" || url.pathname === "/v1/m3/status") proxyCloudApi();'
|
|
});
|
|
assert.equal(pass.status, "pass");
|
|
|
|
const fail = checkFrontendNoDirectRuntimeCalls({
|
|
appSource: `
|
|
async function runM3IoAction() {
|
|
await fetch("http://hwlab-gateway-simu-1.hwlab-dev.svc.cluster.local:7101/invoke");
|
|
}
|
|
`,
|
|
artifactPublisherSource: ""
|
|
});
|
|
assert.equal(fail.status, "fail");
|
|
assert.ok(fail.issues.some((issue) => /direct runtime|same-origin|runM3IoAction/u.test(issue)));
|
|
});
|
|
|
|
test("M3 IO E2E live response schema requires identifiers and frontendBypass=false", () => {
|
|
const [step] = expectedM3IoLiveSequence();
|
|
const valid = validateM3IoLiveResponse({
|
|
status: "succeeded",
|
|
accepted: true,
|
|
action: step.action,
|
|
chain: {
|
|
projectId: "prj_m3_hardware_loop",
|
|
sourceGatewayId: "gwsimu_1",
|
|
sourceGatewaySessionId: "gws_gwsimu_1",
|
|
sourceResourceId: "res_boxsimu_1",
|
|
sourceBoxId: "boxsimu_1",
|
|
sourcePort: "DO1",
|
|
targetGatewayId: "gwsimu_2",
|
|
targetGatewaySessionId: "gws_gwsimu_2",
|
|
targetResourceId: "res_boxsimu_2",
|
|
targetBoxId: "boxsimu_2",
|
|
targetPort: "DI1",
|
|
patchPanelServiceId: "hwlab-patch-panel"
|
|
},
|
|
controlPath: {
|
|
cloudApi: true,
|
|
gatewaySimu: true,
|
|
boxSimu: true,
|
|
patchPanel: true,
|
|
frontendBypass: false
|
|
},
|
|
operationId: "op_m3_test",
|
|
traceId: "trc_m3_test",
|
|
auditId: "aud_m3_test",
|
|
evidenceId: "evd_m3_test",
|
|
evidenceState: {
|
|
status: "blocked",
|
|
sourceKind: "BLOCKED"
|
|
},
|
|
result: {
|
|
value: true
|
|
}
|
|
}, step);
|
|
assert.equal(valid.status, "pass");
|
|
|
|
const invalid = validateM3IoLiveResponse({
|
|
status: "succeeded",
|
|
accepted: true,
|
|
action: step.action,
|
|
chain: {},
|
|
controlPath: {
|
|
frontendBypass: true
|
|
},
|
|
traceId: "trc_m3_test",
|
|
evidenceState: {
|
|
status: "blocked"
|
|
},
|
|
result: {
|
|
value: false
|
|
}
|
|
}, step);
|
|
assert.equal(invalid.status, "fail");
|
|
assert.ok(invalid.issues.some((issue) => /operationId|frontendBypass|chain|result\.value/u.test(issue)));
|
|
});
|