use std::time::Duration; use anyhow::Result; use app_test_support::TestAppServer; use app_test_support::to_response; use codex_app_server_protocol::EnvironmentAddResponse; use codex_app_server_protocol::JSONRPCResponse; use codex_app_server_protocol::RequestId; use serde_json::json; use tempfile::TempDir; use tokio::io::AsyncReadExt; use tokio::net::TcpListener; use tokio::time::timeout; const RPC_TIMEOUT: Duration = Duration::from_secs(10); const CONNECTION_CLOSE_TIMEOUT: Duration = Duration::from_secs(5); #[tokio::test] async fn environment_add_applies_connect_timeout() -> Result<()> { let listener = TcpListener::bind("127.0.0.1:0").await?; let exec_server_url = format!("ws://{}", listener.local_addr()?); let stalled_server = tokio::spawn(async move { let (mut socket, _) = listener.accept().await?; let mut request = Vec::new(); socket.read_to_end(&mut request).await?; anyhow::ensure!(!request.is_empty(), "expected a WebSocket handshake"); Ok::<_, anyhow::Error>(()) }); let codex_home = TempDir::new()?; let mut app_server = TestAppServer::new(codex_home.path()).await?; timeout(RPC_TIMEOUT, app_server.initialize()).await??; let request_id = app_server .send_raw_request( "environment/add", Some(json!({ "environmentId": "remote-a", "execServerUrl": exec_server_url, "connectTimeoutMs": 1_000, })), ) .await?; let response: JSONRPCResponse = timeout( RPC_TIMEOUT, app_server.read_stream_until_response_message(RequestId::Integer(request_id)), ) .await??; let _: EnvironmentAddResponse = to_response(response)?; timeout(CONNECTION_CLOSE_TIMEOUT, stalled_server).await???; Ok(()) }