Files
pikasTech-HWLAB/tools/hwpod-harness.test.ts
T
2026-07-01 10:53:05 +08:00

402 lines
24 KiB
TypeScript

import assert from "node:assert/strict";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { test } from "bun:test";
import { runHwpodCli, runHwpodCompilerCli, runHwpodCtl } from "./src/hwpod-harness-lib.ts";
const NOW = "2026-06-05T00:00:00.000Z";
test("hwpod-ctl initializes, validates, edits, and binds workspace-local hwpod-spec", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-ctl-"));
const specPath = path.join(root, ".hwlab", "hwpod-spec.yaml");
try {
const init = await runHwpodCtl([
"spec",
"init",
"--spec",
specPath,
"--name",
"stm32-local",
"--node",
"pc-host-1",
"--workspace",
"/workspace/fw"
], { now: () => NOW });
assert.equal(init.exitCode, 0);
assert.equal(init.payload.document.kind, "Hwpod");
assert.equal(init.payload.document.spec.nodeBinding.nodeId, "pc-host-1");
const validate = await runHwpodCtl(["spec", "validate", "--spec", specPath], { now: () => NOW });
assert.equal(validate.exitCode, 0);
assert.deepEqual(validate.payload.fourElements, ["targetDevice", "workspace", "debugProbe", "ioProbe"]);
const set = await runHwpodCtl(["spec", "set", "spec.targetDevice.id", "stm32f103", "--spec", specPath], { now: () => NOW });
assert.equal(set.exitCode, 0);
assert.equal(set.payload.document.spec.targetDevice.id, "stm32f103");
const bind = await runHwpodCtl(["bind", "--spec", specPath, "--node", "edge-gateway-1"], { now: () => NOW });
assert.equal(bind.exitCode, 0);
assert.equal(bind.payload.nodeId, "edge-gateway-1");
assert.match(await readFile(specPath, "utf8"), /nodeId: edge-gateway-1/u);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test("hwpod cli resolves hwpod-id from runtime discovery and applies workspace override", async () => {
const runWorkspace = "F:\\Work\\HWLAB-CASE-F103\\.worktree\\caserun-hwpod-id";
const requests: any[] = [];
const fetchImpl: any = async (url: string, init: any) => {
requests.push({ url, init });
return {
status: 200,
json: async () => ({
ok: true,
status: "completed",
specs: [{
hwpodId: "d601-f103-v2",
authority: "preinstalled-verified-spec",
document: {
apiVersion: "hwlab.dev/v0alpha1",
kind: "Hwpod",
metadata: { uid: "D601-F103-V2", name: "d601-f103-v2" },
spec: {
targetDevice: { board: "D601-F103-V2", mcu: "STM32F103" },
workspace: { path: "F:\\Work\\D601-HWLAB" },
debugProbe: { type: "daplink" },
ioProbe: { uart: { id: "uart/1" } },
nodeBinding: { nodeId: "node-d601-f103-v2" }
}
}
}]
})
};
};
const env = { HWLAB_RUNTIME_API_URL: "http://api.test" };
const validate = await runHwpodCtl(["spec", "validate", "--hwpod-id", "d601-f103-v2", "--workspace-path", runWorkspace], { now: () => NOW, env, fetchImpl });
assert.equal(validate.exitCode, 0);
assert.equal(validate.payload.specPath, "hwpod://d601-f103-v2");
assert.equal(validate.payload.specAuthority, "preinstalled-verified-spec");
assert.equal(validate.payload.document.spec.workspace.path, runWorkspace);
const plan = await runHwpodCli(["workspace", "ls", ".", "--hwpod-id", "d601-f103-v2", "--workspace-path", runWorkspace, "--dry-run"], { now: () => NOW, env, fetchImpl });
assert.equal(plan.exitCode, 0);
assert.equal(plan.payload.hwpodId, "d601-f103-v2");
assert.equal(plan.payload.specPath, "hwpod://d601-f103-v2");
assert.equal(plan.payload.plan.resourceHints.workspacePath, runWorkspace);
assert.equal(plan.payload.plan.source.specAuthority, "preinstalled-verified-spec");
assert.equal(plan.payload.plan.ops[0].args.workspacePath, runWorkspace);
assert.match(requests[0].url, /\/v1\/hwpod\/specs\?hwpodId=d601-f103-v2/u);
});
test("hwpod-compiler-cli compiles workspace-local spec into node ops", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-compiler-"));
const specPath = path.join(root, ".hwlab", "hwpod-spec.yaml");
try {
await runHwpodCtl(["spec", "init", "--spec", specPath, "--node", "pc-host-1", "--workspace", "/workspace/fw"], { now: () => NOW });
const result = await runHwpodCompilerCli([
"compile",
"--spec",
specPath,
"--intent",
"workspace.ls",
"--args",
"{\"path\":\"src\"}"
], { now: () => NOW });
assert.equal(result.exitCode, 0);
assert.equal(result.payload.contractVersion, "hwpod-node-ops-v1");
assert.equal(result.payload.plan.nodeId, "pc-host-1");
assert.equal(result.payload.plan.ops.length, 1);
assert.equal(result.payload.plan.ops[0].op, "workspace.ls");
assert.equal(result.payload.plan.ops[0].args.path, "src");
assert.equal(result.payload.plan.ops[0].args.workspacePath, "/workspace/fw");
await runHwpodCtl(["spec", "set", "spec.workspace.buildCommand", "printf spec-build", "--spec", specPath], { now: () => NOW });
const build = await runHwpodCompilerCli(["compile", "--spec", specPath, "--intent", "debug.build"], { now: () => NOW });
assert.equal(build.exitCode, 0);
assert.equal(build.payload.plan.hwpodId, "local-hwpod");
assert.equal(build.payload.plan.ops[0].op, "debug.build");
assert.equal(build.payload.plan.ops[0].args.command, "printf spec-build");
} finally {
await rm(root, { recursive: true, force: true });
}
});
test("hwpod-compiler-cli generates Keil build and download commands from structured hwpod-spec", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-keil-compiler-"));
const specPath = path.join(root, ".hwlab", "hwpod-spec.yaml");
try {
await runHwpodCtl(["spec", "init", "--spec", specPath, "--node", "node-d601-f103-v2", "--workspace", "F:\\Work\\D601-HWLAB"], { now: () => NOW });
await runHwpodCtl(["spec", "set", "spec.workspace.toolchain", "keil-mdk", "--spec", specPath], { now: () => NOW });
await runHwpodCtl(["spec", "set", "spec.workspace.keilProject", "projects/01_baseline/Projects/MDK-ARM/atk_f103.uvprojx", "--spec", specPath], { now: () => NOW });
await runHwpodCtl(["spec", "set", "spec.workspace.keilTarget", "USART", "--spec", specPath], { now: () => NOW });
await runHwpodCtl(["spec", "set", "spec.workspace.keilCliPath", "C:\\Users\\liang\\.agents\\skills\\keil\\keil-cli.py", "--spec", specPath], { now: () => NOW });
await runHwpodCtl(["spec", "set", "spec.debugProbe.type", "daplink", "--spec", specPath], { now: () => NOW });
await runHwpodCtl(["spec", "set", "spec.debugProbe.adapter", "keil", "--spec", specPath], { now: () => NOW });
await runHwpodCtl(["spec", "set", "spec.debugProbe.probeUid", "3FD750C63E342E24", "--spec", specPath], { now: () => NOW });
await runHwpodCtl(["spec", "set", "spec.debugProbe.probeName", "CherryUSB MicroLink CMSIS-DAP", "--spec", specPath], { now: () => NOW });
const build = await runHwpodCompilerCli(["compile", "--spec", specPath, "--intent", "debug.build"], { now: () => NOW });
assert.equal(build.exitCode, 0);
assert.equal(build.payload.plan.ops[0].op, "cmd.run");
assert.equal(build.payload.plan.ops[0].args.target, "USART");
assert.equal(build.payload.plan.ops[0].args.command, "py");
assert.deepEqual(build.payload.plan.ops[0].args.argv.slice(0, 5), ["-3", "C:\\Users\\liang\\.agents\\skills\\keil\\keil-cli.py", "build", "-p", "F:\\Work\\D601-HWLAB\\projects\\01_baseline\\Projects\\MDK-ARM\\atk_f103.uvprojx"]);
assert.deepEqual(build.payload.plan.ops[0].args.argv.slice(-2), ["-t", "USART"]);
assert.match(build.payload.plan.ops[0].args.commandLine, /^py -3 C:\\Users\\liang\\\.agents\\skills\\keil\\keil-cli\.py build -p/u);
assert.match(build.payload.plan.ops[0].args.commandLine, /F:\\Work\\D601-HWLAB\\projects\\01_baseline\\Projects\\MDK-ARM\\atk_f103\.uvprojx/u);
assert.match(build.payload.plan.ops[0].args.commandLine, / -t USART$/u);
assert.equal(build.payload.plan.ops[0].args.commandBinding.source, "hwpod-compiler.keil-mdk");
assert.equal(build.payload.plan.ops[0].args.commandBinding.action, "build");
const download = await runHwpodCompilerCli(["compile", "--spec", specPath, "--intent", "debug.download"], { now: () => NOW });
assert.equal(download.exitCode, 0);
assert.deepEqual(download.payload.plan.ops.map((op: any) => op.op), ["cmd.run", "cmd.run"]);
assert.equal(download.payload.plan.ops[0].args.step, "keil-sidecar-diagnostics");
assert.equal(download.payload.plan.ops[0].args.command, "node");
assert.deepEqual(JSON.parse(download.payload.plan.ops[0].args.argv[2]), { primary: "F:\\Work\\D601-HWLAB\\projects\\01_baseline\\Projects\\MDK-ARM\\atk_f103.uvoptx", alternate: "F:\\Work\\D601-HWLAB\\projects\\01_baseline\\Projects\\MDK-ARM\\atk_f103.uvopt" });
assert.equal(download.payload.plan.ops[0].args.commandBinding.primary, "F:\\Work\\D601-HWLAB\\projects\\01_baseline\\Projects\\MDK-ARM\\atk_f103.uvoptx");
assert.equal(download.payload.plan.ops[0].args.commandBinding.alternate, "F:\\Work\\D601-HWLAB\\projects\\01_baseline\\Projects\\MDK-ARM\\atk_f103.uvopt");
assert.equal(download.payload.plan.ops[1].args.step, "keil-flash");
assert.equal(download.payload.plan.ops[1].args.command, "py");
assert.deepEqual(download.payload.plan.ops[1].args.argv.slice(0, 5), ["-3", "C:\\Users\\liang\\.agents\\skills\\keil\\keil-cli.py", "flash", "-p", "F:\\Work\\D601-HWLAB\\projects\\01_baseline\\Projects\\MDK-ARM\\atk_f103.uvprojx"]);
assert.deepEqual(download.payload.plan.ops[1].args.argv.slice(-8), ["-m", "daplink", "--program-backend", "keil", "-u", "3FD750C63E342E24", "-t", "USART"]);
assert.equal(download.payload.plan.ops[1].args.commandBinding.action, "download");
assert.equal(download.payload.plan.ops[1].args.commandBinding.keilCommand, "flash");
assert.equal(download.payload.plan.ops[1].args.commandBinding.programBackend, "keil");
assert.equal(download.payload.plan.ops[1].args.commandBinding.autoBindUvoptx, false);
assert.match(download.payload.plan.ops[1].args.commandLine, /^py -3 C:\\Users\\liang\\\.agents\\skills\\keil\\keil-cli\.py flash -p/u);
const jobStatus = await runHwpodCli(["job", "status", "keil_job_123", "--spec", specPath, "--dry-run"], { now: () => NOW });
assert.equal(jobStatus.exitCode, 0);
assert.equal(jobStatus.payload.plan.ops[0].op, "cmd.run");
assert.equal(jobStatus.payload.plan.ops[0].args.step, "keil-job-status");
assert.deepEqual(jobStatus.payload.plan.ops[0].args.argv.slice(0, 3), ["-3", "C:\\Users\\liang\\.agents\\skills\\keil\\keil-cli.py", "job-status"]);
assert.equal(jobStatus.payload.plan.ops[0].args.argv[3], "keil_job_123");
assert.equal(jobStatus.payload.plan.ops[0].args.workspacePath, "C:\\Users\\liang\\.agents\\skills\\keil");
await runHwpodCtl(["spec", "set", "spec.debugProbe.downloadCommand", "custom download command", "--spec", specPath], { now: () => NOW });
const override = await runHwpodCompilerCli(["compile", "--spec", specPath, "--intent", "debug.download"], { now: () => NOW });
assert.equal(override.exitCode, 0);
assert.equal(override.payload.plan.ops[0].op, "debug.download");
assert.equal(override.payload.plan.ops[0].args.command, "custom download command");
assert.equal(override.payload.plan.ops[0].args.commandBinding, undefined);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test("hwpod-cli dry-run invokes hwpod-compiler-cli and exposes hwpod-node-ops plan", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-cli-"));
const specPath = path.join(root, ".hwlab", "hwpod-spec.yaml");
try {
await runHwpodCtl(["spec", "init", "--spec", specPath, "--node", "pc-host-1"], { now: () => NOW });
const inspect = await runHwpodCli(["inspect", "--spec", specPath, "--dry-run"], { now: () => NOW });
assert.equal(inspect.exitCode, 0);
assert.equal(inspect.payload.dryRun, true);
assert.equal(inspect.payload.compilerInvocation.compiler, "hwpod-compiler-cli");
assert.equal(inspect.payload.compilerInvocation.mode, "in-process");
assert.equal(inspect.payload.compilerInvocation.action, "hwpod-compiler.compile");
assert.equal(inspect.payload.plan.source.compiler, "hwpod-compiler-cli");
assert.deepEqual(inspect.payload.plan.ops.map((op: any) => op.op), ["node.health", "node.inventory"]);
const build = await runHwpodCli(["build", "--spec", specPath, "--dry-run", "--target", "Debug", "--command", "printf cli-build", "--reason", "target flow smoke"], { now: () => NOW });
assert.equal(build.exitCode, 0);
assert.equal(build.payload.compilerInvocation.mode, "in-process");
assert.equal(build.payload.plan.ops[0].op, "debug.build");
assert.equal(build.payload.plan.ops[0].args.target, "Debug");
assert.equal(build.payload.plan.ops[0].args.command, "printf cli-build");
assert.equal(build.payload.plan.ops[0].args.reason, "target flow smoke");
const insert = await runHwpodCli(["workspace", "insert-after", "--spec", specPath, "--path", "projects/01_baseline/User/main.c", "--anchor", " while (1) {", "--line", " /* MARKER_OK */", "--expected-sha", "abc123", "--dry-run"], { now: () => NOW });
assert.equal(insert.exitCode, 0);
assert.equal(insert.payload.plan.ops[0].op, "workspace.insert-after");
assert.equal(insert.payload.plan.ops[0].args.anchor, " while (1) {");
assert.equal(insert.payload.plan.ops[0].args.line, " /* MARKER_OK */");
assert.equal(insert.payload.plan.ops[0].args.expectedSha, "abc123");
const insertAliases = await runHwpodCli(["workspace", "insert-after", "--spec", specPath, "--path", "projects/01_baseline/User/main.c", "--marker", "device-pod LCD bring-up", "--content", " /* MARKER_ALIAS */", "--dry-run"], { now: () => NOW });
assert.equal(insertAliases.exitCode, 0);
assert.equal(insertAliases.payload.plan.ops[0].args.anchor, "device-pod LCD bring-up");
assert.equal(insertAliases.payload.plan.ops[0].args.line, " /* MARKER_ALIAS */");
const replace = await runHwpodCli(["workspace", "replace", "--spec", specPath, "--path", "projects/01_baseline/User/main.c", "--find", " /* MARKER_OK */", "--replace", "", "--dry-run"], { now: () => NOW });
assert.equal(replace.exitCode, 0);
assert.equal(replace.payload.plan.ops[0].op, "workspace.replace");
assert.equal(replace.payload.plan.ops[0].args.find, " /* MARKER_OK */");
assert.equal(replace.payload.plan.ops[0].args.replace, "");
const read = await runHwpodCli(["workspace", "read", "--spec", specPath, "projects/01_baseline/User/main.c", "--dry-run"], { now: () => NOW });
assert.equal(read.exitCode, 0);
assert.equal(read.payload.plan.ops[0].op, "workspace.cat");
assert.equal(read.payload.plan.ops[0].args.path, "projects/01_baseline/User/main.c");
const rg = await runHwpodCli(["workspace", "rg", "--spec", specPath, "arm_2d_init", "projects/01_baseline/Middlewares/Arm-2D", "--context", "3", "--max-matches", "20", "--ignore-case", "--dry-run"], { now: () => NOW });
assert.equal(rg.exitCode, 0);
assert.equal(rg.payload.plan.ops[0].op, "workspace.rg");
assert.equal(rg.payload.plan.ops[0].args.pattern, "arm_2d_init");
assert.equal(rg.payload.plan.ops[0].args.path, "projects/01_baseline/Middlewares/Arm-2D");
assert.equal(rg.payload.plan.ops[0].args.context, 3);
assert.equal(rg.payload.plan.ops[0].args.maxMatches, 20);
assert.equal(rg.payload.plan.ops[0].args.ignoreCase, true);
const grepAlias = await runHwpodCli(["workspace", "grep", "--spec", specPath, "arm_2d_tile_t", "projects/01_baseline/Middlewares/Arm-2D", "--dry-run"], { now: () => NOW });
assert.equal(grepAlias.exitCode, 0);
assert.equal(grepAlias.payload.plan.ops[0].op, "workspace.rg");
assert.equal(grepAlias.payload.plan.ops[0].args.pattern, "arm_2d_tile_t");
const grepShortOptions = await runHwpodCli(["workspace", "grep", "--spec", specPath, "typedef struct", "projects/01_baseline/Middlewares/Arm-2D", "-B", "2", "-A5", "-i", "--dry-run"], { now: () => NOW });
assert.equal(grepShortOptions.exitCode, 0);
assert.equal(grepShortOptions.payload.plan.ops[0].op, "workspace.rg");
assert.equal(grepShortOptions.payload.plan.ops[0].args.beforeContext, 2);
assert.equal(grepShortOptions.payload.plan.ops[0].args.afterContext, 5);
assert.equal(grepShortOptions.payload.plan.ops[0].args.ignoreCase, true);
await runHwpodCtl(["spec", "set", "spec.ioProbe.uart.port", "COM9", "--spec", specPath], { now: () => NOW });
await runHwpodCtl(["spec", "set", "spec.ioProbe.uart.baudrate", "115200", "--spec", specPath], { now: () => NOW });
const uart = await runHwpodCli(["uart", "read", "--spec", specPath, "--port", "uart1", "--max-bytes", "512", "--dry-run"], { now: () => NOW });
assert.equal(uart.exitCode, 0);
assert.deepEqual(uart.payload.plan.ops.map((op: any) => op.op), ["cmd.run"]);
assert.equal(uart.payload.plan.ops[0].args.step, "serial-monitor-read");
assert.equal(uart.payload.plan.ops[0].args.workspacePath, "C:\\Users\\liang\\.agents\\skills\\serial-monitor");
assert.equal(uart.payload.plan.ops[0].args.command, "node");
const uartSequence = JSON.parse(uart.payload.plan.ops[0].args.argv[2]);
assert.deepEqual(uartSequence[0], ["bun", "scripts/serial-monitor-cli.ts", "monitor", "start", "-p", "COM9", "-b", "115200"]);
assert.deepEqual(uartSequence[1].slice(0, 5), ["bun", "scripts/serial-monitor-cli.ts", "fetch", "-l", "7"]);
assert.equal(uartSequence[1].includes("--session-only"), false);
assert.equal(uart.payload.plan.ops[0].args.commandBinding.sessionOnly, false);
const uartSessionOnly = await runHwpodCli(["uart", "read", "--spec", specPath, "--port", "uart1", "--max-bytes", "512", "--session-only", "--dry-run"], { now: () => NOW });
const uartSessionSequence = JSON.parse(uartSessionOnly.payload.plan.ops[0].args.argv[2]);
assert.equal(uartSessionSequence[1].includes("--session-only"), true);
assert.equal(uartSessionOnly.payload.plan.ops[0].args.commandBinding.sessionOnly, true);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test("hwpod-cli exposes subcommand help before required args and accepts workspace path aliases", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-cli-help-"));
const specPath = path.join(root, ".hwlab", "hwpod-spec.yaml");
try {
const workspaceHelp = await runHwpodCli(["workspace", "--help"], { now: () => NOW });
assert.equal(workspaceHelp.exitCode, 0);
assert.equal(workspaceHelp.payload.action, "hwpod-cli.workspace.help");
assert.match(JSON.stringify(workspaceHelp.payload.usage), /--hwpod-id d601-f103-v2/u);
assert.match(JSON.stringify(workspaceHelp.payload.usage), /--workspace-path <run-worktree>/u);
const writeHelp = await runHwpodCli(["workspace", "write", "--help"], { now: () => NOW });
assert.equal(writeHelp.exitCode, 0);
assert.equal(writeHelp.payload.action, "hwpod-cli.workspace.help");
assert.match(JSON.stringify(writeHelp.payload.aliases), /remotePath/u);
const jobHelp = await runHwpodCli(["job", "status", "--help"], { now: () => NOW });
assert.equal(jobHelp.exitCode, 0);
assert.equal(jobHelp.payload.action, "hwpod-cli.job.help");
assert.match(JSON.stringify(jobHelp.payload.usage), /hwpod job status <jobId>/u);
await runHwpodCtl(["spec", "init", "--spec", specPath, "--node", "pc-host-1"], { now: () => NOW });
const catByFile = await runHwpodCli(["workspace", "cat", "--spec", specPath, "--file", "projects/01_baseline/User/main.c", "--dry-run"], { now: () => NOW });
assert.equal(catByFile.exitCode, 0);
assert.equal(catByFile.payload.plan.ops[0].op, "workspace.cat");
assert.equal(catByFile.payload.plan.ops[0].args.path, "projects/01_baseline/User/main.c");
const writeByRemotePath = await runHwpodCli(["workspace", "write", "--spec", specPath, "--remote-path", "projects/01_baseline/User/main.c", "--content", "int main(void){}", "--dry-run"], { now: () => NOW });
assert.equal(writeByRemotePath.exitCode, 0);
assert.equal(writeByRemotePath.payload.plan.ops[0].op, "workspace.write");
assert.equal(writeByRemotePath.payload.plan.ops[0].args.path, "projects/01_baseline/User/main.c");
} finally {
await rm(root, { recursive: true, force: true });
}
});
test("hwpod-cli submits compiled node ops to hwlab-api when not dry-run", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-cli-submit-"));
const specPath = path.join(root, ".hwlab", "hwpod-spec.yaml");
const seen: any[] = [];
try {
await runHwpodCtl(["spec", "init", "--spec", specPath, "--node", "pc-host-1"], { now: () => NOW });
const result = await runHwpodCli(["workspace", "ls", "src", "--spec", specPath], {
env: {
HWLAB_RUNTIME_API_URL: "http://cloud.test",
HWLAB_RUNTIME_NAMESPACE: "hwlab-v02",
HWLAB_RUNTIME_ENDPOINT_LOCKED: "1",
HWLAB_API_KEY: "hwl_live_test"
},
fetchImpl: async (url, init) => {
seen.push({ url: String(url), init, body: JSON.parse(String(init?.body ?? "{}")) });
return new Response(JSON.stringify({ ok: true, status: "completed", contractVersion: "hwpod-node-ops-v1", results: [{ op: "workspace.ls", ok: true }] }), { status: 200, headers: { "content-type": "application/json" } });
},
now: () => NOW
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.compilerInvocation.compiler, "hwpod-compiler-cli");
assert.equal(result.payload.compilerInvocation.mode, "in-process");
assert.equal(seen[0].url, "http://cloud.test/v1/hwpod-node-ops");
assert.equal(seen[0].init.headers.authorization, "Bearer hwl_live_test");
assert.equal(seen[0].body.contractVersion, "hwpod-node-ops-v1");
assert.equal(seen[0].body.source.compiler, "hwpod-compiler-cli");
assert.equal(seen[0].body.ops[0].op, "workspace.ls");
} finally {
await rm(root, { recursive: true, force: true });
}
});
test("hwpod-cli surfaces hwpod-node-ops OTel trace id on blocked response", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-cli-blocked-"));
const specPath = path.join(root, ".hwlab", "hwpod-spec.yaml");
const otelTraceId = "fedcba98765432100123456789abcdef";
try {
await runHwpodCtl(["spec", "init", "--spec", specPath, "--node", "pc-host-1"], { now: () => NOW });
const result = await runHwpodCli(["workspace", "ls", "src", "--spec", specPath], {
env: {
HWLAB_RUNTIME_API_URL: "http://cloud.test",
HWLAB_RUNTIME_NAMESPACE: "hwlab-v02",
HWLAB_RUNTIME_ENDPOINT_LOCKED: "1",
HWLAB_API_KEY: "hwl_live_test"
},
fetchImpl: async () => new Response(JSON.stringify({
ok: false,
status: "blocked",
contractVersion: "hwpod-node-ops-v1",
blocker: { code: "hwpod_node_unavailable", layer: "hwpod-node", retryable: true, summary: "no outbound WebSocket hwpod-node is connected" },
results: []
}), { status: 200, headers: { "content-type": "application/json", "x-hwlab-otel-trace-id": otelTraceId } }),
now: () => NOW
});
assert.equal(result.exitCode, 1);
assert.equal(result.payload.ok, false);
assert.equal(result.payload.otelTraceId, otelTraceId);
assert.equal(result.payload.diagnostic.traceLine, `OTel traceId: ${otelTraceId}`);
assert.equal(result.payload.body.blocker.otelTraceId, otelTraceId);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test("hwpod-cli rejects API key aliases and requires HWLAB_API_KEY", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-cli-api-key-alias-"));
const specPath = path.join(root, ".hwlab", "hwpod-spec.yaml");
try {
await runHwpodCtl(["spec", "init", "--spec", specPath, "--node", "pc-host-1"], { now: () => NOW });
const result = await runHwpodCli(["workspace", "ls", "src", "--spec", specPath], {
env: {
HWLAB_RUNTIME_API_URL: "http://cloud.test",
HWLAB_BEARER_TOKEN: "hwl_live_legacy_alias"
},
fetchImpl: async () => {
throw new Error("request should not be sent when an API key alias is present");
},
now: () => NOW
});
assert.equal(result.exitCode, 1);
assert.equal(result.payload.error.code, "unsupported_api_key_source");
assert.deepEqual(result.payload.error.details.unsupported, ["HWLAB_BEARER_TOKEN"]);
assert.equal(JSON.stringify(result.payload).includes("hwl_live_legacy_alias"), false);
} finally {
await rm(root, { recursive: true, force: true });
}
});