Add codex exec testing helpers (#4254)

Add a shortcut to create working directories and run codex exec with
fake server.
This commit is contained in:
pakrym-oai
2025-09-25 17:12:45 -07:00
committed by GitHub
parent 9f2ab97fbc
commit 8e3a048fec
9 changed files with 116 additions and 90 deletions

View File

@@ -8,6 +8,7 @@ path = "lib.rs"
[dependencies]
anyhow = { workspace = true }
assert_cmd = { workspace = true }
codex-core = { workspace = true }
serde_json = { workspace = true }
tempfile = { workspace = true }

View File

@@ -9,6 +9,7 @@ use codex_core::config::ConfigToml;
pub mod responses;
pub mod test_codex;
pub mod test_codex_exec;
/// Returns a default `Config` whose on-disk state is confined to the provided
/// temporary directory. Using a per-test directory keeps tests hermetic and

View File

@@ -2,6 +2,7 @@ use serde_json::Value;
use wiremock::BodyPrintLimit;
use wiremock::Mock;
use wiremock::MockServer;
use wiremock::Respond;
use wiremock::ResponseTemplate;
use wiremock::matchers::method;
use wiremock::matchers::path;
@@ -131,3 +132,41 @@ pub async fn start_mock_server() -> MockServer {
.start()
.await
}
/// Mounts a sequence of SSE response bodies and serves them in order for each
/// POST to `/v1/responses`. Panics if more requests are received than bodies
/// provided. Also asserts the exact number of expected calls.
pub async fn mount_sse_sequence(server: &MockServer, bodies: Vec<String>) {
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
struct SeqResponder {
num_calls: AtomicUsize,
responses: Vec<String>,
}
impl Respond for SeqResponder {
fn respond(&self, _: &wiremock::Request) -> ResponseTemplate {
let call_num = self.num_calls.fetch_add(1, Ordering::SeqCst);
match self.responses.get(call_num) {
Some(body) => ResponseTemplate::new(200)
.insert_header("content-type", "text/event-stream")
.set_body_string(body.clone()),
None => panic!("no response for {call_num}"),
}
}
}
let num_calls = bodies.len();
let responder = SeqResponder {
num_calls: AtomicUsize::new(0),
responses: bodies,
};
Mock::given(method("POST"))
.and(path("/v1/responses"))
.respond_with(responder)
.expect(num_calls as u64)
.mount(server)
.await;
}

View File

@@ -0,0 +1,40 @@
#![allow(clippy::expect_used)]
use std::path::Path;
use tempfile::TempDir;
use wiremock::MockServer;
pub struct TestCodexExecBuilder {
home: TempDir,
cwd: TempDir,
}
impl TestCodexExecBuilder {
pub fn cmd(&self) -> assert_cmd::Command {
let mut cmd = assert_cmd::Command::cargo_bin("codex-exec")
.expect("should find binary for codex-exec");
cmd.current_dir(self.cwd.path())
.env("CODEX_HOME", self.home.path())
.env("OPENAI_API_KEY", "dummy");
cmd
}
pub fn cmd_with_server(&self, server: &MockServer) -> assert_cmd::Command {
let mut cmd = self.cmd();
let base = format!("{}/v1", server.uri());
cmd.env("OPENAI_BASE_URL", base);
cmd
}
pub fn cwd_path(&self) -> &Path {
self.cwd.path()
}
pub fn home_path(&self) -> &Path {
self.home.path()
}
}
pub fn test_codex_exec() -> TestCodexExecBuilder {
TestCodexExecBuilder {
home: TempDir::new().expect("create temp home"),
cwd: TempDir::new().expect("create temp cwd"),
}
}