- commit
- 40b1c7f
- parent
- 284e9ac
- author
- im_wower
- date
- 2026-03-22 22:35:38 +0800 CST
fix(host-ops): normalize exec failure result shape
3 files changed,
+90,
-6
+75,
-1
1@@ -218,6 +218,37 @@ function parseJsonBody(response) {
2 return JSON.parse(response.body);
3 }
4
5+async function withMockedPlatform(platform, callback) {
6+ const descriptor = Object.getOwnPropertyDescriptor(process, "platform");
7+
8+ assert.ok(descriptor);
9+
10+ Object.defineProperty(process, "platform", {
11+ configurable: true,
12+ enumerable: descriptor.enumerable ?? true,
13+ value: platform
14+ });
15+
16+ try {
17+ return await callback();
18+ } finally {
19+ Object.defineProperty(process, "platform", descriptor);
20+ }
21+}
22+
23+function assertEmptyExecResultShape(result) {
24+ assert.deepEqual(result, {
25+ stdout: "",
26+ stderr: "",
27+ exitCode: null,
28+ signal: null,
29+ durationMs: 0,
30+ startedAt: null,
31+ finishedAt: null,
32+ timedOut: false
33+ });
34+}
35+
36 function createWebSocketMessageQueue(socket) {
37 const messages = [];
38 const waiters = [];
39@@ -938,6 +969,48 @@ test("handleConductorHttpRequest serves the migrated local business endpoints fr
40 assert.equal(execPayload.data.operation, "exec");
41 assert.equal(execPayload.data.result.stdout, "host-http-ok");
42
43+ const invalidExecResponse = await handleConductorHttpRequest(
44+ {
45+ body: JSON.stringify({
46+ command: ["echo", "hello"]
47+ }),
48+ method: "POST",
49+ path: "/v1/exec"
50+ },
51+ {
52+ repository,
53+ snapshotLoader: () => snapshot
54+ }
55+ );
56+ assert.equal(invalidExecResponse.status, 200);
57+ const invalidExecPayload = parseJsonBody(invalidExecResponse);
58+ assert.equal(invalidExecPayload.data.ok, false);
59+ assert.equal(invalidExecPayload.data.error.code, "INVALID_INPUT");
60+ assertEmptyExecResultShape(invalidExecPayload.data.result);
61+
62+ const tccExecResponse = await withMockedPlatform("darwin", () =>
63+ handleConductorHttpRequest(
64+ {
65+ body: JSON.stringify({
66+ command: "pwd",
67+ cwd: join(homedir(), "Desktop", "project"),
68+ timeoutMs: 2000
69+ }),
70+ method: "POST",
71+ path: "/v1/exec"
72+ },
73+ {
74+ repository,
75+ snapshotLoader: () => snapshot
76+ }
77+ )
78+ );
79+ assert.equal(tccExecResponse.status, 200);
80+ const tccExecPayload = parseJsonBody(tccExecResponse);
81+ assert.equal(tccExecPayload.data.ok, false);
82+ assert.equal(tccExecPayload.data.error.code, "TCC_PERMISSION_DENIED");
83+ assertEmptyExecResultShape(tccExecPayload.data.result);
84+
85 const systemStateResponse = await handleConductorHttpRequest(
86 {
87 method: "GET",
88@@ -952,7 +1025,8 @@ test("handleConductorHttpRequest serves the migrated local business endpoints fr
89 recursive: true
90 });
91 }
92-});
93+}
94+);
95
96 test("ConductorRuntime serves health and migrated local API endpoints over HTTP", async () => {
97 const stateDir = mkdtempSync(join(tmpdir(), "baa-conductor-runtime-"));
+10,
-0
1@@ -234,6 +234,16 @@ curl -X POST "${LOCAL_API_BASE}/v1/files/write" \
2 "nodeBinary": "/opt/homebrew/bin/node",
3 "requiresFullDiskAccess": true
4 }
5+ },
6+ "result": {
7+ "stdout": "",
8+ "stderr": "",
9+ "exitCode": null,
10+ "signal": null,
11+ "durationMs": 0,
12+ "startedAt": null,
13+ "finishedAt": null,
14+ "timedOut": false
15 }
16 }
17 ```
+5,
-5
1@@ -56,7 +56,6 @@ async function withPatchedEnv(patch, callback) {
2 }
3 }
4 }
5-
6 function assertEmptyExecResultShape(result) {
7 assert.deepEqual(result, {
8 stdout: "",
9@@ -93,8 +92,8 @@ test("executeCommand returns a structured failure for non-zero exit codes", { co
10 assert.equal(result.ok, false);
11 assert.equal(result.operation, "exec");
12 assert.equal(result.error.code, "EXEC_EXIT_NON_ZERO");
13- assert.equal(result.result?.exitCode, 7);
14- assert.equal(result.result?.stderr, "broken");
15+ assert.equal(result.result.exitCode, 7);
16+ assert.equal(result.result.stderr, "broken");
17 });
18
19 test("executeCommand returns a complete empty exec result for INVALID_INPUT failures", { concurrency: false }, async () => {
20@@ -126,7 +125,7 @@ test(
21
22 const outputLines = result.result.stdout.trim().split("\n");
23 assert.equal(outputLines[0], resolvedDirectory);
24- assert.match(outputLines[1], /^v\\d+\\./);
25+ assert.match(outputLines[1], /^v\d+\./);
26 } finally {
27 await rm(directory, { force: true, recursive: true });
28 }
29@@ -145,7 +144,7 @@ test(
30 },
31 () =>
32 executeCommand({
33- command: "node -e \\\"process.stdout.write(JSON.stringify(process.env))\\\"",
34+ command: "node -e \"process.stdout.write(JSON.stringify(process.env))\"",
35 timeoutMs: 2_000
36 })
37 );
38@@ -203,6 +202,7 @@ test(
39 assert.equal(result.error.code, "TCC_PERMISSION_DENIED");
40 assert.equal(result.error.details?.accessPoint, "cwd");
41 assert.equal(result.error.details?.protectedPath, join(homedir(), "Downloads"));
42+ assertEmptyExecResultShape(result.result);
43 }
44 );
45