im_wower
·
2026-03-22
index.test.js
1import assert from "node:assert/strict";
2import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
3import { tmpdir } from "node:os";
4import { join } from "node:path";
5import test from "node:test";
6
7import { runCodexExec } from "../dist/index.js";
8
9async function createFakeCodexCli() {
10 const directoryPath = await mkdtemp(join(tmpdir(), "baa-codex-exec-cli-"));
11 const cliPath = join(directoryPath, "codex");
12 const source = `#!/usr/bin/env node
13import { writeFileSync } from "node:fs";
14
15const args = process.argv.slice(2);
16const delayMs = Number(process.env.FAKE_CODEX_DELAY_MS ?? "0");
17const exitCode = Number(process.env.FAKE_CODEX_EXIT_CODE ?? "0");
18const stderr = process.env.FAKE_CODEX_STDERR ?? "";
19const stdout = process.env.FAKE_CODEX_STDOUT ?? "";
20const outputLastMessageIndex = args.indexOf("--output-last-message");
21const outputLastMessagePath =
22 outputLastMessageIndex === -1 ? null : args[outputLastMessageIndex + 1];
23
24if (delayMs > 0) {
25 await new Promise((resolve) => setTimeout(resolve, delayMs));
26}
27
28if (outputLastMessagePath) {
29 writeFileSync(
30 outputLastMessagePath,
31 process.env.FAKE_CODEX_LAST_MESSAGE ?? "fake final message\\n"
32 );
33}
34
35if (stdout !== "") {
36 process.stdout.write(stdout);
37} else if (args.includes("--json")) {
38 process.stdout.write(
39 JSON.stringify({
40 type: "started",
41 payload: {
42 args,
43 cwd: process.cwd(),
44 prompt: args.at(-1) ?? null
45 }
46 }) + "\\n"
47 );
48 process.stdout.write(JSON.stringify({ type: "completed" }) + "\\n");
49} else {
50 process.stdout.write("plain stdout\\n");
51}
52
53if (stderr !== "") {
54 process.stderr.write(stderr);
55}
56
57process.exit(exitCode);
58`;
59
60 await writeFile(cliPath, source, "utf8");
61 await chmod(cliPath, 0o755);
62
63 return {
64 cliPath,
65 directoryPath
66 };
67}
68
69async function createWorkspace() {
70 return await mkdtemp(join(tmpdir(), "baa-codex-exec-workspace-"));
71}
72
73test("runCodexExec executes a one-shot fallback worker request and returns structured output", async (t) => {
74 const fakeCli = await createFakeCodexCli();
75 const workspacePath = await createWorkspace();
76
77 t.after(async () => {
78 await rm(fakeCli.directoryPath, { force: true, recursive: true });
79 await rm(workspacePath, { force: true, recursive: true });
80 });
81
82 const response = await runCodexExec({
83 purpose: "fallback-worker",
84 prompt: "Summarize the current diff.",
85 cwd: workspacePath,
86 cliPath: fakeCli.cliPath,
87 timeoutMs: 1_000,
88 model: "gpt-5.4",
89 profile: "worker",
90 sandbox: "read-only",
91 skipGitRepoCheck: true,
92 json: true,
93 ephemeral: true,
94 config: ['model_reasoning_effort="medium"'],
95 additionalWritableDirectories: [join(workspacePath, "artifacts")],
96 images: [join(workspacePath, "diagram.png")],
97 env: {
98 FAKE_CODEX_LAST_MESSAGE: "worker completed\n",
99 FAKE_CODEX_STDERR: "simulated warning\n"
100 }
101 });
102
103 assert.equal(response.ok, true);
104 assert.deepEqual(response.invocation.args, [
105 "exec",
106 "--cd",
107 workspacePath,
108 "--color",
109 "never",
110 "--model",
111 "gpt-5.4",
112 "--profile",
113 "worker",
114 "--sandbox",
115 "read-only",
116 "--skip-git-repo-check",
117 "--json",
118 "--ephemeral",
119 "--config",
120 'model_reasoning_effort="medium"',
121 "--add-dir",
122 join(workspacePath, "artifacts"),
123 "--image",
124 join(workspacePath, "diagram.png"),
125 "Summarize the current diff."
126 ]);
127 assert.equal(response.result.exitCode, 0);
128 assert.equal(response.result.timedOut, false);
129 assert.equal(response.result.lastMessage, "worker completed\n");
130 assert.equal(response.result.stderr, "simulated warning\n");
131 assert.equal(response.result.jsonParseErrors.length, 0);
132 assert.equal(response.result.jsonEvents?.length, 2);
133 assert.equal(response.result.jsonEvents?.[0].type, "started");
134 assert.equal(response.result.jsonEvents?.[0].payload.prompt, "Summarize the current diff.");
135 assert.equal(
136 response.result.jsonEvents?.[0].payload.args.includes("--output-last-message"),
137 true
138 );
139});
140
141test("runCodexExec reports non-zero exits without dropping stdout or stderr", async (t) => {
142 const fakeCli = await createFakeCodexCli();
143 const workspacePath = await createWorkspace();
144
145 t.after(async () => {
146 await rm(fakeCli.directoryPath, { force: true, recursive: true });
147 await rm(workspacePath, { force: true, recursive: true });
148 });
149
150 const response = await runCodexExec({
151 purpose: "simple-worker",
152 prompt: "Return an error.",
153 cwd: workspacePath,
154 cliPath: fakeCli.cliPath,
155 timeoutMs: 1_000,
156 env: {
157 FAKE_CODEX_EXIT_CODE: "17",
158 FAKE_CODEX_STDERR: "fatal\n"
159 }
160 });
161
162 assert.equal(response.ok, false);
163 assert.equal(response.error.code, "CODEX_EXEC_EXIT_NON_ZERO");
164 assert.equal(response.invocation?.purpose, "simple-worker");
165 assert.equal(response.result?.exitCode, 17);
166 assert.equal(response.result?.timedOut, false);
167 assert.equal(response.result?.stderr, "fatal\n");
168 assert.equal(response.result?.stdout, "plain stdout\n");
169});
170
171test("runCodexExec reports timeouts for hung fallback processes", async (t) => {
172 const fakeCli = await createFakeCodexCli();
173 const workspacePath = await createWorkspace();
174
175 t.after(async () => {
176 await rm(fakeCli.directoryPath, { force: true, recursive: true });
177 await rm(workspacePath, { force: true, recursive: true });
178 });
179
180 const response = await runCodexExec({
181 purpose: "fallback-worker",
182 prompt: "Sleep too long.",
183 cwd: workspacePath,
184 cliPath: fakeCli.cliPath,
185 timeoutMs: 50,
186 env: {
187 FAKE_CODEX_DELAY_MS: "5000"
188 }
189 });
190
191 assert.equal(response.ok, false);
192 assert.equal(response.error.code, "CODEX_EXEC_TIMEOUT");
193 assert.equal(response.result?.timedOut, true);
194 assert.equal(response.result?.exitCode, null);
195 assert.equal(response.result?.signal, "SIGTERM");
196});
197
198test("runCodexExec rejects stdin-style prompts because the fallback adapter is one-shot only", async () => {
199 const response = await runCodexExec({
200 prompt: "-"
201 });
202
203 assert.equal(response.ok, false);
204 assert.equal(response.error.code, "CODEX_EXEC_INVALID_INPUT");
205});