diff --git a/codex-rs/app-server/tests/suite/archive_conversation.rs b/codex-rs/app-server/tests/suite/archive_conversation.rs index 6dcfefdb..2d8f746a 100644 --- a/codex-rs/app-server/tests/suite/archive_conversation.rs +++ b/codex-rs/app-server/tests/suite/archive_conversation.rs @@ -1,5 +1,4 @@ -use std::path::Path; - +use anyhow::Result; use app_test_support::McpProcess; use app_test_support::to_response; use codex_app_server_protocol::ArchiveConversationParams; @@ -9,45 +8,37 @@ use codex_app_server_protocol::NewConversationParams; use codex_app_server_protocol::NewConversationResponse; use codex_app_server_protocol::RequestId; use codex_core::ARCHIVED_SESSIONS_SUBDIR; +use std::path::Path; use tempfile::TempDir; use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn archive_conversation_moves_rollout_into_archived_directory() { - let codex_home = TempDir::new().expect("create temp dir"); - create_config_toml(codex_home.path()).expect("write config.toml"); +async fn archive_conversation_moves_rollout_into_archived_directory() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path())?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("initialize timeout") - .expect("initialize request"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; let new_request_id = mcp .send_new_conversation_request(NewConversationParams { model: Some("mock-model".to_string()), ..Default::default() }) - .await - .expect("send newConversation"); + .await?; let new_response: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(new_request_id)), ) - .await - .expect("newConversation timeout") - .expect("newConversation response"); + .await??; let NewConversationResponse { conversation_id, rollout_path, .. - } = to_response::(new_response) - .expect("deserialize newConversation response"); + } = to_response::(new_response)?; assert!( rollout_path.exists(), @@ -60,19 +51,15 @@ async fn archive_conversation_moves_rollout_into_archived_directory() { conversation_id, rollout_path: rollout_path.clone(), }) - .await - .expect("send archiveConversation"); + .await?; let archive_response: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(archive_request_id)), ) - .await - .expect("archiveConversation timeout") - .expect("archiveConversation response"); + .await??; let _: ArchiveConversationResponse = - to_response::(archive_response) - .expect("deserialize archiveConversation response"); + to_response::(archive_response)?; let archived_directory = codex_home.path().join(ARCHIVED_SESSIONS_SUBDIR); let archived_rollout_path = @@ -90,6 +77,8 @@ async fn archive_conversation_moves_rollout_into_archived_directory() { "expected archived rollout path {} to exist", archived_rollout_path.display() ); + + Ok(()) } fn create_config_toml(codex_home: &Path) -> std::io::Result<()> { diff --git a/codex-rs/app-server/tests/suite/auth.rs b/codex-rs/app-server/tests/suite/auth.rs index aa6cc1bb..72912362 100644 --- a/codex-rs/app-server/tests/suite/auth.rs +++ b/codex-rs/app-server/tests/suite/auth.rs @@ -1,5 +1,4 @@ -use std::path::Path; - +use anyhow::Result; use app_test_support::McpProcess; use app_test_support::to_response; use codex_app_server_protocol::AuthMode; @@ -11,6 +10,7 @@ use codex_app_server_protocol::LoginApiKeyParams; use codex_app_server_protocol::LoginApiKeyResponse; use codex_app_server_protocol::RequestId; use pretty_assertions::assert_eq; +use std::path::Path; use tempfile::TempDir; use tokio::time::timeout; @@ -71,125 +71,99 @@ forced_login_method = "{forced_method}" std::fs::write(config_toml, contents) } -async fn login_with_api_key_via_request(mcp: &mut McpProcess, api_key: &str) { +async fn login_with_api_key_via_request(mcp: &mut McpProcess, api_key: &str) -> Result<()> { let request_id = mcp .send_login_api_key_request(LoginApiKeyParams { api_key: api_key.to_string(), }) - .await - .unwrap_or_else(|e| panic!("send loginApiKey: {e}")); + .await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .unwrap_or_else(|e| panic!("loginApiKey timeout: {e}")) - .unwrap_or_else(|e| panic!("loginApiKey response: {e}")); - let _: LoginApiKeyResponse = - to_response(resp).unwrap_or_else(|e| panic!("deserialize login response: {e}")); + .await??; + let _: LoginApiKeyResponse = to_response(resp)?; + Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_auth_status_no_auth() { - let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}")); - create_config_toml(codex_home.path()).unwrap_or_else(|err| panic!("write config.toml: {err}")); +async fn get_auth_status_no_auth() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path())?; - let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; let request_id = mcp .send_get_auth_status_request(GetAuthStatusParams { include_token: Some(true), refresh_token: Some(false), }) - .await - .expect("send getAuthStatus"); + .await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .expect("getAuthStatus timeout") - .expect("getAuthStatus response"); - let status: GetAuthStatusResponse = to_response(resp).expect("deserialize status"); + .await??; + let status: GetAuthStatusResponse = to_response(resp)?; assert_eq!(status.auth_method, None, "expected no auth method"); assert_eq!(status.auth_token, None, "expected no token"); + Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_auth_status_with_api_key() { - let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}")); - create_config_toml(codex_home.path()).unwrap_or_else(|err| panic!("write config.toml: {err}")); +async fn get_auth_status_with_api_key() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path())?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - login_with_api_key_via_request(&mut mcp, "sk-test-key").await; + login_with_api_key_via_request(&mut mcp, "sk-test-key").await?; let request_id = mcp .send_get_auth_status_request(GetAuthStatusParams { include_token: Some(true), refresh_token: Some(false), }) - .await - .expect("send getAuthStatus"); + .await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .expect("getAuthStatus timeout") - .expect("getAuthStatus response"); - let status: GetAuthStatusResponse = to_response(resp).expect("deserialize status"); + .await??; + let status: GetAuthStatusResponse = to_response(resp)?; assert_eq!(status.auth_method, Some(AuthMode::ApiKey)); assert_eq!(status.auth_token, Some("sk-test-key".to_string())); + Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_auth_status_with_api_key_when_auth_not_required() { - let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}")); - create_config_toml_custom_provider(codex_home.path(), false) - .unwrap_or_else(|err| panic!("write config.toml: {err}")); +async fn get_auth_status_with_api_key_when_auth_not_required() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml_custom_provider(codex_home.path(), false)?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - login_with_api_key_via_request(&mut mcp, "sk-test-key").await; + login_with_api_key_via_request(&mut mcp, "sk-test-key").await?; let request_id = mcp .send_get_auth_status_request(GetAuthStatusParams { include_token: Some(true), refresh_token: Some(false), }) - .await - .expect("send getAuthStatus"); + .await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .expect("getAuthStatus timeout") - .expect("getAuthStatus response"); - let status: GetAuthStatusResponse = to_response(resp).expect("deserialize status"); + .await??; + let status: GetAuthStatusResponse = to_response(resp)?; assert_eq!(status.auth_method, None, "expected no auth method"); assert_eq!(status.auth_token, None, "expected no token"); assert_eq!( @@ -197,76 +171,60 @@ async fn get_auth_status_with_api_key_when_auth_not_required() { Some(false), "requires_openai_auth should be false", ); + Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_auth_status_with_api_key_no_include_token() { - let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}")); - create_config_toml(codex_home.path()).unwrap_or_else(|err| panic!("write config.toml: {err}")); +async fn get_auth_status_with_api_key_no_include_token() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path())?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - login_with_api_key_via_request(&mut mcp, "sk-test-key").await; + login_with_api_key_via_request(&mut mcp, "sk-test-key").await?; // Build params via struct so None field is omitted in wire JSON. let params = GetAuthStatusParams { include_token: None, refresh_token: Some(false), }; - let request_id = mcp - .send_get_auth_status_request(params) - .await - .expect("send getAuthStatus"); + let request_id = mcp.send_get_auth_status_request(params).await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .expect("getAuthStatus timeout") - .expect("getAuthStatus response"); - let status: GetAuthStatusResponse = to_response(resp).expect("deserialize status"); + .await??; + let status: GetAuthStatusResponse = to_response(resp)?; assert_eq!(status.auth_method, Some(AuthMode::ApiKey)); assert!(status.auth_token.is_none(), "token must be omitted"); + Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn login_api_key_rejected_when_forced_chatgpt() { - let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}")); - create_config_toml_forced_login(codex_home.path(), "chatgpt") - .unwrap_or_else(|err| panic!("write config.toml: {err}")); +async fn login_api_key_rejected_when_forced_chatgpt() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml_forced_login(codex_home.path(), "chatgpt")?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; let request_id = mcp .send_login_api_key_request(LoginApiKeyParams { api_key: "sk-test-key".to_string(), }) - .await - .expect("send loginApiKey"); + .await?; let err: JSONRPCError = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_error_message(RequestId::Integer(request_id)), ) - .await - .expect("loginApiKey error timeout") - .expect("loginApiKey error"); + .await??; assert_eq!( err.error.message, "API key login is disabled. Use ChatGPT login instead." ); + Ok(()) } diff --git a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs b/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs index 5c010bf0..1feda428 100644 --- a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs +++ b/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs @@ -1,5 +1,4 @@ -use std::path::Path; - +use anyhow::Result; use app_test_support::McpProcess; use app_test_support::create_final_assistant_message_sse_response; use app_test_support::create_mock_chat_completions_server; @@ -32,26 +31,27 @@ use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; use pretty_assertions::assert_eq; use std::env; +use std::path::Path; use tempfile::TempDir; use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); #[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn test_codex_jsonrpc_conversation_flow() { +async fn test_codex_jsonrpc_conversation_flow() -> Result<()> { if env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() { println!( "Skipping test because it cannot execute when network is disabled in a Codex sandbox." ); - return; + return Ok(()); } - let tmp = TempDir::new().expect("tmp dir"); + let tmp = TempDir::new()?; // Temporary Codex home with config pointing at the mock server. let codex_home = tmp.path().join("codex_home"); - std::fs::create_dir(&codex_home).expect("create codex home dir"); + std::fs::create_dir(&codex_home)?; let working_directory = tmp.path().join("workdir"); - std::fs::create_dir(&working_directory).expect("create working directory"); + std::fs::create_dir(&working_directory)?; // Create a mock model server that immediately ends each turn. // Two turns are expected: initial session configure + one user message. @@ -61,20 +61,15 @@ async fn test_codex_jsonrpc_conversation_flow() { Some(&working_directory), Some(5000), "call1234", - ) - .expect("create shell sse response"), - create_final_assistant_message_sse_response("Enjoy your new git repo!") - .expect("create final assistant message"), + )?, + create_final_assistant_message_sse_response("Enjoy your new git repo!")?, ]; let server = create_mock_chat_completions_server(responses).await; - create_config_toml(&codex_home, &server.uri()).expect("write config"); + create_config_toml(&codex_home, &server.uri())?; // Start MCP server and initialize. - let mut mcp = McpProcess::new(&codex_home).await.expect("spawn mcp"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init error"); + let mut mcp = McpProcess::new(&codex_home).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; // 1) newConversation let new_conv_id = mcp @@ -82,17 +77,13 @@ async fn test_codex_jsonrpc_conversation_flow() { cwd: Some(working_directory.to_string_lossy().into_owned()), ..Default::default() }) - .await - .expect("send newConversation"); + .await?; let new_conv_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), ) - .await - .expect("newConversation timeout") - .expect("newConversation resp"); - let new_conv_resp = to_response::(new_conv_resp) - .expect("deserialize newConversation response"); + .await??; + let new_conv_resp = to_response::(new_conv_resp)?; let NewConversationResponse { conversation_id, model, @@ -107,18 +98,14 @@ async fn test_codex_jsonrpc_conversation_flow() { conversation_id, experimental_raw_events: false, }) - .await - .expect("send addConversationListener"); + .await?; let add_listener_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), ) - .await - .expect("addConversationListener timeout") - .expect("addConversationListener resp"); + .await??; let AddConversationSubscriptionResponse { subscription_id } = - to_response::(add_listener_resp) - .expect("deserialize addConversationListener response"); + to_response::(add_listener_resp)?; // 3) sendUserMessage (should trigger notifications; we only validate an OK response) let send_user_id = mcp @@ -128,17 +115,13 @@ async fn test_codex_jsonrpc_conversation_flow() { text: "text".to_string(), }], }) - .await - .expect("send sendUserMessage"); + .await?; let send_user_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(send_user_id)), ) - .await - .expect("sendUserMessage timeout") - .expect("sendUserMessage resp"); - let SendUserMessageResponse {} = to_response::(send_user_resp) - .expect("deserialize sendUserMessage response"); + .await??; + let SendUserMessageResponse {} = to_response::(send_user_resp)?; // Verify the task_finished notification is received. // Note this also ensures that the final request to the server was made. @@ -146,9 +129,7 @@ async fn test_codex_jsonrpc_conversation_flow() { DEFAULT_READ_TIMEOUT, mcp.read_stream_until_notification_message("codex/event/task_complete"), ) - .await - .expect("task_finished_notification timeout") - .expect("task_finished_notification resp"); + .await??; let serde_json::Value::Object(map) = task_finished_notification .params .expect("notification should have params") @@ -166,33 +147,31 @@ async fn test_codex_jsonrpc_conversation_flow() { .send_remove_conversation_listener_request(RemoveConversationListenerParams { subscription_id, }) - .await - .expect("send removeConversationListener"); + .await?; let remove_listener_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(remove_listener_id)), ) - .await - .expect("removeConversationListener timeout") - .expect("removeConversationListener resp"); - let RemoveConversationSubscriptionResponse {} = - to_response(remove_listener_resp).expect("deserialize removeConversationListener response"); + .await??; + let RemoveConversationSubscriptionResponse {} = to_response(remove_listener_resp)?; + + Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn test_send_user_turn_changes_approval_policy_behavior() { +async fn test_send_user_turn_changes_approval_policy_behavior() -> Result<()> { if env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() { println!( "Skipping test because it cannot execute when network is disabled in a Codex sandbox." ); - return; + return Ok(()); } - let tmp = TempDir::new().expect("tmp dir"); + let tmp = TempDir::new()?; let codex_home = tmp.path().join("codex_home"); - std::fs::create_dir(&codex_home).expect("create codex home dir"); + std::fs::create_dir(&codex_home)?; let working_directory = tmp.path().join("workdir"); - std::fs::create_dir(&working_directory).expect("create working directory"); + std::fs::create_dir(&working_directory)?; // Mock server will request a python shell call for the first and second turn, then finish. let responses = vec![ @@ -205,10 +184,8 @@ async fn test_send_user_turn_changes_approval_policy_behavior() { Some(&working_directory), Some(5000), "call1", - ) - .expect("create first shell sse response"), - create_final_assistant_message_sse_response("done 1") - .expect("create final assistant message 1"), + )?, + create_final_assistant_message_sse_response("done 1")?, create_shell_sse_response( vec![ "python3".to_string(), @@ -218,20 +195,15 @@ async fn test_send_user_turn_changes_approval_policy_behavior() { Some(&working_directory), Some(5000), "call2", - ) - .expect("create second shell sse response"), - create_final_assistant_message_sse_response("done 2") - .expect("create final assistant message 2"), + )?, + create_final_assistant_message_sse_response("done 2")?, ]; let server = create_mock_chat_completions_server(responses).await; - create_config_toml(&codex_home, &server.uri()).expect("write config"); + create_config_toml(&codex_home, &server.uri())?; // Start MCP server and initialize. - let mut mcp = McpProcess::new(&codex_home).await.expect("spawn mcp"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init error"); + let mut mcp = McpProcess::new(&codex_home).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; // 1) Start conversation with approval_policy=untrusted let new_conv_id = mcp @@ -239,19 +211,15 @@ async fn test_send_user_turn_changes_approval_policy_behavior() { cwd: Some(working_directory.to_string_lossy().into_owned()), ..Default::default() }) - .await - .expect("send newConversation"); + .await?; let new_conv_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), ) - .await - .expect("newConversation timeout") - .expect("newConversation resp"); + .await??; let NewConversationResponse { conversation_id, .. - } = to_response::(new_conv_resp) - .expect("deserialize newConversation response"); + } = to_response::(new_conv_resp)?; // 2) addConversationListener let add_listener_id = mcp @@ -259,19 +227,14 @@ async fn test_send_user_turn_changes_approval_policy_behavior() { conversation_id, experimental_raw_events: false, }) - .await - .expect("send addConversationListener"); - let _: AddConversationSubscriptionResponse = - to_response::( - timeout( - DEFAULT_READ_TIMEOUT, - mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), - ) - .await - .expect("addConversationListener timeout") - .expect("addConversationListener resp"), + .await?; + let _: AddConversationSubscriptionResponse = to_response::( + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), ) - .expect("deserialize addConversationListener response"); + .await??, + )?; // 3) sendUserMessage triggers a shell call; approval policy is Untrusted so we should get an elicitation let send_user_id = mcp @@ -281,27 +244,21 @@ async fn test_send_user_turn_changes_approval_policy_behavior() { text: "run python".to_string(), }], }) - .await - .expect("send sendUserMessage"); + .await?; let _send_user_resp: SendUserMessageResponse = to_response::( timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(send_user_id)), ) - .await - .expect("sendUserMessage timeout") - .expect("sendUserMessage resp"), - ) - .expect("deserialize sendUserMessage response"); + .await??, + )?; // Expect an ExecCommandApproval request (elicitation) let request = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_request_message(), ) - .await - .expect("waiting for exec approval request timeout") - .expect("exec approval request"); + .await??; let ServerRequest::ExecCommandApproval { request_id, params } = request else { panic!("expected ExecCommandApproval request, got: {request:?}"); }; @@ -330,17 +287,14 @@ async fn test_send_user_turn_changes_approval_policy_behavior() { request_id, serde_json::json!({ "decision": codex_core::protocol::ReviewDecision::Approved }), ) - .await - .expect("send approval response"); + .await?; // Wait for first TaskComplete let _ = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_notification_message("codex/event/task_complete"), ) - .await - .expect("task_complete 1 timeout") - .expect("task_complete 1 notification"); + .await??; // 4) sendUserTurn with approval_policy=never should run without elicitation let send_turn_id = mcp @@ -356,19 +310,15 @@ async fn test_send_user_turn_changes_approval_policy_behavior() { effort: Some(ReasoningEffort::Medium), summary: ReasoningSummary::Auto, }) - .await - .expect("send sendUserTurn"); + .await?; // Acknowledge sendUserTurn let _send_turn_resp: SendUserTurnResponse = to_response::( timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(send_turn_id)), ) - .await - .expect("sendUserTurn timeout") - .expect("sendUserTurn resp"), - ) - .expect("deserialize sendUserTurn response"); + .await??, + )?; // Ensure we do NOT receive an ExecCommandApproval request before the task completes. // If any Request is seen while waiting for task_complete, the helper will error and the test fails. @@ -376,31 +326,31 @@ async fn test_send_user_turn_changes_approval_policy_behavior() { DEFAULT_READ_TIMEOUT, mcp.read_stream_until_notification_message("codex/event/task_complete"), ) - .await - .expect("task_complete 2 timeout") - .expect("task_complete 2 notification"); + .await??; + + Ok(()) } // Helper: minimal config.toml pointing at mock provider. #[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() { +async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() -> Result<()> { if env::var(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR).is_ok() { println!( "Skipping test because it cannot execute when network is disabled in a Codex sandbox." ); - return; + return Ok(()); } - let tmp = TempDir::new().expect("tmp dir"); + let tmp = TempDir::new()?; let codex_home = tmp.path().join("codex_home"); - std::fs::create_dir(&codex_home).expect("create codex home dir"); + std::fs::create_dir(&codex_home)?; let workspace_root = tmp.path().join("workspace"); - std::fs::create_dir(&workspace_root).expect("create workspace root"); + std::fs::create_dir(&workspace_root)?; let first_cwd = workspace_root.join("turn1"); let second_cwd = workspace_root.join("turn2"); - std::fs::create_dir(&first_cwd).expect("create first cwd"); - std::fs::create_dir(&second_cwd).expect("create second cwd"); + std::fs::create_dir(&first_cwd)?; + std::fs::create_dir(&second_cwd)?; let responses = vec![ create_shell_sse_response( @@ -412,10 +362,8 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() { None, Some(5000), "call-first", - ) - .expect("create first shell response"), - create_final_assistant_message_sse_response("done first") - .expect("create first final assistant message"), + )?, + create_final_assistant_message_sse_response("done first")?, create_shell_sse_response( vec![ "bash".to_string(), @@ -425,21 +373,14 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() { None, Some(5000), "call-second", - ) - .expect("create second shell response"), - create_final_assistant_message_sse_response("done second") - .expect("create second final assistant message"), + )?, + create_final_assistant_message_sse_response("done second")?, ]; let server = create_mock_chat_completions_server(responses).await; - create_config_toml(&codex_home, &server.uri()).expect("write config"); + create_config_toml(&codex_home, &server.uri())?; - let mut mcp = McpProcess::new(&codex_home) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new(&codex_home).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; let new_conv_id = mcp .send_new_conversation_request(NewConversationParams { @@ -448,36 +389,29 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() { sandbox: Some(SandboxMode::WorkspaceWrite), ..Default::default() }) - .await - .expect("send newConversation"); + .await?; let new_conv_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), ) - .await - .expect("newConversation timeout") - .expect("newConversation resp"); + .await??; let NewConversationResponse { conversation_id, model, .. - } = to_response::(new_conv_resp) - .expect("deserialize newConversation response"); + } = to_response::(new_conv_resp)?; let add_listener_id = mcp .send_add_conversation_listener_request(AddConversationListenerParams { conversation_id, experimental_raw_events: false, }) - .await - .expect("send addConversationListener"); + .await?; timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), ) - .await - .expect("addConversationListener timeout") - .expect("addConversationListener resp"); + .await??; let first_turn_id = mcp .send_send_user_turn_request(SendUserTurnParams { @@ -497,22 +431,17 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() { effort: Some(ReasoningEffort::Medium), summary: ReasoningSummary::Auto, }) - .await - .expect("send first sendUserTurn"); + .await?; timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(first_turn_id)), ) - .await - .expect("sendUserTurn 1 timeout") - .expect("sendUserTurn 1 resp"); + .await??; timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_notification_message("codex/event/task_complete"), ) - .await - .expect("task_complete 1 timeout") - .expect("task_complete 1 notification"); + .await??; let second_turn_id = mcp .send_send_user_turn_request(SendUserTurnParams { @@ -527,23 +456,18 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() { effort: Some(ReasoningEffort::Medium), summary: ReasoningSummary::Auto, }) - .await - .expect("send second sendUserTurn"); + .await?; timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(second_turn_id)), ) - .await - .expect("sendUserTurn 2 timeout") - .expect("sendUserTurn 2 resp"); + .await??; let exec_begin_notification = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_notification_message("codex/event/exec_command_begin"), ) - .await - .expect("exec_command_begin timeout") - .expect("exec_command_begin notification"); + .await??; let params = exec_begin_notification .params .clone() @@ -571,9 +495,9 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() { DEFAULT_READ_TIMEOUT, mcp.read_stream_until_notification_message("codex/event/task_complete"), ) - .await - .expect("task_complete 2 timeout") - .expect("task_complete 2 notification"); + .await??; + + Ok(()) } fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> { diff --git a/codex-rs/app-server/tests/suite/config.rs b/codex-rs/app-server/tests/suite/config.rs index 2809d68c..227d30bb 100644 --- a/codex-rs/app-server/tests/suite/config.rs +++ b/codex-rs/app-server/tests/suite/config.rs @@ -1,6 +1,4 @@ -use std::collections::HashMap; -use std::path::Path; - +use anyhow::Result; use app_test_support::McpProcess; use app_test_support::to_response; use codex_app_server_protocol::GetUserSavedConfigResponse; @@ -17,6 +15,8 @@ use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::SandboxMode; use codex_protocol::config_types::Verbosity; use pretty_assertions::assert_eq; +use std::collections::HashMap; +use std::path::Path; use tempfile::TempDir; use tokio::time::timeout; @@ -60,31 +60,21 @@ chatgpt_base_url = "https://api.chatgpt.com" } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn get_config_toml_parses_all_fields() { - let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}")); - create_config_toml(codex_home.path()).expect("write config.toml"); +async fn get_config_toml_parses_all_fields() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path())?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - let request_id = mcp - .send_get_user_saved_config_request() - .await - .expect("send getUserSavedConfig"); + let request_id = mcp.send_get_user_saved_config_request().await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .expect("getUserSavedConfig timeout") - .expect("getUserSavedConfig response"); + .await??; - let config: GetUserSavedConfigResponse = to_response(resp).expect("deserialize config"); + let config: GetUserSavedConfigResponse = to_response(resp)?; let expected = GetUserSavedConfigResponse { config: UserSavedConfig { approval_policy: Some(AskForApproval::OnRequest), @@ -122,33 +112,24 @@ async fn get_config_toml_parses_all_fields() { }; assert_eq!(config, expected); + Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_config_toml_empty() { - let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}")); +async fn get_config_toml_empty() -> Result<()> { + let codex_home = TempDir::new()?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - let request_id = mcp - .send_get_user_saved_config_request() - .await - .expect("send getUserSavedConfig"); + let request_id = mcp.send_get_user_saved_config_request().await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .expect("getUserSavedConfig timeout") - .expect("getUserSavedConfig response"); + .await??; - let config: GetUserSavedConfigResponse = to_response(resp).expect("deserialize config"); + let config: GetUserSavedConfigResponse = to_response(resp)?; let expected = GetUserSavedConfigResponse { config: UserSavedConfig { approval_policy: None, @@ -167,4 +148,5 @@ async fn get_config_toml_empty() { }; assert_eq!(config, expected); + Ok(()) } diff --git a/codex-rs/app-server/tests/suite/create_conversation.rs b/codex-rs/app-server/tests/suite/create_conversation.rs index f507baa3..7788b8f3 100644 --- a/codex-rs/app-server/tests/suite/create_conversation.rs +++ b/codex-rs/app-server/tests/suite/create_conversation.rs @@ -1,5 +1,4 @@ -use std::path::Path; - +use anyhow::Result; use app_test_support::McpProcess; use app_test_support::create_final_assistant_message_sse_response; use app_test_support::create_mock_chat_completions_server; @@ -15,31 +14,25 @@ use codex_app_server_protocol::SendUserMessageParams; use codex_app_server_protocol::SendUserMessageResponse; use pretty_assertions::assert_eq; use serde_json::json; +use std::path::Path; use tempfile::TempDir; use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_conversation_create_and_send_message_ok() { +async fn test_conversation_create_and_send_message_ok() -> Result<()> { // Mock server – we won't strictly rely on it, but provide one to satisfy any model wiring. - let responses = vec![ - create_final_assistant_message_sse_response("Done").expect("build mock assistant message"), - ]; + let responses = vec![create_final_assistant_message_sse_response("Done")?]; let server = create_mock_chat_completions_server(responses).await; // Temporary Codex home with config pointing at the mock server. - let codex_home = TempDir::new().expect("create temp dir"); - create_config_toml(codex_home.path(), &server.uri()).expect("write config.toml"); + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; // Start MCP server process and initialize. - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; // Create a conversation via the new JSON-RPC API. let new_conv_id = mcp @@ -47,22 +40,18 @@ async fn test_conversation_create_and_send_message_ok() { model: Some("o3".to_string()), ..Default::default() }) - .await - .expect("send newConversation"); + .await?; let new_conv_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), ) - .await - .expect("newConversation timeout") - .expect("newConversation resp"); + .await??; let NewConversationResponse { conversation_id, model, reasoning_effort: _, rollout_path: _, - } = to_response::(new_conv_resp) - .expect("deserialize newConversation response"); + } = to_response::(new_conv_resp)?; assert_eq!(model, "o3"); // Add a listener so we receive notifications for this conversation (not strictly required for this test). @@ -71,19 +60,15 @@ async fn test_conversation_create_and_send_message_ok() { conversation_id, experimental_raw_events: false, }) - .await - .expect("send addConversationListener"); + .await?; let _sub: AddConversationSubscriptionResponse = to_response::( timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), ) - .await - .expect("addConversationListener timeout") - .expect("addConversationListener resp"), - ) - .expect("deserialize addConversationListener response"); + .await??, + )?; // Now send a user message via the wire API and expect an OK (empty object) result. let send_id = mcp @@ -93,36 +78,32 @@ async fn test_conversation_create_and_send_message_ok() { text: "Hello".to_string(), }], }) - .await - .expect("send sendUserMessage"); + .await?; let send_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(send_id)), ) - .await - .expect("sendUserMessage timeout") - .expect("sendUserMessage resp"); - let _ok: SendUserMessageResponse = to_response::(send_resp) - .expect("deserialize sendUserMessage response"); + .await??; + let _ok: SendUserMessageResponse = to_response::(send_resp)?; // avoid race condition by waiting for the mock server to receive the chat.completions request let deadline = std::time::Instant::now() + DEFAULT_READ_TIMEOUT; - loop { + let requests = loop { let requests = server.received_requests().await.unwrap_or_default(); if !requests.is_empty() { - break; + break requests; } if std::time::Instant::now() >= deadline { panic!("mock server did not receive the chat.completions request in time"); } tokio::time::sleep(std::time::Duration::from_millis(10)).await; - } + }; // Verify the outbound request body matches expectations for Chat Completions. - let request = &server.received_requests().await.unwrap()[0]; - let body = request - .body_json::() - .expect("parse request body as JSON"); + let request = requests + .first() + .expect("mock server should have received at least one request"); + let body = request.body_json::()?; assert_eq!(body["model"], json!("o3")); assert!(body["stream"].as_bool().unwrap_or(false)); let messages = body["messages"] @@ -133,6 +114,7 @@ async fn test_conversation_create_and_send_message_ok() { assert_eq!(last["content"], json!("Hello")); drop(server); + Ok(()) } // Helper to create a config.toml pointing at the mock model server. diff --git a/codex-rs/app-server/tests/suite/fuzzy_file_search.rs b/codex-rs/app-server/tests/suite/fuzzy_file_search.rs index a2bc974a..9c95e3de 100644 --- a/codex-rs/app-server/tests/suite/fuzzy_file_search.rs +++ b/codex-rs/app-server/tests/suite/fuzzy_file_search.rs @@ -1,5 +1,5 @@ -use anyhow::Context; use anyhow::Result; +use anyhow::anyhow; use app_test_support::McpProcess; use codex_app_server_protocol::JSONRPCResponse; use codex_app_server_protocol::RequestId; @@ -13,48 +13,39 @@ const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_fuzzy_file_search_sorts_and_includes_indices() -> Result<()> { // Prepare a temporary Codex home and a separate root with test files. - let codex_home = TempDir::new().context("create temp codex home")?; - let root = TempDir::new().context("create temp search root")?; + let codex_home = TempDir::new()?; + let root = TempDir::new()?; // Create files designed to have deterministic ordering for query "abe". - std::fs::write(root.path().join("abc"), "x").context("write file abc")?; - std::fs::write(root.path().join("abcde"), "x").context("write file abcde")?; - std::fs::write(root.path().join("abexy"), "x").context("write file abexy")?; - std::fs::write(root.path().join("zzz.txt"), "x").context("write file zzz")?; + std::fs::write(root.path().join("abc"), "x")?; + std::fs::write(root.path().join("abcde"), "x")?; + std::fs::write(root.path().join("abexy"), "x")?; + std::fs::write(root.path().join("zzz.txt"), "x")?; let sub_dir = root.path().join("sub"); - std::fs::create_dir_all(&sub_dir).context("create sub dir")?; + std::fs::create_dir_all(&sub_dir)?; let sub_abce_path = sub_dir.join("abce"); - std::fs::write(&sub_abce_path, "x").context("write file sub/abce")?; + std::fs::write(&sub_abce_path, "x")?; let sub_abce_rel = sub_abce_path - .strip_prefix(root.path()) - .context("strip root prefix from sub/abce")? + .strip_prefix(root.path())? .to_string_lossy() .to_string(); // Start MCP server and initialize. - let mut mcp = McpProcess::new(codex_home.path()) - .await - .context("spawn mcp")?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .context("init timeout")? - .context("init failed")?; + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; let root_path = root.path().to_string_lossy().to_string(); // Send fuzzyFileSearch request. let request_id = mcp .send_fuzzy_file_search_request("abe", vec![root_path.clone()], None) - .await - .context("send fuzzyFileSearch")?; + .await?; // Read response and verify shape and ordering. let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .context("fuzzyFileSearch timeout")? - .context("fuzzyFileSearch resp")?; + .await??; let value = resp.result; // The path separator on Windows affects the score. @@ -94,24 +85,18 @@ async fn test_fuzzy_file_search_sorts_and_includes_indices() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_fuzzy_file_search_accepts_cancellation_token() -> Result<()> { - let codex_home = TempDir::new().context("create temp codex home")?; - let root = TempDir::new().context("create temp search root")?; + let codex_home = TempDir::new()?; + let root = TempDir::new()?; - std::fs::write(root.path().join("alpha.txt"), "contents").context("write alpha")?; + std::fs::write(root.path().join("alpha.txt"), "contents")?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .context("spawn mcp")?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .context("init timeout")? - .context("init failed")?; + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; let root_path = root.path().to_string_lossy().to_string(); let request_id = mcp .send_fuzzy_file_search_request("alp", vec![root_path.clone()], None) - .await - .context("send fuzzyFileSearch")?; + .await?; let request_id_2 = mcp .send_fuzzy_file_search_request( @@ -119,23 +104,20 @@ async fn test_fuzzy_file_search_accepts_cancellation_token() -> Result<()> { vec![root_path.clone()], Some(request_id.to_string()), ) - .await - .context("send fuzzyFileSearch")?; + .await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id_2)), ) - .await - .context("fuzzyFileSearch timeout")? - .context("fuzzyFileSearch resp")?; + .await??; let files = resp .result .get("files") - .context("files key missing")? + .ok_or_else(|| anyhow!("files key missing"))? .as_array() - .context("files not array")? + .ok_or_else(|| anyhow!("files not array"))? .clone(); assert_eq!(files.len(), 1); diff --git a/codex-rs/app-server/tests/suite/list_resume.rs b/codex-rs/app-server/tests/suite/list_resume.rs index 85416f60..44578e04 100644 --- a/codex-rs/app-server/tests/suite/list_resume.rs +++ b/codex-rs/app-server/tests/suite/list_resume.rs @@ -1,6 +1,4 @@ -use std::fs; -use std::path::Path; - +use anyhow::Result; use app_test_support::McpProcess; use app_test_support::to_response; use codex_app_server_protocol::JSONRPCNotification; @@ -15,6 +13,8 @@ use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::SessionConfiguredNotification; use pretty_assertions::assert_eq; use serde_json::json; +use std::fs; +use std::path::Path; use tempfile::TempDir; use tokio::time::timeout; use uuid::Uuid; @@ -22,38 +22,33 @@ use uuid::Uuid; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_list_and_resume_conversations() { +async fn test_list_and_resume_conversations() -> Result<()> { // Prepare a temporary CODEX_HOME with a few fake rollout files. - let codex_home = TempDir::new().expect("create temp dir"); + let codex_home = TempDir::new()?; create_fake_rollout( codex_home.path(), "2025-01-02T12-00-00", "2025-01-02T12:00:00Z", "Hello A", Some("openai"), - ); + )?; create_fake_rollout( codex_home.path(), "2025-01-01T13-00-00", "2025-01-01T13:00:00Z", "Hello B", Some("openai"), - ); + )?; create_fake_rollout( codex_home.path(), "2025-01-01T12-00-00", "2025-01-01T12:00:00Z", "Hello C", None, - ); + )?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; // Request first page with size 2 let req_id = mcp @@ -62,17 +57,14 @@ async fn test_list_and_resume_conversations() { cursor: None, model_providers: None, }) - .await - .expect("send listConversations"); + .await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(req_id)), ) - .await - .expect("listConversations timeout") - .expect("listConversations resp"); + .await??; let ListConversationsResponse { items, next_cursor } = - to_response::(resp).expect("deserialize response"); + to_response::(resp)?; assert_eq!(items.len(), 2); // Newest first; preview text should match @@ -90,20 +82,17 @@ async fn test_list_and_resume_conversations() { cursor: next_cursor, model_providers: None, }) - .await - .expect("send listConversations page 2"); + .await?; let resp2: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(req_id2)), ) - .await - .expect("listConversations page 2 timeout") - .expect("listConversations page 2 resp"); + .await??; let ListConversationsResponse { items: items2, next_cursor: next2, .. - } = to_response::(resp2).expect("deserialize response"); + } = to_response::(resp2)?; assert_eq!(items2.len(), 1); assert_eq!(items2[0].preview, "Hello C"); assert_eq!(items2[0].model_provider, "openai"); @@ -116,7 +105,7 @@ async fn test_list_and_resume_conversations() { "2025-01-01T11:30:00Z", "Hello TP", Some("test-provider"), - ); + )?; // Filtering by model provider should return only matching sessions. let filter_req_id = mcp @@ -125,19 +114,16 @@ async fn test_list_and_resume_conversations() { cursor: None, model_providers: Some(vec!["test-provider".to_string()]), }) - .await - .expect("send listConversations filtered"); + .await?; let filter_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(filter_req_id)), ) - .await - .expect("listConversations filtered timeout") - .expect("listConversations filtered resp"); + .await??; let ListConversationsResponse { items: filtered_items, next_cursor: filtered_next, - } = to_response::(filter_resp).expect("deserialize filtered"); + } = to_response::(filter_resp)?; assert_eq!(filtered_items.len(), 1); assert_eq!(filtered_next, None); assert_eq!(filtered_items[0].preview, "Hello TP"); @@ -150,20 +136,16 @@ async fn test_list_and_resume_conversations() { cursor: None, model_providers: Some(Vec::new()), }) - .await - .expect("send listConversations unfiltered"); + .await?; let unfiltered_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(unfiltered_req_id)), ) - .await - .expect("listConversations unfiltered timeout") - .expect("listConversations unfiltered resp"); + .await??; let ListConversationsResponse { items: unfiltered_items, next_cursor: unfiltered_next, - } = to_response::(unfiltered_resp) - .expect("deserialize unfiltered response"); + } = to_response::(unfiltered_resp)?; assert_eq!(unfiltered_items.len(), 4); assert!(unfiltered_next.is_none()); @@ -173,19 +155,16 @@ async fn test_list_and_resume_conversations() { cursor: None, model_providers: Some(vec!["other".to_string()]), }) - .await - .expect("send listConversations filtered empty"); + .await?; let empty_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(empty_req_id)), ) - .await - .expect("listConversations filtered empty timeout") - .expect("listConversations filtered empty resp"); + .await??; let ListConversationsResponse { items: empty_items, next_cursor: empty_next, - } = to_response::(empty_resp).expect("deserialize filtered empty"); + } = to_response::(empty_resp)?; assert!(empty_items.is_empty()); assert!(empty_next.is_none()); @@ -198,20 +177,15 @@ async fn test_list_and_resume_conversations() { ..Default::default() }), }) - .await - .expect("send resumeConversation"); + .await?; // Expect a codex/event notification with msg.type == sessionConfigured let notification: JSONRPCNotification = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_notification_message("sessionConfigured"), ) - .await - .expect("sessionConfigured notification timeout") - .expect("sessionConfigured notification"); - let session_configured: ServerNotification = notification - .try_into() - .expect("deserialize sessionConfigured notification"); + .await??; + let session_configured: ServerNotification = notification.try_into()?; // Basic shape assertion: ensure event type is sessionConfigured let ServerNotification::SessionConfigured(SessionConfiguredNotification { model, @@ -229,15 +203,14 @@ async fn test_list_and_resume_conversations() { DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(resume_req_id)), ) - .await - .expect("resumeConversation timeout") - .expect("resumeConversation resp"); + .await??; let ResumeConversationResponse { conversation_id, .. - } = to_response::(resume_resp) - .expect("deserialize resumeConversation response"); + } = to_response::(resume_resp)?; // conversation id should be a valid UUID assert!(!conversation_id.to_string().is_empty()); + + Ok(()) } fn create_fake_rollout( @@ -246,14 +219,14 @@ fn create_fake_rollout( meta_rfc3339: &str, preview: &str, model_provider: Option<&str>, -) { +) -> Result<()> { let uuid = Uuid::new_v4(); // sessions/YYYY/MM/DD/ derived from filename_ts (YYYY-MM-DDThh-mm-ss) let year = &filename_ts[0..4]; let month = &filename_ts[5..7]; let day = &filename_ts[8..10]; let dir = codex_home.join("sessions").join(year).join(month).join(day); - fs::create_dir_all(&dir).unwrap_or_else(|e| panic!("create sessions dir: {e}")); + fs::create_dir_all(&dir)?; let file_path = dir.join(format!("rollout-{filename_ts}-{uuid}.jsonl")); let mut lines = Vec::new(); @@ -303,6 +276,6 @@ fn create_fake_rollout( }) .to_string(), ); - fs::write(file_path, lines.join("\n") + "\n") - .unwrap_or_else(|e| panic!("write rollout file: {e}")); + fs::write(file_path, lines.join("\n") + "\n")?; + Ok(()) } diff --git a/codex-rs/app-server/tests/suite/login.rs b/codex-rs/app-server/tests/suite/login.rs index 471ffea8..c5470c3e 100644 --- a/codex-rs/app-server/tests/suite/login.rs +++ b/codex-rs/app-server/tests/suite/login.rs @@ -1,6 +1,4 @@ -use std::path::Path; -use std::time::Duration; - +use anyhow::Result; use app_test_support::McpProcess; use app_test_support::to_response; use codex_app_server_protocol::CancelLoginChatGptParams; @@ -15,6 +13,8 @@ use codex_app_server_protocol::RequestId; use codex_core::auth::AuthCredentialsStoreMode; use codex_login::login_with_api_key; use serial_test::serial; +use std::path::Path; +use std::time::Duration; use tempfile::TempDir; use tokio::time::timeout; @@ -43,37 +43,26 @@ stream_max_retries = 0 } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn logout_chatgpt_removes_auth() { - let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}")); - create_config_toml(codex_home.path()).expect("write config.toml"); +async fn logout_chatgpt_removes_auth() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path())?; login_with_api_key( codex_home.path(), "sk-test-key", AuthCredentialsStoreMode::File, - ) - .expect("seed api key"); + )?; assert!(codex_home.path().join("auth.json").exists()); - let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - let id = mcp - .send_logout_chat_gpt_request() - .await - .expect("send logoutChatGpt"); + let id = mcp.send_logout_chat_gpt_request().await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(id)), ) - .await - .expect("logoutChatGpt timeout") - .expect("logoutChatGpt response"); - let _ok: LogoutChatGptResponse = to_response(resp).expect("deserialize logout response"); + .await??; + let _ok: LogoutChatGptResponse = to_response(resp)?; assert!( !codex_home.path().join("auth.json").exists(), @@ -86,63 +75,47 @@ async fn logout_chatgpt_removes_auth() { include_token: Some(true), refresh_token: Some(false), }) - .await - .expect("send getAuthStatus"); + .await?; let status_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(status_id)), ) - .await - .expect("getAuthStatus timeout") - .expect("getAuthStatus response"); - let status: GetAuthStatusResponse = to_response(status_resp).expect("deserialize status"); + .await??; + let status: GetAuthStatusResponse = to_response(status_resp)?; assert_eq!(status.auth_method, None); assert_eq!(status.auth_token, None); + Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] // Serialize tests that launch the login server since it binds to a fixed port. #[serial(login_port)] -async fn login_and_cancel_chatgpt() { - let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}")); - create_config_toml(codex_home.path()).unwrap_or_else(|err| panic!("write config.toml: {err}")); +async fn login_and_cancel_chatgpt() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path())?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - let login_id = mcp - .send_login_chat_gpt_request() - .await - .expect("send loginChatGpt"); + let login_id = mcp.send_login_chat_gpt_request().await?; let login_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(login_id)), ) - .await - .expect("loginChatGpt timeout") - .expect("loginChatGpt response"); - let login: LoginChatGptResponse = to_response(login_resp).expect("deserialize login resp"); + .await??; + let login: LoginChatGptResponse = to_response(login_resp)?; let cancel_id = mcp .send_cancel_login_chat_gpt_request(CancelLoginChatGptParams { login_id: login.login_id, }) - .await - .expect("send cancelLoginChatGpt"); + .await?; let cancel_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(cancel_id)), ) - .await - .expect("cancelLoginChatGpt timeout") - .expect("cancelLoginChatGpt response"); - let _ok: CancelLoginChatGptResponse = - to_response(cancel_resp).expect("deserialize cancel response"); + .await??; + let _ok: CancelLoginChatGptResponse = to_response(cancel_resp)?; // Optionally observe the completion notification; do not fail if it races. let maybe_note = timeout( @@ -153,6 +126,7 @@ async fn login_and_cancel_chatgpt() { if maybe_note.is_err() { eprintln!("warning: did not observe login_chat_gpt_complete notification after cancel"); } + Ok(()) } fn create_config_toml_forced_login(codex_home: &Path, forced_method: &str) -> std::io::Result<()> { @@ -185,68 +159,48 @@ forced_chatgpt_workspace_id = "{workspace_id}" } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn login_chatgpt_rejected_when_forced_api() { - let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}")); - create_config_toml_forced_login(codex_home.path(), "api") - .unwrap_or_else(|err| panic!("write config.toml: {err}")); +async fn login_chatgpt_rejected_when_forced_api() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml_forced_login(codex_home.path(), "api")?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - let request_id = mcp - .send_login_chat_gpt_request() - .await - .expect("send loginChatGpt"); + let request_id = mcp.send_login_chat_gpt_request().await?; let err: JSONRPCError = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_error_message(RequestId::Integer(request_id)), ) - .await - .expect("loginChatGpt error timeout") - .expect("loginChatGpt error"); + .await??; assert_eq!( err.error.message, "ChatGPT login is disabled. Use API key login instead." ); + Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] // Serialize tests that launch the login server since it binds to a fixed port. #[serial(login_port)] -async fn login_chatgpt_includes_forced_workspace_query_param() { - let codex_home = TempDir::new().unwrap_or_else(|e| panic!("create tempdir: {e}")); - create_config_toml_forced_workspace(codex_home.path(), "ws-forced") - .unwrap_or_else(|err| panic!("write config.toml: {err}")); +async fn login_chatgpt_includes_forced_workspace_query_param() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml_forced_workspace(codex_home.path(), "ws-forced")?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - let request_id = mcp - .send_login_chat_gpt_request() - .await - .expect("send loginChatGpt"); + let request_id = mcp.send_login_chat_gpt_request().await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .expect("loginChatGpt timeout") - .expect("loginChatGpt response"); + .await??; - let login: LoginChatGptResponse = to_response(resp).expect("deserialize login resp"); + let login: LoginChatGptResponse = to_response(resp)?; assert!( login.auth_url.contains("allowed_workspace_id=ws-forced"), "auth URL should include forced workspace" ); + Ok(()) } diff --git a/codex-rs/app-server/tests/suite/rate_limits.rs b/codex-rs/app-server/tests/suite/rate_limits.rs index 65e92946..16cbf153 100644 --- a/codex-rs/app-server/tests/suite/rate_limits.rs +++ b/codex-rs/app-server/tests/suite/rate_limits.rs @@ -1,4 +1,3 @@ -use anyhow::Context; use anyhow::Result; use app_test_support::ChatGptAuthFixture; use app_test_support::McpProcess; @@ -29,28 +28,18 @@ const INVALID_REQUEST_ERROR_CODE: i64 = -32600; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_account_rate_limits_requires_auth() -> Result<()> { - let codex_home = TempDir::new().context("create codex home tempdir")?; + let codex_home = TempDir::new()?; - let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]) - .await - .context("spawn mcp process")?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .context("initialize timeout")? - .context("initialize request")?; + let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - let request_id = mcp - .send_get_account_rate_limits_request() - .await - .context("send account/rateLimits/read")?; + let request_id = mcp.send_get_account_rate_limits_request().await?; let error: JSONRPCError = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_error_message(RequestId::Integer(request_id)), ) - .await - .context("account/rateLimits/read timeout")? - .context("account/rateLimits/read error")?; + .await??; assert_eq!(error.id, RequestId::Integer(request_id)); assert_eq!(error.error.code, INVALID_REQUEST_ERROR_CODE); @@ -64,30 +53,20 @@ async fn get_account_rate_limits_requires_auth() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_account_rate_limits_requires_chatgpt_auth() -> Result<()> { - let codex_home = TempDir::new().context("create codex home tempdir")?; + let codex_home = TempDir::new()?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .context("spawn mcp process")?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .context("initialize timeout")? - .context("initialize request")?; + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; login_with_api_key(&mut mcp, "sk-test-key").await?; - let request_id = mcp - .send_get_account_rate_limits_request() - .await - .context("send account/rateLimits/read")?; + let request_id = mcp.send_get_account_rate_limits_request().await?; let error: JSONRPCError = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_error_message(RequestId::Integer(request_id)), ) - .await - .context("account/rateLimits/read timeout")? - .context("account/rateLimits/read error")?; + .await??; assert_eq!(error.id, RequestId::Integer(request_id)); assert_eq!(error.error.code, INVALID_REQUEST_ERROR_CODE); @@ -101,19 +80,18 @@ async fn get_account_rate_limits_requires_chatgpt_auth() -> Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_account_rate_limits_returns_snapshot() -> Result<()> { - let codex_home = TempDir::new().context("create codex home tempdir")?; + let codex_home = TempDir::new()?; write_chatgpt_auth( codex_home.path(), ChatGptAuthFixture::new("chatgpt-token") .account_id("account-123") .plan_type("pro"), AuthCredentialsStoreMode::File, - ) - .context("write chatgpt auth")?; + )?; let server = MockServer::start().await; let server_url = server.uri(); - write_chatgpt_base_url(codex_home.path(), &server_url).context("write chatgpt base url")?; + write_chatgpt_base_url(codex_home.path(), &server_url)?; let primary_reset_timestamp = chrono::DateTime::parse_from_rfc3339("2025-01-01T00:02:00Z") .expect("parse primary reset timestamp") @@ -149,29 +127,18 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> { .mount(&server) .await; - let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]) - .await - .context("spawn mcp process")?; - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .context("initialize timeout")? - .context("initialize request")?; + let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - let request_id = mcp - .send_get_account_rate_limits_request() - .await - .context("send account/rateLimits/read")?; + let request_id = mcp.send_get_account_rate_limits_request().await?; let response: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .context("account/rateLimits/read timeout")? - .context("account/rateLimits/read response")?; + .await??; - let received: GetAccountRateLimitsResponse = - to_response(response).context("deserialize rate limit response")?; + let received: GetAccountRateLimitsResponse = to_response(response)?; let expected = GetAccountRateLimitsResponse { rate_limits: RateLimitSnapshot { @@ -197,16 +164,13 @@ async fn login_with_api_key(mcp: &mut McpProcess, api_key: &str) -> Result<()> { .send_login_api_key_request(LoginApiKeyParams { api_key: api_key.to_string(), }) - .await - .context("send loginApiKey")?; + .await?; timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .context("loginApiKey timeout")? - .context("loginApiKey response")?; + .await??; Ok(()) } diff --git a/codex-rs/app-server/tests/suite/send_message.rs b/codex-rs/app-server/tests/suite/send_message.rs index b3f04c33..8d4e508c 100644 --- a/codex-rs/app-server/tests/suite/send_message.rs +++ b/codex-rs/app-server/tests/suite/send_message.rs @@ -1,5 +1,4 @@ -use std::path::Path; - +use anyhow::Result; use app_test_support::McpProcess; use app_test_support::create_final_assistant_message_sse_response; use app_test_support::create_mock_chat_completions_server; @@ -18,49 +17,42 @@ use codex_protocol::ConversationId; use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; use pretty_assertions::assert_eq; +use std::path::Path; use tempfile::TempDir; use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); #[tokio::test] -async fn test_send_message_success() { +async fn test_send_message_success() -> Result<()> { // Spin up a mock completions server that immediately ends the Codex turn. // Two Codex turns hit the mock model (session start + send-user-message). Provide two SSE responses. let responses = vec![ - create_final_assistant_message_sse_response("Done").expect("build mock assistant message"), - create_final_assistant_message_sse_response("Done").expect("build mock assistant message"), + create_final_assistant_message_sse_response("Done")?, + create_final_assistant_message_sse_response("Done")?, ]; let server = create_mock_chat_completions_server(responses).await; // Create a temporary Codex home with config pointing at the mock server. - let codex_home = TempDir::new().expect("create temp dir"); - create_config_toml(codex_home.path(), &server.uri()).expect("write config.toml"); + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; // Start MCP server process and initialize. - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timed out") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; // Start a conversation using the new wire API. let new_conv_id = mcp .send_new_conversation_request(NewConversationParams::default()) - .await - .expect("send newConversation"); + .await?; let new_conv_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), ) - .await - .expect("newConversation timeout") - .expect("newConversation resp"); + .await??; let NewConversationResponse { conversation_id, .. - } = to_response::<_>(new_conv_resp).expect("deserialize newConversation response"); + } = to_response::<_>(new_conv_resp)?; // 2) addConversationListener let add_listener_id = mcp @@ -68,25 +60,27 @@ async fn test_send_message_success() { conversation_id, experimental_raw_events: false, }) - .await - .expect("send addConversationListener"); + .await?; let add_listener_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), ) - .await - .expect("addConversationListener timeout") - .expect("addConversationListener resp"); + .await??; let AddConversationSubscriptionResponse { subscription_id: _ } = - to_response::<_>(add_listener_resp).expect("deserialize addConversationListener response"); + to_response::<_>(add_listener_resp)?; // Now exercise sendUserMessage twice. - send_message("Hello", conversation_id, &mut mcp).await; - send_message("Hello again", conversation_id, &mut mcp).await; + send_message("Hello", conversation_id, &mut mcp).await?; + send_message("Hello again", conversation_id, &mut mcp).await?; + Ok(()) } #[expect(clippy::expect_used)] -async fn send_message(message: &str, conversation_id: ConversationId, mcp: &mut McpProcess) { +async fn send_message( + message: &str, + conversation_id: ConversationId, + mcp: &mut McpProcess, +) -> Result<()> { // Now exercise sendUserMessage. let send_id = mcp .send_send_user_message_request(SendUserMessageParams { @@ -95,19 +89,15 @@ async fn send_message(message: &str, conversation_id: ConversationId, mcp: &mut text: message.to_string(), }], }) - .await - .expect("send sendUserMessage"); + .await?; let response: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(send_id)), ) - .await - .expect("sendUserMessage response timeout") - .expect("sendUserMessage response error"); + .await??; - let _ok: SendUserMessageResponse = to_response::(response) - .expect("deserialize sendUserMessage response"); + let _ok: SendUserMessageResponse = to_response::(response)?; // Verify the task_finished notification is received. // Note this also ensures that the final request to the server was made. @@ -115,9 +105,7 @@ async fn send_message(message: &str, conversation_id: ConversationId, mcp: &mut DEFAULT_READ_TIMEOUT, mcp.read_stream_until_notification_message("codex/event/task_complete"), ) - .await - .expect("task_finished_notification timeout") - .expect("task_finished_notification resp"); + .await??; let serde_json::Value::Object(map) = task_finished_notification .params .expect("notification should have params") @@ -139,57 +127,45 @@ async fn send_message(message: &str, conversation_id: ConversationId, mcp: &mut raw_attempt.is_err(), "unexpected raw item notification when not opted in" ); + Ok(()) } #[tokio::test] -async fn test_send_message_raw_notifications_opt_in() { - let responses = vec![ - create_final_assistant_message_sse_response("Done").expect("build mock assistant message"), - ]; +async fn test_send_message_raw_notifications_opt_in() -> Result<()> { + let responses = vec![create_final_assistant_message_sse_response("Done")?]; let server = create_mock_chat_completions_server(responses).await; - let codex_home = TempDir::new().expect("create temp dir"); - create_config_toml(codex_home.path(), &server.uri()).expect("write config.toml"); + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timed out") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; let new_conv_id = mcp .send_new_conversation_request(NewConversationParams::default()) - .await - .expect("send newConversation"); + .await?; let new_conv_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(new_conv_id)), ) - .await - .expect("newConversation timeout") - .expect("newConversation resp"); + .await??; let NewConversationResponse { conversation_id, .. - } = to_response::<_>(new_conv_resp).expect("deserialize newConversation response"); + } = to_response::<_>(new_conv_resp)?; let add_listener_id = mcp .send_add_conversation_listener_request(AddConversationListenerParams { conversation_id, experimental_raw_events: true, }) - .await - .expect("send addConversationListener"); + .await?; let add_listener_resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), ) - .await - .expect("addConversationListener timeout") - .expect("addConversationListener resp"); + .await??; let AddConversationSubscriptionResponse { subscription_id: _ } = - to_response::<_>(add_listener_resp).expect("deserialize addConversationListener response"); + to_response::<_>(add_listener_resp)?; let send_id = mcp .send_send_user_message_request(SendUserMessageParams { @@ -198,8 +174,7 @@ async fn test_send_message_raw_notifications_opt_in() { text: "Hello".to_string(), }], }) - .await - .expect("send sendUserMessage"); + .await?; let instructions = read_raw_response_item(&mut mcp, conversation_id).await; assert_instructions_message(&instructions); @@ -211,11 +186,8 @@ async fn test_send_message_raw_notifications_opt_in() { DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(send_id)), ) - .await - .expect("sendUserMessage response timeout") - .expect("sendUserMessage response error"); - let _ok: SendUserMessageResponse = to_response::(response) - .expect("deserialize sendUserMessage response"); + .await??; + let _ok: SendUserMessageResponse = to_response::(response)?; let user_message = read_raw_response_item(&mut mcp, conversation_id).await; assert_user_message(&user_message, "Hello"); @@ -228,17 +200,16 @@ async fn test_send_message_raw_notifications_opt_in() { mcp.read_stream_until_notification_message("codex/event/task_complete"), ) .await; + + Ok(()) } #[tokio::test] -async fn test_send_message_session_not_found() { +async fn test_send_message_session_not_found() -> Result<()> { // Start MCP without creating a Codex session - let codex_home = TempDir::new().expect("tempdir"); - let mut mcp = McpProcess::new(codex_home.path()).await.expect("spawn"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("timeout") - .expect("init"); + let codex_home = TempDir::new()?; + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; let unknown = ConversationId::new(); let req_id = mcp @@ -248,18 +219,16 @@ async fn test_send_message_session_not_found() { text: "ping".to_string(), }], }) - .await - .expect("send sendUserMessage"); + .await?; // Expect an error response for unknown conversation. let err = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_error_message(RequestId::Integer(req_id)), ) - .await - .expect("timeout") - .expect("error"); + .await??; assert_eq!(err.id, RequestId::Integer(req_id)); + Ok(()) } // --------------------------------------------------------------------------- diff --git a/codex-rs/app-server/tests/suite/set_default_model.rs b/codex-rs/app-server/tests/suite/set_default_model.rs index 6769faa9..0c2aa229 100644 --- a/codex-rs/app-server/tests/suite/set_default_model.rs +++ b/codex-rs/app-server/tests/suite/set_default_model.rs @@ -1,5 +1,4 @@ -use std::path::Path; - +use anyhow::Result; use app_test_support::McpProcess; use app_test_support::to_response; use codex_app_server_protocol::JSONRPCResponse; @@ -8,50 +7,38 @@ use codex_app_server_protocol::SetDefaultModelParams; use codex_app_server_protocol::SetDefaultModelResponse; use codex_core::config::ConfigToml; use pretty_assertions::assert_eq; +use std::path::Path; use tempfile::TempDir; use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn set_default_model_persists_overrides() { - let codex_home = TempDir::new().expect("create tempdir"); - create_config_toml(codex_home.path()).expect("write config.toml"); +async fn set_default_model_persists_overrides() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path())?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("init timeout") - .expect("init failed"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; let params = SetDefaultModelParams { model: Some("gpt-4.1".to_string()), reasoning_effort: None, }; - let request_id = mcp - .send_set_default_model_request(params) - .await - .expect("send setDefaultModel"); + let request_id = mcp.send_set_default_model_request(params).await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .expect("setDefaultModel timeout") - .expect("setDefaultModel response"); + .await??; - let _: SetDefaultModelResponse = - to_response(resp).expect("deserialize setDefaultModel response"); + let _: SetDefaultModelResponse = to_response(resp)?; let config_path = codex_home.path().join("config.toml"); - let config_contents = tokio::fs::read_to_string(&config_path) - .await - .expect("read config.toml"); - let config_toml: ConfigToml = toml::from_str(&config_contents).expect("parse config.toml"); + let config_contents = tokio::fs::read_to_string(&config_path).await?; + let config_toml: ConfigToml = toml::from_str(&config_contents)?; assert_eq!( ConfigToml { @@ -61,6 +48,7 @@ async fn set_default_model_persists_overrides() { }, config_toml, ); + Ok(()) } // Helper to create a config.toml; mirrors create_conversation.rs diff --git a/codex-rs/app-server/tests/suite/user_agent.rs b/codex-rs/app-server/tests/suite/user_agent.rs index 95a0b1a3..52ba6e56 100644 --- a/codex-rs/app-server/tests/suite/user_agent.rs +++ b/codex-rs/app-server/tests/suite/user_agent.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use app_test_support::McpProcess; use app_test_support::to_response; use codex_app_server_protocol::GetUserAgentResponse; @@ -10,28 +11,18 @@ use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_user_agent_returns_current_codex_user_agent() { - let codex_home = TempDir::new().unwrap_or_else(|err| panic!("create tempdir: {err}")); +async fn get_user_agent_returns_current_codex_user_agent() -> Result<()> { + let codex_home = TempDir::new()?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("initialize timeout") - .expect("initialize request"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - let request_id = mcp - .send_get_user_agent_request() - .await - .expect("send getUserAgent"); + let request_id = mcp.send_get_user_agent_request().await?; let response: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .expect("getUserAgent timeout") - .expect("getUserAgent response"); + .await??; let os_info = os_info::get(); let user_agent = format!( @@ -42,9 +33,9 @@ async fn get_user_agent_returns_current_codex_user_agent() { codex_core::terminal::user_agent() ); - let received: GetUserAgentResponse = - to_response(response).expect("deserialize getUserAgent response"); + let received: GetUserAgentResponse = to_response(response)?; let expected = GetUserAgentResponse { user_agent }; assert_eq!(received, expected); + Ok(()) } diff --git a/codex-rs/app-server/tests/suite/user_info.rs b/codex-rs/app-server/tests/suite/user_info.rs index 849a22ac..6a44f1a3 100644 --- a/codex-rs/app-server/tests/suite/user_info.rs +++ b/codex-rs/app-server/tests/suite/user_info.rs @@ -1,5 +1,4 @@ -use std::time::Duration; - +use anyhow::Result; use app_test_support::ChatGptAuthFixture; use app_test_support::McpProcess; use app_test_support::to_response; @@ -9,14 +8,15 @@ use codex_app_server_protocol::RequestId; use codex_app_server_protocol::UserInfoResponse; use codex_core::auth::AuthCredentialsStoreMode; use pretty_assertions::assert_eq; +use std::time::Duration; use tempfile::TempDir; use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(10); #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn user_info_returns_email_from_auth_json() { - let codex_home = TempDir::new().expect("create tempdir"); +async fn user_info_returns_email_from_auth_json() -> Result<()> { + let codex_home = TempDir::new()?; write_chatgpt_auth( codex_home.path(), @@ -24,30 +24,23 @@ async fn user_info_returns_email_from_auth_json() { .refresh_token("refresh") .email("user@example.com"), AuthCredentialsStoreMode::File, - ) - .expect("write chatgpt auth"); + )?; - let mut mcp = McpProcess::new(codex_home.path()) - .await - .expect("spawn mcp process"); - timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) - .await - .expect("initialize timeout") - .expect("initialize request"); + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; - let request_id = mcp.send_user_info_request().await.expect("send userInfo"); + let request_id = mcp.send_user_info_request().await?; let response: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, mcp.read_stream_until_response_message(RequestId::Integer(request_id)), ) - .await - .expect("userInfo timeout") - .expect("userInfo response"); + .await??; - let received: UserInfoResponse = to_response(response).expect("deserialize userInfo response"); + let received: UserInfoResponse = to_response(response)?; let expected = UserInfoResponse { alleged_user_email: Some("user@example.com".to_string()), }; assert_eq!(received, expected); + Ok(()) }