2026-05-28 15:11:30 +00:00
import { chmodSync , writeFileSync } from "node:fs" ;
import { mkdtempSync } from "node:fs" ;
import { tmpdir } from "node:os" ;
import path from "node:path" ;
2026-06-29 12:47:44 +00:00
import { describe , test } from "node:test" ;
import { expect } from "expect" ;
2026-07-10 18:49:20 +10:00
import { buildCodexExecArgs , checkCodexAvailability , executeCodexCommandWithFallback , resolveCodexCommand } from "../src/codex-runtime.js" ;
2026-05-28 15:11:30 +00:00
describe ( "codex runtime" , () => {
test ( "resolves Codex command from CODEX_BIN only" , () => {
expect ( resolveCodexCommand ({ CODEX_BIN : "codex-custom" })). toBe ( "codex-custom" );
expect ( resolveCodexCommand ({ OPEN_GAMESTUDIO_CODEX_BIN : "legacy-codex" })). toBe ( "codex" );
expect ( resolveCodexCommand ({})). toBe ( "codex" );
});
2026-07-10 18:49:20 +10:00
test ( "builds structured codex exec args with cwd, sandbox, model, reasoning, and stdin prompt" , () => {
const args = buildCodexExecArgs ({ projectRoot : "/repo" , sandbox : "read-only" , model : "gpt-5.6-sol" , reasoningEffort : "high" });
2026-05-28 15:11:30 +00:00
expect ( args ). toContain ( "exec" );
2026-06-29 22:04:26 +00:00
expect ( args ). toContain ( "--model" );
2026-07-10 18:49:20 +10:00
expect ( args ). toContain ( "gpt-5.6-sol" );
expect ( args ). toEqual ( expect . arrayContaining ([ "-c" , 'model_reasoning_effort="high"' ]));
2026-05-28 15:11:30 +00:00
expect ( args ). toContain ( "--cd" );
expect ( args ). toContain ( "/repo" );
expect ( args ). toContain ( "--sandbox" );
expect ( args ). toContain ( "read-only" );
expect ( args ). toContain ( "-" );
});
test ( "reports availability failures without throwing" , async () => {
const result = await checkCodexAvailability ({ codexBin : "/missing/codex" });
expect ( result . ok ). toBe ( false );
expect ( result . command ). toBe ( "/missing/codex" );
expect ( result . reason ). toMatch ( /not found|unavailable/i );
});
test ( "reports authenticated stub as available" , async () => {
const cwd = mkdtempSync ( path . join ( tmpdir (), "ogs-codex-" ));
const stub = path . join ( cwd , "codex-stub.mjs" );
writeFileSync ( stub , "#!/usr/bin/env node\nconsole.log('codex 1.0.0');\n" );
chmodSync ( stub , 0 o755 );
const result = await checkCodexAvailability ({ codexBin : stub });
expect ( result . ok ). toBe ( true );
expect ( result . command ). toBe ( stub );
});
2026-07-10 18:49:20 +10:00
test ( "uses the designated fallback only when the primary model is unavailable" , async () => {
const cwd = mkdtempSync ( path . join ( tmpdir (), "ogs-codex-fallback-" ));
const stub = path . join ( cwd , "codex-stub.mjs" );
writeFileSync ( stub , `#!/usr/bin/env node \ nconst model = process.argv[process.argv.indexOf('--model') + 1]; \ nif (model === 'gpt-5.6-sol') { console.error('model gpt-5.6-sol is not available for this account'); process.exit(1); } \ nconsole.log(model); \ n` );
chmodSync ( stub , 0 o755 );
const primary = { command : stub , args : buildCodexExecArgs ({ projectRoot : cwd , model : "gpt-5.6-sol" , reasoningEffort : "high" }) };
const fallback = { command : stub , args : buildCodexExecArgs ({ projectRoot : cwd , model : "gpt-5.5" , reasoningEffort : "high" }) };
const result = await executeCodexCommandWithFallback ({ primary , fallback , primaryModel : "gpt-5.6-sol" , fallbackModel : "gpt-5.5" }, "prompt" , { cwd });
expect ( result . status ). toBe ( 0 );
expect ( result . stdout . trim ()). toBe ( "gpt-5.5" );
expect ( result . executedModel ). toBe ( "gpt-5.5" );
expect ( result . fallbackFromModel ). toBe ( "gpt-5.6-sol" );
});
test ( "does not hide ordinary execution failures behind a fallback" , async () => {
const cwd = mkdtempSync ( path . join ( tmpdir (), "ogs-codex-no-fallback-" ));
const stub = path . join ( cwd , "codex-stub.mjs" );
writeFileSync ( stub , "#!/usr/bin/env node\nconsole.error('tests failed'); process.exit(2);\n" );
chmodSync ( stub , 0 o755 );
const primary = { command : stub , args : buildCodexExecArgs ({ projectRoot : cwd , model : "gpt-5.6-terra" , reasoningEffort : "medium" }) };
const fallback = { command : stub , args : buildCodexExecArgs ({ projectRoot : cwd , model : "gpt-5.4" , reasoningEffort : "medium" }) };
const result = await executeCodexCommandWithFallback ({ primary , fallback , primaryModel : "gpt-5.6-terra" , fallbackModel : "gpt-5.4" }, "prompt" , { cwd });
expect ( result . status ). toBe ( 2 );
expect ( result . executedModel ). toBe ( "gpt-5.6-terra" );
expect ( result . fallbackFromModel ). toBeUndefined ();
});
2026-05-28 15:11:30 +00:00
});