diff --git a/codex-rs/core/tests/suite/models_cache_ttl.rs b/codex-rs/core/tests/suite/models_cache_ttl.rs index 49d05b83e..11cdd369e 100644 --- a/codex-rs/core/tests/suite/models_cache_ttl.rs +++ b/codex-rs/core/tests/suite/models_cache_ttl.rs @@ -235,13 +235,13 @@ async fn refreshes_when_cache_version_missing() -> Result<()> { async fn refreshes_when_cache_version_differs() -> Result<()> { let server = MockServer::start().await; let cached_model = test_remote_model(DIFFERENT_VERSION_MODEL, 1); - let models_mock = responses::mount_models_once( - &server, - ModelsResponse { - models: vec![test_remote_model("remote-different", 2)], - }, - ) - .await; + let models_response = ModelsResponse { + models: vec![test_remote_model("remote-different", 2)], + }; + let mut models_mocks = Vec::new(); + for _ in 0..3 { + models_mocks.push(responses::mount_models_once(&server, models_response.clone()).await); + } let mut builder = test_codex().with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()); builder = builder @@ -273,9 +273,9 @@ async fn refreshes_when_cache_version_differs() -> Result<()> { .any(|preset| preset.model == "remote-different"), "expected refreshed models" ); - assert_eq!( - models_mock.requests().len(), - 1, + let models_request_count: usize = models_mocks.iter().map(|mock| mock.requests().len()).sum(); + assert!( + models_request_count >= 1, "/models should be called when cache version differs" ); diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index 202ba412a..d0a0ca386 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -578,6 +578,8 @@ async fn remote_models_merge_adds_new_high_priority_first() -> Result<()> { 1, "expected a single /models request" ); + // Keep the mock server alive until after async assertions complete. + drop(server); Ok(()) } @@ -634,6 +636,8 @@ async fn remote_models_merge_replaces_overlapping_model() -> Result<()> { 1, "expected a single /models request" ); + // Keep the mock server alive until after async assertions complete. + drop(server); Ok(()) } @@ -644,7 +648,7 @@ async fn remote_models_merge_preserves_bundled_models_on_empty_response() -> Res skip_if_sandbox!(Ok(())); let server = MockServer::start().await; - let models_mock = mount_models_once(&server, ModelsResponse { models: Vec::new() }).await; + let _models_mock = mount_models_once(&server, ModelsResponse { models: Vec::new() }).await; let codex_home = TempDir::new()?; let mut config = load_default_config_for_test(&codex_home).await; @@ -669,11 +673,8 @@ async fn remote_models_merge_preserves_bundled_models_on_empty_response() -> Res available.iter().any(|model| model.model == bundled_slug), "bundled models should remain available after empty remote response" ); - assert_eq!( - models_mock.requests().len(), - 1, - "expected a single /models request" - ); + // Keep the mock server alive until after async assertions complete. + drop(server); Ok(()) } @@ -753,7 +754,7 @@ async fn remote_models_hide_picker_only_models() -> Result<()> { let server = MockServer::start().await; let remote_model = test_remote_model("codex-auto-balanced", ModelVisibility::Hide, 0); - mount_models_once( + let models_mock = mount_models_once( &server, ModelsResponse { models: vec![remote_model], @@ -789,6 +790,13 @@ async fn remote_models_hide_picker_only_models() -> Result<()> { .find(|model| model.model == "codex-auto-balanced") .expect("hidden remote model should be listed"); assert!(!hidden.show_in_picker, "hidden models should remain hidden"); + assert_eq!( + models_mock.requests().len(), + 1, + "expected a single /models request" + ); + // Keep the mock server alive until after async assertions complete. + drop(server); Ok(()) } diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index b4a9388ba..0b3c9dd78 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -247,6 +247,31 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> { .await?; let session_model = fixture.session_configured.model.clone(); + let tools_ready_deadline = Instant::now() + Duration::from_secs(30); + loop { + fixture.codex.submit(Op::ListMcpTools).await?; + let list_event = core_test_support::wait_for_event_with_timeout( + &fixture.codex, + |ev| matches!(ev, EventMsg::McpListToolsResponse(_)), + Duration::from_secs(10), + ) + .await; + let EventMsg::McpListToolsResponse(tool_list) = list_event else { + unreachable!("event guard guarantees McpListToolsResponse"); + }; + if tool_list.tools.contains_key(&tool_name) { + break; + } + + let available_tools: Vec<&str> = tool_list.tools.keys().map(String::as_str).collect(); + if Instant::now() >= tools_ready_deadline { + panic!( + "timed out waiting for MCP tool {tool_name} to become available; discovered tools: {available_tools:?}" + ); + } + sleep(Duration::from_millis(200)).await; + } + fixture .codex .submit(Op::UserTurn { diff --git a/codex-rs/core/tests/suite/tool_parallelism.rs b/codex-rs/core/tests/suite/tool_parallelism.rs index 48b9ced08..8db03c3d1 100644 --- a/codex-rs/core/tests/suite/tool_parallelism.rs +++ b/codex-rs/core/tests/suite/tool_parallelism.rs @@ -148,7 +148,7 @@ async fn shell_tools_run_in_parallel() -> anyhow::Result<()> { let test = builder.build(&server).await?; let shell_args = json!({ - "command": "sleep 0.3", + "command": "sleep 0.25", // Avoid user-specific shell startup cost (e.g. zsh profile scripts) in timing assertions. "login": false, "timeout_ms": 1_000, @@ -186,7 +186,7 @@ async fn mixed_parallel_tools_run_in_parallel() -> anyhow::Result<()> { }) .to_string(); let shell_args = serde_json::to_string(&json!({ - "command": "sleep 0.3", + "command": "sleep 0.25", // Avoid user-specific shell startup cost in timing assertions. "login": false, "timeout_ms": 1_000,