From 3d4ced3ff5a647e90e6ed8b568588b24fcff2e91 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 18 Dec 2025 16:12:52 -0800 Subject: [PATCH] chore: migrate from Config::load_from_base_config_with_overrides to ConfigBuilder (#8276) https://github.com/openai/codex/pull/8235 introduced `ConfigBuilder` and this PR updates all call non-test call sites to use it instead of `Config::load_from_base_config_with_overrides()`. This is important because `load_from_base_config_with_overrides()` uses an empty `ConfigRequirements`, which is a reasonable default for testing so the tests are not influenced by the settings on the host. This method is now guarded by `#[cfg(test)]` so it cannot be used by business logic. Because `ConfigBuilder::build()` is `async`, many of the test methods had to be migrated to be `async`, as well. On the bright side, this made it possible to eliminate a bunch of `block_on_future()` stuff. --- codex-rs/Cargo.lock | 1 - codex-rs/core/Cargo.toml | 1 - codex-rs/core/src/auth.rs | 26 +- codex-rs/core/src/codex.rs | 110 ++-- codex-rs/core/src/codex_delegate.rs | 2 +- codex-rs/core/src/config/edit.rs | 21 +- codex-rs/core/src/config/mod.rs | 7 +- codex-rs/core/src/conversation_manager.rs | 6 +- codex-rs/core/src/message_history.rs | 26 +- .../core/src/openai_models/models_manager.rs | 48 +- codex-rs/core/src/project_doc.rs | 47 +- codex-rs/core/src/skills/loader.rs | 112 ++-- codex-rs/core/src/tools/handlers/shell.rs | 6 +- codex-rs/core/src/unified_exec/mod.rs | 16 +- codex-rs/core/src/user_shell_command.rs | 12 +- .../core/tests/chat_completions_payload.rs | 2 +- codex-rs/core/tests/chat_completions_sse.rs | 2 +- codex-rs/core/tests/common/lib.rs | 16 +- codex-rs/core/tests/common/test_codex.rs | 2 +- codex-rs/core/tests/responses_headers.rs | 6 +- codex-rs/core/tests/suite/client.rs | 26 +- codex-rs/core/tests/suite/compact.rs | 18 +- .../core/tests/suite/compact_resume_fork.rs | 2 +- .../core/tests/suite/fork_conversation.rs | 2 +- codex-rs/core/tests/suite/list_models.rs | 4 +- codex-rs/core/tests/suite/model_overrides.rs | 4 +- codex-rs/core/tests/suite/remote_models.rs | 6 +- codex-rs/core/tests/suite/resume_warning.rs | 2 +- codex-rs/core/tests/suite/review.rs | 6 +- codex-rs/core/tests/suite/user_shell_cmd.rs | 4 +- codex-rs/tui/src/app.rs | 42 +- codex-rs/tui/src/chatwidget/tests.rs | 594 +++++++++--------- codex-rs/tui/src/history_cell.rs | 30 +- codex-rs/tui/src/lib.rs | 40 +- codex-rs/tui/src/resume_picker.rs | 66 +- codex-rs/tui/src/status/tests.rs | 100 ++- codex-rs/tui2/src/app.rs | 48 +- codex-rs/tui2/src/chatwidget/tests.rs | 558 ++++++++-------- codex-rs/tui2/src/history_cell.rs | 30 +- codex-rs/tui2/src/lib.rs | 40 +- codex-rs/tui2/src/resume_picker.rs | 66 +- codex-rs/tui2/src/status/tests.rs | 100 ++- 42 files changed, 1081 insertions(+), 1176 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index e58a5fa62..a6c7b4ee3 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1323,7 +1323,6 @@ dependencies = [ "thiserror 2.0.17", "time", "tokio", - "tokio-test", "tokio-util", "toml 0.9.5", "toml_edit", diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 2b51b784c..bb1db41dc 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -132,7 +132,6 @@ predicates = { workspace = true } pretty_assertions = { workspace = true } serial_test = { workspace = true } tempfile = { workspace = true } -tokio-test = { workspace = true } tracing-subscriber = { workspace = true } tracing-test = { workspace = true, features = ["no-env-filter"] } walkdir = { workspace = true } diff --git a/codex-rs/core/src/auth.rs b/codex-rs/core/src/auth.rs index 8b4448106..96714e3f7 100644 --- a/codex-rs/core/src/auth.rs +++ b/codex-rs/core/src/auth.rs @@ -636,8 +636,7 @@ mod tests { use crate::auth::storage::FileAuthStorage; use crate::auth::storage::get_auth_file; use crate::config::Config; - use crate::config::ConfigOverrides; - use crate::config::ConfigToml; + use crate::config::ConfigBuilder; use crate::token_data::IdTokenInfo; use crate::token_data::KnownPlan as InternalKnownPlan; use crate::token_data::PlanType as InternalPlanType; @@ -862,17 +861,16 @@ mod tests { Ok(fake_jwt) } - fn build_config( + async fn build_config( codex_home: &Path, forced_login_method: Option, forced_chatgpt_workspace_id: Option, ) -> Config { - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - codex_home.to_path_buf(), - ) - .expect("config should load"); + let mut config = ConfigBuilder::default() + .codex_home(codex_home.to_path_buf()) + .build() + .await + .expect("config should load"); config.forced_login_method = forced_login_method; config.forced_chatgpt_workspace_id = forced_chatgpt_workspace_id; config @@ -915,7 +913,7 @@ mod tests { login_with_api_key(codex_home.path(), "sk-test", AuthCredentialsStoreMode::File) .expect("seed api key"); - let config = build_config(codex_home.path(), Some(ForcedLoginMethod::Chatgpt), None); + let config = build_config(codex_home.path(), Some(ForcedLoginMethod::Chatgpt), None).await; let err = super::enforce_login_restrictions(&config) .await @@ -941,7 +939,7 @@ mod tests { ) .expect("failed to write auth file"); - let config = build_config(codex_home.path(), None, Some("org_mine".to_string())); + let config = build_config(codex_home.path(), None, Some("org_mine".to_string())).await; let err = super::enforce_login_restrictions(&config) .await @@ -967,7 +965,7 @@ mod tests { ) .expect("failed to write auth file"); - let config = build_config(codex_home.path(), None, Some("org_mine".to_string())); + let config = build_config(codex_home.path(), None, Some("org_mine".to_string())).await; super::enforce_login_restrictions(&config) .await @@ -985,7 +983,7 @@ mod tests { login_with_api_key(codex_home.path(), "sk-test", AuthCredentialsStoreMode::File) .expect("seed api key"); - let config = build_config(codex_home.path(), None, Some("org_mine".to_string())); + let config = build_config(codex_home.path(), None, Some("org_mine".to_string())).await; super::enforce_login_restrictions(&config) .await @@ -1002,7 +1000,7 @@ mod tests { let _guard = EnvVarGuard::set(CODEX_API_KEY_ENV_VAR, "sk-env"); let codex_home = tempdir().unwrap(); - let config = build_config(codex_home.path(), Some(ForcedLoginMethod::Chatgpt), None); + let config = build_config(codex_home.path(), Some(ForcedLoginMethod::Chatgpt), None).await; let err = super::enforce_login_restrictions(&config) .await diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 5deca299f..f0d205658 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -2750,8 +2750,7 @@ pub(crate) use tests::make_session_and_context_with_rx; mod tests { use super::*; use crate::CodexAuth; - use crate::config::ConfigOverrides; - use crate::config::ConfigToml; + use crate::config::ConfigBuilder; use crate::exec::ExecToolCallOutput; use crate::function_tool::FunctionCallError; use crate::shell::default_user_shell; @@ -2778,6 +2777,7 @@ mod tests { use codex_app_server_protocol::AuthMode; use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; + use std::path::Path; use std::time::Duration; use tokio::time::sleep; @@ -2790,9 +2790,9 @@ mod tests { use std::sync::Arc; use std::time::Duration as StdDuration; - #[test] - fn reconstruct_history_matches_live_compactions() { - let (session, turn_context) = make_session_and_context(); + #[tokio::test] + async fn reconstruct_history_matches_live_compactions() { + let (session, turn_context) = make_session_and_context().await; let (rollout_items, expected) = sample_rollout(&session, &turn_context); let reconstructed = session.reconstruct_history_from_rollout(&turn_context, &rollout_items); @@ -2800,47 +2800,40 @@ mod tests { assert_eq!(expected, reconstructed); } - #[test] - fn record_initial_history_reconstructs_resumed_transcript() { - let (session, turn_context) = make_session_and_context(); + #[tokio::test] + async fn record_initial_history_reconstructs_resumed_transcript() { + let (session, turn_context) = make_session_and_context().await; let (rollout_items, expected) = sample_rollout(&session, &turn_context); - tokio_test::block_on(session.record_initial_history(InitialHistory::Resumed( - ResumedHistory { + session + .record_initial_history(InitialHistory::Resumed(ResumedHistory { conversation_id: ConversationId::default(), history: rollout_items, rollout_path: PathBuf::from("/tmp/resume.jsonl"), - }, - ))); + })) + .await; - let actual = tokio_test::block_on(async { - session.state.lock().await.clone_history().get_history() - }); + let actual = session.state.lock().await.clone_history().get_history(); assert_eq!(expected, actual); } - #[test] - fn record_initial_history_reconstructs_forked_transcript() { - let (session, turn_context) = make_session_and_context(); + #[tokio::test] + async fn record_initial_history_reconstructs_forked_transcript() { + let (session, turn_context) = make_session_and_context().await; let (rollout_items, expected) = sample_rollout(&session, &turn_context); - tokio_test::block_on(session.record_initial_history(InitialHistory::Forked(rollout_items))); + session + .record_initial_history(InitialHistory::Forked(rollout_items)) + .await; - let actual = tokio_test::block_on(async { - session.state.lock().await.clone_history().get_history() - }); + let actual = session.state.lock().await.clone_history().get_history(); assert_eq!(expected, actual); } - #[test] - fn set_rate_limits_retains_previous_credits() { + #[tokio::test] + async fn set_rate_limits_retains_previous_credits() { let codex_home = tempfile::tempdir().expect("create temp dir"); - let config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - codex_home.path().to_path_buf(), - ) - .expect("load default test config"); + let config = build_test_config(codex_home.path()).await; let config = Arc::new(config); let model = ModelsManager::get_model_offline(config.model.as_deref()); let session_configuration = SessionConfiguration { @@ -2904,15 +2897,10 @@ mod tests { ); } - #[test] - fn set_rate_limits_updates_plan_type_when_present() { + #[tokio::test] + async fn set_rate_limits_updates_plan_type_when_present() { let codex_home = tempfile::tempdir().expect("create temp dir"); - let config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - codex_home.path().to_path_buf(), - ) - .expect("load default test config"); + let config = build_test_config(codex_home.path()).await; let config = Arc::new(config); let model = ModelsManager::get_model_offline(config.model.as_deref()); let session_configuration = SessionConfiguration { @@ -3002,8 +2990,8 @@ mod tests { assert_eq!(expected, got); } - #[test] - fn includes_timed_out_message() { + #[tokio::test] + async fn includes_timed_out_message() { let exec = ExecToolCallOutput { exit_code: 0, stdout: StreamOutput::new(String::new()), @@ -3012,7 +3000,7 @@ mod tests { duration: StdDuration::from_secs(1), timed_out: true, }; - let (_, turn_context) = make_session_and_context(); + let (_, turn_context) = make_session_and_context().await; let out = format_exec_output_str(&exec, turn_context.truncation_policy); @@ -3085,6 +3073,14 @@ mod tests { }) } + async fn build_test_config(codex_home: &Path) -> Config { + ConfigBuilder::default() + .codex_home(codex_home.to_path_buf()) + .build() + .await + .expect("load default test config") + } + fn otel_manager( conversation_id: ConversationId, config: &Config, @@ -3104,15 +3100,10 @@ mod tests { ) } - pub(crate) fn make_session_and_context() -> (Session, TurnContext) { + pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { let (tx_event, _rx_event) = async_channel::unbounded(); let codex_home = tempfile::tempdir().expect("create temp dir"); - let config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - codex_home.path().to_path_buf(), - ) - .expect("load default test config"); + let config = build_test_config(codex_home.path()).await; let config = Arc::new(config); let conversation_id = ConversationId::default(); let auth_manager = @@ -3191,19 +3182,14 @@ mod tests { // Like make_session_and_context, but returns Arc and the event receiver // so tests can assert on emitted events. - pub(crate) fn make_session_and_context_with_rx() -> ( + pub(crate) async fn make_session_and_context_with_rx() -> ( Arc, Arc, async_channel::Receiver, ) { let (tx_event, rx_event) = async_channel::unbounded(); let codex_home = tempfile::tempdir().expect("create temp dir"); - let config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - codex_home.path().to_path_buf(), - ) - .expect("load default test config"); + let config = build_test_config(codex_home.path()).await; let config = Arc::new(config); let conversation_id = ConversationId::default(); let auth_manager = @@ -3282,7 +3268,7 @@ mod tests { #[tokio::test] async fn record_model_warning_appends_user_message() { - let (mut session, turn_context) = make_session_and_context(); + let (mut session, turn_context) = make_session_and_context().await; let mut features = Features::with_defaults(); features.enable(Feature::ModelWarnings); session.features = features; @@ -3341,7 +3327,7 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[test_log::test] async fn abort_regular_task_emits_turn_aborted_only() { - let (sess, tc, rx) = make_session_and_context_with_rx(); + let (sess, tc, rx) = make_session_and_context_with_rx().await; let input = vec![UserInput::Text { text: "hello".to_string(), }]; @@ -3370,7 +3356,7 @@ mod tests { #[tokio::test] async fn abort_gracefuly_emits_turn_aborted_only() { - let (sess, tc, rx) = make_session_and_context_with_rx(); + let (sess, tc, rx) = make_session_and_context_with_rx().await; let input = vec![UserInput::Text { text: "hello".to_string(), }]; @@ -3396,7 +3382,7 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn abort_review_task_emits_exited_then_aborted_and_records_history() { - let (sess, tc, rx) = make_session_and_context_with_rx(); + let (sess, tc, rx) = make_session_and_context_with_rx().await; let input = vec![UserInput::Text { text: "start review".to_string(), }]; @@ -3444,7 +3430,7 @@ mod tests { #[tokio::test] async fn fatal_tool_error_stops_turn_and_reports_error() { - let (session, turn_context, _rx) = make_session_and_context_with_rx(); + let (session, turn_context, _rx) = make_session_and_context_with_rx().await; let tools = { session .services @@ -3607,7 +3593,7 @@ mod tests { use crate::turn_diff_tracker::TurnDiffTracker; use std::collections::HashMap; - let (session, mut turn_context_raw) = make_session_and_context(); + let (session, mut turn_context_raw) = make_session_and_context().await; // Ensure policy is NOT OnRequest so the early rejection path triggers turn_context_raw.approval_policy = AskForApproval::OnFailure; let session = Arc::new(session); @@ -3738,7 +3724,7 @@ mod tests { use crate::sandboxing::SandboxPermissions; use crate::turn_diff_tracker::TurnDiffTracker; - let (session, mut turn_context_raw) = make_session_and_context(); + let (session, mut turn_context_raw) = make_session_and_context().await; turn_context_raw.approval_policy = AskForApproval::OnFailure; let session = Arc::new(session); let turn_context = Arc::new(turn_context_raw); diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs index c7aebbaf9..240a26704 100644 --- a/codex-rs/core/src/codex_delegate.rs +++ b/codex-rs/core/src/codex_delegate.rs @@ -366,7 +366,7 @@ mod tests { rx_event: rx_events, }); - let (session, ctx, _rx_evt) = crate::codex::make_session_and_context_with_rx(); + let (session, ctx, _rx_evt) = crate::codex::make_session_and_context_with_rx().await; let (tx_out, rx_out) = bounded(1); tx_out diff --git a/codex-rs/core/src/config/edit.rs b/codex-rs/core/src/config/edit.rs index 58ffbbae3..a24c09e36 100644 --- a/codex-rs/core/src/config/edit.rs +++ b/codex-rs/core/src/config/edit.rs @@ -694,7 +694,6 @@ mod tests { use codex_protocol::openai_models::ReasoningEffort; use pretty_assertions::assert_eq; use tempfile::tempdir; - use tokio::runtime::Builder; use toml::Value as TomlValue; #[test] @@ -1455,22 +1454,16 @@ model_reasoning_effort = "high" assert_eq!(contents, initial_expected); } - #[test] - fn blocking_set_asynchronous_helpers_available() { - let rt = Builder::new_current_thread() - .enable_all() - .build() - .expect("runtime"); + #[tokio::test] + async fn blocking_set_asynchronous_helpers_available() { let tmp = tempdir().expect("tmpdir"); let codex_home = tmp.path().to_path_buf(); - rt.block_on(async { - ConfigEditsBuilder::new(&codex_home) - .set_hide_full_access_warning(true) - .apply() - .await - .expect("persist"); - }); + ConfigEditsBuilder::new(&codex_home) + .set_hide_full_access_warning(true) + .apply() + .await + .expect("persist"); let raw = std::fs::read_to_string(codex_home.join(CONFIG_TOML_FILE)).expect("read config"); let notice = toml::from_str::(&raw) diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 438e441b5..c958bcabb 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -992,14 +992,13 @@ pub fn resolve_oss_provider( } impl Config { - /// Meant to be used exclusively for tests. For new tests, prefer using - /// [ConfigBuilder::build()], if possible, so ultimately we can make this - /// method private to this file. - pub fn load_from_base_config_with_overrides( + #[cfg(test)] + fn load_from_base_config_with_overrides( cfg: ConfigToml, overrides: ConfigOverrides, codex_home: PathBuf, ) -> std::io::Result { + // Note this ignores requirements.toml enforcement for tests. let requirements = ConfigRequirements::default(); Self::load_config_with_requirements(cfg, overrides, codex_home, requirements) } diff --git a/codex-rs/core/src/conversation_manager.rs b/codex-rs/core/src/conversation_manager.rs index ce38b0018..084b73886 100644 --- a/codex-rs/core/src/conversation_manager.rs +++ b/codex-rs/core/src/conversation_manager.rs @@ -379,9 +379,9 @@ mod tests { assert_matches!(truncated2, InitialHistory::New); } - #[test] - fn ignores_session_prefix_messages_when_truncating() { - let (session, turn_context) = make_session_and_context(); + #[tokio::test] + async fn ignores_session_prefix_messages_when_truncating() { + let (session, turn_context) = make_session_and_context().await; let mut items = session.build_initial_context(&turn_context); items.push(user_msg("feature request")); items.push(assistant_msg("ack")); diff --git a/codex-rs/core/src/message_history.rs b/codex-rs/core/src/message_history.rs index ecc685133..733e8e800 100644 --- a/codex-rs/core/src/message_history.rs +++ b/codex-rs/core/src/message_history.rs @@ -401,9 +401,7 @@ fn history_log_id(_metadata: &std::fs::Metadata) -> Option { #[cfg(test)] mod tests { use super::*; - use crate::config::Config; - use crate::config::ConfigOverrides; - use crate::config::ConfigToml; + use crate::config::ConfigBuilder; use codex_protocol::ConversationId; use pretty_assertions::assert_eq; use std::fs::File; @@ -493,12 +491,11 @@ mod tests { async fn append_entry_trims_history_when_beyond_max_bytes() { let codex_home = TempDir::new().expect("create temp dir"); - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - codex_home.path().to_path_buf(), - ) - .expect("load config"); + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .build() + .await + .expect("load config"); let conversation_id = ConversationId::new(); @@ -541,12 +538,11 @@ mod tests { async fn append_entry_trims_history_to_soft_cap() { let codex_home = TempDir::new().expect("create temp dir"); - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - codex_home.path().to_path_buf(), - ) - .expect("load config"); + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .build() + .await + .expect("load config"); let conversation_id = ConversationId::new(); diff --git a/codex-rs/core/src/openai_models/models_manager.rs b/codex-rs/core/src/openai_models/models_manager.rs index 9969a3a9c..7f54c4f85 100644 --- a/codex-rs/core/src/openai_models/models_manager.rs +++ b/codex-rs/core/src/openai_models/models_manager.rs @@ -314,9 +314,7 @@ mod tests { use super::*; use crate::CodexAuth; use crate::auth::AuthCredentialsStoreMode; - use crate::config::Config; - use crate::config::ConfigOverrides; - use crate::config::ConfigToml; + use crate::config::ConfigBuilder; use crate::features::Feature; use crate::model_provider_info::WireApi; use codex_protocol::openai_models::ModelsResponse; @@ -397,12 +395,11 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - codex_home.path().to_path_buf(), - ) - .expect("load default test config"); + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .build() + .await + .expect("load default test config"); config.features.enable(Feature::RemoteModels); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); @@ -455,12 +452,11 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - codex_home.path().to_path_buf(), - ) - .expect("load default test config"); + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .build() + .await + .expect("load default test config"); config.features.enable(Feature::RemoteModels); let auth_manager = Arc::new(AuthManager::new( codex_home.path().to_path_buf(), @@ -511,12 +507,11 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - codex_home.path().to_path_buf(), - ) - .expect("load default test config"); + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .build() + .await + .expect("load default test config"); config.features.enable(Feature::RemoteModels); let auth_manager = Arc::new(AuthManager::new( codex_home.path().to_path_buf(), @@ -587,12 +582,11 @@ mod tests { .await; let codex_home = tempdir().expect("temp dir"); - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - codex_home.path().to_path_buf(), - ) - .expect("load default test config"); + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .build() + .await + .expect("load default test config"); config.features.enable(Feature::RemoteModels); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); diff --git a/codex-rs/core/src/project_doc.rs b/codex-rs/core/src/project_doc.rs index f115b1295..cb2499cbb 100644 --- a/codex-rs/core/src/project_doc.rs +++ b/codex-rs/core/src/project_doc.rs @@ -232,8 +232,7 @@ fn merge_project_docs_with_skills( #[cfg(test)] mod tests { use super::*; - use crate::config::ConfigOverrides; - use crate::config::ConfigToml; + use crate::config::ConfigBuilder; use crate::skills::load_skills; use std::fs; use std::path::PathBuf; @@ -244,14 +243,13 @@ mod tests { /// optionally specify a custom `instructions` string – when `None` the /// value is cleared to mimic a scenario where no system instructions have /// been configured. - fn make_config(root: &TempDir, limit: usize, instructions: Option<&str>) -> Config { + async fn make_config(root: &TempDir, limit: usize, instructions: Option<&str>) -> Config { let codex_home = TempDir::new().unwrap(); - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - codex_home.path().to_path_buf(), - ) - .expect("defaults for test should always succeed"); + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .build() + .await + .expect("defaults for test should always succeed"); config.cwd = root.path().to_path_buf(); config.project_doc_max_bytes = limit; @@ -260,13 +258,13 @@ mod tests { config } - fn make_config_with_fallback( + async fn make_config_with_fallback( root: &TempDir, limit: usize, instructions: Option<&str>, fallbacks: &[&str], ) -> Config { - let mut config = make_config(root, limit, instructions); + let mut config = make_config(root, limit, instructions).await; config.project_doc_fallback_filenames = fallbacks .iter() .map(std::string::ToString::to_string) @@ -279,7 +277,7 @@ mod tests { async fn no_doc_file_returns_none() { let tmp = tempfile::tempdir().expect("tempdir"); - let res = get_user_instructions(&make_config(&tmp, 4096, None), None).await; + let res = get_user_instructions(&make_config(&tmp, 4096, None).await, None).await; assert!( res.is_none(), "Expected None when AGENTS.md is absent and no system instructions provided" @@ -293,7 +291,7 @@ mod tests { let tmp = tempfile::tempdir().expect("tempdir"); fs::write(tmp.path().join("AGENTS.md"), "hello world").unwrap(); - let res = get_user_instructions(&make_config(&tmp, 4096, None), None) + let res = get_user_instructions(&make_config(&tmp, 4096, None).await, None) .await .expect("doc expected"); @@ -312,7 +310,7 @@ mod tests { let huge = "A".repeat(LIMIT * 2); // 2 KiB fs::write(tmp.path().join("AGENTS.md"), &huge).unwrap(); - let res = get_user_instructions(&make_config(&tmp, LIMIT, None), None) + let res = get_user_instructions(&make_config(&tmp, LIMIT, None).await, None) .await .expect("doc expected"); @@ -341,7 +339,7 @@ mod tests { std::fs::create_dir_all(&nested).unwrap(); // Build config pointing at the nested dir. - let mut cfg = make_config(&repo, 4096, None); + let mut cfg = make_config(&repo, 4096, None).await; cfg.cwd = nested; let res = get_user_instructions(&cfg, None) @@ -356,7 +354,7 @@ mod tests { let tmp = tempfile::tempdir().expect("tempdir"); fs::write(tmp.path().join("AGENTS.md"), "something").unwrap(); - let res = get_user_instructions(&make_config(&tmp, 0, None), None).await; + let res = get_user_instructions(&make_config(&tmp, 0, None).await, None).await; assert!( res.is_none(), "With limit 0 the function should return None" @@ -372,7 +370,7 @@ mod tests { const INSTRUCTIONS: &str = "base instructions"; - let res = get_user_instructions(&make_config(&tmp, 4096, Some(INSTRUCTIONS)), None) + let res = get_user_instructions(&make_config(&tmp, 4096, Some(INSTRUCTIONS)).await, None) .await .expect("should produce a combined instruction string"); @@ -389,7 +387,8 @@ mod tests { const INSTRUCTIONS: &str = "some instructions"; - let res = get_user_instructions(&make_config(&tmp, 4096, Some(INSTRUCTIONS)), None).await; + let res = + get_user_instructions(&make_config(&tmp, 4096, Some(INSTRUCTIONS)).await, None).await; assert_eq!(res, Some(INSTRUCTIONS.to_string())); } @@ -415,7 +414,7 @@ mod tests { std::fs::create_dir_all(&nested).unwrap(); fs::write(nested.join("AGENTS.md"), "crate doc").unwrap(); - let mut cfg = make_config(&repo, 4096, None); + let mut cfg = make_config(&repo, 4096, None).await; cfg.cwd = nested; let res = get_user_instructions(&cfg, None) @@ -431,7 +430,7 @@ mod tests { fs::write(tmp.path().join(DEFAULT_PROJECT_DOC_FILENAME), "versioned").unwrap(); fs::write(tmp.path().join(LOCAL_PROJECT_DOC_FILENAME), "local").unwrap(); - let cfg = make_config(&tmp, 4096, None); + let cfg = make_config(&tmp, 4096, None).await; let res = get_user_instructions(&cfg, None) .await @@ -453,7 +452,7 @@ mod tests { let tmp = tempfile::tempdir().expect("tempdir"); fs::write(tmp.path().join("EXAMPLE.md"), "example instructions").unwrap(); - let cfg = make_config_with_fallback(&tmp, 4096, None, &["EXAMPLE.md"]); + let cfg = make_config_with_fallback(&tmp, 4096, None, &["EXAMPLE.md"]).await; let res = get_user_instructions(&cfg, None) .await @@ -469,7 +468,7 @@ mod tests { fs::write(tmp.path().join("AGENTS.md"), "primary").unwrap(); fs::write(tmp.path().join("EXAMPLE.md"), "secondary").unwrap(); - let cfg = make_config_with_fallback(&tmp, 4096, None, &["EXAMPLE.md", ".example.md"]); + let cfg = make_config_with_fallback(&tmp, 4096, None, &["EXAMPLE.md", ".example.md"]).await; let res = get_user_instructions(&cfg, None) .await @@ -493,7 +492,7 @@ mod tests { let tmp = tempfile::tempdir().expect("tempdir"); fs::write(tmp.path().join("AGENTS.md"), "base doc").unwrap(); - let cfg = make_config(&tmp, 4096, None); + let cfg = make_config(&tmp, 4096, None).await; create_skill( cfg.codex_home.clone(), "pdf-processing", @@ -524,7 +523,7 @@ mod tests { #[tokio::test] async fn skills_render_without_project_doc() { let tmp = tempfile::tempdir().expect("tempdir"); - let cfg = make_config(&tmp, 4096, None); + let cfg = make_config(&tmp, 4096, None).await; create_skill(cfg.codex_home.clone(), "linting", "run clippy"); let skills = load_skills(&cfg); diff --git a/codex-rs/core/src/skills/loader.rs b/codex-rs/core/src/skills/loader.rs index 3fbcfc93d..ca330a0e5 100644 --- a/codex-rs/core/src/skills/loader.rs +++ b/codex-rs/core/src/skills/loader.rs @@ -302,21 +302,19 @@ fn extract_frontmatter(contents: &str) -> Option { #[cfg(test)] mod tests { use super::*; - use crate::config::ConfigOverrides; - use crate::config::ConfigToml; + use crate::config::ConfigBuilder; use codex_protocol::protocol::SkillScope; use pretty_assertions::assert_eq; use std::path::Path; use std::process::Command; use tempfile::TempDir; - fn make_config(codex_home: &TempDir) -> Config { - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - codex_home.path().to_path_buf(), - ) - .expect("defaults for test should always succeed"); + async fn make_config(codex_home: &TempDir) -> Config { + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .build() + .await + .expect("defaults for test should always succeed"); config.cwd = codex_home.path().to_path_buf(); config @@ -352,11 +350,11 @@ mod tests { path } - #[test] - fn loads_valid_skill() { + #[tokio::test] + async fn loads_valid_skill() { let codex_home = tempfile::tempdir().expect("tempdir"); write_skill(&codex_home, "demo", "demo-skill", "does things\ncarefully"); - let cfg = make_config(&codex_home); + let cfg = make_config(&codex_home).await; let outcome = load_skills(&cfg); assert!( @@ -376,15 +374,15 @@ mod tests { ); } - #[test] - fn loads_short_description_from_metadata() { + #[tokio::test] + async fn loads_short_description_from_metadata() { let codex_home = tempfile::tempdir().expect("tempdir"); let skill_dir = codex_home.path().join("skills/demo"); fs::create_dir_all(&skill_dir).unwrap(); let contents = "---\nname: demo-skill\ndescription: long description\nmetadata:\n short-description: short summary\n---\n\n# Body\n"; fs::write(skill_dir.join(SKILLS_FILENAME), contents).unwrap(); - let cfg = make_config(&codex_home); + let cfg = make_config(&codex_home).await; let outcome = load_skills(&cfg); assert!( outcome.errors.is_empty(), @@ -398,8 +396,8 @@ mod tests { ); } - #[test] - fn enforces_short_description_length_limits() { + #[tokio::test] + async fn enforces_short_description_length_limits() { let codex_home = tempfile::tempdir().expect("tempdir"); let skill_dir = codex_home.path().join("skills/demo"); fs::create_dir_all(&skill_dir).unwrap(); @@ -409,7 +407,7 @@ mod tests { ); fs::write(skill_dir.join(SKILLS_FILENAME), contents).unwrap(); - let cfg = make_config(&codex_home); + let cfg = make_config(&codex_home).await; let outcome = load_skills(&cfg); assert_eq!(outcome.skills.len(), 0); assert_eq!(outcome.errors.len(), 1); @@ -422,8 +420,8 @@ mod tests { ); } - #[test] - fn skips_hidden_and_invalid() { + #[tokio::test] + async fn skips_hidden_and_invalid() { let codex_home = tempfile::tempdir().expect("tempdir"); let hidden_dir = codex_home.path().join("skills/.hidden"); fs::create_dir_all(&hidden_dir).unwrap(); @@ -438,7 +436,7 @@ mod tests { fs::create_dir_all(&invalid_dir).unwrap(); fs::write(invalid_dir.join(SKILLS_FILENAME), "---\nname: bad").unwrap(); - let cfg = make_config(&codex_home); + let cfg = make_config(&codex_home).await; let outcome = load_skills(&cfg); assert_eq!(outcome.skills.len(), 0); assert_eq!(outcome.errors.len(), 1); @@ -450,12 +448,12 @@ mod tests { ); } - #[test] - fn enforces_length_limits() { + #[tokio::test] + async fn enforces_length_limits() { let codex_home = tempfile::tempdir().expect("tempdir"); let max_desc = "\u{1F4A1}".repeat(MAX_DESCRIPTION_LEN); write_skill(&codex_home, "max-len", "max-len", &max_desc); - let cfg = make_config(&codex_home); + let cfg = make_config(&codex_home).await; let outcome = load_skills(&cfg); assert!( @@ -476,8 +474,8 @@ mod tests { ); } - #[test] - fn loads_skills_from_repo_root() { + #[tokio::test] + async fn loads_skills_from_repo_root() { let codex_home = tempfile::tempdir().expect("tempdir"); let repo_dir = tempfile::tempdir().expect("tempdir"); @@ -493,7 +491,7 @@ mod tests { .join(REPO_ROOT_CONFIG_DIR_NAME) .join(SKILLS_DIR_NAME); write_skill_at(&skills_root, "repo", "repo-skill", "from repo"); - let mut cfg = make_config(&codex_home); + let mut cfg = make_config(&codex_home).await; cfg.cwd = repo_dir.path().to_path_buf(); let repo_root = normalize_path(&skills_root).unwrap_or_else(|_| skills_root.clone()); @@ -509,8 +507,8 @@ mod tests { assert!(skill.path.starts_with(&repo_root)); } - #[test] - fn loads_skills_from_nearest_codex_dir_under_repo_root() { + #[tokio::test] + async fn loads_skills_from_nearest_codex_dir_under_repo_root() { let codex_home = tempfile::tempdir().expect("tempdir"); let repo_dir = tempfile::tempdir().expect("tempdir"); @@ -544,7 +542,7 @@ mod tests { "from nested", ); - let mut cfg = make_config(&codex_home); + let mut cfg = make_config(&codex_home).await; cfg.cwd = nested_dir; let outcome = load_skills(&cfg); @@ -557,8 +555,8 @@ mod tests { assert_eq!(outcome.skills[0].name, "nested-skill"); } - #[test] - fn loads_skills_from_codex_dir_when_not_git_repo() { + #[tokio::test] + async fn loads_skills_from_codex_dir_when_not_git_repo() { let codex_home = tempfile::tempdir().expect("tempdir"); let work_dir = tempfile::tempdir().expect("tempdir"); @@ -572,7 +570,7 @@ mod tests { "from cwd", ); - let mut cfg = make_config(&codex_home); + let mut cfg = make_config(&codex_home).await; cfg.cwd = work_dir.path().to_path_buf(); let outcome = load_skills(&cfg); @@ -586,8 +584,8 @@ mod tests { assert_eq!(outcome.skills[0].scope, SkillScope::Repo); } - #[test] - fn deduplicates_by_name_preferring_repo_over_user() { + #[tokio::test] + async fn deduplicates_by_name_preferring_repo_over_user() { let codex_home = tempfile::tempdir().expect("tempdir"); let repo_dir = tempfile::tempdir().expect("tempdir"); @@ -609,7 +607,7 @@ mod tests { "from repo", ); - let mut cfg = make_config(&codex_home); + let mut cfg = make_config(&codex_home).await; cfg.cwd = repo_dir.path().to_path_buf(); let outcome = load_skills(&cfg); @@ -623,14 +621,14 @@ mod tests { assert_eq!(outcome.skills[0].scope, SkillScope::Repo); } - #[test] - fn loads_system_skills_with_lowest_priority() { + #[tokio::test] + async fn loads_system_skills_with_lowest_priority() { let codex_home = tempfile::tempdir().expect("tempdir"); write_system_skill(&codex_home, "system", "dupe-skill", "from system"); write_skill(&codex_home, "user", "dupe-skill", "from user"); - let cfg = make_config(&codex_home); + let cfg = make_config(&codex_home).await; let outcome = load_skills(&cfg); assert!( outcome.errors.is_empty(), @@ -642,8 +640,8 @@ mod tests { assert_eq!(outcome.skills[0].scope, SkillScope::User); } - #[test] - fn repo_skills_search_does_not_escape_repo_root() { + #[tokio::test] + async fn repo_skills_search_does_not_escape_repo_root() { let codex_home = tempfile::tempdir().expect("tempdir"); let outer_dir = tempfile::tempdir().expect("tempdir"); let repo_dir = outer_dir.path().join("repo"); @@ -666,7 +664,7 @@ mod tests { .expect("git init"); assert!(status.success(), "git init failed"); - let mut cfg = make_config(&codex_home); + let mut cfg = make_config(&codex_home).await; cfg.cwd = repo_dir; let outcome = load_skills(&cfg); @@ -678,8 +676,8 @@ mod tests { assert_eq!(outcome.skills.len(), 0); } - #[test] - fn loads_skills_when_cwd_is_file_in_repo() { + #[tokio::test] + async fn loads_skills_when_cwd_is_file_in_repo() { let codex_home = tempfile::tempdir().expect("tempdir"); let repo_dir = tempfile::tempdir().expect("tempdir"); @@ -702,7 +700,7 @@ mod tests { let file_path = repo_dir.path().join("some-file.txt"); fs::write(&file_path, "contents").unwrap(); - let mut cfg = make_config(&codex_home); + let mut cfg = make_config(&codex_home).await; cfg.cwd = file_path; let outcome = load_skills(&cfg); @@ -716,8 +714,8 @@ mod tests { assert_eq!(outcome.skills[0].scope, SkillScope::Repo); } - #[test] - fn non_git_repo_skills_search_does_not_walk_parents() { + #[tokio::test] + async fn non_git_repo_skills_search_does_not_walk_parents() { let codex_home = tempfile::tempdir().expect("tempdir"); let outer_dir = tempfile::tempdir().expect("tempdir"); let nested_dir = outer_dir.path().join("nested/inner"); @@ -733,7 +731,7 @@ mod tests { "from outer", ); - let mut cfg = make_config(&codex_home); + let mut cfg = make_config(&codex_home).await; cfg.cwd = nested_dir; let outcome = load_skills(&cfg); @@ -745,14 +743,14 @@ mod tests { assert_eq!(outcome.skills.len(), 0); } - #[test] - fn loads_skills_from_system_cache_when_present() { + #[tokio::test] + async fn loads_skills_from_system_cache_when_present() { let codex_home = tempfile::tempdir().expect("tempdir"); let work_dir = tempfile::tempdir().expect("tempdir"); write_system_skill(&codex_home, "system", "system-skill", "from system"); - let mut cfg = make_config(&codex_home); + let mut cfg = make_config(&codex_home).await; cfg.cwd = work_dir.path().to_path_buf(); let outcome = load_skills(&cfg); @@ -766,15 +764,15 @@ mod tests { assert_eq!(outcome.skills[0].scope, SkillScope::System); } - #[test] - fn deduplicates_by_name_preferring_user_over_system() { + #[tokio::test] + async fn deduplicates_by_name_preferring_user_over_system() { let codex_home = tempfile::tempdir().expect("tempdir"); let work_dir = tempfile::tempdir().expect("tempdir"); write_skill(&codex_home, "user", "dupe-skill", "from user"); write_system_skill(&codex_home, "system", "dupe-skill", "from system"); - let mut cfg = make_config(&codex_home); + let mut cfg = make_config(&codex_home).await; cfg.cwd = work_dir.path().to_path_buf(); let outcome = load_skills(&cfg); @@ -788,8 +786,8 @@ mod tests { assert_eq!(outcome.skills[0].scope, SkillScope::User); } - #[test] - fn deduplicates_by_name_preferring_repo_over_system() { + #[tokio::test] + async fn deduplicates_by_name_preferring_repo_over_system() { let codex_home = tempfile::tempdir().expect("tempdir"); let repo_dir = tempfile::tempdir().expect("tempdir"); @@ -811,7 +809,7 @@ mod tests { ); write_system_skill(&codex_home, "system", "dupe-skill", "from system"); - let mut cfg = make_config(&codex_home); + let mut cfg = make_config(&codex_home).await; cfg.cwd = repo_dir.path().to_path_buf(); let outcome = load_skills(&cfg); diff --git a/codex-rs/core/src/tools/handlers/shell.rs b/codex-rs/core/src/tools/handlers/shell.rs index bcc4ed930..624094a5a 100644 --- a/codex-rs/core/src/tools/handlers/shell.rs +++ b/codex-rs/core/src/tools/handlers/shell.rs @@ -358,9 +358,9 @@ mod tests { )); } - #[test] - fn shell_command_handler_to_exec_params_uses_session_shell_and_turn_context() { - let (session, turn_context) = make_session_and_context(); + #[tokio::test] + async fn shell_command_handler_to_exec_params_uses_session_shell_and_turn_context() { + let (session, turn_context) = make_session_and_context().await; let command = "echo hello".to_string(); let workdir = Some("subdir".to_string()); diff --git a/codex-rs/core/src/unified_exec/mod.rs b/codex-rs/core/src/unified_exec/mod.rs index 814001f41..2cb30e5aa 100644 --- a/codex-rs/core/src/unified_exec/mod.rs +++ b/codex-rs/core/src/unified_exec/mod.rs @@ -187,8 +187,8 @@ mod tests { use super::session::OutputBufferState; - fn test_session_and_turn() -> (Arc, Arc) { - let (session, mut turn) = make_session_and_context(); + async fn test_session_and_turn() -> (Arc, Arc) { + let (session, mut turn) = make_session_and_context().await; turn.approval_policy = AskForApproval::Never; turn.sandbox_policy = SandboxPolicy::DangerFullAccess; (Arc::new(session), Arc::new(turn)) @@ -266,7 +266,7 @@ mod tests { async fn unified_exec_persists_across_requests() -> anyhow::Result<()> { skip_if_sandbox!(Ok(())); - let (session, turn) = test_session_and_turn(); + let (session, turn) = test_session_and_turn().await; let open_shell = exec_command(&session, &turn, "bash -i", 2_500).await?; let process_id = open_shell @@ -302,7 +302,7 @@ mod tests { async fn multi_unified_exec_sessions() -> anyhow::Result<()> { skip_if_sandbox!(Ok(())); - let (session, turn) = test_session_and_turn(); + let (session, turn) = test_session_and_turn().await; let shell_a = exec_command(&session, &turn, "bash -i", 2_500).await?; let session_a = shell_a @@ -354,7 +354,7 @@ mod tests { async fn unified_exec_timeouts() -> anyhow::Result<()> { skip_if_sandbox!(Ok(())); - let (session, turn) = test_session_and_turn(); + let (session, turn) = test_session_and_turn().await; let open_shell = exec_command(&session, &turn, "bash -i", 2_500).await?; let process_id = open_shell @@ -398,7 +398,7 @@ mod tests { #[tokio::test] #[ignore] // Ignored while we have a better way to test this. async fn requests_with_large_timeout_are_capped() -> anyhow::Result<()> { - let (session, turn) = test_session_and_turn(); + let (session, turn) = test_session_and_turn().await; let result = exec_command(&session, &turn, "echo codex", 120_000).await?; @@ -411,7 +411,7 @@ mod tests { #[tokio::test] #[ignore] // Ignored while we have a better way to test this. async fn completed_commands_do_not_persist_sessions() -> anyhow::Result<()> { - let (session, turn) = test_session_and_turn(); + let (session, turn) = test_session_and_turn().await; let result = exec_command(&session, &turn, "echo codex", 2_500).await?; assert!( @@ -438,7 +438,7 @@ mod tests { async fn reusing_completed_session_returns_unknown_session() -> anyhow::Result<()> { skip_if_sandbox!(Ok(())); - let (session, turn) = test_session_and_turn(); + let (session, turn) = test_session_and_turn().await; let open_shell = exec_command(&session, &turn, "bash -i", 2_500).await?; let process_id = open_shell diff --git a/codex-rs/core/src/user_shell_command.rs b/codex-rs/core/src/user_shell_command.rs index 857e01c06..fb8efcc09 100644 --- a/codex-rs/core/src/user_shell_command.rs +++ b/codex-rs/core/src/user_shell_command.rs @@ -80,8 +80,8 @@ mod tests { assert!(!is_user_shell_command_text("echo hi")); } - #[test] - fn formats_basic_record() { + #[tokio::test] + async fn formats_basic_record() { let exec_output = ExecToolCallOutput { exit_code: 0, stdout: StreamOutput::new("hi".to_string()), @@ -90,7 +90,7 @@ mod tests { duration: Duration::from_secs(1), timed_out: false, }; - let (_, turn_context) = make_session_and_context(); + let (_, turn_context) = make_session_and_context().await; let item = user_shell_command_record_item("echo hi", &exec_output, &turn_context); let ResponseItem::Message { content, .. } = item else { panic!("expected message"); @@ -104,8 +104,8 @@ mod tests { ); } - #[test] - fn uses_aggregated_output_over_streams() { + #[tokio::test] + async fn uses_aggregated_output_over_streams() { let exec_output = ExecToolCallOutput { exit_code: 42, stdout: StreamOutput::new("stdout-only".to_string()), @@ -114,7 +114,7 @@ mod tests { duration: Duration::from_millis(120), timed_out: false, }; - let (_, turn_context) = make_session_and_context(); + let (_, turn_context) = make_session_and_context().await; let record = format_user_shell_command_record("false", &exec_output, &turn_context); assert_eq!( record, diff --git a/codex-rs/core/tests/chat_completions_payload.rs b/codex-rs/core/tests/chat_completions_payload.rs index 3e53fa85c..586793547 100644 --- a/codex-rs/core/tests/chat_completions_payload.rs +++ b/codex-rs/core/tests/chat_completions_payload.rs @@ -65,7 +65,7 @@ async fn run_request(input: Vec) -> Value { Ok(dir) => dir, Err(e) => panic!("failed to create TempDir: {e}"), }; - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider_id = provider.name.clone(); config.model_provider = provider.clone(); config.show_raw_agent_reasoning = true; diff --git a/codex-rs/core/tests/chat_completions_sse.rs b/codex-rs/core/tests/chat_completions_sse.rs index 969fa47b8..f58b03922 100644 --- a/codex-rs/core/tests/chat_completions_sse.rs +++ b/codex-rs/core/tests/chat_completions_sse.rs @@ -64,7 +64,7 @@ async fn run_stream_with_bytes(sse_body: &[u8]) -> Vec { Ok(dir) => dir, Err(e) => panic!("failed to create TempDir: {e}"), }; - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider_id = provider.name.clone(); config.model_provider = provider.clone(); config.show_raw_agent_reasoning = true; diff --git a/codex-rs/core/tests/common/lib.rs b/codex-rs/core/tests/common/lib.rs index 280b76dea..63791127b 100644 --- a/codex-rs/core/tests/common/lib.rs +++ b/codex-rs/core/tests/common/lib.rs @@ -4,8 +4,8 @@ use tempfile::TempDir; use codex_core::CodexConversation; use codex_core::config::Config; +use codex_core::config::ConfigBuilder; use codex_core::config::ConfigOverrides; -use codex_core::config::ConfigToml; use codex_utils_absolute_path::AbsolutePathBuf; use regex_lite::Regex; use std::path::PathBuf; @@ -75,13 +75,13 @@ pub fn test_tmp_path_buf() -> PathBuf { /// Returns a default `Config` whose on-disk state is confined to the provided /// temporary directory. Using a per-test directory keeps tests hermetic and /// avoids clobbering a developer’s real `~/.codex`. -pub fn load_default_config_for_test(codex_home: &TempDir) -> Config { - Config::load_from_base_config_with_overrides( - ConfigToml::default(), - default_test_overrides(), - codex_home.path().to_path_buf(), - ) - .expect("defaults for test should always succeed") +pub async fn load_default_config_for_test(codex_home: &TempDir) -> Config { + ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .harness_overrides(default_test_overrides()) + .build() + .await + .expect("defaults for test should always succeed") } #[cfg(target_os = "linux")] diff --git a/codex-rs/core/tests/common/test_codex.rs b/codex-rs/core/tests/common/test_codex.rs index 59379d768..1e574cdef 100644 --- a/codex-rs/core/tests/common/test_codex.rs +++ b/codex-rs/core/tests/common/test_codex.rs @@ -178,7 +178,7 @@ impl TestCodexBuilder { ..built_in_model_providers()["openai"].clone() }; let cwd = Arc::new(TempDir::new()?); - let mut config = load_default_config_for_test(home); + let mut config = load_default_config_for_test(home).await; config.cwd = cwd.path().to_path_buf(); config.model_provider = model_provider; for hook in self.pre_build_hooks.drain(..) { diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index 382c8875c..5c32685cc 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -57,7 +57,7 @@ async fn responses_stream_includes_subagent_header_on_review() { }; let codex_home = TempDir::new().expect("failed to create TempDir"); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider_id = provider.name.clone(); config.model_provider = provider.clone(); let effort = config.model_reasoning_effort; @@ -151,7 +151,7 @@ async fn responses_stream_includes_subagent_header_on_other() { }; let codex_home = TempDir::new().expect("failed to create TempDir"); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider_id = provider.name.clone(); config.model_provider = provider.clone(); let effort = config.model_reasoning_effort; @@ -241,7 +241,7 @@ async fn responses_respects_model_family_overrides_from_config() { }; let codex_home = TempDir::new().expect("failed to create TempDir"); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model = Some("gpt-3.5-turbo".to_string()); config.model_provider_id = provider.name.clone(); config.model_provider = provider.clone(); diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 35a67a692..bda232433 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -254,7 +254,7 @@ async fn resume_includes_initial_messages_and_sends_prior_items() { ..built_in_model_providers()["openai"].clone() }; let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider = model_provider; // Also configure user instructions to ensure they are NOT delivered on resume. config.user_instructions = Some("be nice".to_string()); @@ -343,7 +343,7 @@ async fn includes_conversation_id_and_model_headers_in_request() { // Init session let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider = model_provider; let conversation_manager = ConversationManager::with_models_provider_and_home( @@ -403,7 +403,7 @@ async fn includes_base_instructions_override_in_request() { ..built_in_model_providers()["openai"].clone() }; let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.base_instructions = Some("test instructions".to_string()); config.model_provider = model_provider; @@ -467,7 +467,7 @@ async fn chatgpt_auth_sends_correct_request() { // Init session let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider = model_provider; let conversation_manager = ConversationManager::with_models_provider_and_home( create_dummy_codex_auth(), @@ -559,7 +559,7 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() { Some("acc-123"), ); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider = model_provider; let auth_manager = @@ -602,7 +602,7 @@ async fn includes_user_instructions_message_in_request() { }; let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider = model_provider; config.user_instructions = Some("be nice".to_string()); @@ -671,7 +671,7 @@ async fn skills_append_to_instructions() { ) .expect("write skill"); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider = model_provider; config.cwd = codex_home.path().to_path_buf(); config.features.enable(Feature::Skills); @@ -1029,7 +1029,7 @@ async fn includes_developer_instructions_message_in_request() { }; let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider = model_provider; config.user_instructions = Some("be nice".to_string()); config.developer_instructions = Some("be useful".to_string()); @@ -1119,7 +1119,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { }; let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider_id = provider.name.clone(); config.model_provider = provider.clone(); let effort = config.model_reasoning_effort; @@ -1261,7 +1261,7 @@ async fn token_count_includes_rate_limits_snapshot() { provider.base_url = Some(format!("{}/v1", server.uri())); let home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.model_provider = provider; let conversation_manager = ConversationManager::with_models_provider_and_home( @@ -1616,7 +1616,7 @@ async fn azure_overrides_assign_properties_used_for_responses_url() { // Init session let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider = provider; let conversation_manager = ConversationManager::with_models_provider_and_home( @@ -1698,7 +1698,7 @@ async fn env_var_overrides_loaded_auth() { // Init session let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider = provider; let conversation_manager = ConversationManager::with_models_provider_and_home( @@ -1780,7 +1780,7 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() { // Init session with isolated codex home. let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider = model_provider; let conversation_manager = ConversationManager::with_models_provider_and_home( diff --git a/codex-rs/core/tests/suite/compact.rs b/codex-rs/core/tests/suite/compact.rs index dd8e4ca2c..4f57330a2 100644 --- a/codex-rs/core/tests/suite/compact.rs +++ b/codex-rs/core/tests/suite/compact.rs @@ -137,7 +137,7 @@ async fn summarize_context_three_requests_and_instructions() { // Build config pointing to the mock server and spawn Codex. let model_provider = non_openai_model_provider(&server); let home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.model_provider = model_provider; set_test_compact_prompt(&mut config); config.model_auto_compact_token_limit = Some(200_000); @@ -331,7 +331,7 @@ async fn manual_compact_uses_custom_prompt() { let model_provider = non_openai_model_provider(&server); let home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.model_provider = model_provider; config.compact_prompt = Some(custom_prompt.to_string()); @@ -411,7 +411,7 @@ async fn manual_compact_emits_api_and_local_token_usage_events() { let model_provider = non_openai_model_provider(&server); let home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.model_provider = model_provider; set_test_compact_prompt(&mut config); @@ -1062,7 +1062,7 @@ async fn auto_compact_runs_after_token_limit_hit() { let model_provider = non_openai_model_provider(&server); let home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.model_provider = model_provider; set_test_compact_prompt(&mut config); config.model_auto_compact_token_limit = Some(200_000); @@ -1285,7 +1285,7 @@ async fn auto_compact_persists_rollout_entries() { let model_provider = non_openai_model_provider(&server); let home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.model_provider = model_provider; set_test_compact_prompt(&mut config); config.model_auto_compact_token_limit = Some(200_000); @@ -1397,7 +1397,7 @@ async fn manual_compact_retries_after_context_window_error() { let model_provider = non_openai_model_provider(&server); let home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.model_provider = model_provider; set_test_compact_prompt(&mut config); config.model_auto_compact_token_limit = Some(200_000); @@ -1530,7 +1530,7 @@ async fn manual_compact_twice_preserves_latest_user_messages() { let model_provider = non_openai_model_provider(&server); let home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.model_provider = model_provider; set_test_compact_prompt(&mut config); let codex = ConversationManager::with_models_provider( @@ -1733,7 +1733,7 @@ async fn auto_compact_allows_multiple_attempts_when_interleaved_with_other_turn_ let model_provider = non_openai_model_provider(&server); let home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.model_provider = model_provider; set_test_compact_prompt(&mut config); config.model_auto_compact_token_limit = Some(200); @@ -1844,7 +1844,7 @@ async fn auto_compact_triggers_after_function_call_over_95_percent_usage() { let model_provider = non_openai_model_provider(&server); let home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.model_provider = model_provider; set_test_compact_prompt(&mut config); config.model_context_window = Some(context_window); diff --git a/codex-rs/core/tests/suite/compact_resume_fork.rs b/codex-rs/core/tests/suite/compact_resume_fork.rs index 188e38da1..75468ae14 100644 --- a/codex-rs/core/tests/suite/compact_resume_fork.rs +++ b/codex-rs/core/tests/suite/compact_resume_fork.rs @@ -862,7 +862,7 @@ async fn start_test_conversation( ..built_in_model_providers()["openai"].clone() }; let home = TempDir::new().expect("create temp dir"); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.model_provider = model_provider; config.compact_prompt = Some(SUMMARIZATION_PROMPT.to_string()); if let Some(model) = model { diff --git a/codex-rs/core/tests/suite/fork_conversation.rs b/codex-rs/core/tests/suite/fork_conversation.rs index a82b47621..d302b4d77 100644 --- a/codex-rs/core/tests/suite/fork_conversation.rs +++ b/codex-rs/core/tests/suite/fork_conversation.rs @@ -51,7 +51,7 @@ async fn fork_conversation_twice_drops_to_first_message() { }; let home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.model_provider = model_provider.clone(); let config_for_fork = config.clone(); diff --git a/codex-rs/core/tests/suite/list_models.rs b/codex-rs/core/tests/suite/list_models.rs index 8cbcc063a..565b978fa 100644 --- a/codex-rs/core/tests/suite/list_models.rs +++ b/codex-rs/core/tests/suite/list_models.rs @@ -12,7 +12,7 @@ use tempfile::tempdir; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn list_models_returns_api_key_models() -> Result<()> { let codex_home = tempdir()?; - let config = load_default_config_for_test(&codex_home); + let config = load_default_config_for_test(&codex_home).await; let manager = ConversationManager::with_models_provider( CodexAuth::from_api_key("sk-test"), built_in_model_providers()["openai"].clone(), @@ -28,7 +28,7 @@ async fn list_models_returns_api_key_models() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn list_models_returns_chatgpt_models() -> Result<()> { let codex_home = tempdir()?; - let config = load_default_config_for_test(&codex_home); + let config = load_default_config_for_test(&codex_home).await; let manager = ConversationManager::with_models_provider( CodexAuth::create_dummy_chatgpt_auth_for_testing(), built_in_model_providers()["openai"].clone(), diff --git a/codex-rs/core/tests/suite/model_overrides.rs b/codex-rs/core/tests/suite/model_overrides.rs index 53a45e678..f7cdac67c 100644 --- a/codex-rs/core/tests/suite/model_overrides.rs +++ b/codex-rs/core/tests/suite/model_overrides.rs @@ -19,7 +19,7 @@ async fn override_turn_context_does_not_persist_when_config_exists() { .await .expect("seed config.toml"); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model = Some("gpt-4o".to_string()); let conversation_manager = ConversationManager::with_models_provider( @@ -62,7 +62,7 @@ async fn override_turn_context_does_not_create_config_file() { "test setup should start without config" ); - let config = load_default_config_for_test(&codex_home); + let config = load_default_config_for_test(&codex_home).await; let conversation_manager = ConversationManager::with_models_provider( CodexAuth::from_api_key("Test API Key"), diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index f95eef7ad..3c4d389ec 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -316,7 +316,7 @@ async fn remote_models_preserve_builtin_presets() -> Result<()> { .await; let codex_home = TempDir::new()?; - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.features.enable(Feature::RemoteModels); let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); @@ -374,7 +374,7 @@ async fn remote_models_hide_picker_only_models() -> Result<()> { .await; let codex_home = TempDir::new()?; - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.features.enable(Feature::RemoteModels); let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); @@ -440,7 +440,7 @@ where let home = Arc::new(TempDir::new()?); let cwd = Arc::new(TempDir::new()?); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.cwd = cwd.path().to_path_buf(); config.features.enable(Feature::RemoteModels); diff --git a/codex-rs/core/tests/suite/resume_warning.rs b/codex-rs/core/tests/suite/resume_warning.rs index 4b6a13315..99fdafe08 100644 --- a/codex-rs/core/tests/suite/resume_warning.rs +++ b/codex-rs/core/tests/suite/resume_warning.rs @@ -42,7 +42,7 @@ fn resume_history( async fn emits_warning_when_resumed_model_differs() { // Arrange a config with a current model and a prior rollout recorded under a different model. let home = TempDir::new().expect("tempdir"); - let mut config = load_default_config_for_test(&home); + let mut config = load_default_config_for_test(&home).await; config.model = Some("current-model".to_string()); // Ensure cwd is absolute (the helper sets it to the temp dir already). assert!(config.cwd.is_absolute()); diff --git a/codex-rs/core/tests/suite/review.rs b/codex-rs/core/tests/suite/review.rs index 4597c0f19..fba7af588 100644 --- a/codex-rs/core/tests/suite/review.rs +++ b/codex-rs/core/tests/suite/review.rs @@ -453,7 +453,7 @@ async fn review_input_isolated_from_parent_history() { // Seed a parent session history via resume file with both user + assistant items. let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.model_provider = ModelProviderInfo { base_url: Some(format!("{}/v1", server.uri())), ..built_in_model_providers()["openai"].clone() @@ -740,7 +740,7 @@ where base_url: Some(format!("{}/v1", server.uri())), ..built_in_model_providers()["openai"].clone() }; - let mut config = load_default_config_for_test(codex_home); + let mut config = load_default_config_for_test(codex_home).await; config.model_provider = model_provider; mutator(&mut config); let conversation_manager = ConversationManager::with_models_provider( @@ -769,7 +769,7 @@ where base_url: Some(format!("{}/v1", server.uri())), ..built_in_model_providers()["openai"].clone() }; - let mut config = load_default_config_for_test(codex_home); + let mut config = load_default_config_for_test(codex_home).await; config.model_provider = model_provider; mutator(&mut config); let conversation_manager = ConversationManager::with_models_provider( diff --git a/codex-rs/core/tests/suite/user_shell_cmd.rs b/codex-rs/core/tests/suite/user_shell_cmd.rs index 8472399ce..270cb8048 100644 --- a/codex-rs/core/tests/suite/user_shell_cmd.rs +++ b/codex-rs/core/tests/suite/user_shell_cmd.rs @@ -39,7 +39,7 @@ async fn user_shell_cmd_ls_and_cat_in_temp_dir() { // Load config and pin cwd to the temp dir so ls/cat operate there. let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); + let mut config = load_default_config_for_test(&codex_home).await; config.cwd = cwd.path().to_path_buf(); let conversation_manager = ConversationManager::with_models_provider( @@ -100,7 +100,7 @@ async fn user_shell_cmd_ls_and_cat_in_temp_dir() { async fn user_shell_cmd_can_be_interrupted() { // Set up isolated config and conversation. let codex_home = TempDir::new().unwrap(); - let config = load_default_config_for_test(&codex_home); + let config = load_default_config_for_test(&codex_home).await; let conversation_manager = ConversationManager::with_models_provider( codex_core::CodexAuth::from_api_key("dummy"), config.model_provider.clone(), diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index e6c17da3b..fac532f9e 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -1252,8 +1252,8 @@ mod tests { use std::sync::Arc; use std::sync::atomic::AtomicBool; - fn make_test_app() -> App { - let (chat_widget, app_event_tx, _rx, _op_rx) = make_chatwidget_manual_with_sender(); + async fn make_test_app() -> App { + let (chat_widget, app_event_tx, _rx, _op_rx) = make_chatwidget_manual_with_sender().await; let config = chat_widget.config_ref().clone(); let current_model = chat_widget.get_model_family().get_model_slug().to_string(); let server = Arc::new(ConversationManager::with_models_provider( @@ -1287,12 +1287,12 @@ mod tests { } } - fn make_test_app_with_channels() -> ( + async fn make_test_app_with_channels() -> ( App, tokio::sync::mpsc::UnboundedReceiver, tokio::sync::mpsc::UnboundedReceiver, ) { - let (chat_widget, app_event_tx, rx, op_rx) = make_chatwidget_manual_with_sender(); + let (chat_widget, app_event_tx, rx, op_rx) = make_chatwidget_manual_with_sender().await; let config = chat_widget.config_ref().clone(); let current_model = chat_widget.get_model_family().get_model_slug().to_string(); let server = Arc::new(ConversationManager::with_models_provider( @@ -1334,8 +1334,8 @@ mod tests { codex_core::openai_models::model_presets::all_model_presets().clone() } - #[test] - fn model_migration_prompt_only_shows_for_deprecated_models() { + #[tokio::test] + async fn model_migration_prompt_only_shows_for_deprecated_models() { let seen = BTreeMap::new(); assert!(should_show_model_migration_prompt( "gpt-5", @@ -1369,8 +1369,8 @@ mod tests { )); } - #[test] - fn model_migration_prompt_respects_hide_flag_and_self_target() { + #[tokio::test] + async fn model_migration_prompt_respects_hide_flag_and_self_target() { let mut seen = BTreeMap::new(); seen.insert("gpt-5".to_string(), "gpt-5.1".to_string()); assert!(!should_show_model_migration_prompt( @@ -1387,8 +1387,8 @@ mod tests { )); } - #[test] - fn model_migration_prompt_skips_when_target_missing() { + #[tokio::test] + async fn model_migration_prompt_skips_when_target_missing() { let mut available = all_model_presets(); let mut current = available .iter() @@ -1415,9 +1415,9 @@ mod tests { assert!(target_preset_for_upgrade(&available, "missing-target").is_none()); } - #[test] - fn update_reasoning_effort_updates_config() { - let mut app = make_test_app(); + #[tokio::test] + async fn update_reasoning_effort_updates_config() { + let mut app = make_test_app().await; app.config.model_reasoning_effort = Some(ReasoningEffortConfig::Medium); app.chat_widget .set_reasoning_effort(Some(ReasoningEffortConfig::Medium)); @@ -1434,9 +1434,9 @@ mod tests { ); } - #[test] - fn backtrack_selection_with_duplicate_history_targets_unique_turn() { - let mut app = make_test_app(); + #[tokio::test] + async fn backtrack_selection_with_duplicate_history_targets_unique_turn() { + let mut app = make_test_app().await; let user_cell = |text: &str| -> Arc { Arc::new(UserHistoryCell { @@ -1503,7 +1503,7 @@ mod tests { #[tokio::test] async fn new_session_requests_shutdown_for_previous_conversation() { - let (mut app, mut app_event_rx, mut op_rx) = make_test_app_with_channels(); + let (mut app, mut app_event_rx, mut op_rx) = make_test_app_with_channels().await; let conversation_id = ConversationId::new(); let event = SessionConfiguredEvent { @@ -1537,13 +1537,13 @@ mod tests { } } - #[test] - fn session_summary_skip_zero_usage() { + #[tokio::test] + async fn session_summary_skip_zero_usage() { assert!(session_summary(TokenUsage::default(), None).is_none()); } - #[test] - fn session_summary_includes_resume_hint() { + #[tokio::test] + async fn session_summary_includes_resume_hint() { let usage = TokenUsage { input_tokens: 10, output_tokens: 2, diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 55c12a342..5efcbcd3c 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -8,8 +8,7 @@ use codex_common::approval_presets::builtin_approval_presets; use codex_core::AuthManager; use codex_core::CodexAuth; use codex_core::config::Config; -use codex_core::config::ConfigOverrides; -use codex_core::config::ConfigToml; +use codex_core::config::ConfigBuilder; use codex_core::config::Constrained; use codex_core::config::ConstraintError; use codex_core::openai_models::models_manager::ModelsManager; @@ -74,15 +73,14 @@ fn set_windows_sandbox_enabled(enabled: bool) { codex_core::set_windows_sandbox_enabled(enabled); } -fn test_config() -> Config { +async fn test_config() -> Config { // Use base defaults to avoid depending on host state. - - Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - std::env::temp_dir(), - ) - .expect("config") + let codex_home = std::env::temp_dir(); + ConfigBuilder::default() + .codex_home(codex_home.clone()) + .build() + .await + .expect("config") } fn snapshot(percent: f64) -> RateLimitSnapshot { @@ -98,9 +96,9 @@ fn snapshot(percent: f64) -> RateLimitSnapshot { } } -#[test] -fn resumed_initial_messages_render_history() { - let (mut chat, mut rx, _ops) = make_chatwidget_manual(None); +#[tokio::test] +async fn resumed_initial_messages_render_history() { + let (mut chat, mut rx, _ops) = make_chatwidget_manual(None).await; let conversation_id = ConversationId::new(); let rollout_file = NamedTempFile::new().unwrap(); @@ -154,9 +152,9 @@ fn resumed_initial_messages_render_history() { } /// Entering review mode uses the hint provided by the review request. -#[test] -fn entered_review_mode_uses_request_hint() { - let (mut chat, mut rx, _ops) = make_chatwidget_manual(None); +#[tokio::test] +async fn entered_review_mode_uses_request_hint() { + let (mut chat, mut rx, _ops) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "review-start".into(), @@ -175,9 +173,9 @@ fn entered_review_mode_uses_request_hint() { } /// Entering review mode renders the current changes banner when requested. -#[test] -fn entered_review_mode_defaults_to_current_changes_banner() { - let (mut chat, mut rx, _ops) = make_chatwidget_manual(None); +#[tokio::test] +async fn entered_review_mode_defaults_to_current_changes_banner() { + let (mut chat, mut rx, _ops) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "review-start".into(), @@ -194,9 +192,9 @@ fn entered_review_mode_defaults_to_current_changes_banner() { } /// Exiting review restores the pre-review context window indicator. -#[test] -fn review_restores_context_window_indicator() { - let (mut chat, mut rx, _ops) = make_chatwidget_manual(None); +#[tokio::test] +async fn review_restores_context_window_indicator() { + let (mut chat, mut rx, _ops) = make_chatwidget_manual(None).await; let context_window = 13_000; let pre_review_tokens = 12_700; // ~30% remaining after subtracting baseline. @@ -243,9 +241,9 @@ fn review_restores_context_window_indicator() { } /// Receiving a TokenCount event without usage clears the context indicator. -#[test] -fn token_count_none_resets_context_indicator() { - let (mut chat, _rx, _ops) = make_chatwidget_manual(None); +#[tokio::test] +async fn token_count_none_resets_context_indicator() { + let (mut chat, _rx, _ops) = make_chatwidget_manual(None).await; let context_window = 13_000; let pre_compact_tokens = 12_700; @@ -269,9 +267,9 @@ fn token_count_none_resets_context_indicator() { assert_eq!(chat.bottom_pane.context_window_percent(), None); } -#[test] -fn context_indicator_shows_used_tokens_when_window_unknown() { - let (mut chat, _rx, _ops) = make_chatwidget_manual(Some("unknown-model")); +#[tokio::test] +async fn context_indicator_shows_used_tokens_when_window_unknown() { + let (mut chat, _rx, _ops) = make_chatwidget_manual(Some("unknown-model")).await; chat.config.model_context_window = None; let auto_compact_limit = 200_000; @@ -312,7 +310,7 @@ fn context_indicator_shows_used_tokens_when_window_unknown() { async fn helpers_are_available_and_do_not_panic() { let (tx_raw, _rx) = unbounded_channel::(); let tx = AppEventSender::new(tx_raw); - let cfg = test_config(); + let cfg = test_config().await; let resolved_model = ModelsManager::get_model_offline(cfg.model.as_deref()); let model_family = ModelsManager::construct_model_family_offline(&resolved_model, &cfg); let conversation_manager = Arc::new(ConversationManager::with_models_provider( @@ -339,7 +337,7 @@ async fn helpers_are_available_and_do_not_panic() { } // --- Helpers for tests that need direct construction and event draining --- -fn make_chatwidget_manual( +async fn make_chatwidget_manual( model_override: Option<&str>, ) -> ( ChatWidget, @@ -349,7 +347,7 @@ fn make_chatwidget_manual( let (tx_raw, rx) = unbounded_channel::(); let app_event_tx = AppEventSender::new(tx_raw); let (op_tx, op_rx) = unbounded_channel::(); - let mut cfg = test_config(); + let mut cfg = test_config().await; let resolved_model = model_override .map(str::to_owned) .unwrap_or_else(|| ModelsManager::get_model_offline(cfg.model.as_deref())); @@ -418,13 +416,13 @@ fn set_chatgpt_auth(chat: &mut ChatWidget) { chat.models_manager = Arc::new(ModelsManager::new(chat.auth_manager.clone())); } -pub(crate) fn make_chatwidget_manual_with_sender() -> ( +pub(crate) async fn make_chatwidget_manual_with_sender() -> ( ChatWidget, AppEventSender, tokio::sync::mpsc::UnboundedReceiver, tokio::sync::mpsc::UnboundedReceiver, ) { - let (widget, rx, op_rx) = make_chatwidget_manual(None); + let (widget, rx, op_rx) = make_chatwidget_manual(None).await; let app_event_tx = widget.app_event_tx.clone(); (widget, app_event_tx, rx, op_rx) } @@ -471,8 +469,8 @@ fn make_token_info(total_tokens: i64, context_window: i64) -> TokenUsageInfo { } } -#[test] -fn rate_limit_warnings_emit_thresholds() { +#[tokio::test] +async fn rate_limit_warnings_emit_thresholds() { let mut state = RateLimitWarningState::default(); let mut warnings: Vec = Vec::new(); @@ -503,8 +501,8 @@ fn rate_limit_warnings_emit_thresholds() { ); } -#[test] -fn test_rate_limit_warnings_monthly() { +#[tokio::test] +async fn test_rate_limit_warnings_monthly() { let mut state = RateLimitWarningState::default(); let mut warnings: Vec = Vec::new(); @@ -518,9 +516,9 @@ fn test_rate_limit_warnings_monthly() { ); } -#[test] -fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.on_rate_limit_snapshot(Some(RateLimitSnapshot { primary: None, @@ -567,9 +565,9 @@ fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() { ); } -#[test] -fn rate_limit_snapshot_updates_and_retains_plan_type() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn rate_limit_snapshot_updates_and_retains_plan_type() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.on_rate_limit_snapshot(Some(RateLimitSnapshot { primary: Some(RateLimitWindow { @@ -620,9 +618,9 @@ fn rate_limit_snapshot_updates_and_retains_plan_type() { assert_eq!(chat.plan_type, Some(PlanType::Pro)); } -#[test] -fn rate_limit_switch_prompt_skips_when_on_lower_cost_model() { - let (mut chat, _, _) = make_chatwidget_manual(Some(NUDGE_MODEL_SLUG)); +#[tokio::test] +async fn rate_limit_switch_prompt_skips_when_on_lower_cost_model() { + let (mut chat, _, _) = make_chatwidget_manual(Some(NUDGE_MODEL_SLUG)).await; chat.auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); @@ -634,10 +632,10 @@ fn rate_limit_switch_prompt_skips_when_on_lower_cost_model() { )); } -#[test] -fn rate_limit_switch_prompt_shows_once_per_session() { +#[tokio::test] +async fn rate_limit_switch_prompt_shows_once_per_session() { let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); - let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")); + let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")).await; chat.auth_manager = AuthManager::from_auth_for_testing(auth); chat.on_rate_limit_snapshot(Some(snapshot(90.0))); @@ -658,10 +656,10 @@ fn rate_limit_switch_prompt_shows_once_per_session() { )); } -#[test] -fn rate_limit_switch_prompt_respects_hidden_notice() { +#[tokio::test] +async fn rate_limit_switch_prompt_respects_hidden_notice() { let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); - let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")); + let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")).await; chat.auth_manager = AuthManager::from_auth_for_testing(auth); chat.config.notices.hide_rate_limit_model_nudge = Some(true); @@ -673,10 +671,10 @@ fn rate_limit_switch_prompt_respects_hidden_notice() { )); } -#[test] -fn rate_limit_switch_prompt_defers_until_task_complete() { +#[tokio::test] +async fn rate_limit_switch_prompt_defers_until_task_complete() { let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); - let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")); + let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")).await; chat.auth_manager = AuthManager::from_auth_for_testing(auth); chat.bottom_pane.set_task_running(true); @@ -694,9 +692,9 @@ fn rate_limit_switch_prompt_defers_until_task_complete() { )); } -#[test] -fn rate_limit_switch_prompt_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")); +#[tokio::test] +async fn rate_limit_switch_prompt_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await; chat.auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); @@ -709,9 +707,9 @@ fn rate_limit_switch_prompt_popup_snapshot() { // (removed experimental resize snapshot test) -#[test] -fn exec_approval_emits_proposed_command_and_decision_history() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_approval_emits_proposed_command_and_decision_history() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Trigger an exec approval request with a short, single-line command let ev = ExecApprovalRequestEvent { @@ -753,9 +751,9 @@ fn exec_approval_emits_proposed_command_and_decision_history() { ); } -#[test] -fn exec_approval_decision_truncates_multiline_and_long_commands() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_approval_decision_truncates_multiline_and_long_commands() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Multiline command: modal should show full command, history records decision only let ev_multi = ExecApprovalRequestEvent { @@ -936,9 +934,9 @@ fn get_available_model(chat: &ChatWidget, model: &str) -> ModelPreset { .unwrap_or_else(|| panic!("{model} preset not found")) } -#[test] -fn empty_enter_during_task_does_not_queue() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn empty_enter_during_task_does_not_queue() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Simulate running task so submissions would normally be queued. chat.bottom_pane.set_task_running(true); @@ -950,9 +948,9 @@ fn empty_enter_during_task_does_not_queue() { assert!(chat.queued_user_messages.is_empty()); } -#[test] -fn alt_up_edits_most_recent_queued_message() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn alt_up_edits_most_recent_queued_message() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Simulate a running task so messages would normally be queued. chat.bottom_pane.set_task_running(true); @@ -983,9 +981,9 @@ fn alt_up_edits_most_recent_queued_message() { /// Pressing Up to recall the most recent history entry and immediately queuing /// it while a task is running should always enqueue the same text, even when it /// is queued repeatedly. -#[test] -fn enqueueing_history_prompt_multiple_times_is_stable() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn enqueueing_history_prompt_multiple_times_is_stable() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Submit an initial prompt to seed history. chat.bottom_pane.set_composer_text("repeat me".to_string()); @@ -1009,9 +1007,9 @@ fn enqueueing_history_prompt_multiple_times_is_stable() { } } -#[test] -fn streaming_final_answer_keeps_task_running_state() { - let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn streaming_final_answer_keeps_task_running_state() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await; chat.on_task_started(); chat.on_agent_message_delta("Final answer line\n".to_string()); @@ -1039,9 +1037,9 @@ fn streaming_final_answer_keeps_task_running_state() { assert!(chat.bottom_pane.ctrl_c_quit_hint_visible()); } -#[test] -fn ctrl_c_shutdown_ignores_caps_lock() { - let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn ctrl_c_shutdown_ignores_caps_lock() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await; chat.handle_key_event(KeyEvent::new(KeyCode::Char('C'), KeyModifiers::CONTROL)); @@ -1051,9 +1049,9 @@ fn ctrl_c_shutdown_ignores_caps_lock() { } } -#[test] -fn ctrl_c_cleared_prompt_is_recoverable_via_history() { - let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn ctrl_c_cleared_prompt_is_recoverable_via_history() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await; chat.bottom_pane.insert_str("draft message "); chat.bottom_pane @@ -1085,9 +1083,9 @@ fn ctrl_c_cleared_prompt_is_recoverable_via_history() { ); } -#[test] -fn exec_history_cell_shows_working_then_completed() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_history_cell_shows_working_then_completed() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Begin command let begin = begin_exec(&mut chat, "call-1", "echo done"); @@ -1115,9 +1113,9 @@ fn exec_history_cell_shows_working_then_completed() { ); } -#[test] -fn exec_history_cell_shows_working_then_failed() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_history_cell_shows_working_then_failed() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Begin command let begin = begin_exec(&mut chat, "call-2", "false"); @@ -1139,9 +1137,9 @@ fn exec_history_cell_shows_working_then_failed() { assert!(blob.to_lowercase().contains("bloop"), "expected error text"); } -#[test] -fn exec_end_without_begin_uses_event_command() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_end_without_begin_uses_event_command() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let command = vec![ "bash".to_string(), "-lc".to_string(), @@ -1182,9 +1180,9 @@ fn exec_end_without_begin_uses_event_command() { ); } -#[test] -fn exec_history_shows_unified_exec_startup_commands() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_history_shows_unified_exec_startup_commands() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.on_task_started(); let begin = begin_exec_with_source( @@ -1209,9 +1207,9 @@ fn exec_history_shows_unified_exec_startup_commands() { ); } -#[test] -fn exec_history_shows_unified_exec_tool_calls() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_history_shows_unified_exec_tool_calls() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.on_task_started(); let begin = begin_exec_with_source( @@ -1226,9 +1224,9 @@ fn exec_history_shows_unified_exec_tool_calls() { assert_eq!(blob, "• Explored\n └ List ls\n"); } -#[test] -fn unified_exec_end_after_task_complete_is_suppressed() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn unified_exec_end_after_task_complete_is_suppressed() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.on_task_started(); let begin = begin_exec_with_source( @@ -1251,9 +1249,9 @@ fn unified_exec_end_after_task_complete_is_suppressed() { /// Selecting the custom prompt option from the review popup sends /// OpenReviewCustomPrompt to the app event channel. -#[test] -fn review_popup_custom_prompt_action_sends_event() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn review_popup_custom_prompt_action_sends_event() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Open the preset selection popup chat.open_review_popup(); @@ -1276,9 +1274,9 @@ fn review_popup_custom_prompt_action_sends_event() { assert!(found, "expected OpenReviewCustomPrompt event to be sent"); } -#[test] -fn slash_init_skips_when_project_doc_exists() { - let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_init_skips_when_project_doc_exists() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await; let tempdir = tempdir().unwrap(); let existing_path = tempdir.path().join(DEFAULT_PROJECT_DOC_FILENAME); std::fs::write(&existing_path, "existing instructions").unwrap(); @@ -1308,36 +1306,36 @@ fn slash_init_skips_when_project_doc_exists() { ); } -#[test] -fn slash_quit_requests_exit() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_quit_requests_exit() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.dispatch_command(SlashCommand::Quit); assert_matches!(rx.try_recv(), Ok(AppEvent::ExitRequest)); } -#[test] -fn slash_exit_requests_exit() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_exit_requests_exit() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.dispatch_command(SlashCommand::Exit); assert_matches!(rx.try_recv(), Ok(AppEvent::ExitRequest)); } -#[test] -fn slash_resume_opens_picker() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_resume_opens_picker() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.dispatch_command(SlashCommand::Resume); assert_matches!(rx.try_recv(), Ok(AppEvent::OpenResumePicker)); } -#[test] -fn slash_undo_sends_op() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_undo_sends_op() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.dispatch_command(SlashCommand::Undo); @@ -1347,9 +1345,9 @@ fn slash_undo_sends_op() { } } -#[test] -fn slash_rollout_displays_current_path() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_rollout_displays_current_path() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let rollout_path = PathBuf::from("/tmp/codex-test-rollout.jsonl"); chat.current_rollout_path = Some(rollout_path.clone()); @@ -1364,9 +1362,9 @@ fn slash_rollout_displays_current_path() { ); } -#[test] -fn slash_rollout_handles_missing_path() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_rollout_handles_missing_path() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.dispatch_command(SlashCommand::Rollout); @@ -1383,9 +1381,9 @@ fn slash_rollout_handles_missing_path() { ); } -#[test] -fn undo_success_events_render_info_messages() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn undo_success_events_render_info_messages() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "turn-1".to_string(), @@ -1420,9 +1418,9 @@ fn undo_success_events_render_info_messages() { ); } -#[test] -fn undo_failure_events_render_error_message() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn undo_failure_events_render_error_message() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "turn-2".to_string(), @@ -1455,9 +1453,9 @@ fn undo_failure_events_render_error_message() { ); } -#[test] -fn undo_started_hides_interrupt_hint() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn undo_started_hides_interrupt_hint() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "turn-hint".to_string(), @@ -1475,9 +1473,9 @@ fn undo_started_hides_interrupt_hint() { } /// The commit picker shows only commit subjects (no timestamps). -#[test] -fn review_commit_picker_shows_subjects_without_timestamps() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn review_commit_picker_shows_subjects_without_timestamps() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Open the Review presets parent popup. chat.open_review_popup(); @@ -1537,9 +1535,9 @@ fn review_commit_picker_shows_subjects_without_timestamps() { /// Submitting the custom prompt view sends Op::Review with the typed prompt /// and uses the same text for the user-facing hint. -#[test] -fn custom_prompt_submit_sends_review_op() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn custom_prompt_submit_sends_review_op() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.show_review_custom_prompt(); // Paste prompt text via ChatWidget handler, then submit @@ -1565,9 +1563,9 @@ fn custom_prompt_submit_sends_review_op() { } /// Hitting Enter on an empty custom prompt view does not submit. -#[test] -fn custom_prompt_enter_empty_does_not_send() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn custom_prompt_enter_empty_does_not_send() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.show_review_custom_prompt(); // Enter without any text @@ -1577,9 +1575,9 @@ fn custom_prompt_enter_empty_does_not_send() { assert!(rx.try_recv().is_err(), "no app event should be sent"); } -#[test] -fn view_image_tool_call_adds_history_cell() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn view_image_tool_call_adds_history_cell() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let image_path = chat.config.cwd.join("example.png"); chat.handle_codex_event(Event { @@ -1598,9 +1596,9 @@ fn view_image_tool_call_adds_history_cell() { // Snapshot test: interrupting a running exec finalizes the active cell with a red ✗ // marker (replacing the spinner) and flushes it into history. -#[test] -fn interrupt_exec_marks_failed_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn interrupt_exec_marks_failed_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Begin a long-running command so we have an active exec cell with a spinner. begin_exec(&mut chat, "call-int", "sleep 1"); @@ -1627,9 +1625,9 @@ fn interrupt_exec_marks_failed_snapshot() { // Snapshot test: after an interrupted turn, a gentle error message is inserted // suggesting the user to tell the model what to do differently and to use /feedback. -#[test] -fn interrupted_turn_error_message_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn interrupted_turn_error_message_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Simulate an in-progress task so the widget is in a running state. chat.handle_codex_event(Event { @@ -1658,9 +1656,9 @@ fn interrupted_turn_error_message_snapshot() { /// Opening custom prompt from the review popup, pressing Esc returns to the /// parent popup, pressing Esc again dismisses all panels (back to normal mode). -#[test] -fn review_custom_prompt_escape_navigates_back_then_dismisses() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn review_custom_prompt_escape_navigates_back_then_dismisses() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Open the Review presets parent popup. chat.open_review_popup(); @@ -1695,7 +1693,7 @@ fn review_custom_prompt_escape_navigates_back_then_dismisses() { /// parent popup, pressing Esc again dismisses all panels (back to normal mode). #[tokio::test] async fn review_branch_picker_escape_navigates_back_then_dismisses() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Open the Review presets parent popup. chat.open_review_popup(); @@ -1780,9 +1778,9 @@ fn render_bottom_popup(chat: &ChatWidget, width: u16) -> String { lines.join("\n") } -#[test] -fn experimental_features_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn experimental_features_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; let features = vec![ BetaFeatureItem { @@ -1805,9 +1803,9 @@ fn experimental_features_popup_snapshot() { assert_snapshot!("experimental_features_popup", popup); } -#[test] -fn experimental_features_toggle_saves_on_exit() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn experimental_features_toggle_saves_on_exit() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let expected_feature = Feature::GhostCommit; let view = ExperimentalFeaturesView::new( @@ -1845,18 +1843,18 @@ fn experimental_features_toggle_saves_on_exit() { assert_eq!(updates, vec![(expected_feature, true)]); } -#[test] -fn model_selection_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5-codex")); +#[tokio::test] +async fn model_selection_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5-codex")).await; chat.open_model_popup(); let popup = render_bottom_popup(&chat, 80); assert_snapshot!("model_selection_popup", popup); } -#[test] -fn approvals_selection_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn approvals_selection_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.config.notices.hide_full_access_warning = None; chat.open_approvals_popup(); @@ -1870,8 +1868,8 @@ fn approvals_selection_popup_snapshot() { assert_snapshot!("approvals_selection_popup", popup); } -#[test] -fn preset_matching_ignores_extra_writable_roots() { +#[tokio::test] +async fn preset_matching_ignores_extra_writable_roots() { let preset = builtin_approval_presets() .into_iter() .find(|p| p.id == "auto") @@ -1893,9 +1891,9 @@ fn preset_matching_ignores_extra_writable_roots() { ); } -#[test] -fn full_access_confirmation_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn full_access_confirmation_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; let preset = builtin_approval_presets() .into_iter() @@ -1908,9 +1906,9 @@ fn full_access_confirmation_popup_snapshot() { } #[cfg(target_os = "windows")] -#[test] -fn windows_auto_mode_prompt_requests_enabling_sandbox_feature() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn windows_auto_mode_prompt_requests_enabling_sandbox_feature() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; let preset = builtin_approval_presets() .into_iter() @@ -1926,9 +1924,9 @@ fn windows_auto_mode_prompt_requests_enabling_sandbox_feature() { } #[cfg(target_os = "windows")] -#[test] -fn startup_prompts_for_windows_sandbox_when_agent_requested() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn startup_prompts_for_windows_sandbox_when_agent_requested() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; set_windows_sandbox_enabled(false); chat.config.forced_auto_mode_downgraded_on_windows = true; @@ -1948,9 +1946,9 @@ fn startup_prompts_for_windows_sandbox_when_agent_requested() { set_windows_sandbox_enabled(true); } -#[test] -fn model_reasoning_selection_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")); +#[tokio::test] +async fn model_reasoning_selection_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await; set_chatgpt_auth(&mut chat); chat.config.model_reasoning_effort = Some(ReasoningEffortConfig::High); @@ -1962,9 +1960,9 @@ fn model_reasoning_selection_popup_snapshot() { assert_snapshot!("model_reasoning_selection_popup", popup); } -#[test] -fn model_reasoning_selection_popup_extra_high_warning_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")); +#[tokio::test] +async fn model_reasoning_selection_popup_extra_high_warning_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await; set_chatgpt_auth(&mut chat); chat.config.model_reasoning_effort = Some(ReasoningEffortConfig::XHigh); @@ -1976,9 +1974,9 @@ fn model_reasoning_selection_popup_extra_high_warning_snapshot() { assert_snapshot!("model_reasoning_selection_popup_extra_high_warning", popup); } -#[test] -fn reasoning_popup_shows_extra_high_with_space() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")); +#[tokio::test] +async fn reasoning_popup_shows_extra_high_with_space() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await; set_chatgpt_auth(&mut chat); @@ -1996,9 +1994,9 @@ fn reasoning_popup_shows_extra_high_with_space() { ); } -#[test] -fn single_reasoning_option_skips_selection() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn single_reasoning_option_skips_selection() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let single_effort = vec![ReasoningEffortPreset { effort: ReasoningEffortConfig::High, @@ -2037,9 +2035,9 @@ fn single_reasoning_option_skips_selection() { ); } -#[test] -fn feedback_selection_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn feedback_selection_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Open the feedback category selection popup via slash command. chat.dispatch_command(SlashCommand::Feedback); @@ -2048,9 +2046,9 @@ fn feedback_selection_popup_snapshot() { assert_snapshot!("feedback_selection_popup", popup); } -#[test] -fn feedback_upload_consent_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn feedback_upload_consent_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Open the consent popup directly for a chosen category. chat.open_feedback_consent(crate::app_event::FeedbackCategory::Bug); @@ -2059,9 +2057,9 @@ fn feedback_upload_consent_popup_snapshot() { assert_snapshot!("feedback_upload_consent_popup", popup); } -#[test] -fn reasoning_popup_escape_returns_to_model_popup() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")); +#[tokio::test] +async fn reasoning_popup_escape_returns_to_model_popup() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await; chat.open_model_popup(); let preset = get_available_model(&chat, "gpt-5.1-codex-max"); @@ -2077,9 +2075,9 @@ fn reasoning_popup_escape_returns_to_model_popup() { assert!(!after_escape.contains("Select Reasoning Level")); } -#[test] -fn exec_history_extends_previous_when_consecutive() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_history_extends_previous_when_consecutive() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // 1) Start "ls -la" (List) let begin_ls = begin_exec(&mut chat, "call-ls", "ls -la"); @@ -2108,9 +2106,9 @@ fn exec_history_extends_previous_when_consecutive() { assert_snapshot!("exploring_step6_finish_cat_bar", active_blob(&chat)); } -#[test] -fn user_shell_command_renders_output_not_exploring() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn user_shell_command_renders_output_not_exploring() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let begin_ls = begin_exec_with_source( &mut chat, @@ -2130,10 +2128,10 @@ fn user_shell_command_renders_output_not_exploring() { assert_snapshot!("user_shell_ls_output", blob); } -#[test] -fn disabled_slash_command_while_task_running_snapshot() { +#[tokio::test] +async fn disabled_slash_command_while_task_running_snapshot() { // Build a chat widget and simulate an active task - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.bottom_pane.set_task_running(true); // Dispatch a command that is unavailable while a task runs (e.g., /model) @@ -2149,9 +2147,9 @@ fn disabled_slash_command_while_task_running_snapshot() { assert_snapshot!(blob); } -#[test] -fn approvals_popup_shows_disabled_presets() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn approvals_popup_shows_disabled_presets() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.config.approval_policy = Constrained::new(AskForApproval::OnRequest, |candidate| match candidate { @@ -2185,9 +2183,9 @@ fn approvals_popup_shows_disabled_presets() { ); } -#[test] -fn approvals_popup_navigation_skips_disabled() { - let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn approvals_popup_navigation_skips_disabled() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await; chat.config.approval_policy = Constrained::new(AskForApproval::OnRequest, |candidate| match candidate { @@ -2262,10 +2260,10 @@ fn approvals_popup_navigation_skips_disabled() { // // Synthesizes a Codex ExecApprovalRequest event to trigger the approval modal // and snapshots the visual output using the ratatui TestBackend. -#[test] -fn approval_modal_exec_snapshot() -> anyhow::Result<()> { +#[tokio::test] +async fn approval_modal_exec_snapshot() -> anyhow::Result<()> { // Build a chat widget with manual channels to avoid spawning the agent. - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Ensure policy allows surfacing approvals explicitly (not strictly required for direct event). chat.config.approval_policy.set(AskForApproval::OnRequest)?; // Inject an exec approval request to display the approval modal. @@ -2319,9 +2317,9 @@ fn approval_modal_exec_snapshot() -> anyhow::Result<()> { // Snapshot test: command approval modal without a reason // Ensures spacing looks correct when no reason text is provided. -#[test] -fn approval_modal_exec_without_reason_snapshot() -> anyhow::Result<()> { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn approval_modal_exec_without_reason_snapshot() -> anyhow::Result<()> { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.config.approval_policy.set(AskForApproval::OnRequest)?; let ev = ExecApprovalRequestEvent { @@ -2359,9 +2357,9 @@ fn approval_modal_exec_without_reason_snapshot() -> anyhow::Result<()> { } // Snapshot test: patch approval modal -#[test] -fn approval_modal_patch_snapshot() -> anyhow::Result<()> { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn approval_modal_patch_snapshot() -> anyhow::Result<()> { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.config.approval_policy.set(AskForApproval::OnRequest)?; // Build a small changeset and a reason/grant_root to exercise the prompt text. @@ -2400,9 +2398,9 @@ fn approval_modal_patch_snapshot() -> anyhow::Result<()> { Ok(()) } -#[test] -fn interrupt_restores_queued_messages_into_composer() { - let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn interrupt_restores_queued_messages_into_composer() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await; // Simulate a running task to enable queuing of user inputs. chat.bottom_pane.set_task_running(true); @@ -2439,9 +2437,9 @@ fn interrupt_restores_queued_messages_into_composer() { let _ = drain_insert_history(&mut rx); } -#[test] -fn interrupt_prepends_queued_messages_before_existing_composer_text() { - let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn interrupt_prepends_queued_messages_before_existing_composer_text() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await; chat.bottom_pane.set_task_running(true); chat.bottom_pane @@ -2475,11 +2473,11 @@ fn interrupt_prepends_queued_messages_before_existing_composer_text() { // Snapshot test: ChatWidget at very small heights (idle) // Ensures overall layout behaves when terminal height is extremely constrained. -#[test] -fn ui_snapshots_small_heights_idle() { +#[tokio::test] +async fn ui_snapshots_small_heights_idle() { use ratatui::Terminal; use ratatui::backend::TestBackend; - let (chat, _rx, _op_rx) = make_chatwidget_manual(None); + let (chat, _rx, _op_rx) = make_chatwidget_manual(None).await; for h in [1u16, 2, 3] { let name = format!("chat_small_idle_h{h}"); let mut terminal = Terminal::new(TestBackend::new(40, h)).expect("create terminal"); @@ -2492,11 +2490,11 @@ fn ui_snapshots_small_heights_idle() { // Snapshot test: ChatWidget at very small heights (task running) // Validates how status + composer are presented within tight space. -#[test] -fn ui_snapshots_small_heights_task_running() { +#[tokio::test] +async fn ui_snapshots_small_heights_task_running() { use ratatui::Terminal; use ratatui::backend::TestBackend; - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Activate status line chat.handle_codex_event(Event { id: "task-1".into(), @@ -2523,11 +2521,11 @@ fn ui_snapshots_small_heights_task_running() { // Snapshot test: status widget + approval modal active together // The modal takes precedence visually; this captures the layout with a running // task (status indicator active) while an approval request is shown. -#[test] -fn status_widget_and_approval_modal_snapshot() { +#[tokio::test] +async fn status_widget_and_approval_modal_snapshot() { use codex_core::protocol::ExecApprovalRequestEvent; - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Begin a running task so the status indicator would be active. chat.handle_codex_event(Event { id: "task-1".into(), @@ -2577,9 +2575,9 @@ fn status_widget_and_approval_modal_snapshot() { // Snapshot test: status widget active (StatusIndicatorView) // Ensures the VT100 rendering of the status indicator is stable when active. -#[test] -fn status_widget_active_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn status_widget_active_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Activate the status indicator by simulating a task start. chat.handle_codex_event(Event { id: "task-1".into(), @@ -2604,9 +2602,9 @@ fn status_widget_active_snapshot() { assert_snapshot!("status_widget_active", terminal.backend()); } -#[test] -fn mcp_startup_header_booting_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn mcp_startup_header_booting_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.show_welcome_banner = false; chat.handle_codex_event(Event { @@ -2626,9 +2624,9 @@ fn mcp_startup_header_booting_snapshot() { assert_snapshot!("mcp_startup_header_booting", terminal.backend()); } -#[test] -fn background_event_updates_status_header() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn background_event_updates_status_header() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "bg-1".into(), @@ -2642,9 +2640,9 @@ fn background_event_updates_status_header() { assert!(drain_insert_history(&mut rx).is_empty()); } -#[test] -fn apply_patch_events_emit_history_cells() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_events_emit_history_cells() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // 1) Approval request -> proposed patch summary cell let mut changes = HashMap::new(); @@ -2740,9 +2738,9 @@ fn apply_patch_events_emit_history_cells() { ); } -#[test] -fn apply_patch_manual_approval_adjusts_header() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_manual_approval_adjusts_header() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let mut proposed_changes = HashMap::new(); proposed_changes.insert( @@ -2789,9 +2787,9 @@ fn apply_patch_manual_approval_adjusts_header() { ); } -#[test] -fn apply_patch_manual_flow_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_manual_flow_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let mut proposed_changes = HashMap::new(); proposed_changes.insert( @@ -2842,9 +2840,9 @@ fn apply_patch_manual_flow_snapshot() { ); } -#[test] -fn apply_patch_approval_sends_op_with_submission_id() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_approval_sends_op_with_submission_id() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Simulate receiving an approval request with a distinct submission id and call id let mut changes = HashMap::new(); changes.insert( @@ -2881,9 +2879,9 @@ fn apply_patch_approval_sends_op_with_submission_id() { assert!(found, "expected PatchApproval op to be sent"); } -#[test] -fn apply_patch_full_flow_integration_like() { - let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_full_flow_integration_like() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await; // 1) Backend requests approval let mut changes = HashMap::new(); @@ -2959,9 +2957,9 @@ fn apply_patch_full_flow_integration_like() { }); } -#[test] -fn apply_patch_untrusted_shows_approval_modal() -> anyhow::Result<()> { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_untrusted_shows_approval_modal() -> anyhow::Result<()> { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Ensure approval policy is untrusted (OnRequest) chat.config.approval_policy.set(AskForApproval::OnRequest)?; @@ -3006,9 +3004,9 @@ fn apply_patch_untrusted_shows_approval_modal() -> anyhow::Result<()> { Ok(()) } -#[test] -fn apply_patch_request_shows_diff_summary() -> anyhow::Result<()> { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_request_shows_diff_summary() -> anyhow::Result<()> { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Ensure we are in OnRequest so an approval is surfaced chat.config.approval_policy.set(AskForApproval::OnRequest)?; @@ -3074,9 +3072,9 @@ fn apply_patch_request_shows_diff_summary() -> anyhow::Result<()> { Ok(()) } -#[test] -fn plan_update_renders_history_cell() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn plan_update_renders_history_cell() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let update = UpdatePlanArgs { explanation: Some("Adapting plan".to_string()), plan: vec![ @@ -3110,9 +3108,9 @@ fn plan_update_renders_history_cell() { assert!(blob.contains("Write tests")); } -#[test] -fn stream_error_updates_status_indicator() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn stream_error_updates_status_indicator() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.bottom_pane.set_task_running(true); let msg = "Reconnecting... 2/5"; chat.handle_codex_event(Event { @@ -3135,9 +3133,9 @@ fn stream_error_updates_status_indicator() { assert_eq!(status.header(), msg); } -#[test] -fn warning_event_adds_warning_history_cell() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn warning_event_adds_warning_history_cell() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "sub-1".into(), msg: EventMsg::Warning(WarningEvent { @@ -3154,9 +3152,9 @@ fn warning_event_adds_warning_history_cell() { ); } -#[test] -fn stream_recovery_restores_previous_status_header() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn stream_recovery_restores_previous_status_header() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "task".into(), msg: EventMsg::TaskStarted(TaskStartedEvent { @@ -3187,9 +3185,9 @@ fn stream_recovery_restores_previous_status_header() { assert!(chat.retry_status_header.is_none()); } -#[test] -fn multiple_agent_messages_in_single_turn_emit_multiple_headers() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn multiple_agent_messages_in_single_turn_emit_multiple_headers() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Begin turn chat.handle_codex_event(Event { @@ -3241,9 +3239,9 @@ fn multiple_agent_messages_in_single_turn_emit_multiple_headers() { assert!(first_idx < second_idx, "messages out of order: {combined}"); } -#[test] -fn final_reasoning_then_message_without_deltas_are_rendered() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn final_reasoning_then_message_without_deltas_are_rendered() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // No deltas; only final reasoning followed by final message. chat.handle_codex_event(Event { @@ -3268,9 +3266,9 @@ fn final_reasoning_then_message_without_deltas_are_rendered() { assert_snapshot!(combined); } -#[test] -fn deltas_then_same_final_message_are_rendered_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn deltas_then_same_final_message_are_rendered_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Stream some reasoning deltas first. chat.handle_codex_event(Event { @@ -3332,9 +3330,9 @@ fn deltas_then_same_final_message_are_rendered_snapshot() { // Combined visual snapshot using vt100 for history + direct buffer overlay for UI. // This renders the final visual as seen in a terminal: history above, then a blank line, // then the exec block, another blank line, the status line, a blank line, and the composer. -#[test] -fn chatwidget_exec_and_status_layout_vt100_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn chatwidget_exec_and_status_layout_vt100_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "t1".into(), msg: EventMsg::AgentMessage(AgentMessageEvent { message: "I’m going to search the repo for where “Change Approved” is rendered to update that view.".into() }), @@ -3424,9 +3422,9 @@ fn chatwidget_exec_and_status_layout_vt100_snapshot() { } // E2E vt100 snapshot for complex markdown with indented and nested fenced code blocks -#[test] -fn chatwidget_markdown_code_blocks_vt100_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn chatwidget_markdown_code_blocks_vt100_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Simulate a final agent message via streaming deltas instead of a single message @@ -3515,9 +3513,9 @@ printf 'fenced within fenced\n' assert_snapshot!(term.backend().vt100().screen().contents()); } -#[test] -fn chatwidget_tall() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn chatwidget_tall() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "t1".into(), msg: EventMsg::TaskStarted(TaskStartedEvent { diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 1dce96636..db7d12142 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -1682,8 +1682,7 @@ mod tests { use crate::exec_cell::ExecCall; use crate::exec_cell::ExecCell; use codex_core::config::Config; - use codex_core::config::ConfigOverrides; - use codex_core::config::ConfigToml; + use codex_core::config::ConfigBuilder; use codex_core::config::types::McpServerConfig; use codex_core::config::types::McpServerTransportConfig; use codex_core::openai_models::models_manager::ModelsManager; @@ -1700,14 +1699,13 @@ mod tests { use mcp_types::TextContent; use mcp_types::Tool; use mcp_types::ToolInputSchema; - - fn test_config() -> Config { - Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - std::env::temp_dir(), - ) - .expect("config") + async fn test_config() -> Config { + let codex_home = std::env::temp_dir(); + ConfigBuilder::default() + .codex_home(codex_home.clone()) + .build() + .await + .expect("config") } fn render_lines(lines: &[Line<'static>]) -> Vec { @@ -1785,9 +1783,9 @@ mod tests { insta::assert_snapshot!(rendered); } - #[test] - fn mcp_tools_output_masks_sensitive_values() { - let mut config = test_config(); + #[tokio::test] + async fn mcp_tools_output_masks_sensitive_values() { + let mut config = test_config().await; let mut env = HashMap::new(); env.insert("TOKEN".to_string(), "secret".to_string()); let stdio_config = McpServerConfig { @@ -2618,9 +2616,9 @@ mod tests { assert_eq!(rendered, vec!["• Detailed reasoning goes here."]); } - #[test] - fn reasoning_summary_block_respects_config_overrides() { - let mut config = test_config(); + #[tokio::test] + async fn reasoning_summary_block_respects_config_overrides() { + let mut config = test_config().await; config.model = Some("gpt-3.5-turbo".to_string()); config.model_supports_reasoning_summaries = Some(true); config.model_reasoning_summary_format = Some(ReasoningSummaryFormat::Experimental); diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 005446c5f..0a8621341 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -596,21 +596,23 @@ fn should_show_login_screen(login_status: LoginStatus, config: &Config) -> bool #[cfg(test)] mod tests { use super::*; - use codex_core::config::ConfigOverrides; - use codex_core::config::ConfigToml; + use codex_core::config::ConfigBuilder; use codex_core::config::ProjectConfig; use serial_test::serial; use tempfile::TempDir; - #[test] + async fn build_config(temp_dir: &TempDir) -> std::io::Result { + ConfigBuilder::default() + .codex_home(temp_dir.path().to_path_buf()) + .build() + .await + } + + #[tokio::test] #[serial] - fn windows_skips_trust_prompt_without_sandbox() -> std::io::Result<()> { + async fn windows_skips_trust_prompt_without_sandbox() -> std::io::Result<()> { let temp_dir = TempDir::new()?; - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - temp_dir.path().to_path_buf(), - )?; + let mut config = build_config(&temp_dir).await?; config.did_user_set_custom_approval_policy_or_sandbox_mode = false; config.active_project = ProjectConfig { trust_level: None }; config.set_windows_sandbox_globally(false); @@ -629,15 +631,11 @@ mod tests { } Ok(()) } - #[test] + #[tokio::test] #[serial] - fn windows_shows_trust_prompt_with_sandbox() -> std::io::Result<()> { + async fn windows_shows_trust_prompt_with_sandbox() -> std::io::Result<()> { let temp_dir = TempDir::new()?; - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - temp_dir.path().to_path_buf(), - )?; + let mut config = build_config(&temp_dir).await?; config.did_user_set_custom_approval_policy_or_sandbox_mode = false; config.active_project = ProjectConfig { trust_level: None }; config.set_windows_sandbox_globally(true); @@ -656,15 +654,11 @@ mod tests { } Ok(()) } - #[test] - fn untrusted_project_skips_trust_prompt() -> std::io::Result<()> { + #[tokio::test] + async fn untrusted_project_skips_trust_prompt() -> std::io::Result<()> { use codex_protocol::config_types::TrustLevel; let temp_dir = TempDir::new()?; - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - temp_dir.path().to_path_buf(), - )?; + let mut config = build_config(&temp_dir).await?; config.did_user_set_custom_approval_policy_or_sandbox_mode = false; config.active_project = ProjectConfig { trust_level: Some(TrustLevel::Untrusted), diff --git a/codex-rs/tui/src/resume_picker.rs b/codex-rs/tui/src/resume_picker.rs index 7f3665d56..0f55bb5e0 100644 --- a/codex-rs/tui/src/resume_picker.rs +++ b/codex-rs/tui/src/resume_picker.rs @@ -1059,7 +1059,6 @@ mod tests { use crossterm::event::KeyModifiers; use insta::assert_snapshot; use serde_json::json; - use std::future::Future; use std::path::PathBuf; use std::sync::Arc; use std::sync::Mutex; @@ -1106,14 +1105,6 @@ mod tests { } } - fn block_on_future, T>(future: F) -> T { - tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(future) - } - #[test] fn preview_uses_first_message_input_text() { let head = vec![ @@ -1267,8 +1258,8 @@ mod tests { assert_snapshot!("resume_picker_table", snapshot); } - #[test] - fn resume_picker_screen_snapshot() { + #[tokio::test] + async fn resume_picker_screen_snapshot() { use crate::custom_terminal::Terminal; use crate::test_backend::VT100Backend; use uuid::Uuid; @@ -1360,14 +1351,15 @@ mod tests { None, ); - let page = block_on_future(RolloutRecorder::list_conversations( + let page = RolloutRecorder::list_conversations( &state.codex_home, PAGE_SIZE, None, INTERACTIVE_SESSION_SOURCES, Some(&[String::from("openai")]), "openai", - )) + ) + .await .expect("list conversations"); let rows = rows_from_items(page.items); @@ -1526,8 +1518,8 @@ mod tests { assert!(guard[0].search_token.is_none()); } - #[test] - fn page_navigation_uses_view_rows() { + #[tokio::test] + async fn page_navigation_uses_view_rows() { let loader: PageLoader = Arc::new(|_| {}); let mut state = PickerState::new( PathBuf::from("/tmp"), @@ -1551,33 +1543,27 @@ mod tests { state.update_view_rows(5); assert_eq!(state.selected, 0); - block_on_future(async { - state - .handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE)) - .await - .unwrap(); - }); + state + .handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE)) + .await + .unwrap(); assert_eq!(state.selected, 5); - block_on_future(async { - state - .handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE)) - .await - .unwrap(); - }); + state + .handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE)) + .await + .unwrap(); assert_eq!(state.selected, 10); - block_on_future(async { - state - .handle_key(KeyEvent::new(KeyCode::PageUp, KeyModifiers::NONE)) - .await - .unwrap(); - }); + state + .handle_key(KeyEvent::new(KeyCode::PageUp, KeyModifiers::NONE)) + .await + .unwrap(); assert_eq!(state.selected, 5); } - #[test] - fn up_at_bottom_does_not_scroll_when_visible() { + #[tokio::test] + async fn up_at_bottom_does_not_scroll_when_visible() { let loader: PageLoader = Arc::new(|_| {}); let mut state = PickerState::new( PathBuf::from("/tmp"), @@ -1606,12 +1592,10 @@ mod tests { let initial_top = state.scroll_top; assert_eq!(initial_top, state.filtered_rows.len().saturating_sub(5)); - block_on_future(async { - state - .handle_key(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE)) - .await - .unwrap(); - }); + state + .handle_key(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE)) + .await + .unwrap(); assert_eq!(state.scroll_top, initial_top); assert_eq!(state.selected, state.filtered_rows.len().saturating_sub(2)); diff --git a/codex-rs/tui/src/status/tests.rs b/codex-rs/tui/src/status/tests.rs index 53c728526..836c6572e 100644 --- a/codex-rs/tui/src/status/tests.rs +++ b/codex-rs/tui/src/status/tests.rs @@ -6,8 +6,7 @@ use chrono::TimeZone; use chrono::Utc; use codex_core::AuthManager; use codex_core::config::Config; -use codex_core::config::ConfigOverrides; -use codex_core::config::ConfigToml; +use codex_core::config::ConfigBuilder; use codex_core::openai_models::model_family::ModelFamily; use codex_core::openai_models::models_manager::ModelsManager; use codex_core::protocol::CreditsSnapshot; @@ -22,13 +21,12 @@ use ratatui::prelude::*; use std::path::PathBuf; use tempfile::TempDir; -fn test_config(temp_home: &TempDir) -> Config { - Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - temp_home.path().to_path_buf(), - ) - .expect("load config") +async fn test_config(temp_home: &TempDir) -> Config { + ConfigBuilder::default() + .codex_home(temp_home.path().to_path_buf()) + .build() + .await + .expect("load config") } fn test_auth_manager(config: &Config) -> AuthManager { @@ -84,10 +82,10 @@ fn reset_at_from(captured_at: &chrono::DateTime, seconds: i64) -> .timestamp() } -#[test] -fn status_snapshot_includes_reasoning_details() { +#[tokio::test] +async fn status_snapshot_includes_reasoning_details() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.model_provider_id = "openai".to_string(); config.model_reasoning_effort = Some(ReasoningEffort::High); @@ -155,10 +153,10 @@ fn status_snapshot_includes_reasoning_details() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_includes_monthly_limit() { +#[tokio::test] +async fn status_snapshot_includes_monthly_limit() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.model_provider_id = "openai".to_string(); config.cwd = PathBuf::from("/workspace/tests"); @@ -212,10 +210,10 @@ fn status_snapshot_includes_monthly_limit() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_shows_unlimited_credits() { +#[tokio::test] +async fn status_snapshot_shows_unlimited_credits() { let temp_home = TempDir::new().expect("temp home"); - let config = test_config(&temp_home); + let config = test_config(&temp_home).await; let auth_manager = test_auth_manager(&config); let usage = TokenUsage::default(); let captured_at = chrono::Local @@ -256,10 +254,10 @@ fn status_snapshot_shows_unlimited_credits() { ); } -#[test] -fn status_snapshot_shows_positive_credits() { +#[tokio::test] +async fn status_snapshot_shows_positive_credits() { let temp_home = TempDir::new().expect("temp home"); - let config = test_config(&temp_home); + let config = test_config(&temp_home).await; let auth_manager = test_auth_manager(&config); let usage = TokenUsage::default(); let captured_at = chrono::Local @@ -300,10 +298,10 @@ fn status_snapshot_shows_positive_credits() { ); } -#[test] -fn status_snapshot_hides_zero_credits() { +#[tokio::test] +async fn status_snapshot_hides_zero_credits() { let temp_home = TempDir::new().expect("temp home"); - let config = test_config(&temp_home); + let config = test_config(&temp_home).await; let auth_manager = test_auth_manager(&config); let usage = TokenUsage::default(); let captured_at = chrono::Local @@ -342,10 +340,10 @@ fn status_snapshot_hides_zero_credits() { ); } -#[test] -fn status_snapshot_hides_when_has_no_credits_flag() { +#[tokio::test] +async fn status_snapshot_hides_when_has_no_credits_flag() { let temp_home = TempDir::new().expect("temp home"); - let config = test_config(&temp_home); + let config = test_config(&temp_home).await; let auth_manager = test_auth_manager(&config); let usage = TokenUsage::default(); let captured_at = chrono::Local @@ -384,10 +382,10 @@ fn status_snapshot_hides_when_has_no_credits_flag() { ); } -#[test] -fn status_card_token_usage_excludes_cached_tokens() { +#[tokio::test] +async fn status_card_token_usage_excludes_cached_tokens() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.cwd = PathBuf::from("/workspace/tests"); @@ -427,10 +425,10 @@ fn status_card_token_usage_excludes_cached_tokens() { ); } -#[test] -fn status_snapshot_truncates_in_narrow_terminal() { +#[tokio::test] +async fn status_snapshot_truncates_in_narrow_terminal() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.model_provider_id = "openai".to_string(); config.model_reasoning_effort = Some(ReasoningEffort::High); @@ -487,10 +485,10 @@ fn status_snapshot_truncates_in_narrow_terminal() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_shows_missing_limits_message() { +#[tokio::test] +async fn status_snapshot_shows_missing_limits_message() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.cwd = PathBuf::from("/workspace/tests"); @@ -532,10 +530,10 @@ fn status_snapshot_shows_missing_limits_message() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_includes_credits_and_limits() { +#[tokio::test] +async fn status_snapshot_includes_credits_and_limits() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex".to_string()); config.cwd = PathBuf::from("/workspace/tests"); @@ -596,10 +594,10 @@ fn status_snapshot_includes_credits_and_limits() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_shows_empty_limits_message() { +#[tokio::test] +async fn status_snapshot_shows_empty_limits_message() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.cwd = PathBuf::from("/workspace/tests"); @@ -648,10 +646,10 @@ fn status_snapshot_shows_empty_limits_message() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_shows_stale_limits_message() { +#[tokio::test] +async fn status_snapshot_shows_stale_limits_message() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.cwd = PathBuf::from("/workspace/tests"); @@ -709,10 +707,10 @@ fn status_snapshot_shows_stale_limits_message() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_cached_limits_hide_credits_without_flag() { +#[tokio::test] +async fn status_snapshot_cached_limits_hide_credits_without_flag() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex".to_string()); config.cwd = PathBuf::from("/workspace/tests"); @@ -774,10 +772,10 @@ fn status_snapshot_cached_limits_hide_credits_without_flag() { assert_snapshot!(sanitized); } -#[test] -fn status_context_window_uses_last_usage() { +#[tokio::test] +async fn status_context_window_uses_last_usage() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model_context_window = Some(272_000); let auth_manager = test_auth_manager(&config); diff --git a/codex-rs/tui2/src/app.rs b/codex-rs/tui2/src/app.rs index 25b9861ab..a241cc879 100644 --- a/codex-rs/tui2/src/app.rs +++ b/codex-rs/tui2/src/app.rs @@ -2134,8 +2134,8 @@ mod tests { use std::sync::Arc; use std::sync::atomic::AtomicBool; - fn make_test_app() -> App { - let (chat_widget, app_event_tx, _rx, _op_rx) = make_chatwidget_manual_with_sender(); + async fn make_test_app() -> App { + let (chat_widget, app_event_tx, _rx, _op_rx) = make_chatwidget_manual_with_sender().await; let config = chat_widget.config_ref().clone(); let current_model = chat_widget.get_model_family().get_model_slug().to_string(); let server = Arc::new(ConversationManager::with_models_provider( @@ -2173,12 +2173,12 @@ mod tests { } } - fn make_test_app_with_channels() -> ( + async fn make_test_app_with_channels() -> ( App, tokio::sync::mpsc::UnboundedReceiver, tokio::sync::mpsc::UnboundedReceiver, ) { - let (chat_widget, app_event_tx, rx, op_rx) = make_chatwidget_manual_with_sender(); + let (chat_widget, app_event_tx, rx, op_rx) = make_chatwidget_manual_with_sender().await; let config = chat_widget.config_ref().clone(); let current_model = chat_widget.get_model_family().get_model_slug().to_string(); let server = Arc::new(ConversationManager::with_models_provider( @@ -2224,8 +2224,8 @@ mod tests { codex_core::openai_models::model_presets::all_model_presets().clone() } - #[test] - fn model_migration_prompt_only_shows_for_deprecated_models() { + #[tokio::test] + async fn model_migration_prompt_only_shows_for_deprecated_models() { let seen = BTreeMap::new(); assert!(should_show_model_migration_prompt( "gpt-5", @@ -2259,8 +2259,8 @@ mod tests { )); } - #[test] - fn model_migration_prompt_respects_hide_flag_and_self_target() { + #[tokio::test] + async fn model_migration_prompt_respects_hide_flag_and_self_target() { let mut seen = BTreeMap::new(); seen.insert("gpt-5".to_string(), "gpt-5.1".to_string()); assert!(!should_show_model_migration_prompt( @@ -2277,9 +2277,9 @@ mod tests { )); } - #[test] - fn update_reasoning_effort_updates_config() { - let mut app = make_test_app(); + #[tokio::test] + async fn update_reasoning_effort_updates_config() { + let mut app = make_test_app().await; app.config.model_reasoning_effort = Some(ReasoningEffortConfig::Medium); app.chat_widget .set_reasoning_effort(Some(ReasoningEffortConfig::Medium)); @@ -2296,9 +2296,9 @@ mod tests { ); } - #[test] - fn backtrack_selection_with_duplicate_history_targets_unique_turn() { - let mut app = make_test_app(); + #[tokio::test] + async fn backtrack_selection_with_duplicate_history_targets_unique_turn() { + let mut app = make_test_app().await; let user_cell = |text: &str| -> Arc { Arc::new(UserHistoryCell { @@ -2363,12 +2363,12 @@ mod tests { assert_eq!(prefill, "follow-up (edited)"); } - #[test] - fn transcript_selection_moves_with_scroll() { + #[tokio::test] + async fn transcript_selection_moves_with_scroll() { use ratatui::buffer::Buffer; use ratatui::layout::Rect; - let mut app = make_test_app(); + let mut app = make_test_app().await; app.transcript_total_lines = 3; let area = Rect { @@ -2427,7 +2427,7 @@ mod tests { #[tokio::test] async fn new_session_requests_shutdown_for_previous_conversation() { - let (mut app, mut app_event_rx, mut op_rx) = make_test_app_with_channels(); + let (mut app, mut app_event_rx, mut op_rx) = make_test_app_with_channels().await; let conversation_id = ConversationId::new(); let event = SessionConfiguredEvent { @@ -2461,13 +2461,13 @@ mod tests { } } - #[test] - fn session_summary_skip_zero_usage() { + #[tokio::test] + async fn session_summary_skip_zero_usage() { assert!(session_summary(TokenUsage::default(), None).is_none()); } - #[test] - fn render_lines_to_ansi_pads_user_rows_to_full_width() { + #[tokio::test] + async fn render_lines_to_ansi_pads_user_rows_to_full_width() { let line: Line<'static> = Line::from("hi"); let lines = vec![line]; let line_meta = vec![TranscriptLineMeta::CellLine { @@ -2482,8 +2482,8 @@ mod tests { assert!(rendered[0].contains("hi")); } - #[test] - fn session_summary_includes_resume_hint() { + #[tokio::test] + async fn session_summary_includes_resume_hint() { let usage = TokenUsage { input_tokens: 10, output_tokens: 2, diff --git a/codex-rs/tui2/src/chatwidget/tests.rs b/codex-rs/tui2/src/chatwidget/tests.rs index b90cc6e96..fee5a837f 100644 --- a/codex-rs/tui2/src/chatwidget/tests.rs +++ b/codex-rs/tui2/src/chatwidget/tests.rs @@ -8,8 +8,7 @@ use codex_common::approval_presets::builtin_approval_presets; use codex_core::AuthManager; use codex_core::CodexAuth; use codex_core::config::Config; -use codex_core::config::ConfigOverrides; -use codex_core::config::ConfigToml; +use codex_core::config::ConfigBuilder; use codex_core::config::Constrained; use codex_core::openai_models::models_manager::ModelsManager; use codex_core::protocol::AgentMessageDeltaEvent; @@ -73,15 +72,14 @@ fn set_windows_sandbox_enabled(enabled: bool) { codex_core::set_windows_sandbox_enabled(enabled); } -fn test_config() -> Config { +async fn test_config() -> Config { // Use base defaults to avoid depending on host state. - - Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - std::env::temp_dir(), - ) - .expect("config") + let codex_home = std::env::temp_dir(); + ConfigBuilder::default() + .codex_home(codex_home.clone()) + .build() + .await + .expect("config") } fn snapshot(percent: f64) -> RateLimitSnapshot { @@ -97,9 +95,9 @@ fn snapshot(percent: f64) -> RateLimitSnapshot { } } -#[test] -fn resumed_initial_messages_render_history() { - let (mut chat, mut rx, _ops) = make_chatwidget_manual(None); +#[tokio::test] +async fn resumed_initial_messages_render_history() { + let (mut chat, mut rx, _ops) = make_chatwidget_manual(None).await; let conversation_id = ConversationId::new(); let rollout_file = NamedTempFile::new().unwrap(); @@ -153,9 +151,9 @@ fn resumed_initial_messages_render_history() { } /// Entering review mode uses the hint provided by the review request. -#[test] -fn entered_review_mode_uses_request_hint() { - let (mut chat, mut rx, _ops) = make_chatwidget_manual(None); +#[tokio::test] +async fn entered_review_mode_uses_request_hint() { + let (mut chat, mut rx, _ops) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "review-start".into(), @@ -174,9 +172,9 @@ fn entered_review_mode_uses_request_hint() { } /// Entering review mode renders the current changes banner when requested. -#[test] -fn entered_review_mode_defaults_to_current_changes_banner() { - let (mut chat, mut rx, _ops) = make_chatwidget_manual(None); +#[tokio::test] +async fn entered_review_mode_defaults_to_current_changes_banner() { + let (mut chat, mut rx, _ops) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "review-start".into(), @@ -193,9 +191,9 @@ fn entered_review_mode_defaults_to_current_changes_banner() { } /// Exiting review restores the pre-review context window indicator. -#[test] -fn review_restores_context_window_indicator() { - let (mut chat, mut rx, _ops) = make_chatwidget_manual(None); +#[tokio::test] +async fn review_restores_context_window_indicator() { + let (mut chat, mut rx, _ops) = make_chatwidget_manual(None).await; let context_window = 13_000; let pre_review_tokens = 12_700; // ~30% remaining after subtracting baseline. @@ -242,9 +240,9 @@ fn review_restores_context_window_indicator() { } /// Receiving a TokenCount event without usage clears the context indicator. -#[test] -fn token_count_none_resets_context_indicator() { - let (mut chat, _rx, _ops) = make_chatwidget_manual(None); +#[tokio::test] +async fn token_count_none_resets_context_indicator() { + let (mut chat, _rx, _ops) = make_chatwidget_manual(None).await; let context_window = 13_000; let pre_compact_tokens = 12_700; @@ -268,9 +266,9 @@ fn token_count_none_resets_context_indicator() { assert_eq!(chat.bottom_pane.context_window_percent(), None); } -#[test] -fn context_indicator_shows_used_tokens_when_window_unknown() { - let (mut chat, _rx, _ops) = make_chatwidget_manual(Some("unknown-model")); +#[tokio::test] +async fn context_indicator_shows_used_tokens_when_window_unknown() { + let (mut chat, _rx, _ops) = make_chatwidget_manual(Some("unknown-model")).await; chat.config.model_context_window = None; let auto_compact_limit = 200_000; @@ -311,7 +309,7 @@ fn context_indicator_shows_used_tokens_when_window_unknown() { async fn helpers_are_available_and_do_not_panic() { let (tx_raw, _rx) = unbounded_channel::(); let tx = AppEventSender::new(tx_raw); - let cfg = test_config(); + let cfg = test_config().await; let resolved_model = ModelsManager::get_model_offline(cfg.model.as_deref()); let model_family = ModelsManager::construct_model_family_offline(&resolved_model, &cfg); let conversation_manager = Arc::new(ConversationManager::with_models_provider( @@ -338,7 +336,7 @@ async fn helpers_are_available_and_do_not_panic() { } // --- Helpers for tests that need direct construction and event draining --- -fn make_chatwidget_manual( +async fn make_chatwidget_manual( model_override: Option<&str>, ) -> ( ChatWidget, @@ -348,7 +346,7 @@ fn make_chatwidget_manual( let (tx_raw, rx) = unbounded_channel::(); let app_event_tx = AppEventSender::new(tx_raw); let (op_tx, op_rx) = unbounded_channel::(); - let mut cfg = test_config(); + let mut cfg = test_config().await; let resolved_model = model_override .map(str::to_owned) .unwrap_or_else(|| ModelsManager::get_model_offline(cfg.model.as_deref())); @@ -416,13 +414,13 @@ fn set_chatgpt_auth(chat: &mut ChatWidget) { chat.models_manager = Arc::new(ModelsManager::new(chat.auth_manager.clone())); } -pub(crate) fn make_chatwidget_manual_with_sender() -> ( +pub(crate) async fn make_chatwidget_manual_with_sender() -> ( ChatWidget, AppEventSender, tokio::sync::mpsc::UnboundedReceiver, tokio::sync::mpsc::UnboundedReceiver, ) { - let (widget, rx, op_rx) = make_chatwidget_manual(None); + let (widget, rx, op_rx) = make_chatwidget_manual(None).await; let app_event_tx = widget.app_event_tx.clone(); (widget, app_event_tx, rx, op_rx) } @@ -469,8 +467,8 @@ fn make_token_info(total_tokens: i64, context_window: i64) -> TokenUsageInfo { } } -#[test] -fn rate_limit_warnings_emit_thresholds() { +#[tokio::test] +async fn rate_limit_warnings_emit_thresholds() { let mut state = RateLimitWarningState::default(); let mut warnings: Vec = Vec::new(); @@ -501,8 +499,8 @@ fn rate_limit_warnings_emit_thresholds() { ); } -#[test] -fn test_rate_limit_warnings_monthly() { +#[tokio::test] +async fn test_rate_limit_warnings_monthly() { let mut state = RateLimitWarningState::default(); let mut warnings: Vec = Vec::new(); @@ -516,9 +514,9 @@ fn test_rate_limit_warnings_monthly() { ); } -#[test] -fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.on_rate_limit_snapshot(Some(RateLimitSnapshot { primary: None, @@ -565,9 +563,9 @@ fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() { ); } -#[test] -fn rate_limit_snapshot_updates_and_retains_plan_type() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn rate_limit_snapshot_updates_and_retains_plan_type() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.on_rate_limit_snapshot(Some(RateLimitSnapshot { primary: Some(RateLimitWindow { @@ -618,9 +616,9 @@ fn rate_limit_snapshot_updates_and_retains_plan_type() { assert_eq!(chat.plan_type, Some(PlanType::Pro)); } -#[test] -fn rate_limit_switch_prompt_skips_when_on_lower_cost_model() { - let (mut chat, _, _) = make_chatwidget_manual(Some(NUDGE_MODEL_SLUG)); +#[tokio::test] +async fn rate_limit_switch_prompt_skips_when_on_lower_cost_model() { + let (mut chat, _, _) = make_chatwidget_manual(Some(NUDGE_MODEL_SLUG)).await; chat.auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); @@ -632,10 +630,10 @@ fn rate_limit_switch_prompt_skips_when_on_lower_cost_model() { )); } -#[test] -fn rate_limit_switch_prompt_shows_once_per_session() { +#[tokio::test] +async fn rate_limit_switch_prompt_shows_once_per_session() { let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); - let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")); + let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")).await; chat.auth_manager = AuthManager::from_auth_for_testing(auth); chat.on_rate_limit_snapshot(Some(snapshot(90.0))); @@ -656,10 +654,10 @@ fn rate_limit_switch_prompt_shows_once_per_session() { )); } -#[test] -fn rate_limit_switch_prompt_respects_hidden_notice() { +#[tokio::test] +async fn rate_limit_switch_prompt_respects_hidden_notice() { let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); - let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")); + let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")).await; chat.auth_manager = AuthManager::from_auth_for_testing(auth); chat.config.notices.hide_rate_limit_model_nudge = Some(true); @@ -671,10 +669,10 @@ fn rate_limit_switch_prompt_respects_hidden_notice() { )); } -#[test] -fn rate_limit_switch_prompt_defers_until_task_complete() { +#[tokio::test] +async fn rate_limit_switch_prompt_defers_until_task_complete() { let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); - let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")); + let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")).await; chat.auth_manager = AuthManager::from_auth_for_testing(auth); chat.bottom_pane.set_task_running(true); @@ -692,9 +690,9 @@ fn rate_limit_switch_prompt_defers_until_task_complete() { )); } -#[test] -fn rate_limit_switch_prompt_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")); +#[tokio::test] +async fn rate_limit_switch_prompt_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await; chat.auth_manager = AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()); @@ -707,9 +705,9 @@ fn rate_limit_switch_prompt_popup_snapshot() { // (removed experimental resize snapshot test) -#[test] -fn exec_approval_emits_proposed_command_and_decision_history() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_approval_emits_proposed_command_and_decision_history() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Trigger an exec approval request with a short, single-line command let ev = ExecApprovalRequestEvent { @@ -751,9 +749,9 @@ fn exec_approval_emits_proposed_command_and_decision_history() { ); } -#[test] -fn exec_approval_decision_truncates_multiline_and_long_commands() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_approval_decision_truncates_multiline_and_long_commands() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Multiline command: modal should show full command, history records decision only let ev_multi = ExecApprovalRequestEvent { @@ -934,9 +932,9 @@ fn get_available_model(chat: &ChatWidget, model: &str) -> ModelPreset { .unwrap_or_else(|| panic!("{model} preset not found")) } -#[test] -fn empty_enter_during_task_does_not_queue() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn empty_enter_during_task_does_not_queue() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Simulate running task so submissions would normally be queued. chat.bottom_pane.set_task_running(true); @@ -948,9 +946,9 @@ fn empty_enter_during_task_does_not_queue() { assert!(chat.queued_user_messages.is_empty()); } -#[test] -fn alt_up_edits_most_recent_queued_message() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn alt_up_edits_most_recent_queued_message() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Simulate a running task so messages would normally be queued. chat.bottom_pane.set_task_running(true); @@ -981,9 +979,9 @@ fn alt_up_edits_most_recent_queued_message() { /// Pressing Up to recall the most recent history entry and immediately queuing /// it while a task is running should always enqueue the same text, even when it /// is queued repeatedly. -#[test] -fn enqueueing_history_prompt_multiple_times_is_stable() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn enqueueing_history_prompt_multiple_times_is_stable() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Submit an initial prompt to seed history. chat.bottom_pane.set_composer_text("repeat me".to_string()); @@ -1007,9 +1005,9 @@ fn enqueueing_history_prompt_multiple_times_is_stable() { } } -#[test] -fn streaming_final_answer_keeps_task_running_state() { - let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn streaming_final_answer_keeps_task_running_state() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await; chat.on_task_started(); chat.on_agent_message_delta("Final answer line\n".to_string()); @@ -1037,9 +1035,9 @@ fn streaming_final_answer_keeps_task_running_state() { assert!(chat.bottom_pane.ctrl_c_quit_hint_visible()); } -#[test] -fn ctrl_c_shutdown_ignores_caps_lock() { - let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn ctrl_c_shutdown_ignores_caps_lock() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await; chat.handle_key_event(KeyEvent::new(KeyCode::Char('C'), KeyModifiers::CONTROL)); @@ -1049,9 +1047,9 @@ fn ctrl_c_shutdown_ignores_caps_lock() { } } -#[test] -fn ctrl_c_cleared_prompt_is_recoverable_via_history() { - let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn ctrl_c_cleared_prompt_is_recoverable_via_history() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await; chat.bottom_pane.insert_str("draft message "); chat.bottom_pane @@ -1083,9 +1081,9 @@ fn ctrl_c_cleared_prompt_is_recoverable_via_history() { ); } -#[test] -fn exec_history_cell_shows_working_then_completed() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_history_cell_shows_working_then_completed() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Begin command let begin = begin_exec(&mut chat, "call-1", "echo done"); @@ -1113,9 +1111,9 @@ fn exec_history_cell_shows_working_then_completed() { ); } -#[test] -fn exec_history_cell_shows_working_then_failed() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_history_cell_shows_working_then_failed() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Begin command let begin = begin_exec(&mut chat, "call-2", "false"); @@ -1137,9 +1135,9 @@ fn exec_history_cell_shows_working_then_failed() { assert!(blob.to_lowercase().contains("bloop"), "expected error text"); } -#[test] -fn exec_end_without_begin_uses_event_command() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_end_without_begin_uses_event_command() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let command = vec![ "bash".to_string(), "-lc".to_string(), @@ -1180,9 +1178,9 @@ fn exec_end_without_begin_uses_event_command() { ); } -#[test] -fn exec_history_shows_unified_exec_startup_commands() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_history_shows_unified_exec_startup_commands() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let begin = begin_exec_with_source( &mut chat, @@ -1208,9 +1206,9 @@ fn exec_history_shows_unified_exec_startup_commands() { /// Selecting the custom prompt option from the review popup sends /// OpenReviewCustomPrompt to the app event channel. -#[test] -fn review_popup_custom_prompt_action_sends_event() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn review_popup_custom_prompt_action_sends_event() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Open the preset selection popup chat.open_review_popup(); @@ -1233,9 +1231,9 @@ fn review_popup_custom_prompt_action_sends_event() { assert!(found, "expected OpenReviewCustomPrompt event to be sent"); } -#[test] -fn slash_init_skips_when_project_doc_exists() { - let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_init_skips_when_project_doc_exists() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await; let tempdir = tempdir().unwrap(); let existing_path = tempdir.path().join(DEFAULT_PROJECT_DOC_FILENAME); std::fs::write(&existing_path, "existing instructions").unwrap(); @@ -1265,36 +1263,36 @@ fn slash_init_skips_when_project_doc_exists() { ); } -#[test] -fn slash_quit_requests_exit() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_quit_requests_exit() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.dispatch_command(SlashCommand::Quit); assert_matches!(rx.try_recv(), Ok(AppEvent::ExitRequest)); } -#[test] -fn slash_exit_requests_exit() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_exit_requests_exit() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.dispatch_command(SlashCommand::Exit); assert_matches!(rx.try_recv(), Ok(AppEvent::ExitRequest)); } -#[test] -fn slash_resume_opens_picker() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_resume_opens_picker() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.dispatch_command(SlashCommand::Resume); assert_matches!(rx.try_recv(), Ok(AppEvent::OpenResumePicker)); } -#[test] -fn slash_undo_sends_op() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_undo_sends_op() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.dispatch_command(SlashCommand::Undo); @@ -1304,9 +1302,9 @@ fn slash_undo_sends_op() { } } -#[test] -fn slash_rollout_displays_current_path() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_rollout_displays_current_path() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let rollout_path = PathBuf::from("/tmp/codex-test-rollout.jsonl"); chat.current_rollout_path = Some(rollout_path.clone()); @@ -1321,9 +1319,9 @@ fn slash_rollout_displays_current_path() { ); } -#[test] -fn slash_rollout_handles_missing_path() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn slash_rollout_handles_missing_path() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.dispatch_command(SlashCommand::Rollout); @@ -1340,9 +1338,9 @@ fn slash_rollout_handles_missing_path() { ); } -#[test] -fn undo_success_events_render_info_messages() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn undo_success_events_render_info_messages() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "turn-1".to_string(), @@ -1377,9 +1375,9 @@ fn undo_success_events_render_info_messages() { ); } -#[test] -fn undo_failure_events_render_error_message() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn undo_failure_events_render_error_message() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "turn-2".to_string(), @@ -1412,9 +1410,9 @@ fn undo_failure_events_render_error_message() { ); } -#[test] -fn undo_started_hides_interrupt_hint() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn undo_started_hides_interrupt_hint() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "turn-hint".to_string(), @@ -1432,9 +1430,9 @@ fn undo_started_hides_interrupt_hint() { } /// The commit picker shows only commit subjects (no timestamps). -#[test] -fn review_commit_picker_shows_subjects_without_timestamps() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn review_commit_picker_shows_subjects_without_timestamps() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Open the Review presets parent popup. chat.open_review_popup(); @@ -1494,9 +1492,9 @@ fn review_commit_picker_shows_subjects_without_timestamps() { /// Submitting the custom prompt view sends Op::Review with the typed prompt /// and uses the same text for the user-facing hint. -#[test] -fn custom_prompt_submit_sends_review_op() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn custom_prompt_submit_sends_review_op() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.show_review_custom_prompt(); // Paste prompt text via ChatWidget handler, then submit @@ -1522,9 +1520,9 @@ fn custom_prompt_submit_sends_review_op() { } /// Hitting Enter on an empty custom prompt view does not submit. -#[test] -fn custom_prompt_enter_empty_does_not_send() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn custom_prompt_enter_empty_does_not_send() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.show_review_custom_prompt(); // Enter without any text @@ -1534,9 +1532,9 @@ fn custom_prompt_enter_empty_does_not_send() { assert!(rx.try_recv().is_err(), "no app event should be sent"); } -#[test] -fn view_image_tool_call_adds_history_cell() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn view_image_tool_call_adds_history_cell() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let image_path = chat.config.cwd.join("example.png"); chat.handle_codex_event(Event { @@ -1555,9 +1553,9 @@ fn view_image_tool_call_adds_history_cell() { // Snapshot test: interrupting a running exec finalizes the active cell with a red ✗ // marker (replacing the spinner) and flushes it into history. -#[test] -fn interrupt_exec_marks_failed_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn interrupt_exec_marks_failed_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Begin a long-running command so we have an active exec cell with a spinner. begin_exec(&mut chat, "call-int", "sleep 1"); @@ -1584,9 +1582,9 @@ fn interrupt_exec_marks_failed_snapshot() { // Snapshot test: after an interrupted turn, a gentle error message is inserted // suggesting the user to tell the model what to do differently and to use /feedback. -#[test] -fn interrupted_turn_error_message_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn interrupted_turn_error_message_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Simulate an in-progress task so the widget is in a running state. chat.handle_codex_event(Event { @@ -1615,9 +1613,9 @@ fn interrupted_turn_error_message_snapshot() { /// Opening custom prompt from the review popup, pressing Esc returns to the /// parent popup, pressing Esc again dismisses all panels (back to normal mode). -#[test] -fn review_custom_prompt_escape_navigates_back_then_dismisses() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn review_custom_prompt_escape_navigates_back_then_dismisses() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Open the Review presets parent popup. chat.open_review_popup(); @@ -1652,7 +1650,7 @@ fn review_custom_prompt_escape_navigates_back_then_dismisses() { /// parent popup, pressing Esc again dismisses all panels (back to normal mode). #[tokio::test] async fn review_branch_picker_escape_navigates_back_then_dismisses() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Open the Review presets parent popup. chat.open_review_popup(); @@ -1737,18 +1735,18 @@ fn render_bottom_popup(chat: &ChatWidget, width: u16) -> String { lines.join("\n") } -#[test] -fn model_selection_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5-codex")); +#[tokio::test] +async fn model_selection_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5-codex")).await; chat.open_model_popup(); let popup = render_bottom_popup(&chat, 80); assert_snapshot!("model_selection_popup", popup); } -#[test] -fn approvals_selection_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn approvals_selection_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.config.notices.hide_full_access_warning = None; chat.open_approvals_popup(); @@ -1762,8 +1760,8 @@ fn approvals_selection_popup_snapshot() { assert_snapshot!("approvals_selection_popup", popup); } -#[test] -fn preset_matching_ignores_extra_writable_roots() { +#[tokio::test] +async fn preset_matching_ignores_extra_writable_roots() { let preset = builtin_approval_presets() .into_iter() .find(|p| p.id == "auto") @@ -1785,9 +1783,9 @@ fn preset_matching_ignores_extra_writable_roots() { ); } -#[test] -fn full_access_confirmation_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn full_access_confirmation_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; let preset = builtin_approval_presets() .into_iter() @@ -1800,9 +1798,9 @@ fn full_access_confirmation_popup_snapshot() { } #[cfg(target_os = "windows")] -#[test] -fn windows_auto_mode_prompt_requests_enabling_sandbox_feature() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn windows_auto_mode_prompt_requests_enabling_sandbox_feature() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; let preset = builtin_approval_presets() .into_iter() @@ -1818,9 +1816,9 @@ fn windows_auto_mode_prompt_requests_enabling_sandbox_feature() { } #[cfg(target_os = "windows")] -#[test] -fn startup_prompts_for_windows_sandbox_when_agent_requested() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn startup_prompts_for_windows_sandbox_when_agent_requested() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; set_windows_sandbox_enabled(false); chat.config.forced_auto_mode_downgraded_on_windows = true; @@ -1840,9 +1838,9 @@ fn startup_prompts_for_windows_sandbox_when_agent_requested() { set_windows_sandbox_enabled(true); } -#[test] -fn model_reasoning_selection_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")); +#[tokio::test] +async fn model_reasoning_selection_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await; set_chatgpt_auth(&mut chat); chat.config.model_reasoning_effort = Some(ReasoningEffortConfig::High); @@ -1854,9 +1852,9 @@ fn model_reasoning_selection_popup_snapshot() { assert_snapshot!("model_reasoning_selection_popup", popup); } -#[test] -fn model_reasoning_selection_popup_extra_high_warning_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")); +#[tokio::test] +async fn model_reasoning_selection_popup_extra_high_warning_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await; set_chatgpt_auth(&mut chat); chat.config.model_reasoning_effort = Some(ReasoningEffortConfig::XHigh); @@ -1868,9 +1866,9 @@ fn model_reasoning_selection_popup_extra_high_warning_snapshot() { assert_snapshot!("model_reasoning_selection_popup_extra_high_warning", popup); } -#[test] -fn reasoning_popup_shows_extra_high_with_space() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")); +#[tokio::test] +async fn reasoning_popup_shows_extra_high_with_space() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await; set_chatgpt_auth(&mut chat); @@ -1888,9 +1886,9 @@ fn reasoning_popup_shows_extra_high_with_space() { ); } -#[test] -fn single_reasoning_option_skips_selection() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn single_reasoning_option_skips_selection() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let single_effort = vec![ReasoningEffortPreset { effort: ReasoningEffortConfig::High, @@ -1929,9 +1927,9 @@ fn single_reasoning_option_skips_selection() { ); } -#[test] -fn feedback_selection_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn feedback_selection_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Open the feedback category selection popup via slash command. chat.dispatch_command(SlashCommand::Feedback); @@ -1940,9 +1938,9 @@ fn feedback_selection_popup_snapshot() { assert_snapshot!("feedback_selection_popup", popup); } -#[test] -fn feedback_upload_consent_popup_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn feedback_upload_consent_popup_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Open the consent popup directly for a chosen category. chat.open_feedback_consent(crate::app_event::FeedbackCategory::Bug); @@ -1951,9 +1949,9 @@ fn feedback_upload_consent_popup_snapshot() { assert_snapshot!("feedback_upload_consent_popup", popup); } -#[test] -fn reasoning_popup_escape_returns_to_model_popup() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")); +#[tokio::test] +async fn reasoning_popup_escape_returns_to_model_popup() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await; chat.open_model_popup(); let preset = get_available_model(&chat, "gpt-5.1-codex-max"); @@ -1969,9 +1967,9 @@ fn reasoning_popup_escape_returns_to_model_popup() { assert!(!after_escape.contains("Select Reasoning Level")); } -#[test] -fn exec_history_extends_previous_when_consecutive() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn exec_history_extends_previous_when_consecutive() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // 1) Start "ls -la" (List) let begin_ls = begin_exec(&mut chat, "call-ls", "ls -la"); @@ -2000,9 +1998,9 @@ fn exec_history_extends_previous_when_consecutive() { assert_snapshot!("exploring_step6_finish_cat_bar", active_blob(&chat)); } -#[test] -fn user_shell_command_renders_output_not_exploring() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn user_shell_command_renders_output_not_exploring() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let begin_ls = begin_exec_with_source( &mut chat, @@ -2022,10 +2020,10 @@ fn user_shell_command_renders_output_not_exploring() { assert_snapshot!("user_shell_ls_output", blob); } -#[test] -fn disabled_slash_command_while_task_running_snapshot() { +#[tokio::test] +async fn disabled_slash_command_while_task_running_snapshot() { // Build a chat widget and simulate an active task - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.bottom_pane.set_task_running(true); // Dispatch a command that is unavailable while a task runs (e.g., /model) @@ -2046,10 +2044,10 @@ fn disabled_slash_command_while_task_running_snapshot() { // // Synthesizes a Codex ExecApprovalRequest event to trigger the approval modal // and snapshots the visual output using the ratatui TestBackend. -#[test] -fn approval_modal_exec_snapshot() { +#[tokio::test] +async fn approval_modal_exec_snapshot() { // Build a chat widget with manual channels to avoid spawning the agent. - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Ensure policy allows surfacing approvals explicitly (not strictly required for direct event). chat.config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest); // Inject an exec approval request to display the approval modal. @@ -2101,9 +2099,9 @@ fn approval_modal_exec_snapshot() { // Snapshot test: command approval modal without a reason // Ensures spacing looks correct when no reason text is provided. -#[test] -fn approval_modal_exec_without_reason_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn approval_modal_exec_without_reason_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest); let ev = ExecApprovalRequestEvent { @@ -2139,9 +2137,9 @@ fn approval_modal_exec_without_reason_snapshot() { } // Snapshot test: patch approval modal -#[test] -fn approval_modal_patch_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn approval_modal_patch_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest); // Build a small changeset and a reason/grant_root to exercise the prompt text. @@ -2178,9 +2176,9 @@ fn approval_modal_patch_snapshot() { ); } -#[test] -fn interrupt_restores_queued_messages_into_composer() { - let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn interrupt_restores_queued_messages_into_composer() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await; // Simulate a running task to enable queuing of user inputs. chat.bottom_pane.set_task_running(true); @@ -2217,9 +2215,9 @@ fn interrupt_restores_queued_messages_into_composer() { let _ = drain_insert_history(&mut rx); } -#[test] -fn interrupt_prepends_queued_messages_before_existing_composer_text() { - let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn interrupt_prepends_queued_messages_before_existing_composer_text() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await; chat.bottom_pane.set_task_running(true); chat.bottom_pane @@ -2253,11 +2251,11 @@ fn interrupt_prepends_queued_messages_before_existing_composer_text() { // Snapshot test: ChatWidget at very small heights (idle) // Ensures overall layout behaves when terminal height is extremely constrained. -#[test] -fn ui_snapshots_small_heights_idle() { +#[tokio::test] +async fn ui_snapshots_small_heights_idle() { use ratatui::Terminal; use ratatui::backend::TestBackend; - let (chat, _rx, _op_rx) = make_chatwidget_manual(None); + let (chat, _rx, _op_rx) = make_chatwidget_manual(None).await; for h in [1u16, 2, 3] { let name = format!("chat_small_idle_h{h}"); let mut terminal = Terminal::new(TestBackend::new(40, h)).expect("create terminal"); @@ -2270,11 +2268,11 @@ fn ui_snapshots_small_heights_idle() { // Snapshot test: ChatWidget at very small heights (task running) // Validates how status + composer are presented within tight space. -#[test] -fn ui_snapshots_small_heights_task_running() { +#[tokio::test] +async fn ui_snapshots_small_heights_task_running() { use ratatui::Terminal; use ratatui::backend::TestBackend; - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Activate status line chat.handle_codex_event(Event { id: "task-1".into(), @@ -2301,11 +2299,11 @@ fn ui_snapshots_small_heights_task_running() { // Snapshot test: status widget + approval modal active together // The modal takes precedence visually; this captures the layout with a running // task (status indicator active) while an approval request is shown. -#[test] -fn status_widget_and_approval_modal_snapshot() { +#[tokio::test] +async fn status_widget_and_approval_modal_snapshot() { use codex_core::protocol::ExecApprovalRequestEvent; - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Begin a running task so the status indicator would be active. chat.handle_codex_event(Event { id: "task-1".into(), @@ -2355,9 +2353,9 @@ fn status_widget_and_approval_modal_snapshot() { // Snapshot test: status widget active (StatusIndicatorView) // Ensures the VT100 rendering of the status indicator is stable when active. -#[test] -fn status_widget_active_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn status_widget_active_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Activate the status indicator by simulating a task start. chat.handle_codex_event(Event { id: "task-1".into(), @@ -2382,9 +2380,9 @@ fn status_widget_active_snapshot() { assert_snapshot!("status_widget_active", terminal.backend()); } -#[test] -fn mcp_startup_header_booting_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn mcp_startup_header_booting_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.show_welcome_banner = false; chat.handle_codex_event(Event { @@ -2404,9 +2402,9 @@ fn mcp_startup_header_booting_snapshot() { assert_snapshot!("mcp_startup_header_booting", terminal.backend()); } -#[test] -fn background_event_updates_status_header() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn background_event_updates_status_header() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "bg-1".into(), @@ -2420,9 +2418,9 @@ fn background_event_updates_status_header() { assert!(drain_insert_history(&mut rx).is_empty()); } -#[test] -fn apply_patch_events_emit_history_cells() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_events_emit_history_cells() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // 1) Approval request -> proposed patch summary cell let mut changes = HashMap::new(); @@ -2518,9 +2516,9 @@ fn apply_patch_events_emit_history_cells() { ); } -#[test] -fn apply_patch_manual_approval_adjusts_header() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_manual_approval_adjusts_header() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let mut proposed_changes = HashMap::new(); proposed_changes.insert( @@ -2567,9 +2565,9 @@ fn apply_patch_manual_approval_adjusts_header() { ); } -#[test] -fn apply_patch_manual_flow_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_manual_flow_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let mut proposed_changes = HashMap::new(); proposed_changes.insert( @@ -2620,9 +2618,9 @@ fn apply_patch_manual_flow_snapshot() { ); } -#[test] -fn apply_patch_approval_sends_op_with_submission_id() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_approval_sends_op_with_submission_id() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Simulate receiving an approval request with a distinct submission id and call id let mut changes = HashMap::new(); changes.insert( @@ -2659,9 +2657,9 @@ fn apply_patch_approval_sends_op_with_submission_id() { assert!(found, "expected PatchApproval op to be sent"); } -#[test] -fn apply_patch_full_flow_integration_like() { - let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_full_flow_integration_like() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await; // 1) Backend requests approval let mut changes = HashMap::new(); @@ -2737,9 +2735,9 @@ fn apply_patch_full_flow_integration_like() { }); } -#[test] -fn apply_patch_untrusted_shows_approval_modal() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_untrusted_shows_approval_modal() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; // Ensure approval policy is untrusted (OnRequest) chat.config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest); @@ -2782,9 +2780,9 @@ fn apply_patch_untrusted_shows_approval_modal() { ); } -#[test] -fn apply_patch_request_shows_diff_summary() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn apply_patch_request_shows_diff_summary() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Ensure we are in OnRequest so an approval is surfaced chat.config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest); @@ -2848,9 +2846,9 @@ fn apply_patch_request_shows_diff_summary() { ); } -#[test] -fn plan_update_renders_history_cell() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn plan_update_renders_history_cell() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; let update = UpdatePlanArgs { explanation: Some("Adapting plan".to_string()), plan: vec![ @@ -2884,9 +2882,9 @@ fn plan_update_renders_history_cell() { assert!(blob.contains("Write tests")); } -#[test] -fn stream_error_updates_status_indicator() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn stream_error_updates_status_indicator() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.bottom_pane.set_task_running(true); let msg = "Reconnecting... 2/5"; chat.handle_codex_event(Event { @@ -2909,9 +2907,9 @@ fn stream_error_updates_status_indicator() { assert_eq!(status.header(), msg); } -#[test] -fn warning_event_adds_warning_history_cell() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn warning_event_adds_warning_history_cell() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "sub-1".into(), msg: EventMsg::Warning(WarningEvent { @@ -2928,9 +2926,9 @@ fn warning_event_adds_warning_history_cell() { ); } -#[test] -fn stream_recovery_restores_previous_status_header() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn stream_recovery_restores_previous_status_header() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "task".into(), msg: EventMsg::TaskStarted(TaskStartedEvent { @@ -2961,9 +2959,9 @@ fn stream_recovery_restores_previous_status_header() { assert!(chat.retry_status_header.is_none()); } -#[test] -fn multiple_agent_messages_in_single_turn_emit_multiple_headers() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn multiple_agent_messages_in_single_turn_emit_multiple_headers() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Begin turn chat.handle_codex_event(Event { @@ -3015,9 +3013,9 @@ fn multiple_agent_messages_in_single_turn_emit_multiple_headers() { assert!(first_idx < second_idx, "messages out of order: {combined}"); } -#[test] -fn final_reasoning_then_message_without_deltas_are_rendered() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn final_reasoning_then_message_without_deltas_are_rendered() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // No deltas; only final reasoning followed by final message. chat.handle_codex_event(Event { @@ -3042,9 +3040,9 @@ fn final_reasoning_then_message_without_deltas_are_rendered() { assert_snapshot!(combined); } -#[test] -fn deltas_then_same_final_message_are_rendered_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn deltas_then_same_final_message_are_rendered_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Stream some reasoning deltas first. chat.handle_codex_event(Event { @@ -3106,9 +3104,9 @@ fn deltas_then_same_final_message_are_rendered_snapshot() { // Combined visual snapshot using vt100 for history + direct buffer overlay for UI. // This renders the final visual as seen in a terminal: history above, then a blank line, // then the exec block, another blank line, the status line, a blank line, and the composer. -#[test] -fn chatwidget_exec_and_status_layout_vt100_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn chatwidget_exec_and_status_layout_vt100_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "t1".into(), msg: EventMsg::AgentMessage(AgentMessageEvent { message: "I’m going to search the repo for where “Change Approved” is rendered to update that view.".into() }), @@ -3198,9 +3196,9 @@ fn chatwidget_exec_and_status_layout_vt100_snapshot() { } // E2E vt100 snapshot for complex markdown with indented and nested fenced code blocks -#[test] -fn chatwidget_markdown_code_blocks_vt100_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn chatwidget_markdown_code_blocks_vt100_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; // Simulate a final agent message via streaming deltas instead of a single message @@ -3289,9 +3287,9 @@ printf 'fenced within fenced\n' assert_snapshot!(term.backend().vt100().screen().contents()); } -#[test] -fn chatwidget_tall() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None); +#[tokio::test] +async fn chatwidget_tall() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { id: "t1".into(), msg: EventMsg::TaskStarted(TaskStartedEvent { diff --git a/codex-rs/tui2/src/history_cell.rs b/codex-rs/tui2/src/history_cell.rs index f21d56b5c..df4144821 100644 --- a/codex-rs/tui2/src/history_cell.rs +++ b/codex-rs/tui2/src/history_cell.rs @@ -1514,8 +1514,7 @@ mod tests { use crate::exec_cell::ExecCall; use crate::exec_cell::ExecCell; use codex_core::config::Config; - use codex_core::config::ConfigOverrides; - use codex_core::config::ConfigToml; + use codex_core::config::ConfigBuilder; use codex_core::config::types::McpServerConfig; use codex_core::config::types::McpServerTransportConfig; use codex_core::openai_models::models_manager::ModelsManager; @@ -1532,14 +1531,13 @@ mod tests { use mcp_types::TextContent; use mcp_types::Tool; use mcp_types::ToolInputSchema; - - fn test_config() -> Config { - Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - std::env::temp_dir(), - ) - .expect("config") + async fn test_config() -> Config { + let codex_home = std::env::temp_dir(); + ConfigBuilder::default() + .codex_home(codex_home.clone()) + .build() + .await + .expect("config") } fn render_lines(lines: &[Line<'static>]) -> Vec { @@ -1558,9 +1556,9 @@ mod tests { render_lines(&cell.transcript_lines(u16::MAX)) } - #[test] - fn mcp_tools_output_masks_sensitive_values() { - let mut config = test_config(); + #[tokio::test] + async fn mcp_tools_output_masks_sensitive_values() { + let mut config = test_config().await; let mut env = HashMap::new(); env.insert("TOKEN".to_string(), "secret".to_string()); let stdio_config = McpServerConfig { @@ -2391,9 +2389,9 @@ mod tests { assert_eq!(rendered, vec!["• Detailed reasoning goes here."]); } - #[test] - fn reasoning_summary_block_respects_config_overrides() { - let mut config = test_config(); + #[tokio::test] + async fn reasoning_summary_block_respects_config_overrides() { + let mut config = test_config().await; config.model = Some("gpt-3.5-turbo".to_string()); config.model_supports_reasoning_summaries = Some(true); config.model_reasoning_summary_format = Some(ReasoningSummaryFormat::Experimental); diff --git a/codex-rs/tui2/src/lib.rs b/codex-rs/tui2/src/lib.rs index cf3b2289a..e05a17721 100644 --- a/codex-rs/tui2/src/lib.rs +++ b/codex-rs/tui2/src/lib.rs @@ -625,21 +625,23 @@ fn should_show_login_screen(login_status: LoginStatus, config: &Config) -> bool #[cfg(test)] mod tests { use super::*; - use codex_core::config::ConfigOverrides; - use codex_core::config::ConfigToml; + use codex_core::config::ConfigBuilder; use codex_core::config::ProjectConfig; use serial_test::serial; use tempfile::TempDir; - #[test] + async fn build_config(temp_dir: &TempDir) -> std::io::Result { + ConfigBuilder::default() + .codex_home(temp_dir.path().to_path_buf()) + .build() + .await + } + + #[tokio::test] #[serial] - fn windows_skips_trust_prompt_without_sandbox() -> std::io::Result<()> { + async fn windows_skips_trust_prompt_without_sandbox() -> std::io::Result<()> { let temp_dir = TempDir::new()?; - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - temp_dir.path().to_path_buf(), - )?; + let mut config = build_config(&temp_dir).await?; config.did_user_set_custom_approval_policy_or_sandbox_mode = false; config.active_project = ProjectConfig { trust_level: None }; config.set_windows_sandbox_globally(false); @@ -658,15 +660,11 @@ mod tests { } Ok(()) } - #[test] + #[tokio::test] #[serial] - fn windows_shows_trust_prompt_with_sandbox() -> std::io::Result<()> { + async fn windows_shows_trust_prompt_with_sandbox() -> std::io::Result<()> { let temp_dir = TempDir::new()?; - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - temp_dir.path().to_path_buf(), - )?; + let mut config = build_config(&temp_dir).await?; config.did_user_set_custom_approval_policy_or_sandbox_mode = false; config.active_project = ProjectConfig { trust_level: None }; config.set_windows_sandbox_globally(true); @@ -685,15 +683,11 @@ mod tests { } Ok(()) } - #[test] - fn untrusted_project_skips_trust_prompt() -> std::io::Result<()> { + #[tokio::test] + async fn untrusted_project_skips_trust_prompt() -> std::io::Result<()> { use codex_protocol::config_types::TrustLevel; let temp_dir = TempDir::new()?; - let mut config = Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - temp_dir.path().to_path_buf(), - )?; + let mut config = build_config(&temp_dir).await?; config.did_user_set_custom_approval_policy_or_sandbox_mode = false; config.active_project = ProjectConfig { trust_level: Some(TrustLevel::Untrusted), diff --git a/codex-rs/tui2/src/resume_picker.rs b/codex-rs/tui2/src/resume_picker.rs index 7f3665d56..0f55bb5e0 100644 --- a/codex-rs/tui2/src/resume_picker.rs +++ b/codex-rs/tui2/src/resume_picker.rs @@ -1059,7 +1059,6 @@ mod tests { use crossterm::event::KeyModifiers; use insta::assert_snapshot; use serde_json::json; - use std::future::Future; use std::path::PathBuf; use std::sync::Arc; use std::sync::Mutex; @@ -1106,14 +1105,6 @@ mod tests { } } - fn block_on_future, T>(future: F) -> T { - tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(future) - } - #[test] fn preview_uses_first_message_input_text() { let head = vec![ @@ -1267,8 +1258,8 @@ mod tests { assert_snapshot!("resume_picker_table", snapshot); } - #[test] - fn resume_picker_screen_snapshot() { + #[tokio::test] + async fn resume_picker_screen_snapshot() { use crate::custom_terminal::Terminal; use crate::test_backend::VT100Backend; use uuid::Uuid; @@ -1360,14 +1351,15 @@ mod tests { None, ); - let page = block_on_future(RolloutRecorder::list_conversations( + let page = RolloutRecorder::list_conversations( &state.codex_home, PAGE_SIZE, None, INTERACTIVE_SESSION_SOURCES, Some(&[String::from("openai")]), "openai", - )) + ) + .await .expect("list conversations"); let rows = rows_from_items(page.items); @@ -1526,8 +1518,8 @@ mod tests { assert!(guard[0].search_token.is_none()); } - #[test] - fn page_navigation_uses_view_rows() { + #[tokio::test] + async fn page_navigation_uses_view_rows() { let loader: PageLoader = Arc::new(|_| {}); let mut state = PickerState::new( PathBuf::from("/tmp"), @@ -1551,33 +1543,27 @@ mod tests { state.update_view_rows(5); assert_eq!(state.selected, 0); - block_on_future(async { - state - .handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE)) - .await - .unwrap(); - }); + state + .handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE)) + .await + .unwrap(); assert_eq!(state.selected, 5); - block_on_future(async { - state - .handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE)) - .await - .unwrap(); - }); + state + .handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE)) + .await + .unwrap(); assert_eq!(state.selected, 10); - block_on_future(async { - state - .handle_key(KeyEvent::new(KeyCode::PageUp, KeyModifiers::NONE)) - .await - .unwrap(); - }); + state + .handle_key(KeyEvent::new(KeyCode::PageUp, KeyModifiers::NONE)) + .await + .unwrap(); assert_eq!(state.selected, 5); } - #[test] - fn up_at_bottom_does_not_scroll_when_visible() { + #[tokio::test] + async fn up_at_bottom_does_not_scroll_when_visible() { let loader: PageLoader = Arc::new(|_| {}); let mut state = PickerState::new( PathBuf::from("/tmp"), @@ -1606,12 +1592,10 @@ mod tests { let initial_top = state.scroll_top; assert_eq!(initial_top, state.filtered_rows.len().saturating_sub(5)); - block_on_future(async { - state - .handle_key(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE)) - .await - .unwrap(); - }); + state + .handle_key(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE)) + .await + .unwrap(); assert_eq!(state.scroll_top, initial_top); assert_eq!(state.selected, state.filtered_rows.len().saturating_sub(2)); diff --git a/codex-rs/tui2/src/status/tests.rs b/codex-rs/tui2/src/status/tests.rs index 53c728526..836c6572e 100644 --- a/codex-rs/tui2/src/status/tests.rs +++ b/codex-rs/tui2/src/status/tests.rs @@ -6,8 +6,7 @@ use chrono::TimeZone; use chrono::Utc; use codex_core::AuthManager; use codex_core::config::Config; -use codex_core::config::ConfigOverrides; -use codex_core::config::ConfigToml; +use codex_core::config::ConfigBuilder; use codex_core::openai_models::model_family::ModelFamily; use codex_core::openai_models::models_manager::ModelsManager; use codex_core::protocol::CreditsSnapshot; @@ -22,13 +21,12 @@ use ratatui::prelude::*; use std::path::PathBuf; use tempfile::TempDir; -fn test_config(temp_home: &TempDir) -> Config { - Config::load_from_base_config_with_overrides( - ConfigToml::default(), - ConfigOverrides::default(), - temp_home.path().to_path_buf(), - ) - .expect("load config") +async fn test_config(temp_home: &TempDir) -> Config { + ConfigBuilder::default() + .codex_home(temp_home.path().to_path_buf()) + .build() + .await + .expect("load config") } fn test_auth_manager(config: &Config) -> AuthManager { @@ -84,10 +82,10 @@ fn reset_at_from(captured_at: &chrono::DateTime, seconds: i64) -> .timestamp() } -#[test] -fn status_snapshot_includes_reasoning_details() { +#[tokio::test] +async fn status_snapshot_includes_reasoning_details() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.model_provider_id = "openai".to_string(); config.model_reasoning_effort = Some(ReasoningEffort::High); @@ -155,10 +153,10 @@ fn status_snapshot_includes_reasoning_details() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_includes_monthly_limit() { +#[tokio::test] +async fn status_snapshot_includes_monthly_limit() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.model_provider_id = "openai".to_string(); config.cwd = PathBuf::from("/workspace/tests"); @@ -212,10 +210,10 @@ fn status_snapshot_includes_monthly_limit() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_shows_unlimited_credits() { +#[tokio::test] +async fn status_snapshot_shows_unlimited_credits() { let temp_home = TempDir::new().expect("temp home"); - let config = test_config(&temp_home); + let config = test_config(&temp_home).await; let auth_manager = test_auth_manager(&config); let usage = TokenUsage::default(); let captured_at = chrono::Local @@ -256,10 +254,10 @@ fn status_snapshot_shows_unlimited_credits() { ); } -#[test] -fn status_snapshot_shows_positive_credits() { +#[tokio::test] +async fn status_snapshot_shows_positive_credits() { let temp_home = TempDir::new().expect("temp home"); - let config = test_config(&temp_home); + let config = test_config(&temp_home).await; let auth_manager = test_auth_manager(&config); let usage = TokenUsage::default(); let captured_at = chrono::Local @@ -300,10 +298,10 @@ fn status_snapshot_shows_positive_credits() { ); } -#[test] -fn status_snapshot_hides_zero_credits() { +#[tokio::test] +async fn status_snapshot_hides_zero_credits() { let temp_home = TempDir::new().expect("temp home"); - let config = test_config(&temp_home); + let config = test_config(&temp_home).await; let auth_manager = test_auth_manager(&config); let usage = TokenUsage::default(); let captured_at = chrono::Local @@ -342,10 +340,10 @@ fn status_snapshot_hides_zero_credits() { ); } -#[test] -fn status_snapshot_hides_when_has_no_credits_flag() { +#[tokio::test] +async fn status_snapshot_hides_when_has_no_credits_flag() { let temp_home = TempDir::new().expect("temp home"); - let config = test_config(&temp_home); + let config = test_config(&temp_home).await; let auth_manager = test_auth_manager(&config); let usage = TokenUsage::default(); let captured_at = chrono::Local @@ -384,10 +382,10 @@ fn status_snapshot_hides_when_has_no_credits_flag() { ); } -#[test] -fn status_card_token_usage_excludes_cached_tokens() { +#[tokio::test] +async fn status_card_token_usage_excludes_cached_tokens() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.cwd = PathBuf::from("/workspace/tests"); @@ -427,10 +425,10 @@ fn status_card_token_usage_excludes_cached_tokens() { ); } -#[test] -fn status_snapshot_truncates_in_narrow_terminal() { +#[tokio::test] +async fn status_snapshot_truncates_in_narrow_terminal() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.model_provider_id = "openai".to_string(); config.model_reasoning_effort = Some(ReasoningEffort::High); @@ -487,10 +485,10 @@ fn status_snapshot_truncates_in_narrow_terminal() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_shows_missing_limits_message() { +#[tokio::test] +async fn status_snapshot_shows_missing_limits_message() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.cwd = PathBuf::from("/workspace/tests"); @@ -532,10 +530,10 @@ fn status_snapshot_shows_missing_limits_message() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_includes_credits_and_limits() { +#[tokio::test] +async fn status_snapshot_includes_credits_and_limits() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex".to_string()); config.cwd = PathBuf::from("/workspace/tests"); @@ -596,10 +594,10 @@ fn status_snapshot_includes_credits_and_limits() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_shows_empty_limits_message() { +#[tokio::test] +async fn status_snapshot_shows_empty_limits_message() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.cwd = PathBuf::from("/workspace/tests"); @@ -648,10 +646,10 @@ fn status_snapshot_shows_empty_limits_message() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_shows_stale_limits_message() { +#[tokio::test] +async fn status_snapshot_shows_stale_limits_message() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex-max".to_string()); config.cwd = PathBuf::from("/workspace/tests"); @@ -709,10 +707,10 @@ fn status_snapshot_shows_stale_limits_message() { assert_snapshot!(sanitized); } -#[test] -fn status_snapshot_cached_limits_hide_credits_without_flag() { +#[tokio::test] +async fn status_snapshot_cached_limits_hide_credits_without_flag() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model = Some("gpt-5.1-codex".to_string()); config.cwd = PathBuf::from("/workspace/tests"); @@ -774,10 +772,10 @@ fn status_snapshot_cached_limits_hide_credits_without_flag() { assert_snapshot!(sanitized); } -#[test] -fn status_context_window_uses_last_usage() { +#[tokio::test] +async fn status_context_window_uses_last_usage() { let temp_home = TempDir::new().expect("temp home"); - let mut config = test_config(&temp_home); + let mut config = test_config(&temp_home).await; config.model_context_window = Some(272_000); let auth_manager = test_auth_manager(&config);