feat(hwpod): 补齐拓扑与节点接入控制台

This commit is contained in:
root
2026-07-13 04:09:31 +02:00
parent 63a5cfff4a
commit 652f1f9c6b
14 changed files with 1369 additions and 122 deletions
+47 -3
View File
@@ -33,6 +33,8 @@ test("cloud-api exposes hwpod-node-ops contract on /v1", async () => {
assert.equal(payload.hwpod.contractVersion, "hwpod-node-ops-v1");
assert.equal(payload.hwpod.apiRole, "node-ops-forwarder");
assert.equal(payload.hwpod.specDiscoveryRoute, "/v1/hwpod/specs");
assert.equal(payload.hwpod.topologyRoute, "/v1/hwpod/topology");
assert.equal(payload.hwpod.topologyContractVersion, "hwpod-topology-v1");
assert.ok(payload.hwpod.supportedOps.includes("node.diagnostics"));
assert.ok(payload.hwpod.supportedOps.includes("workspace.ls"));
assert.ok(payload.hwpod.supportedOps.includes("workspace.insert-after"));
@@ -66,6 +68,11 @@ test("cloud-api exposes hwlab-node Python update metadata", async () => {
assert.equal(payload.latestVersion, "0.1.1");
assert.equal(payload.downloadUrl, "https://hwlab.pikapython.com/downloads/hwlab-node.py");
assert.equal(payload.sha256, "abc123");
assert.equal(payload.artifact.fileName, "hwlab-node.py");
assert.equal(payload.artifact.sha256, "abc123");
assert.ok(payload.artifact.sizeBytes > 0);
assert.equal(payload.readinessStages.at(-1).id, "hwpod-ready");
assert.ok(payload.installConditions.some((item: any) => item.id === "outbound-websocket"));
const currentResponse = await fetch(`${serverUrl(server)}/v1/hwlab-node/update?current=0.1.1&channel=stable&platform=windows`);
const currentPayload = await currentResponse.json();
@@ -85,15 +92,18 @@ test("cloud-api serves bundled hwlab-node Python updater artifact by default", a
const payload = await response.json();
assert.equal(response.status, 200);
assert.equal(payload.updateAvailable, true);
assert.equal(payload.latestVersion, "0.1.1");
assert.equal(payload.latestVersion, "0.1.2");
assert.equal(payload.downloadUrl, "https://hwlab.pikapython.com/v1/hwlab-node/download/hwlab-node.py");
assert.match(payload.sha256, /^[a-f0-9]{64}$/u);
assert.equal(payload.artifact.version, "0.1.2");
assert.equal(payload.artifact.fileName, "hwlab-node.py");
assert.ok(payload.artifact.sizeBytes > 0);
const download = await fetch(`${serverUrl(server)}/v1/hwlab-node/download/hwlab-node.py`);
const text = await download.text();
assert.equal(download.status, 200);
assert.equal(download.headers.get("x-hwlab-node-version"), "0.1.1");
assert.ok(text.includes('APP_VERSION = "0.1.1"'));
assert.equal(download.headers.get("x-hwlab-node-version"), "0.1.2");
assert.ok(text.includes('APP_VERSION = "0.1.2"'));
} finally {
await close(server);
}
@@ -187,6 +197,40 @@ test("cloud-api probes discovered hwpod-spec availability through node-ops", asy
}
});
test("cloud-api serves the typed HWPOD topology read model from its existing owner", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-topology-"));
const specDir = path.join(root, ".hwlab");
await mkdir(specDir, { recursive: true });
await writeFile(path.join(specDir, "hwpod-spec.yaml"), sampleSpecYaml(), "utf8");
const server = createHwpodTestCloudApiServer({
env: { PATH: process.env.PATH, HWLAB_CODE_AGENT_WORKSPACE: root },
hwpodNodeWsRegistry: {
hasNode: () => false,
dispatch: async () => ({ ok: false, status: "blocked", results: [] }),
describe: () => ({
mode: "hwpod-node-outbound-native-ws",
connectedCount: 1,
pendingCount: 0,
nodes: [{ nodeId: "node-d601-f103-v2", name: "D601 Python Node", status: "online", platform: "win32", version: "0.1.2-python-gui", capabilities: ["workspace.ls"], inFlightCount: 0, maxInFlight: 1 }]
})
}
});
await listen(server);
try {
const response = await fetch(`${serverUrl(server)}/v1/hwpod/topology?resource=node`);
const payload = await response.json();
assert.equal(response.status, 200);
assert.equal(payload.contractVersion, "hwpod-topology-v1");
assert.equal(payload.owner, "cloud-api-hwpod-domain");
assert.equal(payload.items[0].nodeId, "node-d601-f103-v2");
assert.equal(payload.items[0].deviceCount, 1);
assert.equal(payload.items[0].capabilityMismatch, true);
} finally {
await close(server);
await rm(root, { recursive: true, force: true });
}
});
test("cloud-api forwards valid hwpod-node-ops plans to injected node handler", async () => {
const seen: any[] = [];
const server = createHwpodTestCloudApiServer({