Files
pikasTech-HWLAB/web/hwlab-cloud-web/scripts/workbench-r3-live-hwpod-parity.test.ts
2026-06-20 16:26:54 +08:00

91 lines
5.8 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import type { ApiResult, HwpodNodeOpsResponse, HwpodSpecsResponse, LiveProbePayload, LiveSurface } from "../src/types/index.ts";
import { traceExplorerHref } from "../src/config/runtime.ts";
import { summarizeBuilds, summarizeHwpodNodeOps, summarizeProbeRows } from "../src/stores/workbench-live.ts";
import { liveCall } from "../src/stores/workbench.ts";
Object.defineProperty(globalThis, "window", {
configurable: true,
value: {
HWLAB_CLOUD_WEB_CONFIG: {
displayTime: { timeZone: "Asia/Shanghai", locale: "zh-CN", label: "北京时间" }
}
}
});
test("R3 probe summary exposes React WorkbenchProbe rows", () => {
const summary = summarizeProbeRows(liveSurface());
assert.equal(summary.rows.length, 3);
assert.equal(summary.okCount, 3);
assert.equal(summary.tone, "ok");
assert.ok(summary.rows.some((row) => row.path === "/health/live" && row.detail.includes("revision=c0cbdbb38d")));
});
test("R3 build summary maps live-builds services instead of raw JSON", () => {
const summary = summarizeBuilds(liveSurface());
assert.equal(summary.rows.length, 2);
assert.equal(summary.tone, "warn");
assert.match(summary.countsText ?? "", /服务 2 个/);
assert.ok(summary.rows.some((row) => row.label === "hwlab-cloud-web" && row.reason?.includes("构建时间不可用")));
});
test("R3 HWPOD summary combines specs availability, node-ops and blocker stream", () => {
const summary = summarizeHwpodNodeOps({ live: liveSurface() });
assert.equal(summary.contractVersion, "hwpod-node-ops-v1");
assert.equal(summary.hwpodName, "d601-f103-v2");
assert.equal(summary.nodeId, "node-d601-f103-v2");
assert.equal(summary.supportedOpsCount, 3);
assert.equal(summary.connectedCount, 0);
assert.ok(summary.tiles.some((tile) => tile.label === "Contract"));
assert.ok(summary.tiles.some((tile) => tile.label === "Ops"));
assert.ok(summary.errors.some((error) => error.code === "hwpod_node_unavailable"));
assert.ok(summary.streamLines.some((line) => line.includes("op_01_workspace_ls workspace.ls hwpod_node_unavailable")));
});
test("R3 live refresh keeps partial evidence when one endpoint throws", async () => {
const failed = await liveCall("live builds", async () => {
throw new Error("network edge reset");
});
assert.equal(failed.ok, false);
assert.equal(failed.status, 0);
assert.match(failed.error ?? "", /live builds: network edge reset/);
const passed = await liveCall("health/live", async () => ok<LiveProbePayload>({ status: "ok", revision: "155530d8d9355887a23a08e341291994cc312f54" }));
assert.equal(passed.ok, true);
assert.equal(passed.data?.revision, "155530d8d9355887a23a08e341291994cc312f54");
});
test("R3 trace explorer href is config gated and trace_id only", () => {
window.HWLAB_CLOUD_WEB_CONFIG = {
displayTime: { timeZone: "Asia/Shanghai", locale: "zh-CN", label: "北京时间" },
workbench: { traceExplorerUrlTemplate: "/v1/workbench/traces/{trace_id}/events" }
};
assert.equal(traceExplorerHref("trc_completed"), "/v1/workbench/traces/trc_completed/events");
assert.equal(traceExplorerHref("bad/trace"), null);
window.HWLAB_CLOUD_WEB_CONFIG.workbench = { traceExplorerUrlTemplate: "/trace/{trace_id}?prompt={prompt}" };
assert.equal(traceExplorerHref("trc_completed"), null);
window.HWLAB_CLOUD_WEB_CONFIG.workbench = {};
assert.equal(traceExplorerHref("trc_completed"), null);
});
function liveSurface(): LiveSurface {
const liveProbe = ok<LiveProbePayload>({ serviceId: "hwlab-cloud-api", environment: "v03", status: "ok", ready: true, revision: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd", observedAt: "2026-06-13T17:59:15.000Z" });
return {
healthLive: liveProbe,
health: ok({ serviceId: "hwlab-edge-proxy", status: "ok", ready: true, revision: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd" }),
restIndex: ok({ serviceId: "hwlab-cloud-api", status: "ok", ready: true, revision: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd" }),
hwpodSpecs: ok<HwpodSpecsResponse>({ ok: true, status: "completed", count: 1, availableCount: 0, nodeOpsContractVersion: "hwpod-node-ops-v1", specs: [{ name: "d601-f103-v2", hwpodId: "d601-f103-v2", nodeId: "node-d601-f103-v2", workspacePath: "F:\\Work\\D601-HWLAB", source: { caseId: "d601-f103-v2-compile" }, availability: { ok: false, status: "blocked", checkedAt: "2026-06-13T17:59:15.785Z", results: [{ opId: "op_01_workspace_ls", op: "workspace.ls", ok: false, status: "blocked", blocker: { code: "hwpod_node_unavailable", layer: "hwpod-node", retryable: true, summary: "no outbound WebSocket hwpod-node is connected" } }] } }] }),
hwpodNodeOps: ok<HwpodNodeOpsResponse>({ ok: true, status: "ready", contractVersion: "hwpod-node-ops-v1", route: "/v1/hwpod-node-ops", supportedOps: ["node.health", "workspace.ls", "debug.build"], websocket: { connectedCount: 0, pendingCount: 0, nodes: [] } }),
liveBuilds: ok({ status: "ok", counts: { total: 2, withBuildTime: 0, unavailable: 2, external: 0 }, services: [{ serviceId: "hwlab-cloud-api", status: "build_time_unavailable", build: { createdAt: null, unavailableReason: "构建时间不可用:live tag 与 deploy metadata 不匹配", metadataSource: "live-health" }, image: { tag: "env-abc", digest: "sha256:abcdef0123456789" }, commit: { id: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd" }, revision: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd" }, { serviceId: "hwlab-cloud-web", status: "build_time_unavailable", build: { createdAt: null, unavailableReason: "构建时间不可用:live tag 与 deploy metadata 不匹配" }, image: { tag: "env-web" }, commit: { id: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd" }, revision: "c0cbdbb38de77f1ecc9d1222438a13294d81d4dd" }] }),
loadedAt: "2026-06-13T18:00:00.000Z"
};
}
function ok<T>(data: T): ApiResult<T> {
return { ok: true, status: 200, data, error: null };
}