import assert from "node:assert/strict"; import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { test } from "bun:test"; import { prepareCaseRun } from "./src/hwlab-caserun-lib.ts"; import { subjectWorktreeFailureDetails } from "./src/hwlab-caserun-subject.ts"; const NOW = "2026-06-06T00:00:00.000Z"; const SUBJECT_COMMIT = "df7a4e6e551fa90d64bde5537cc000f89d63dd20"; const TEST_RUNTIME_API_URL = "http://api.test"; test("subject worktree failures preserve common Windows operation identity without run-specific constants", () => { for (const repoLocalPath of ["F:\\Work\\HWLAB-CASE-F103", "F:\\Work\\ConStart"]) { const plan = { contractVersion: "hwpod-node-ops-v1", planId: "case_subject_test", hwpodId: "d601-f103-v2", nodeId: "node-d601-f103-v2", intent: "cmd.run", ops: [ { opId: "op_01_subject_commit", op: "cmd.run", args: { workspacePath: repoLocalPath } }, { opId: "op_02_subject_worktree_add", op: "cmd.run", args: { workspacePath: repoLocalPath } }, { opId: "op_03_subject_worktree_head", op: "cmd.run", args: { workspacePath: repoLocalPath } } ] }; const body = { contractVersion: "hwpod-node-ops-v1", planId: plan.planId, hwpodId: plan.hwpodId, nodeId: plan.nodeId, status: "blocked", results: plan.ops.map((op) => ({ opId: op.opId, op: op.op, ok: false, status: "blocked", blocker: { code: "hwpod_node_unavailable", layer: "hwpod-node", retryable: true, summary: "node unavailable token=hwl_live_should_not_leak" } })), blocker: { code: "hwpod_node_unavailable", layer: "hwpod-node", retryable: true, summary: "node unavailable token=hwl_live_should_not_leak", details: { dispatchMode: "none" } } }; const failure = subjectWorktreeFailureDetails(body, plan, { stderr: "" }); assert.equal(failure.partialWrite, false); assert.equal(failure.dispatched, false); assert.equal(failure.blocker.details.dispatched, false); assert.equal(failure.operation.nodeId, "node-d601-f103-v2"); assert.deepEqual(failure.operation.ops.map((op: any) => op.opId), plan.ops.map((op) => op.opId)); assert.doesNotMatch(failure.errorSummary, /hwl_live_should_not_leak/u); } }); test("subject worktree failure facts remain null unless dispatch or writes are authoritative", () => { const plan = { contractVersion: "hwpod-node-ops-v1", planId: "case_subject_facts", hwpodId: "d601-f103-v2", nodeId: "node-d601-f103-v2", intent: "cmd.run", ops: [{ opId: "op_02_subject_worktree_add", op: "cmd.run", args: {} }] }; const unknown = subjectWorktreeFailureDetails({ status: "blocked", results: [{ opId: "op_02_subject_worktree_add", ok: false, status: "blocked" }] }, plan, { stderr: "blocked" }); assert.equal(unknown.dispatched, null); assert.equal(unknown.partialWrite, null); const dispatched = subjectWorktreeFailureDetails({ status: "failed", blocker: { details: { dispatchMode: "websocket" } }, results: [{ opId: "op_02_subject_worktree_add", ok: false, status: "failed" }] }, plan, { stderr: "failed" }); assert.equal(dispatched.dispatched, true); assert.equal(dispatched.partialWrite, null); const written = subjectWorktreeFailureDetails({ status: "failed", dispatched: true, results: [{ opId: "op_02_subject_worktree_add", ok: true, status: "completed" }] }, plan, { stderr: "later operation failed" }); assert.equal(written.dispatched, true); assert.equal(written.partialWrite, true); }); test("CaseRun records source root baseline so pre-existing dirty files are not attributed to the run", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-caserun-source-root-")); try { const caseRepo = path.join(root, "case-registry"); const caseDir = path.join(caseRepo, "cases", "dirty-source-root"); await mkdir(path.join(caseRepo, ".git"), { recursive: true }); await mkdir(caseDir, { recursive: true }); await writeFile(path.join(caseRepo, ".git", "HEAD"), "ref: refs/heads/main\n", "utf8"); await writeFile(path.join(caseDir, "case.json"), JSON.stringify({ contractVersion: "hwpod-case-v1", caseId: "dirty-source-root", hwpodSpec: "hwpod-spec.yaml", subject: { repoLocalPath: "F:\\Work\\HWLAB-CASE-F103", commitId: SUBJECT_COMMIT, subdir: "projects/01_baseline" }, agentTask: { workspace: "subjectWorktree", prompt: "record source root baseline", constraints: [] } }, null, 2), "utf8"); await writeFile(path.join(caseDir, "hwpod-spec.yaml"), [ "apiVersion: hwlab.dev/v0alpha1", "kind: Hwpod", "metadata:", " name: d601-f103-v2", "spec:", " targetDevice:", " board: D601-F103-V2", " mcu: STM32F103", " workspace:", " path: \"F:\\\\Work\\\\HWLAB-CASE-F103\"", " toolchain: keil-mdk", " keilProject: projects/01_baseline/Projects/MDK-ARM/atk_f103.uvprojx", " debugProbe:", " type: daplink", " adapter: keil", " ioProbe:", " uart:", " id: uart/1", " port: COM9", " nodeBinding:", " nodeId: node-d601-f103-v2", "" ].join("\n"), "utf8"); const dirtyStatus = " M projects/01_baseline/Projects/MDK-ARM/atk_f103.uvprojx\n"; const dirtyDiff = "diff --git a/projects/01_baseline/Projects/MDK-ARM/atk_f103.uvprojx b/projects/01_baseline/Projects/MDK-ARM/atk_f103.uvprojx\n+Keil.STM32F1xx_DFP.2.4.1\n"; const fetchImpl = async (_url: string, init: RequestInit) => { const plan = JSON.parse(String(init.body)); const results = plan.ops.map((op: any) => ({ opId: op.opId, op: op.op, ok: true, status: "completed", output: outputForGit(op.args.argv, dirtyStatus, dirtyDiff) })); return new Response(JSON.stringify({ ok: true, status: "completed", body: { results } }), { status: 200, headers: { "content-type": "application/json" } }); }; const result = await prepareCaseRun({ parsed: { _: [], caseRepo, runId: "source-root-baseline" }, env: { HWLAB_RUNTIME_API_URL: TEST_RUNTIME_API_URL }, fetchImpl: fetchImpl as typeof fetch, cwd: root, now: () => NOW, sleep: async () => {}, rest: ["prepare", "dirty-source-root"] }); assert.equal(result.status, "succeeded"); assert.equal(result.summary.status, "prepared"); assert.equal(result.run.subject.sourceRootBaseline.statusShort, dirtyStatus.trim()); assert.equal(result.run.subject.sourceRootAfterPrepare.statusShort, dirtyStatus.trim()); assert.equal(result.run.subject.sourceRootBaseline.diffSha256, result.run.subject.sourceRootAfterPrepare.diffSha256); assert.match(await readFile(result.specPath, "utf8"), /caserun-source-root-baseline/u); } finally { await rm(root, { recursive: true, force: true }); } }); function outputForGit(argv: string[], dirtyStatus: string, dirtyDiff: string) { const command = argv.join(" "); if (command.includes("cat-file -e")) return { exitCode: 0, stdout: "", stderr: "" }; if (command.includes("worktree add")) return { exitCode: 0, stdout: "Preparing worktree\n", stderr: "" }; if (command.includes("rev-parse HEAD")) return { exitCode: 0, stdout: `${SUBJECT_COMMIT}\n`, stderr: "" }; if (command.endsWith("status --short")) return { exitCode: 0, stdout: dirtyStatus, stderr: "" }; if (command.endsWith("diff --stat")) return { exitCode: 0, stdout: " projects/01_baseline/Projects/MDK-ARM/atk_f103.uvprojx | 8 ++++----\n", stderr: "" }; if (command.endsWith("diff --binary")) return { exitCode: 0, stdout: dirtyDiff, stderr: "" }; return { exitCode: 0, stdout: "", stderr: "" }; }