mirror of
https://github.com/openai/codex.git
synced 2026-02-01 22:47:52 +00:00
Compare commits
2 Commits
dev/zhao/7
...
codex/impl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efccf74170 | ||
|
|
b1fd1bd96f |
146
codex-cli/tests/cli-full-conversation.test.ts
Normal file
146
codex-cli/tests/cli-full-conversation.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
|
||||
const originalArgv = process.argv.slice();
|
||||
const originalCwd = process.cwd();
|
||||
const originalEnv = { ...process.env };
|
||||
const originalNode = process.versions.node;
|
||||
const originalPlatform = process.platform;
|
||||
|
||||
function setupCommonMocks(tmpDir: string) {
|
||||
vi.doMock("../src/utils/logger/log.js", () => ({
|
||||
__esModule: true,
|
||||
initLogger: () => ({ isLoggingEnabled: () => false, log: () => {} }),
|
||||
}));
|
||||
vi.doMock("../src/utils/check-updates.js", () => ({
|
||||
__esModule: true,
|
||||
checkForUpdates: vi.fn(),
|
||||
}));
|
||||
vi.doMock("../src/utils/get-api-key.js", () => ({
|
||||
__esModule: true,
|
||||
getApiKey: () => "test-key",
|
||||
maybeRedeemCredits: async () => {},
|
||||
}));
|
||||
vi.doMock("../src/approvals.js", () => ({
|
||||
__esModule: true,
|
||||
alwaysApprovedCommands: new Set<string>(),
|
||||
canAutoApprove: () => ({ type: "auto-approve", runInSandbox: false }),
|
||||
isSafeCommand: () => null,
|
||||
}));
|
||||
vi.doMock("src/approvals.js", () => ({
|
||||
__esModule: true,
|
||||
alwaysApprovedCommands: new Set<string>(),
|
||||
canAutoApprove: () => ({ type: "auto-approve", runInSandbox: false }),
|
||||
isSafeCommand: () => null,
|
||||
}));
|
||||
vi.doMock("../src/format-command.js", () => ({
|
||||
__esModule: true,
|
||||
formatCommandForDisplay: (cmd: Array<string>) => cmd.join(" "),
|
||||
}));
|
||||
vi.doMock("src/format-command.js", () => ({
|
||||
__esModule: true,
|
||||
formatCommandForDisplay: (cmd: Array<string>) => cmd.join(" "),
|
||||
}));
|
||||
vi.doMock("../src/utils/agent/log.js", () => ({
|
||||
__esModule: true,
|
||||
log: () => {},
|
||||
isLoggingEnabled: () => false,
|
||||
}));
|
||||
vi.doMock("../src/utils/config.js", () => ({
|
||||
__esModule: true,
|
||||
loadConfig: () => ({
|
||||
model: "test-model",
|
||||
instructions: "",
|
||||
provider: "openai",
|
||||
notify: false,
|
||||
approvalMode: undefined,
|
||||
tools: { shell: { maxBytes: 1024, maxLines: 100 } },
|
||||
disableResponseStorage: false,
|
||||
reasoningEffort: "medium",
|
||||
}),
|
||||
PRETTY_PRINT: true,
|
||||
INSTRUCTIONS_FILEPATH: path.join(tmpDir, "instructions.md"),
|
||||
}));
|
||||
}
|
||||
|
||||
describe("CLI Full Conversation Integration", () => {
|
||||
let tmpDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-cli-test-"));
|
||||
process.chdir(tmpDir);
|
||||
process.env.CODEX_UNSAFE_ALLOW_NO_SANDBOX = "1";
|
||||
Object.defineProperty(process.versions, "node", { value: "22.0.0" });
|
||||
Object.defineProperty(process, "platform", { value: "win32" });
|
||||
setupCommonMocks(tmpDir);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.argv = originalArgv.slice();
|
||||
process.chdir(originalCwd);
|
||||
process.env = { ...originalEnv };
|
||||
Object.defineProperty(process.versions, "node", { value: originalNode });
|
||||
Object.defineProperty(process, "platform", { value: originalPlatform });
|
||||
vi.restoreAllMocks();
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("prints the assistant response and exits", async () => {
|
||||
class MsgStream {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield {
|
||||
type: "response.output_item.done",
|
||||
item: {
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: "Hello, world." }],
|
||||
},
|
||||
} as any;
|
||||
yield {
|
||||
type: "response.completed",
|
||||
response: {
|
||||
id: "resp1",
|
||||
status: "completed",
|
||||
output: [
|
||||
{
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: "Hello, world." }],
|
||||
},
|
||||
],
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
}
|
||||
|
||||
vi.mock("openai", () => ({
|
||||
__esModule: true,
|
||||
default: class FakeOpenAI {
|
||||
public responses = { create: async () => new MsgStream() };
|
||||
},
|
||||
APIConnectionTimeoutError: class APIConnectionTimeoutError extends Error {},
|
||||
}));
|
||||
|
||||
const logs: Array<string> = [];
|
||||
vi.spyOn(console, "log").mockImplementation((...args) => {
|
||||
logs.push(args.join(" "));
|
||||
});
|
||||
|
||||
const exitSpy = vi
|
||||
.spyOn(process, "exit")
|
||||
.mockImplementation(((code?: number) => {
|
||||
throw new Error(`exit:${code}`);
|
||||
}) as any);
|
||||
|
||||
process.argv = ["node", "codex", "-q", "Hello", "--full-auto"];
|
||||
|
||||
await expect(import("../src/cli.tsx")).rejects.toThrow("exit:0");
|
||||
|
||||
const hasText = logs.some((l) => l.includes("assistant: Hello, world."));
|
||||
expect(hasText).toBe(true);
|
||||
expect(exitSpy).toHaveBeenCalledWith(0);
|
||||
});
|
||||
});
|
||||
181
codex-cli/tests/cli-tool-invocation-flow.test.ts
Normal file
181
codex-cli/tests/cli-tool-invocation-flow.test.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
|
||||
const originalArgv = process.argv.slice();
|
||||
const originalCwd = process.cwd();
|
||||
const originalEnv = { ...process.env };
|
||||
const originalNode = process.versions.node;
|
||||
const originalPlatform = process.platform;
|
||||
|
||||
// Utility to setup mocks common to CLI tests
|
||||
function setupCommonMocks(tmpDir: string) {
|
||||
vi.doMock("../src/utils/logger/log.js", () => ({
|
||||
__esModule: true,
|
||||
initLogger: () => ({ isLoggingEnabled: () => false, log: () => {} }),
|
||||
}));
|
||||
vi.doMock("../src/utils/check-updates.js", () => ({
|
||||
__esModule: true,
|
||||
checkForUpdates: vi.fn(),
|
||||
}));
|
||||
vi.doMock("../src/utils/get-api-key.js", () => ({
|
||||
__esModule: true,
|
||||
getApiKey: () => "test-key",
|
||||
maybeRedeemCredits: async () => {},
|
||||
}));
|
||||
vi.doMock("../src/approvals.js", () => ({
|
||||
__esModule: true,
|
||||
alwaysApprovedCommands: new Set<string>(),
|
||||
canAutoApprove: () => ({ type: "auto-approve", runInSandbox: false }),
|
||||
isSafeCommand: () => null,
|
||||
}));
|
||||
vi.doMock("src/approvals.js", () => ({
|
||||
__esModule: true,
|
||||
alwaysApprovedCommands: new Set<string>(),
|
||||
canAutoApprove: () => ({ type: "auto-approve", runInSandbox: false }),
|
||||
isSafeCommand: () => null,
|
||||
}));
|
||||
vi.doMock("../src/format-command.js", () => ({
|
||||
__esModule: true,
|
||||
formatCommandForDisplay: (cmd: Array<string>) => cmd.join(" "),
|
||||
}));
|
||||
vi.doMock("src/format-command.js", () => ({
|
||||
__esModule: true,
|
||||
formatCommandForDisplay: (cmd: Array<string>) => cmd.join(" "),
|
||||
}));
|
||||
vi.doMock("../src/utils/agent/log.js", () => ({
|
||||
__esModule: true,
|
||||
log: () => {},
|
||||
isLoggingEnabled: () => false,
|
||||
}));
|
||||
vi.doMock("../src/utils/config.js", () => ({
|
||||
__esModule: true,
|
||||
loadConfig: () => ({
|
||||
model: "test-model",
|
||||
instructions: "",
|
||||
provider: "openai",
|
||||
notify: false,
|
||||
approvalMode: undefined,
|
||||
tools: { shell: { maxBytes: 1024, maxLines: 100 } },
|
||||
disableResponseStorage: false,
|
||||
reasoningEffort: "medium",
|
||||
}),
|
||||
PRETTY_PRINT: true,
|
||||
INSTRUCTIONS_FILEPATH: path.join(tmpDir, "instructions.md"),
|
||||
}));
|
||||
}
|
||||
|
||||
describe("CLI Tool Invocation Flow", () => {
|
||||
let tmpDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-cli-test-"));
|
||||
process.chdir(tmpDir);
|
||||
process.env.CODEX_UNSAFE_ALLOW_NO_SANDBOX = "1";
|
||||
Object.defineProperty(process.versions, "node", { value: "22.0.0" });
|
||||
Object.defineProperty(process, "platform", { value: "win32" });
|
||||
setupCommonMocks(tmpDir);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.argv = originalArgv.slice();
|
||||
process.chdir(originalCwd);
|
||||
process.env = { ...originalEnv };
|
||||
Object.defineProperty(process.versions, "node", { value: originalNode });
|
||||
Object.defineProperty(process, "platform", { value: originalPlatform });
|
||||
vi.restoreAllMocks();
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("executes a shell command returned by the model", async () => {
|
||||
class CallStream {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield {
|
||||
type: "response.output_item.done",
|
||||
item: {
|
||||
type: "function_call",
|
||||
id: "call_1",
|
||||
name: "shell",
|
||||
arguments: JSON.stringify({ cmd: ["echo", "Hello"] }),
|
||||
},
|
||||
} as any;
|
||||
yield {
|
||||
type: "response.completed",
|
||||
response: {
|
||||
id: "resp1",
|
||||
status: "completed",
|
||||
output: [
|
||||
{
|
||||
type: "function_call",
|
||||
id: "call_1",
|
||||
name: "shell",
|
||||
arguments: JSON.stringify({ cmd: ["echo", "Hello"] }),
|
||||
},
|
||||
],
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
}
|
||||
|
||||
class DoneStream {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield {
|
||||
type: "response.output_item.done",
|
||||
item: {
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: "done" }],
|
||||
},
|
||||
} as any;
|
||||
yield {
|
||||
type: "response.completed",
|
||||
response: {
|
||||
id: "resp2",
|
||||
status: "completed",
|
||||
output: [
|
||||
{
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: "done" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
}
|
||||
|
||||
let call = 0;
|
||||
vi.mock("openai", () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default: class FakeOpenAI {
|
||||
public responses = {
|
||||
create: async () => {
|
||||
call += 1;
|
||||
return call === 1 ? new CallStream() : new DoneStream();
|
||||
},
|
||||
};
|
||||
},
|
||||
APIConnectionTimeoutError: class APIConnectionTimeoutError extends Error {},
|
||||
};
|
||||
});
|
||||
|
||||
const logs: Array<string> = [];
|
||||
vi.spyOn(console, "log").mockImplementation((...args) => {
|
||||
logs.push(args.join(" "));
|
||||
});
|
||||
|
||||
vi.spyOn(process, "exit").mockImplementation(((code?: number) => {
|
||||
throw new Error(`exit:${code}`);
|
||||
}) as any);
|
||||
|
||||
process.argv = ["node", "codex", "--full-auto", "-q", "hi"];
|
||||
|
||||
await expect(import("../src/cli.tsx")).rejects.toThrow("exit:0");
|
||||
|
||||
const hasOutput = logs.some((l) => l.includes("command.stdout") && l.includes("Hello"));
|
||||
expect(hasOutput).toBe(true);
|
||||
});
|
||||
});
|
||||
3
codex-rs/Cargo.lock
generated
3
codex-rs/Cargo.lock
generated
@@ -669,14 +669,17 @@ name = "codex-exec"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
"chrono",
|
||||
"clap",
|
||||
"codex-common",
|
||||
"codex-core",
|
||||
"codex-linux-sandbox",
|
||||
"owo-colors",
|
||||
"predicates",
|
||||
"serde_json",
|
||||
"shlex",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
|
||||
@@ -37,3 +37,8 @@ tokio = { version = "1", features = [
|
||||
] }
|
||||
tracing = { version = "0.1.41", features = ["log"] }
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2"
|
||||
predicates = "3"
|
||||
tempfile = "3"
|
||||
|
||||
29
codex-rs/exec/tests/tool_invocation.rs
Normal file
29
codex-rs/exec/tests/tool_invocation.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
use codex_core::exec::{ExecParams, SandboxType, process_exec_tool_call};
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Notify;
|
||||
|
||||
#[tokio::test]
|
||||
async fn echo_command_outputs_text() {
|
||||
let params = ExecParams {
|
||||
command: vec!["echo".into(), "Hello".into()],
|
||||
cwd: std::env::current_dir().unwrap(),
|
||||
timeout_ms: None,
|
||||
env: HashMap::new(),
|
||||
};
|
||||
let policy = SandboxPolicy::new_workspace_write_policy();
|
||||
let output = process_exec_tool_call(
|
||||
params,
|
||||
SandboxType::None,
|
||||
Arc::new(Notify::new()),
|
||||
&policy,
|
||||
&None,
|
||||
)
|
||||
.await
|
||||
.expect("exec failed");
|
||||
assert_eq!(output.exit_code, 0);
|
||||
assert!(output.stdout.contains("Hello"));
|
||||
}
|
||||
Reference in New Issue
Block a user