diff --git a/codex-rs/core/tests/common/test_codex.rs b/codex-rs/core/tests/common/test_codex.rs index a247453775..2026f14567 100644 --- a/codex-rs/core/tests/common/test_codex.rs +++ b/codex-rs/core/tests/common/test_codex.rs @@ -32,6 +32,7 @@ use wiremock::Match; use wiremock::matchers::path_regex; type ConfigMutator = dyn FnOnce(&mut Config) + Send; +type PreConfigHook = dyn FnOnce(&Path) + Send + 'static; type PreBuildHook = dyn FnOnce(&Path) + Send + 'static; /// A collection of different ways the model can output an apply_patch call @@ -56,6 +57,7 @@ pub enum ShellModelOutput { pub struct TestCodexBuilder { config_mutators: Vec>, auth: CodexAuth, + pre_config_hooks: Vec>, pre_build_hooks: Vec>, home: Option>, } @@ -89,6 +91,14 @@ impl TestCodexBuilder { self } + pub fn with_pre_config_hook(mut self, hook: F) -> Self + where + F: FnOnce(&Path) + Send + 'static, + { + self.pre_config_hooks.push(Box::new(hook)); + self + } + pub fn with_home(mut self, home: Arc) -> Self { self.home = Some(home); self @@ -208,6 +218,9 @@ impl TestCodexBuilder { ..built_in_model_providers()["openai"].clone() }; let cwd = Arc::new(TempDir::new()?); + for hook in self.pre_config_hooks.drain(..) { + hook(home.path()); + } let mut config = load_default_config_for_test(home).await; config.cwd = cwd.path().to_path_buf(); config.model_provider = model_provider; @@ -446,6 +459,7 @@ pub fn test_codex() -> TestCodexBuilder { TestCodexBuilder { config_mutators: vec![], auth: CodexAuth::from_api_key("dummy"), + pre_config_hooks: vec![], pre_build_hooks: vec![], home: None, } diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index 9bf4ef8ef2..ea0036c992 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -78,4 +78,4 @@ mod unstable_features_warning; mod user_notification; mod user_shell_cmd; mod view_image; -mod web_search_cached; +mod web_search; diff --git a/codex-rs/core/tests/suite/web_search_cached.rs b/codex-rs/core/tests/suite/web_search.rs similarity index 68% rename from codex-rs/core/tests/suite/web_search_cached.rs rename to codex-rs/core/tests/suite/web_search.rs index 261efaf942..62b18b75a1 100644 --- a/codex-rs/core/tests/suite/web_search_cached.rs +++ b/codex-rs/core/tests/suite/web_search.rs @@ -10,6 +10,8 @@ use core_test_support::test_codex::test_codex; use pretty_assertions::assert_eq; use serde_json::Value; +const CONFIG_TOML: &str = "config.toml"; + fn sse_completed(id: &str) -> String { load_sse_fixture_with_id("../fixtures/completed_template.json", id) } @@ -86,3 +88,40 @@ async fn web_search_mode_takes_precedence_over_legacy_flags_in_request_body() { "web_search mode should win over legacy web_search_request" ); } + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn danger_full_access_config_defaults_web_search_to_live() { + skip_if_no_network!(); + + let server = start_mock_server().await; + let sse = sse_completed("resp-1"); + let resp_mock = responses::mount_sse_once(&server, sse).await; + + let mut builder = test_codex() + .with_model("gpt-5-codex") + .with_config(|config| { + config.features.disable(Feature::WebSearchCached); + config.features.disable(Feature::WebSearchRequest); + }) + .with_pre_config_hook(|home| { + let config_path = home.join(CONFIG_TOML); + std::fs::write(config_path, "sandbox_mode = \"danger-full-access\"\n") + .expect("seed config.toml"); + }); + let test = builder + .build(&server) + .await + .expect("create test Codex conversation"); + + test.submit_turn("hello danger full access web search") + .await + .expect("submit turn"); + + let body = resp_mock.single_request().body_json(); + let tool = find_web_search_tool(&body); + assert_eq!( + tool.get("external_web_access").and_then(Value::as_bool), + Some(true), + "danger-full-access should default web_search to live" + ); +}