mod common; use std::sync::Arc; use std::thread; use std::time::{Duration, Instant}; #[test] fn test_concurrent_requests_multiple_hosts() { let temp_dir = common::setup_test_environment(); // Create content for multiple hosts let hosts = vec!["site1.com", "site2.org", "site3.net"]; let mut host_roots = Vec::new(); for host in &hosts { let root_dir = temp_dir.path().join(host.replace(".", "_")); std::fs::create_dir(&root_dir).unwrap(); std::fs::write( root_dir.join("index.gmi"), format!("Welcome to {}", host), ).unwrap(); host_roots.push(root_dir); } // Create config with multiple hosts let config_path = temp_dir.path().join("config.toml"); let port = 1969 + (std::process::id() % 1000) as u16; let mut config_content = format!(r#" bind_host = "127.0.0.1" port = {} "#, port); for (i, host) in hosts.iter().enumerate() { config_content.push_str(&format!(r#" ["{}"] root = "{}" cert = "{}" key = "{}" "#, host, host_roots[i].display(), temp_dir.path().join("cert.pem").display(), temp_dir.path().join("key.pem").display())); } std::fs::write(&config_path, config_content).unwrap(); // Start server with TLS let mut server_process = std::process::Command::new(env!("CARGO_BIN_EXE_pollux")) .arg("--config") .arg(&config_path) .spawn() .unwrap(); std::thread::sleep(Duration::from_millis(500)); // Spawn multiple threads making concurrent requests let mut handles = Vec::new(); let port_arc = Arc::new(port); for i in 0..10 { let host = hosts[i % hosts.len()].to_string(); let port_clone = Arc::clone(&port_arc); let handle = thread::spawn(move || { let response = make_gemini_request("127.0.0.1", *port_clone, &format!("gemini://{}/", host)); assert!(response.starts_with("20"), "Request {} failed: {}", i, response); assert!(response.contains(&format!("Welcome to {}", host)), "Wrong content for request {}: {}", i, response); response }); handles.push(handle); } // Collect results let mut results = Vec::new(); for handle in handles { results.push(handle.join().unwrap()); } assert_eq!(results.len(), 10, "All concurrent requests should complete"); server_process.kill().unwrap(); } #[test] fn test_mixed_valid_invalid_hostnames() { let temp_dir = common::setup_test_environment(); // Create content for one valid host let root_dir = temp_dir.path().join("valid_site"); std::fs::create_dir(&root_dir).unwrap(); std::fs::write(root_dir.join("index.gmi"), "Valid site content").unwrap(); // Create config let config_path = temp_dir.path().join("config.toml"); let port = 1970 + (std::process::id() % 1000) as u16; let config_content = format!(r#" bind_host = "127.0.0.1" port = {} ["valid.com"] root = "{}" cert = "{}" key = "{}" "#, port, root_dir.display(), temp_dir.path().join("cert.pem").display(), temp_dir.path().join("key.pem").display()); std::fs::write(&config_path, config_content).unwrap(); // Start server with TLS let mut server_process = std::process::Command::new(env!("CARGO_BIN_EXE_pollux")) .arg("--config") .arg(&config_path) .spawn() .unwrap(); std::thread::sleep(Duration::from_millis(500)); // Test valid hostname let valid_response = make_gemini_request("127.0.0.1", port, "gemini://valid.com/"); assert!(valid_response.starts_with("20"), "Valid host should work: {}", valid_response); assert!(valid_response.contains("Valid site content"), "Should serve correct content: {}", valid_response); // Test various invalid hostnames let invalid_hosts = vec![ "invalid.com", "unknown.net", "nonexistent.invalid", "site.with.dots.com", ]; for invalid_host in invalid_hosts { let response = make_gemini_request("127.0.0.1", port, &format!("gemini://{}/", invalid_host)); assert!(response.starts_with("53"), "Invalid host '{}' should return 53, got: {}", invalid_host, response); } server_process.kill().unwrap(); } #[test] fn test_load_performance_basic() { let temp_dir = common::setup_test_environment(); // Create a simple host let root_dir = temp_dir.path().join("perf_test"); std::fs::create_dir(&root_dir).unwrap(); std::fs::write(root_dir.join("index.gmi"), "Performance test content").unwrap(); let config_path = temp_dir.path().join("config.toml"); let port = 1971 + (std::process::id() % 1000) as u16; let config_content = format!(r#" bind_host = "127.0.0.1" port = {} ["perf.com"] root = "{}" cert = "{}" key = "{}" "#, port, root_dir.display(), temp_dir.path().join("cert.pem").display(), temp_dir.path().join("key.pem").display()); std::fs::write(&config_path, config_content).unwrap(); // Start server with TLS let mut server_process = std::process::Command::new(env!("CARGO_BIN_EXE_pollux")) .arg("--config") .arg(&config_path) .spawn() .unwrap(); std::thread::sleep(Duration::from_millis(500)); // Measure time for multiple requests let start = Instant::now(); const NUM_REQUESTS: usize = 50; for i in 0..NUM_REQUESTS { let response = make_gemini_request("127.0.0.1", port, "gemini://perf.com/"); assert!(response.starts_with("20"), "Request {} failed: {}", i, response); } let elapsed = start.elapsed(); let avg_time = elapsed.as_millis() as f64 / NUM_REQUESTS as f64; println!("Processed {} requests in {:?} (avg: {:.2}ms per request)", NUM_REQUESTS, elapsed, avg_time); // Basic performance check - should be reasonably fast assert!(avg_time < 100.0, "Average request time too slow: {:.2}ms", avg_time); server_process.kill().unwrap(); } #[test] fn test_full_request_lifecycle() { let temp_dir = common::setup_test_environment(); // Create complex content structure let root_dir = temp_dir.path().join("lifecycle_test"); std::fs::create_dir(&root_dir).unwrap(); // Create directory with index let blog_dir = root_dir.join("blog"); std::fs::create_dir(&blog_dir).unwrap(); std::fs::write(blog_dir.join("index.gmi"), "Blog index content").unwrap(); // Create individual file std::fs::write(root_dir.join("about.gmi"), "About page content").unwrap(); // Create root index std::fs::write(root_dir.join("index.gmi"), "Main site content").unwrap(); let config_path = temp_dir.path().join("config.toml"); let port = 1972 + (std::process::id() % 1000) as u16; let cert_path = temp_dir.path().join("cert.pem"); let key_path = temp_dir.path().join("key.pem"); let config_content = format!(r#" bind_host = "127.0.0.1" port = {} ["lifecycle.com"] root = "{}" cert = "{}" key = "{}" "#, port, root_dir.display(), cert_path.display(), key_path.display()); std::fs::write(&config_path, config_content).unwrap(); // Start server with TLS let mut server_process = std::process::Command::new(env!("CARGO_BIN_EXE_pollux")) .arg("--config") .arg(&config_path) .spawn() .unwrap(); std::thread::sleep(Duration::from_millis(500)); // Test root index let root_response = make_gemini_request("127.0.0.1", port, "gemini://lifecycle.com/"); assert!(root_response.starts_with("20"), "Root request failed: {}", root_response); assert!(root_response.contains("Main site content"), "Wrong root content: {}", root_response); // Test explicit index let index_response = make_gemini_request("127.0.0.1", port, "gemini://lifecycle.com/index.gmi"); assert!(index_response.starts_with("20"), "Index request failed: {}", index_response); assert!(index_response.contains("Main site content"), "Wrong index content: {}", index_response); // Test subdirectory index let blog_response = make_gemini_request("127.0.0.1", port, "gemini://lifecycle.com/blog/"); assert!(blog_response.starts_with("20"), "Blog request failed: {}", blog_response); assert!(blog_response.contains("Blog index content"), "Wrong blog content: {}", blog_response); // Test individual file let about_response = make_gemini_request("127.0.0.1", port, "gemini://lifecycle.com/about.gmi"); assert!(about_response.starts_with("20"), "About request failed: {}", about_response); assert!(about_response.contains("About page content"), "Wrong about content: {}", about_response); // Test not found let notfound_response = make_gemini_request("127.0.0.1", port, "gemini://lifecycle.com/nonexistent.gmi"); assert!(notfound_response.starts_with("51"), "Not found should return 51: {}", notfound_response); server_process.kill().unwrap(); } fn make_gemini_request(host: &str, port: u16, url: &str) -> String { // Use the Python client for TLS requests use std::process::Command; let output = Command::new("python3") .arg("tests/gemini_test_client.py") .arg(url) .env("GEMINI_PORT", &port.to_string()) .env("GEMINI_CONNECT_HOST", host) .output(); match output { Ok(result) => { if result.status.success() { String::from_utf8_lossy(&result.stdout).trim().to_string() } else { format!("Error: Python client failed with status {}", result.status) } } Err(e) => format!("Error: Failed to run Python client: {}", e), } }