| #![cfg(unix)] |
|
|
| mod common; |
|
|
| use codex_exec_server::InitializeParams; |
| use codex_exec_server::InitializeResponse; |
| use codex_exec_server_protocol::JSONRPCError; |
| use codex_exec_server_protocol::JSONRPCMessage; |
| use codex_exec_server_protocol::JSONRPCResponse; |
| use common::exec_server::exec_server; |
| use pretty_assertions::assert_eq; |
| use tokio_tungstenite::connect_async; |
| use tokio_tungstenite::tungstenite::Error as WebSocketError; |
| use tokio_tungstenite::tungstenite::client::IntoClientRequest; |
| use tokio_tungstenite::tungstenite::http::HeaderValue; |
| use tokio_tungstenite::tungstenite::http::StatusCode; |
| use tokio_tungstenite::tungstenite::http::header::ORIGIN; |
| use uuid::Uuid; |
|
|
| #[tokio::test(flavor = "multi_thread", worker_threads = 2)] |
| async fn exec_server_reports_malformed_websocket_json_and_keeps_running() -> anyhow::Result<()> { |
| let mut server = exec_server().await?; |
| server.send_raw_text("not-json").await?; |
|
|
| let response = server |
| .wait_for_event(|event| matches!(event, JSONRPCMessage::Error(_))) |
| .await?; |
| let JSONRPCMessage::Error(JSONRPCError { id, error }) = response else { |
| panic!("expected malformed-message error response"); |
| }; |
| assert_eq!(id, codex_exec_server_protocol::RequestId::Integer(-1)); |
| assert_eq!(error.code, -32600); |
| assert!( |
| error |
| .message |
| .starts_with("failed to parse websocket JSON-RPC message from exec-server websocket"), |
| "unexpected malformed-message error: {}", |
| error.message |
| ); |
|
|
| let initialize_id = server |
| .send_request( |
| "initialize", |
| serde_json::to_value(InitializeParams { |
| client_name: "exec-server-test".to_string(), |
| resume_session_id: None, |
| })?, |
| ) |
| .await?; |
|
|
| let response = server |
| .wait_for_event(|event| { |
| matches!( |
| event, |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &initialize_id |
| ) |
| }) |
| .await?; |
| let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else { |
| panic!("expected initialize response after malformed input"); |
| }; |
| assert_eq!(id, initialize_id); |
| let initialize_response: InitializeResponse = serde_json::from_value(result)?; |
| Uuid::parse_str(&initialize_response.session_id)?; |
|
|
| server.shutdown().await?; |
| Ok(()) |
| } |
|
|
| #[tokio::test(flavor = "multi_thread", worker_threads = 2)] |
| async fn exec_server_accepts_binary_websocket_json() -> anyhow::Result<()> { |
| let mut server = exec_server().await?; |
| let initialize_id = codex_exec_server_protocol::RequestId::Integer(1); |
| let initialize = JSONRPCMessage::Request(codex_exec_server_protocol::JSONRPCRequest { |
| id: initialize_id.clone(), |
| method: "initialize".to_string(), |
| params: Some(serde_json::to_value(InitializeParams { |
| client_name: "exec-server-binary-test".to_string(), |
| resume_session_id: None, |
| })?), |
| trace: None, |
| }); |
| server |
| .send_raw_binary(serde_json::to_vec(&initialize)?) |
| .await?; |
|
|
| let response = server |
| .wait_for_event(|event| { |
| matches!( |
| event, |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &initialize_id |
| ) |
| }) |
| .await?; |
| let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else { |
| panic!("expected initialize response for binary input"); |
| }; |
| assert_eq!(id, initialize_id); |
| let initialize_response: InitializeResponse = serde_json::from_value(result)?; |
| Uuid::parse_str(&initialize_response.session_id)?; |
|
|
| server.shutdown().await?; |
| Ok(()) |
| } |
|
|
| #[tokio::test(flavor = "multi_thread", worker_threads = 2)] |
| async fn exec_server_rejects_browser_origin_websocket_handshake() -> anyhow::Result<()> { |
| let mut server = exec_server().await?; |
| let mut request = server.websocket_url().into_client_request()?; |
| request |
| .headers_mut() |
| .insert(ORIGIN, HeaderValue::from_static("https://evil.example")); |
|
|
| let error = match connect_async(request).await { |
| Ok(_) => anyhow::bail!("browser-origin websocket handshake should be rejected"), |
| Err(error) => error, |
| }; |
| let WebSocketError::Http(response) = error else { |
| anyhow::bail!("browser-origin websocket handshake failed unexpectedly: {error}"); |
| }; |
| assert_eq!(response.status(), StatusCode::FORBIDDEN); |
|
|
| server.shutdown().await?; |
| Ok(()) |
| } |
|
|