use tokio::time::Duration; // Removed unused sleep use futures_util::{sink::SinkExt, stream::StreamExt}; use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; use serde_json::Value; // Removed unused json macro import use std::process::Command; use std::thread; use std::sync::Once; // Define a simple JSON-RPC request structure for sending scripts #[derive(serde::Serialize, Debug)] struct JsonRpcRequest { jsonrpc: String, method: String, params: ScriptParams, id: u64, } #[derive(serde::Serialize, Debug)] struct ScriptParams { script: String, } // Define a simple JSON-RPC error response structure for assertion #[derive(serde::Deserialize, Debug)] struct JsonRpcErrorResponse { _jsonrpc: String, // Field is present in response, but not used in assert error: JsonRpcErrorDetails, _id: Option, // Field is present in response, but not used in assert } #[derive(serde::Deserialize, Debug)] struct JsonRpcErrorDetails { code: i32, message: String, } const SERVER_ADDRESS: &str = "ws://127.0.0.1:8088/ws"; // Match port in main.rs or make configurable const TEST_CIRCLE_NAME: &str = "test_timeout_circle"; const SERVER_STARTUP_TIME: Duration = Duration::from_secs(5); // Time to wait for server to start const RHAI_TIMEOUT_SECONDS: u64 = 30; // Should match TASK_TIMEOUT_DURATION in circle_server_ws static START_SERVER: Once = Once::new(); fn ensure_server_is_running() { START_SERVER.call_once(|| { println!("Attempting to start circle_server_ws for integration tests..."); // The server executable will be in target/debug relative to the crate root let server_executable = "target/debug/circle_server_ws"; thread::spawn(move || { let mut child = Command::new(server_executable) .arg("--port=8088") // Use a specific port for testing .arg(format!("--circle-name={}", TEST_CIRCLE_NAME)) .spawn() .expect("Failed to start circle_server_ws. Make sure it's compiled (cargo build)."); let status = child.wait().expect("Failed to wait on server process."); println!("Server process exited with status: {}", status); }); println!("Server start command issued. Waiting for {}s...", SERVER_STARTUP_TIME.as_secs()); thread::sleep(SERVER_STARTUP_TIME); println!("Presumed server started."); }); } #[tokio::test] async fn test_rhai_script_timeout() { ensure_server_is_running(); println!("Connecting to WebSocket server: {}", SERVER_ADDRESS); let (mut ws_stream, _response) = connect_async(SERVER_ADDRESS) .await .expect("Failed to connect to WebSocket server"); println!("Connected to WebSocket server."); // Rhai script designed to run longer than RHAI_TIMEOUT_SECONDS // A large loop should cause a timeout. let long_running_script = format!(" let mut x = 0; for i in 0..999999999 {{ x = x + i; if i % 10000000 == 0 {{ // debug(\"Looping: \" + i); // Optional: for server-side logging if enabled }} }} print(x); // This line will likely not be reached due to timeout "); let request = JsonRpcRequest { jsonrpc: "2.0".to_string(), method: "execute_script".to_string(), params: ScriptParams { script: long_running_script }, id: 1, }; let request_json = serde_json::to_string(&request).expect("Failed to serialize request"); println!("Sending long-running script request: {}", request_json); ws_stream.send(Message::Text(request_json)).await.expect("Failed to send message"); println!("Waiting for response (expecting timeout after ~{}s)..", RHAI_TIMEOUT_SECONDS); // Wait for a response, expecting a timeout error // The server's timeout is RHAI_TIMEOUT_SECONDS, client should wait a bit longer. match tokio::time::timeout(Duration::from_secs(RHAI_TIMEOUT_SECONDS + 15), ws_stream.next()).await { Ok(Some(Ok(Message::Text(text)))) => { println!("Received response: {}", text); let response: Result = serde_json::from_str(&text); match response { Ok(err_resp) => { assert_eq!(err_resp.error.code, -32002, "Error code should indicate timeout."); assert!(err_resp.error.message.contains("timed out"), "Error message should indicate timeout."); println!("Timeout test passed! Received correct timeout error."); } Err(e) => { panic!("Failed to deserialize error response: {}. Raw: {}", e, text); } } } Ok(Some(Ok(other_msg))) => { panic!("Received unexpected message type: {:?}", other_msg); } Ok(Some(Err(e))) => { panic!("WebSocket error: {}", e); } Ok(None) => { panic!("WebSocket stream closed unexpectedly."); } Err(_) => { panic!("Test timed out waiting for server response. Server might not have sent timeout error or took too long."); } } ws_stream.close(None).await.ok(); println!("Test finished, WebSocket closed."); }