import assert from "node:assert/strict"; import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { test } from "bun:test"; import { runHwpodCli } from "../src/hwpod-harness-lib.ts"; test("hwpod-cli help exposes workspace cat and apply-patch stdin usage", async () => { const result = await runHwpodCli(["help"], { now: () => "2026-06-06T00:00:00.000Z" }); assert.equal(result.exitCode, 0); assert.equal(result.payload.action, "hwpod-cli.help"); assert.equal(result.payload.usage.some((item: string) => /workspace cat/u.test(item)), true); assert.equal(result.payload.usage.some((item: string) => /workspace apply-patch/u.test(item)), true); assert.match(result.payload.applyPatchExample, /\*\*\* Begin Patch/u); }); test("hwpod-cli workspace apply-patch passes stdin patch to hwpod-node plan", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwpod-cli-test-")); const specPath = path.join(root, "hwpod-spec.yaml"); const patch = "*** Begin Patch\n*** Update File: app.c\n@@\n old\n+new\n*** End Patch\n"; try { await writeFile(specPath, hwpodSpecText(), "utf8"); const result = await runHwpodCli(["workspace", "apply-patch", "--spec", specPath, "--reason", "test", "--dry-run"], { stdinText: patch, now: () => "2026-06-06T00:00:00.000Z" }); assert.equal(result.exitCode, 0); assert.equal(result.payload.action, "hwpod-cli.plan"); assert.equal(result.payload.intent, "workspace.apply-patch"); assert.equal(result.payload.plan.ops[0].op, "workspace.apply-patch"); assert.equal(result.payload.plan.ops[0].args.patch, patch); assert.equal(result.payload.plan.ops[0].args.reason, "test"); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwpod-cli workspace apply-patch accepts --patch-content alias", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwpod-cli-test-patch-content-")); const specPath = path.join(root, "hwpod-spec.yaml"); const patch = "*** Begin Patch\n*** Update File: app.c\n@@\n-old\n+new\n*** End Patch\n"; try { await writeFile(specPath, hwpodSpecText(), "utf8"); const result = await runHwpodCli(["workspace", "apply-patch", "--spec", specPath, "--patch-content", patch, "--dry-run"], { now: () => "2026-06-06T00:00:00.000Z" }); assert.equal(result.exitCode, 0); assert.equal(result.payload.intent, "workspace.apply-patch"); assert.equal(result.payload.plan.ops[0].args.patch, patch); } finally { await rm(root, { recursive: true, force: true }); } }); test("hwlab-cli hwpod L0 executes workspace edits and a bounded build helper", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwpod-cli-l0-")); const specPath = path.join(root, "hwpod-spec.yaml"); const sourcePath = path.join(root, "app.c"); const artifactPath = path.join(root, "firmware.hex"); const patch = "*** Begin Patch\n*** Update File: app.c\n@@\n-old\n+new\n*** End Patch\n"; try { await writeFile(specPath, hwpodSpecText(root, "node build.mjs"), "utf8"); await writeFile(sourcePath, "old\n", "utf8"); await writeFile(path.join(root, "build.mjs"), "import { writeFileSync } from 'node:fs'; writeFileSync('firmware.hex', ':00000001FF\\n'); console.log('build completed');\n", "utf8"); const edited = await runHwpodCli(["workspace", "apply-patch", "--local", "--spec", specPath, "--patch-content", patch]); assert.equal(edited.exitCode, 0); assert.equal(edited.payload.action, "hwpod-cli.local"); assert.equal(edited.payload.mode, "l0-native-function"); assert.equal(edited.payload.transport, "native-function"); assert.equal(await readFile(sourcePath, "utf8"), "new\n"); const listed = await runHwpodCli(["workspace", "ls", ".", "--local", "--spec", specPath]); assert.equal(listed.payload.body.results[0].output.entries.some((item: any) => item.name === "app.c"), true); const searched = await runHwpodCli(["workspace", "rg", "new", ".", "--local", "--spec", specPath]); assert.equal(searched.payload.body.results[0].output.matchCount, 1); const written = await runHwpodCli(["workspace", "write", "generated.txt", "--content", "alpha\n", "--local", "--spec", specPath]); assert.equal(written.exitCode, 0); assert.equal(await readFile(path.join(root, "generated.txt"), "utf8"), "alpha\n"); const replaced = await runHwpodCli(["workspace", "replace", "app.c", "--find", "new", "--replace", "newer", "--local", "--spec", specPath]); assert.equal(replaced.exitCode, 0); const inserted = await runHwpodCli(["workspace", "insert-after", "app.c", "--anchor", "newer", "--line", "marker", "--local", "--spec", specPath]); assert.equal(inserted.exitCode, 0); const read = await runHwpodCli(["workspace", "cat", "app.c", "--local", "--spec", specPath]); assert.equal(read.payload.body.results[0].output.content, "newer\nmarker\n"); const child = Bun.spawn([ process.execPath, path.join(process.cwd(), "tools/hwlab-cli/bin/hwlab-cli.ts"), "hwpod", "build", "--local", "--spec", specPath, ], { cwd: process.cwd(), stdout: "pipe", stderr: "pipe" }); const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited]); assert.equal(exitCode, 0, stderr); const built = JSON.parse(stdout); assert.equal(built.action, "hwpod-cli.local"); assert.equal(built.mode, "l0-native-function"); assert.match(built.body.results[0].output.stdout, /build completed/u); assert.equal(await readFile(artifactPath, "utf8"), ":00000001FF\n"); } finally { await rm(root, { recursive: true, force: true }); } }); test("HWPOD L0 local mode fails closed without an explicit spec", async () => { const result = await runHwpodCli(["workspace", "ls", ".", "--local"]); assert.equal(result.exitCode, 1); assert.equal(result.payload.error.code, "hwpod_l0_spec_required"); }); test("HWPOD execution modes fail closed when local and over-api are combined", async () => { const result = await runHwpodCli(["workspace", "ls", ".", "--local", "--over-api", "--spec", "unused.yaml"]); assert.equal(result.exitCode, 1); assert.equal(result.payload.error.code, "hwpod_execution_mode_conflict"); }); test("HWPOD L0 and L1 over-api expose the same operation contract", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwpod-cli-compare-")); const specPath = path.join(root, "hwpod-spec.yaml"); const operationId = "hwpod_compare_001"; let submitted: any = null; const fetchImpl = async (input: string | URL | Request, init?: RequestInit) => { const url = new URL(String(input)); if (url.pathname === "/v1/hwpod/operations" && init?.method === "POST") { submitted = JSON.parse(String(init.body)); return Response.json({ ok: true, status: "accepted", operationId: submitted.operationId, workflowId: `hwpod-operation-${submitted.operationId}`, workflowRunId: "run-001", authority: "hwpod-temporal", valuesPrinted: false }, { status: 202 }); } if (url.pathname === `/v1/hwpod/operations/${operationId}` && init?.method === "GET") { return Response.json({ ok: true, operationId, workflowId: `hwpod-operation-${operationId}`, workflowRunId: "run-001", status: "completed", result: { ok: true, status: "completed" }, valuesPrinted: false }); } return Response.json({ ok: false, error: { code: "unexpected_route" } }, { status: 404 }); }; const env = { HWLAB_HWPOD_OPERATION_IDENTITY: operationId }; try { await writeFile(specPath, hwpodSpecText(root), "utf8"); const local = await runHwpodCli(["workspace", "ls", ".", "--local", "--spec", specPath], { env }); const overApi = await runHwpodCli(["workspace", "ls", ".", "--over-api", "--spec", specPath, "--api-base-url", "http://hwpod.test"], { env, fetchImpl: fetchImpl as typeof fetch }); assert.equal(local.exitCode, 0); assert.equal(overApi.exitCode, 0); assert.equal(overApi.payload.action, "hwpod-cli.over-api"); assert.equal(overApi.payload.mode, "l1-native-api"); assert.equal(overApi.payload.transport, "over-api"); assert.equal(overApi.payload.httpStatus, 202); for (const field of ["operationId", "hwpodId", "nodeId", "intent", "contractVersion", "ops"]) { assert.deepEqual(overApi.payload[field], local.payload[field], field); } assert.equal(submitted.operationId, operationId); assert.equal(submitted.plan.planId, operationId); assert.equal(submitted.plan.intent, local.payload.intent); assert.deepEqual(submitted.plan.ops.map((operation: any) => operation.op), local.payload.ops); const status = await runHwpodCli(["operation", "status", operationId, "--over-api", "--api-base-url", "http://hwpod.test"], { env, fetchImpl: fetchImpl as typeof fetch }); assert.equal(status.exitCode, 0); assert.equal(status.payload.action, "hwpod-cli.operation.status"); assert.equal(status.payload.mode, "l1-native-api"); assert.equal(status.payload.operationId, operationId); assert.equal(status.payload.body.status, "completed"); } finally { await rm(root, { recursive: true, force: true }); } }); test("HWPOD operation status requires explicit over-api mode", async () => { const result = await runHwpodCli(["operation", "status", "operation-001"]); assert.equal(result.exitCode, 1); assert.equal(result.payload.error.code, "hwpod_operation_status_over_api_required"); }); function hwpodSpecText(workspacePath = "F:\\Work\\HWLAB-CASE-F103", buildCommand = "") { return `kind: Hwpod\nmetadata:\n name: test-hwpod\nspec:\n nodeBinding:\n nodeId: test-node\n workspace:\n path: ${JSON.stringify(workspacePath)}\n${buildCommand ? ` buildCommand: ${JSON.stringify(buildCommand)}\n` : ""} targetDevice:\n board: D601-F103-V2\n debugProbe:\n type: daplink\n ioProbe:\n uart:\n port: COM9\n`; }