5a7644dac9
Prevent direct hwpod-node URL dispatch from executing plans on the wrong node and expose spawn diagnostics for cmd.run failures.
461 lines
20 KiB
TypeScript
461 lines
20 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { createHash } from "node:crypto";
|
|
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 { createHwpodNodeServer, executeHwpodNodeOpsPlan, resolveHwpodNodeCommand } from "./src/hwpod-node-lib.ts";
|
|
|
|
test("hwpod-node executes minimal workspace node ops", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-"));
|
|
try {
|
|
await writeFile(path.join(root, "main.c"), "int main(void) { return 0; }\n", "utf8");
|
|
const result = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_test",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [
|
|
{ opId: "op_01", op: "node.health", args: { workspacePath: root } },
|
|
{ opId: "op_02", op: "workspace.ls", args: { workspacePath: root, path: "." } },
|
|
{ opId: "op_03", op: "workspace.cat", args: { workspacePath: root, path: "main.c" } }
|
|
]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z" });
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.status, "completed");
|
|
assert.equal(result.results[1].output.entries[0].name, "main.c");
|
|
assert.match(result.results[2].output.content, /int main/u);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
test("hwpod-node HTTP endpoint accepts hwpod-node-ops plans", async () => {
|
|
const server = createHwpodNodeServer({ now: () => "2026-06-05T00:00:00.000Z", nodeId: "pc-host-1" });
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
try {
|
|
const response = await fetch(`http://127.0.0.1:${server.address().port}/v1/hwpod-node-ops`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_http",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [{ opId: "op_01", op: "node.version", args: {} }]
|
|
})
|
|
});
|
|
const payload = await response.json();
|
|
assert.equal(response.status, 200);
|
|
assert.equal(payload.ok, true);
|
|
assert.equal(payload.specAuthority, "none");
|
|
assert.equal(payload.results[0].op, "node.version");
|
|
} finally {
|
|
await new Promise((resolve, reject) => server.close((error: Error | undefined) => (error ? reject(error) : resolve(undefined))));
|
|
}
|
|
});
|
|
|
|
test("hwpod-node executes debug ops only through explicit node-side command bindings", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-debug-"));
|
|
try {
|
|
const result = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_debug_bound",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [{ opId: "op_build", op: "debug.build", args: { workspacePath: root, command: "printf build-ok > build.ok" } }]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z" });
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.results[0].ok, true);
|
|
assert.equal(result.results[0].output.bindingSource, "hwpod-node-ops.command");
|
|
assert.match(await readFile(path.join(root, "build.ok"), "utf8"), /build-ok/u);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("hwpod-node executes debug commandRuns without shell splitting", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-debug-runs-"));
|
|
try {
|
|
const writer = path.join(root, "writer.js");
|
|
await writeFile(writer, "const { writeFileSync } = require('node:fs'); writeFileSync(process.argv[2], process.argv.slice(3).join('|'));\n", "utf8");
|
|
const result = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_debug_runs",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [{ opId: "op_download", op: "debug.download", args: { workspacePath: root, commandRuns: [{ command: process.execPath, argv: [writer, path.join(root, "args.txt"), "CherryUSB MicroLink CMSIS-DAP"] }] } }]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z" });
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.results[0].ok, true);
|
|
assert.equal(result.results[0].output.bindingSource, "hwpod-node-ops.commandRuns");
|
|
assert.equal(result.results[0].output.commands[0].command.at(-1), "CherryUSB MicroLink CMSIS-DAP");
|
|
assert.equal(await readFile(path.join(root, "args.txt"), "utf8"), "CherryUSB MicroLink CMSIS-DAP");
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("hwpod-node reports cmd.run non-zero exits as failed results", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-cmd-fail-"));
|
|
try {
|
|
const result = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_cmd_fail",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [{ opId: "op_cmd", op: "cmd.run", args: { workspacePath: root, command: "sh", argv: ["-c", "echo no >&2; exit 7"] } }]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z" });
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.equal(result.results[0].ok, false);
|
|
assert.equal(result.results[0].status, "failed");
|
|
assert.equal(result.results[0].output.exitCode, 7);
|
|
assert.equal(result.results[0].blocker.code, "hwpod_node_command_failed");
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("hwpod-node refuses plans targeting another node id", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-id-mismatch-"));
|
|
try {
|
|
const result = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_node_mismatch",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "node-d601-f103-v2",
|
|
ops: [{ opId: "op_cmd", op: "cmd.run", args: { workspacePath: root, command: "sh", argv: ["-c", "echo must-not-run"] } }]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z", nodeId: "g14-host-hwpod-node" });
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.equal(result.status, "blocked");
|
|
assert.equal(result.localNodeId, "g14-host-hwpod-node");
|
|
assert.equal(result.results[0].blocker.code, "hwpod_node_id_mismatch");
|
|
assert.equal(result.results[0].blocker.details.requestedNodeId, "node-d601-f103-v2");
|
|
assert.equal(result.results[0].blocker.details.localNodeId, "g14-host-hwpod-node");
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("hwpod-node exposes command spawn diagnostics when executable is missing", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-spawn-missing-"));
|
|
try {
|
|
const result = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_spawn_missing",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [{ opId: "op_cmd", op: "cmd.run", args: { workspacePath: root, command: "hwlab-command-that-does-not-exist-1018", argv: ["--version"] } }]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z", nodeId: "pc-host-1" });
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.equal(result.results[0].status, "blocked");
|
|
assert.equal(result.results[0].blocker.code, "hwpod_node_command_spawn_failed");
|
|
assert.equal(result.results[0].blocker.details.output.commandResolution.requested, "hwlab-command-that-does-not-exist-1018");
|
|
assert.equal(result.results[0].blocker.details.output.spawnDiagnostics.platform, process.platform);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("hwpod-node includes structured UART diagnostics when platform blocks serial access", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-uart-"));
|
|
try {
|
|
const fixture = path.join(root, "serial-monitor-idle.mjs");
|
|
await writeFile(fixture, `
|
|
const args = process.argv.slice(2).join(" ");
|
|
if (args === "monitor status") console.log(JSON.stringify({ action: "monitor_status", success: true, data: { isMonitoring: false, port: null, baudRate: null, lastPort: "COM9", lastBaudRate: 115200 } }));
|
|
else if (args === "ports") console.log(JSON.stringify({ action: "list_ports", success: true, data: { ports: [{ path: "COM9" }] } }));
|
|
else { console.error("unsupported " + args); process.exit(2); }
|
|
`, "utf8");
|
|
const result = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_uart_diag",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [{
|
|
opId: "op_uart",
|
|
op: "io.uart.read",
|
|
args: {
|
|
workspacePath: root,
|
|
port: "uart1",
|
|
serialMonitorDir: root,
|
|
serialMonitorCommand: [process.execPath, fixture],
|
|
ioProbe: { uart: { id: "uart1", port: "COM9", baudrate: 115200 } }
|
|
}
|
|
}]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z" });
|
|
|
|
if (process.platform === "win32") return;
|
|
assert.equal(result.ok, false);
|
|
assert.equal(result.results[0].status, "blocked");
|
|
assert.equal(result.results[0].blocker.code, "hwpod_uart_monitor_not_active");
|
|
assert.equal(result.results[0].blocker.details.requestedPort, "uart1");
|
|
assert.equal(result.results[0].blocker.details.resolvedPort, "COM9");
|
|
assert.equal(result.results[0].blocker.details.baudRate, 115200);
|
|
assert.equal(result.results[0].blocker.details.serialMonitor.monitorStatus.data.isMonitoring, false);
|
|
assert.match(result.results[0].blocker.details.serialMonitor.startCommand, /monitor start -p COM9 -b 115200/u);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("hwpod-node reads UART through serial-monitor binding", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-uart-active-"));
|
|
try {
|
|
const fixture = path.join(root, "serial-monitor-fixture.mjs");
|
|
await writeFile(fixture, `
|
|
const args = process.argv.slice(2).join(" ");
|
|
if (args === "monitor status") console.log(JSON.stringify({ action: "monitor_status", success: true, data: { isMonitoring: true, port: "COM9", baudRate: 115200, startTime: "2026-06-06T10:00:00Z" } }));
|
|
else if (args === "ports") console.log(JSON.stringify({ action: "list_ports", success: true, data: { ports: [{ path: "COM9" }] } }));
|
|
else if (args.startsWith("fetch")) console.log(JSON.stringify({ action: "fetch_data", success: true, data: [{ timestamp: "2026-06-06T10:00:01Z", port: "COM9", data: "UART OK" }], count: 1, totalCount: 1, hasMore: false, truncated: false, sourceFile: "fixture.jsonl" }));
|
|
else { console.error("unsupported " + args); process.exit(2); }
|
|
`, "utf8");
|
|
|
|
const result = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_uart_read",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [{
|
|
opId: "op_uart",
|
|
op: "io.uart.read",
|
|
args: {
|
|
workspacePath: root,
|
|
port: "uart1",
|
|
maxBytes: 128,
|
|
serialMonitorDir: root,
|
|
serialMonitorCommand: [process.execPath, fixture],
|
|
ioProbe: { uart: { id: "uart1", port: "COM9", baudrate: 115200 } }
|
|
}
|
|
}]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z" });
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.results[0].status, "completed");
|
|
assert.equal(result.results[0].output.bindingSource, "serial-monitor-cli");
|
|
assert.equal(result.results[0].output.resolvedPort, "COM9");
|
|
assert.equal(result.results[0].output.text, "UART OK");
|
|
assert.deepEqual(result.results[0].output.command.slice(0, 2), [process.execPath, fixture]);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("hwpod-node resolves Git for Windows when service PATH omits git", async () => {
|
|
const result = await resolveHwpodNodeCommand("git", {
|
|
platform: "win32",
|
|
env: {},
|
|
fileExists: async (candidate: string) => candidate === "C:\\Program Files\\Git\\cmd\\git.exe"
|
|
});
|
|
|
|
assert.equal(result.command, "C:\\Program Files\\Git\\cmd\\git.exe");
|
|
assert.equal(result.resolved, true);
|
|
assert.equal(result.source, "windows-well-known-tool");
|
|
});
|
|
|
|
test("hwpod-node keeps explicit executable paths unchanged", async () => {
|
|
const result = await resolveHwpodNodeCommand("C:\\Tools\\Git\\cmd\\git.exe", {
|
|
platform: "win32",
|
|
env: {},
|
|
fileExists: async () => true
|
|
});
|
|
|
|
assert.equal(result.command, "C:\\Tools\\Git\\cmd\\git.exe");
|
|
assert.equal(result.resolved, false);
|
|
assert.equal(result.source, "explicit-path");
|
|
});
|
|
|
|
test("hwpod-node applies workspace patches through the stable node op", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-patch-"));
|
|
try {
|
|
await writeFile(path.join(root, "main.c"), "int value = 1;\n", "utf8");
|
|
const result = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_patch",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [{
|
|
opId: "op_patch",
|
|
op: "workspace.apply-patch",
|
|
args: {
|
|
workspacePath: root,
|
|
patch: [
|
|
"*** Begin Patch",
|
|
"*** Update File: main.c",
|
|
"@@",
|
|
"-int value = 1;",
|
|
"+int value = 2;",
|
|
"*** End Patch",
|
|
""
|
|
].join("\n")
|
|
}
|
|
}]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z" });
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.results[0].ok, true);
|
|
assert.match(await readFile(path.join(root, "main.c"), "utf8"), /value = 2/u);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("hwpod-node preserves CRLF when applying workspace patches", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-patch-crlf-"));
|
|
try {
|
|
await writeFile(path.join(root, "main.c"), "int main(void) {\r\n while (1) {\r\n }\r\n}\r\n", "utf8");
|
|
const result = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_patch_crlf",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [{
|
|
opId: "op_patch",
|
|
op: "workspace.apply-patch",
|
|
args: {
|
|
workspacePath: root,
|
|
patch: [
|
|
"*** Begin Patch",
|
|
"*** Update File: main.c",
|
|
"@@",
|
|
" while (1) {",
|
|
"+ /* MARKER_OK */",
|
|
" }",
|
|
"*** End Patch",
|
|
""
|
|
].join("\n")
|
|
}
|
|
}]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z" });
|
|
const content = await readFile(path.join(root, "main.c"), "utf8");
|
|
assert.equal(result.ok, true);
|
|
assert.match(content, /\r\n while \(1\) \{\r\n \/\* MARKER_OK \*\/\r\n \}\r\n/u);
|
|
assert.doesNotMatch(content, /[^\r]\n/u);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("hwpod-node returns apply-patch diagnostics when context is missing", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-patch-diagnostics-"));
|
|
try {
|
|
const original = "int main(void) {\r\n while (1) {\r\n }\r\n}\r\n";
|
|
await writeFile(path.join(root, "main.c"), original, "utf8");
|
|
const result = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_patch_diag",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [{
|
|
opId: "op_patch",
|
|
op: "workspace.apply-patch",
|
|
args: {
|
|
workspacePath: root,
|
|
patch: [
|
|
"*** Begin Patch",
|
|
"*** Update File: main.c",
|
|
"@@",
|
|
"- while (2) {",
|
|
"+ while (3) {",
|
|
"*** End Patch",
|
|
""
|
|
].join("\n")
|
|
}
|
|
}]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z" });
|
|
assert.equal(result.ok, false);
|
|
assert.equal(result.results[0].blocker.code, "apply_patch_context_not_found");
|
|
assert.equal(result.results[0].blocker.details.fileSha256, sha256(original));
|
|
assert.equal(result.results[0].blocker.details.lineEnding, "\r\n");
|
|
assert.equal(result.results[0].blocker.details.lineEndingCounts.crlf, 4);
|
|
assert.equal(result.results[0].blocker.details.normalized, true);
|
|
assert.match(result.results[0].blocker.details.nodeVersion, /thin-node-ops/u);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("hwpod-node edits CRLF workspaces with insert-after replace and write ops", async () => {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-node-text-edit-"));
|
|
try {
|
|
const filePath = path.join(root, "projects", "01_baseline", "User", "main.c");
|
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
await writeFile(filePath, "int main(void) {\r\n while (1) {\r\n }\r\n}\r\n", "utf8");
|
|
const before = await readFile(filePath, "utf8");
|
|
const insert = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_insert",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [{
|
|
opId: "op_insert",
|
|
op: "workspace.insert-after",
|
|
args: { workspacePath: root, path: "projects/01_baseline/User/main.c", anchor: " while (1) {", line: " /* MARKER_OK */", expectedSha: sha256(before) }
|
|
}]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z" });
|
|
assert.equal(insert.ok, true);
|
|
assert.equal(insert.results[0].output.before.sha256, sha256(before));
|
|
assert.equal(insert.results[0].output.after.lineEnding, "\r\n");
|
|
assert.deepEqual(insert.results[0].output.diff.preview, ["+ /* MARKER_OK */"]);
|
|
const inserted = await readFile(filePath, "utf8");
|
|
assert.match(inserted, /\r\n \/\* MARKER_OK \*\/\r\n/u);
|
|
assert.doesNotMatch(inserted, /[^\r]\n/u);
|
|
|
|
const replace = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_replace",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [{
|
|
opId: "op_replace",
|
|
op: "workspace.replace",
|
|
args: { workspacePath: root, path: "projects/01_baseline/User/main.c", find: " /* MARKER_OK */", replace: " /* MARKER_REPLACED */", expectedSha: sha256(inserted) }
|
|
}]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z" });
|
|
assert.equal(replace.ok, true);
|
|
assert.equal(replace.results[0].output.occurrences, 1);
|
|
assert.match(await readFile(filePath, "utf8"), /MARKER_REPLACED/u);
|
|
|
|
const dryRun = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_write_dry",
|
|
hwpodId: "hwpod-local",
|
|
nodeId: "pc-host-1",
|
|
ops: [{
|
|
opId: "op_write",
|
|
op: "workspace.write",
|
|
args: { workspacePath: root, path: "new.txt", content: "alpha\nbeta", lineEnding: "crlf", finalNewline: true, dryRun: true }
|
|
}]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z" });
|
|
assert.equal(dryRun.ok, true);
|
|
assert.equal(dryRun.results[0].output.dryRun, true);
|
|
assert.equal(dryRun.results[0].output.after.lineEnding, "\r\n");
|
|
await assert.rejects(() => readFile(path.join(root, "new.txt"), "utf8"), /ENOENT/u);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("hwpod-node preserves Windows absolute workspace paths for PC-host nodes", async () => {
|
|
const result = await executeHwpodNodeOpsPlan({
|
|
contractVersion: "hwpod-node-ops-v1",
|
|
planId: "hwpod_plan_windows_path",
|
|
hwpodId: "d601-f103-v2",
|
|
nodeId: "node-d601-f103-v2",
|
|
ops: [{ opId: "op_01", op: "workspace.ls", args: { workspacePath: "F:\\Work\\D601-HWLAB", path: "." } }]
|
|
}, { now: () => "2026-06-05T00:00:00.000Z" });
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.equal(result.results[0].blocker.code, "ENOENT");
|
|
assert.match(result.results[0].blocker.summary, /scandir 'F:\\Work\\D601-HWLAB'/u);
|
|
assert.doesNotMatch(result.results[0].blocker.summary, /\/root\//u);
|
|
});
|
|
|
|
function sha256(content: string) {
|
|
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
}
|