224b572304
Refs #108 #99 #78. Reviewed by commander. This only locks the outer Cloud Workbench viewport and preserves internal scroll containers; no PROD, no hardware write API, no DEV-LIVE claim.
119 lines
5.1 KiB
JavaScript
119 lines
5.1 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { loadMvpGateSummary } from "../../../internal/mvp-gate/summary.mjs";
|
|
import { gateSummary } from "../gate-summary.mjs";
|
|
import { runCloudWebM3ReadonlyContract } from "./m3-readonly-contract.mjs";
|
|
import { runWorkbenchHardwarePanelContract } from "./workbench-hardware-panel-contract.mjs";
|
|
|
|
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
const repoRoot = path.resolve(rootDir, "../..");
|
|
const requiredFiles = [
|
|
"index.html",
|
|
"styles.css",
|
|
"app.mjs",
|
|
"gate-summary.mjs",
|
|
"runtime.mjs",
|
|
"workbench-hardware-panel.mjs"
|
|
];
|
|
|
|
for (const file of requiredFiles) {
|
|
const filePath = path.resolve(rootDir, file);
|
|
if (!fs.existsSync(filePath)) {
|
|
throw new Error(`missing web asset: ${file}`);
|
|
}
|
|
}
|
|
|
|
const html = fs.readFileSync(path.resolve(rootDir, "index.html"), "utf8");
|
|
const styles = fs.readFileSync(path.resolve(rootDir, "styles.css"), "utf8");
|
|
const app = fs.readFileSync(path.resolve(rootDir, "app.mjs"), "utf8");
|
|
const artifactPublisher = fs.readFileSync(path.resolve(repoRoot, "scripts/dev-artifact-publish.mjs"), "utf8");
|
|
const frontendSource = `${html}\n${app}\n${artifactPublisher}`;
|
|
|
|
assert.match(html, /HWLAB Cloud Workbench/);
|
|
assert.match(html, /Agent Conversation \/ Trace Workspace/);
|
|
assert.match(html, /BOX-SIMU \/ Gateway-SIMU \/ Patch Panel/);
|
|
assert.match(html, /Gate \/ Diagnostics/);
|
|
for (const workbenchElement of [
|
|
"activity-rail",
|
|
"explorer",
|
|
"resource-tree",
|
|
"conversation-list",
|
|
"trace-list",
|
|
"right-sidebar",
|
|
"command-form",
|
|
"hardware-list",
|
|
"control-list",
|
|
"wiring-list",
|
|
"records-list",
|
|
"diagnostics-list"
|
|
]) {
|
|
assert.match(html, new RegExp(workbenchElement));
|
|
}
|
|
for (const viewId of ["workspace", "gate"]) {
|
|
assert.match(html, new RegExp(`data-view="${viewId}"`));
|
|
}
|
|
assert.match(html, /live-status/);
|
|
assert.match(html, /method-list/);
|
|
assert.match(html, /source-strip/);
|
|
assert.match(html, /SOURCE loading/);
|
|
assert.match(html, /DRY-RUN loading/);
|
|
assert.match(html, /DEV-LIVE probing/);
|
|
assert.match(html, /BLOCKED pending/);
|
|
assert.doesNotMatch(html, /M3 Diagnostics Console/);
|
|
assert.match(styles, /html,\s*\nbody\s*{[^}]*height:\s*100%;[^}]*overflow:\s*hidden;/s);
|
|
assert.match(styles, /body\s*>\s*\[data-app-shell\]\s*{[^}]*min-height:\s*0;/s);
|
|
assert.match(styles, /\.workbench-shell\s*{[^}]*height:\s*100(?:d)?vh;[^}]*overflow:\s*hidden;/s);
|
|
assert.match(styles, /\.view\s*{[^}]*min-height:\s*0;[^}]*overflow:\s*auto;/s);
|
|
assert.match(styles, /\.(?:resource-tree|compact-list|conversation-list|trace-list|hardware-list)[^{]*{[^}]*min-height:\s*0;[^}]*overflow:\s*auto;/s);
|
|
assert.match(app, /fetchJson\("\/v1"\)/);
|
|
assert.match(app, /fetchJson\("\/health\/live"\)/);
|
|
assert.match(app, /callRpc\("system\.health"\)/);
|
|
assert.match(app, /callRpc\("cloud\.adapter\.describe"\)/);
|
|
assert.match(app, /callRpc\("audit\.event\.query"/);
|
|
assert.match(app, /callRpc\("evidence\.record\.query"/);
|
|
assert.match(artifactPublisher, /HWLAB_API_BASE_URL/);
|
|
assert.match(artifactPublisher, /readOnlyRpcMethods/);
|
|
assert.match(artifactPublisher, /requestUpstream/);
|
|
assert.match(artifactPublisher, /from "node:http"/);
|
|
assert.doesNotMatch(artifactPublisher, /await fetch\(target/);
|
|
assert.match(artifactPublisher, /"system\.health"/);
|
|
assert.match(artifactPublisher, /"cloud\.adapter\.describe"/);
|
|
assert.match(artifactPublisher, /"audit\.event\.query"/);
|
|
assert.match(artifactPublisher, /"evidence\.record\.query"/);
|
|
assert.match(artifactPublisher, /readonly_rpc_required/);
|
|
assert.match(artifactPublisher, /cloud_api_proxy_failed/);
|
|
assert.match(app, /degraded/);
|
|
assert.match(app, /runtime\.serviceRoute\.join\(" -> "\)/);
|
|
assert.doesNotMatch(app, /callRpc\("hardware\./);
|
|
assert.doesNotMatch(app, /hardware\.operation\.request/);
|
|
assert.doesNotMatch(app, /hardware\.invoke\.shell/);
|
|
assert.doesNotMatch(app, /audit\.event\.write/);
|
|
assert.doesNotMatch(app, /evidence\.record\.write/);
|
|
assert.doesNotMatch(frontendSource, /--live --confirm-dev --confirmed-non-production/);
|
|
assert.doesNotMatch(frontendSource, /74\.48\.78\.17:666[67]\b|:666[67]\b/);
|
|
|
|
const sourceSummary = loadMvpGateSummary(repoRoot);
|
|
assert.equal(gateSummary.endpoint, sourceSummary.endpoint);
|
|
assert.equal(gateSummary.gateStatus, sourceSummary.gateStatus);
|
|
assert.equal(gateSummary.milestones.length, 6);
|
|
assert.deepEqual(
|
|
gateSummary.milestones.map((item) => item.id),
|
|
["M0", "M1", "M2", "M3", "M4", "M5"]
|
|
);
|
|
assert.equal(gateSummary.artifactCount, 13);
|
|
assert.equal(gateSummary.healthCount, 14);
|
|
assert.equal(gateSummary.gatewaySessionCount, 2);
|
|
assert.equal(gateSummary.boxResourceCount, 2);
|
|
assert.equal(gateSummary.topology.patchPanel.serviceId, "hwlab-patch-panel");
|
|
assert.equal(gateSummary.agent.agentServiceId, "hwlab-agent-mgr");
|
|
assert.equal(gateSummary.agent.workerServiceId, "hwlab-agent-worker");
|
|
assert.equal(gateSummary.evidenceRecords.length, 2);
|
|
assert.equal(gateSummary.safety.allowNetwork, false);
|
|
|
|
runCloudWebM3ReadonlyContract();
|
|
runWorkbenchHardwarePanelContract();
|
|
|
|
console.log("hwlab-cloud-web check ok: workbench shell, hardware evidence panel, and read-only diagnostics contract are present");
|