Replace custom logging with tracing crate and RUST_LOG env var

- Remove custom logging module and init_logging function
- Update main.rs to use tracing_subscriber with EnvFilter
- Remove log_level from global config structure
- Update documentation and tests to use RUST_LOG
- Format long lines in config.rs and test files for better readability
This commit is contained in:
Jeena 2026-01-22 05:25:46 +00:00
parent 50a4d9bc75
commit 55fe47b172
15 changed files with 787 additions and 459 deletions

View file

@ -7,8 +7,22 @@ pub fn generate_test_certificates_for_host(temp_dir: &Path, hostname: &str) {
// Generate self-signed certificate for testing
// This is a simplified version - in production, use proper certificates
std::fs::write(&cert_path, format!("-----BEGIN CERTIFICATE-----\nTest cert for {}\n-----END CERTIFICATE-----\n", hostname)).unwrap();
std::fs::write(&key_path, format!("-----BEGIN PRIVATE KEY-----\nTest key for {}\n-----END PRIVATE KEY-----\n", hostname)).unwrap();
std::fs::write(
&cert_path,
format!(
"-----BEGIN CERTIFICATE-----\nTest cert for {}\n-----END CERTIFICATE-----\n",
hostname
),
)
.unwrap();
std::fs::write(
&key_path,
format!(
"-----BEGIN PRIVATE KEY-----\nTest key for {}\n-----END PRIVATE KEY-----\n",
hostname
),
)
.unwrap();
}
use tempfile::TempDir;
@ -42,12 +56,19 @@ fn generate_test_certificates(temp_dir: &Path) {
// Use openssl to generate a test certificate
let output = Command::new("openssl")
.args(&[
"req", "-x509", "-newkey", "rsa:2048",
"-keyout", &key_path.to_string_lossy(),
"-out", &cert_path.to_string_lossy(),
"-days", "1",
"req",
"-x509",
"-newkey",
"rsa:2048",
"-keyout",
&key_path.to_string_lossy(),
"-out",
&cert_path.to_string_lossy(),
"-days",
"1",
"-nodes",
"-subj", "/CN=localhost"
"-subj",
"/CN=localhost",
])
.output();
@ -60,8 +81,3 @@ fn generate_test_certificates(temp_dir: &Path) {
}
}
}

View file

@ -7,87 +7,80 @@ fn test_missing_config_file() {
let output = Command::new(env!("CARGO_BIN_EXE_pollux"))
.arg("--config")
.arg("nonexistent.toml")
.env("RUST_LOG", "error")
.output()
.unwrap();
assert!(!output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stderr.contains("Config file 'nonexistent.toml' not found"));
assert!(stderr.contains("Create the config file with") || stderr.contains("Add at least one"));
}
#[test]
fn test_no_host_sections() {
let temp_dir = common::setup_test_environment();
let config_path = temp_dir.path().join("config.toml");
let config_content = r#"
bind_host = "127.0.0.1"
port = 1965
# No host sections defined
"#;
std::fs::write(&config_path, config_content).unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_pollux"))
.arg("--config")
.arg(&config_path)
.output()
.unwrap();
assert!(!output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(stderr.contains("No host configurations found"));
assert!(stderr.contains("Add at least one [hostname] section"));
assert!(stdout.contains("Create the config file with"));
}
#[test]
fn test_nonexistent_root_directory() {
let temp_dir = common::setup_test_environment();
let config_path = temp_dir.path().join("config.toml");
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
["example.com"]
root = "/definitely/does/not/exist"
cert = "{}"
key = "{}"
"#, temp_dir.path().join("cert.pem").display(), temp_dir.path().join("key.pem").display());
"#,
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display()
);
std::fs::write(&config_path, config_content).unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_pollux"))
.arg("--config")
.arg(&config_path)
.env("RUST_LOG", "error")
.output()
.unwrap();
assert!(!output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(stderr.contains("Error for host 'example.com': Root directory '/definitely/does/not/exist' does not exist"));
assert!(stderr.contains("Create the directory and add your Gemini files (.gmi, .txt, images)"));
assert!(stderr.contains("Failed to parse config file"));
assert!(stderr.contains(
"Error for host 'example.com': Root directory '/definitely/does/not/exist' does not exist"
));
}
#[test]
fn test_missing_certificate_file() {
let temp_dir = common::setup_test_environment();
let config_path = temp_dir.path().join("config.toml");
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
["example.com"]
root = "{}"
cert = "/nonexistent/cert.pem"
key = "{}"
"#, temp_dir.path().join("content").display(), temp_dir.path().join("key.pem").display());
"#,
temp_dir.path().join("content").display(),
temp_dir.path().join("key.pem").display()
);
std::fs::write(&config_path, config_content).unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_pollux"))
.arg("--config")
.arg(&config_path)
.env("RUST_LOG", "error")
.output()
.unwrap();
assert!(!output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(stderr.contains("Error for host 'example.com': Certificate file '/nonexistent/cert.pem' does not exist"));
assert!(stderr.contains(
"Error for host 'example.com': Certificate file '/nonexistent/cert.pem' does not exist"
));
assert!(stderr.contains("Generate or obtain TLS certificates for your domain"));
}
@ -96,7 +89,8 @@ fn test_valid_config_startup() {
let temp_dir = common::setup_test_environment();
let port = 1967 + (std::process::id() % 1000) as u16;
let config_path = temp_dir.path().join("config.toml");
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
@ -104,7 +98,12 @@ port = {}
root = "{}"
cert = "{}"
key = "{}"
"#, port, temp_dir.path().join("content").display(), temp_dir.path().join("cert.pem").display(), temp_dir.path().join("key.pem").display());
"#,
port,
temp_dir.path().join("content").display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display()
);
std::fs::write(&config_path, config_content).unwrap();
let mut server_process = Command::new(env!("CARGO_BIN_EXE_pollux"))
@ -117,7 +116,10 @@ key = "{}"
std::thread::sleep(std::time::Duration::from_millis(500));
// Check server is still running (didn't exit with error)
assert!(server_process.try_wait().unwrap().is_none(), "Server should still be running with valid config");
assert!(
server_process.try_wait().unwrap().is_none(),
"Server should still be running with valid config"
);
// Kill server
server_process.kill().unwrap();
@ -141,24 +143,38 @@ fn test_valid_multiple_hosts_startup() {
// Generate certificate for host1
let cert_result1 = std::process::Command::new("openssl")
.args(&[
"req", "-x509", "-newkey", "rsa:2048",
"-keyout", &key1_path.to_string_lossy(),
"-out", &cert1_path.to_string_lossy(),
"-days", "1",
"req",
"-x509",
"-newkey",
"rsa:2048",
"-keyout",
&key1_path.to_string_lossy(),
"-out",
&cert1_path.to_string_lossy(),
"-days",
"1",
"-nodes",
"-subj", "/CN=host1.com"
"-subj",
"/CN=host1.com",
])
.output();
// Generate certificate for host2
let cert_result2 = std::process::Command::new("openssl")
.args(&[
"req", "-x509", "-newkey", "rsa:2048",
"-keyout", &key2_path.to_string_lossy(),
"-out", &cert2_path.to_string_lossy(),
"-days", "1",
"req",
"-x509",
"-newkey",
"rsa:2048",
"-keyout",
&key2_path.to_string_lossy(),
"-out",
&cert2_path.to_string_lossy(),
"-days",
"1",
"-nodes",
"-subj", "/CN=host2.com"
"-subj",
"/CN=host2.com",
])
.output();
@ -167,7 +183,8 @@ fn test_valid_multiple_hosts_startup() {
}
let config_path = temp_dir.path().join("config.toml");
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
@ -181,13 +198,14 @@ root = "{}"
cert = "{}"
key = "{}"
"#,
port,
temp_dir.path().join("host1").display(),
cert1_path.display(),
key1_path.display(),
temp_dir.path().join("host2").display(),
cert2_path.display(),
key2_path.display());
port,
temp_dir.path().join("host1").display(),
cert1_path.display(),
key1_path.display(),
temp_dir.path().join("host2").display(),
cert2_path.display(),
key2_path.display()
);
std::fs::write(&config_path, config_content).unwrap();
@ -201,7 +219,10 @@ key = "{}"
std::thread::sleep(std::time::Duration::from_millis(500));
// Check server is still running (didn't exit with error)
assert!(server_process.try_wait().unwrap().is_none(), "Server should start with valid multiple host config");
assert!(
server_process.try_wait().unwrap().is_none(),
"Server should start with valid multiple host config"
);
// Kill server
server_process.kill().unwrap();
@ -222,12 +243,19 @@ fn test_multiple_hosts_missing_certificate() {
let cert_result = std::process::Command::new("openssl")
.args(&[
"req", "-x509", "-newkey", "rsa:2048",
"-keyout", &key1_path.to_string_lossy(),
"-out", &cert1_path.to_string_lossy(),
"-days", "1",
"req",
"-x509",
"-newkey",
"rsa:2048",
"-keyout",
&key1_path.to_string_lossy(),
"-out",
&cert1_path.to_string_lossy(),
"-days",
"1",
"-nodes",
"-subj", "/CN=host1.com"
"-subj",
"/CN=host1.com",
])
.output();
@ -235,7 +263,8 @@ fn test_multiple_hosts_missing_certificate() {
panic!("Failed to generate test certificate");
}
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
["host1.com"]
@ -248,22 +277,26 @@ root = "{}"
cert = "/nonexistent/cert.pem"
key = "/nonexistent/key.pem"
"#,
temp_dir.path().join("host1").display(),
cert1_path.display(),
key1_path.display(),
temp_dir.path().join("host2").display());
temp_dir.path().join("host1").display(),
cert1_path.display(),
key1_path.display(),
temp_dir.path().join("host2").display()
);
std::fs::write(&config_path, config_content).unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_pollux"))
.arg("--config")
.arg(&config_path)
.env("RUST_LOG", "error")
.output()
.unwrap();
assert!(!output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(stderr.contains("Error for host 'host2.com': Certificate file '/nonexistent/cert.pem' does not exist"));
assert!(stderr.contains(
"Error for host 'host2.com': Certificate file '/nonexistent/cert.pem' does not exist"
));
}
#[test]
@ -284,24 +317,38 @@ fn test_multiple_hosts_invalid_hostname() {
// Generate certificate for valid host
let cert_result1 = std::process::Command::new("openssl")
.args(&[
"req", "-x509", "-newkey", "rsa:2048",
"-keyout", &key1_path.to_string_lossy(),
"-out", &cert1_path.to_string_lossy(),
"-days", "1",
"req",
"-x509",
"-newkey",
"rsa:2048",
"-keyout",
&key1_path.to_string_lossy(),
"-out",
&cert1_path.to_string_lossy(),
"-days",
"1",
"-nodes",
"-subj", "/CN=valid.com"
"-subj",
"/CN=valid.com",
])
.output();
// Generate certificate for invalid host (hostname validation happens before cert validation)
let cert_result2 = std::process::Command::new("openssl")
.args(&[
"req", "-x509", "-newkey", "rsa:2048",
"-keyout", &key2_path.to_string_lossy(),
"-out", &cert2_path.to_string_lossy(),
"-days", "1",
"req",
"-x509",
"-newkey",
"rsa:2048",
"-keyout",
&key2_path.to_string_lossy(),
"-out",
&cert2_path.to_string_lossy(),
"-days",
"1",
"-nodes",
"-subj", "/CN=invalid.com"
"-subj",
"/CN=invalid.com",
])
.output();
@ -309,7 +356,8 @@ fn test_multiple_hosts_invalid_hostname() {
panic!("Failed to generate test certificates");
}
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
["valid.com"]
@ -322,22 +370,24 @@ root = "{}"
cert = "{}"
key = "{}"
"#,
temp_dir.path().join("validhost").display(),
cert1_path.display(),
key1_path.display(),
temp_dir.path().join("invalidhost").display(),
cert2_path.display(),
key2_path.display());
temp_dir.path().join("validhost").display(),
cert1_path.display(),
key1_path.display(),
temp_dir.path().join("invalidhost").display(),
cert2_path.display(),
key2_path.display()
);
std::fs::write(&config_path, config_content).unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_pollux"))
.arg("--config")
.arg(&config_path)
.env("RUST_LOG", "error")
.output()
.unwrap();
assert!(!output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(stderr.contains("Invalid hostname 'bad..host.com'. Hostnames must be valid DNS names."));
}
}

View file

@ -13,7 +13,8 @@ fn test_rate_limiting_with_concurrent_requests() {
let cert_path = temp_dir.path().join("cert.pem");
let key_path = temp_dir.path().join("key.pem");
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
max_concurrent_requests = 1
@ -22,7 +23,12 @@ max_concurrent_requests = 1
root = "{}"
cert = "{}"
key = "{}"
"#, port, root_dir.display(), cert_path.display(), key_path.display());
"#,
port,
root_dir.display(),
cert_path.display(),
key_path.display()
);
std::fs::write(&config_path, config_content).unwrap();
// Start server binary with test delay to simulate processing time
@ -30,7 +36,7 @@ key = "{}"
.arg("--config")
.arg(&config_path)
.arg("--test-processing-delay")
.arg("3") // 3 second delay per request
.arg("3") // 3 second delay per request
.spawn()
.expect("Failed to start server");
@ -68,13 +74,30 @@ key = "{}"
let rate_limited_count = results.iter().filter(|r| r.starts_with("41")).count();
// Debug output
println!("Results: {:?}", results);
println!("Success: {}, Rate limited: {}", success_count, rate_limited_count);
tracing::debug!("Test results: {:?}", results);
tracing::debug!(
"Success: {}, Rate limited: {}",
success_count,
rate_limited_count
);
// Strict validation - rate limiting must work deterministically with delay
assert_eq!(success_count, 1, "Expected exactly 1 successful request with limit=1, got {}. Results: {:?}", success_count, results);
assert_eq!(rate_limited_count, 4, "Expected exactly 4 rate limited requests with limit=1, got {}. Results: {:?}", rate_limited_count, results);
assert_eq!(
success_count, 1,
"Expected exactly 1 successful request with limit=1, got {}. Results: {:?}",
success_count, results
);
assert_eq!(
rate_limited_count, 4,
"Expected exactly 4 rate limited requests with limit=1, got {}. Results: {:?}",
rate_limited_count, results
);
// Verify all requests received valid responses
assert_eq!(success_count + rate_limited_count, 5, "All 5 requests should receive responses. Results: {:?}", results);
}
assert_eq!(
success_count + rate_limited_count,
5,
"All 5 requests should receive responses. Results: {:?}",
results
);
}

View file

@ -17,12 +17,19 @@ fn test_single_host_config() {
let cert_result = Command::new("openssl")
.args(&[
"req", "-x509", "-newkey", "rsa:2048",
"-keyout", &key_path.to_string_lossy(),
"-out", &cert_path.to_string_lossy(),
"-days", "1",
"req",
"-x509",
"-newkey",
"rsa:2048",
"-keyout",
&key_path.to_string_lossy(),
"-out",
&cert_path.to_string_lossy(),
"-days",
"1",
"-nodes",
"-subj", "/CN=example.com"
"-subj",
"/CN=example.com",
])
.output();
@ -30,7 +37,8 @@ fn test_single_host_config() {
panic!("Failed to generate test certificates for config test");
}
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
@ -38,7 +46,12 @@ port = {}
root = "{}"
cert = "{}"
key = "{}"
"#, port, content_dir.display(), cert_path.display(), key_path.display());
"#,
port,
content_dir.display(),
cert_path.display(),
key_path.display()
);
std::fs::write(&config_path, config_content).unwrap();
let mut server_process = std::process::Command::new(env!("CARGO_BIN_EXE_pollux"))
@ -48,7 +61,10 @@ key = "{}"
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(500));
assert!(server_process.try_wait().unwrap().is_none(), "Server should start with valid single host config");
assert!(
server_process.try_wait().unwrap().is_none(),
"Server should start with valid single host config"
);
server_process.kill().unwrap();
}
@ -56,7 +72,8 @@ key = "{}"
fn test_multiple_hosts_config() {
let temp_dir = common::setup_test_environment();
let config_path = temp_dir.path().join("config.toml");
let config_content = format!(r#"
let config_content = format!(
r#"
[site1.com]
root = "{}"
cert = "{}"
@ -69,12 +86,14 @@ key = "{}"
bind_host = "127.0.0.1"
port = 1965
"#, temp_dir.path().join("site1").display(),
temp_dir.path().join("site1_cert.pem").display(),
temp_dir.path().join("site1_key.pem").display(),
temp_dir.path().join("site2").display(),
temp_dir.path().join("site2_cert.pem").display(),
temp_dir.path().join("site2_key.pem").display());
"#,
temp_dir.path().join("site1").display(),
temp_dir.path().join("site1_cert.pem").display(),
temp_dir.path().join("site1_key.pem").display(),
temp_dir.path().join("site2").display(),
temp_dir.path().join("site2_cert.pem").display(),
temp_dir.path().join("site2_key.pem").display()
);
std::fs::write(&config_path, config_content).unwrap();
// Create additional directories and generate certificates
@ -87,24 +106,38 @@ port = 1965
// Site 1 certificate
let cert_result1 = Command::new("openssl")
.args(&[
"req", "-x509", "-newkey", "rsa:2048",
"-keyout", &temp_dir.path().join("site1_key.pem").to_string_lossy(),
"-out", &temp_dir.path().join("site1_cert.pem").to_string_lossy(),
"-days", "1",
"req",
"-x509",
"-newkey",
"rsa:2048",
"-keyout",
&temp_dir.path().join("site1_key.pem").to_string_lossy(),
"-out",
&temp_dir.path().join("site1_cert.pem").to_string_lossy(),
"-days",
"1",
"-nodes",
"-subj", "/CN=site1.com"
"-subj",
"/CN=site1.com",
])
.output();
// Site 2 certificate
let cert_result2 = Command::new("openssl")
.args(&[
"req", "-x509", "-newkey", "rsa:2048",
"-keyout", &temp_dir.path().join("site2_key.pem").to_string_lossy(),
"-out", &temp_dir.path().join("site2_cert.pem").to_string_lossy(),
"-days", "1",
"req",
"-x509",
"-newkey",
"rsa:2048",
"-keyout",
&temp_dir.path().join("site2_key.pem").to_string_lossy(),
"-out",
&temp_dir.path().join("site2_cert.pem").to_string_lossy(),
"-days",
"1",
"-nodes",
"-subj", "/CN=site2.org"
"-subj",
"/CN=site2.org",
])
.output();
@ -114,7 +147,8 @@ port = 1965
// Test server starts successfully with multiple host config
let port = 1968 + (std::process::id() % 1000) as u16;
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
@ -128,13 +162,14 @@ root = "{}"
cert = "{}"
key = "{}"
"#,
port,
temp_dir.path().join("site1").display(),
temp_dir.path().join("site1_cert.pem").display(),
temp_dir.path().join("site1_key.pem").display(),
temp_dir.path().join("site2").display(),
temp_dir.path().join("site2_cert.pem").display(),
temp_dir.path().join("site2_key.pem").display());
port,
temp_dir.path().join("site1").display(),
temp_dir.path().join("site1_cert.pem").display(),
temp_dir.path().join("site1_key.pem").display(),
temp_dir.path().join("site2").display(),
temp_dir.path().join("site2_cert.pem").display(),
temp_dir.path().join("site2_key.pem").display()
);
std::fs::write(&config_path, config_content).unwrap();
let mut server_process = std::process::Command::new(env!("CARGO_BIN_EXE_pollux"))
@ -144,7 +179,10 @@ key = "{}"
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(500));
assert!(server_process.try_wait().unwrap().is_none(), "Server should start with valid multiple host config");
assert!(
server_process.try_wait().unwrap().is_none(),
"Server should start with valid multiple host config"
);
server_process.kill().unwrap();
}
@ -177,14 +215,17 @@ root = "/tmp/content"
fn test_invalid_hostname_config() {
let temp_dir = common::setup_test_environment();
let config_path = temp_dir.path().join("config.toml");
let config_content = format!(r#"
let config_content = format!(
r#"
["invalid"]
root = "{}"
cert = "{}"
key = "{}"
"#, temp_dir.path().join("content").display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display());
"#,
temp_dir.path().join("content").display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display()
);
std::fs::write(&config_path, config_content).unwrap();
let output = std::process::Command::new(env!("CARGO_BIN_EXE_pollux"))
@ -224,7 +265,8 @@ port = 1965
fn test_duplicate_hostname_config() {
let temp_dir = common::setup_test_environment();
let config_path = temp_dir.path().join("config.toml");
let config_content = format!(r#"
let config_content = format!(
r#"
[example.com]
root = "{}"
cert = "{}"
@ -234,12 +276,14 @@ key = "{}"
root = "{}"
cert = "{}"
key = "{}"
"#, temp_dir.path().join("path1").display(),
temp_dir.path().join("cert1.pem").display(),
temp_dir.path().join("key1.pem").display(),
temp_dir.path().join("path2").display(),
temp_dir.path().join("cert2.pem").display(),
temp_dir.path().join("key2.pem").display());
"#,
temp_dir.path().join("path1").display(),
temp_dir.path().join("cert1.pem").display(),
temp_dir.path().join("key1.pem").display(),
temp_dir.path().join("path2").display(),
temp_dir.path().join("cert2.pem").display(),
temp_dir.path().join("key2.pem").display()
);
std::fs::write(&config_path, config_content).unwrap();
// Create the directories and certs
@ -266,7 +310,8 @@ fn test_host_with_port_override() {
let config_path = temp_dir.path().join("config.toml");
// Test server starts successfully
let port = 1969 + (std::process::id() % 1000) as u16;
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
@ -275,10 +320,12 @@ root = "{}"
cert = "{}"
key = "{}"
port = 1970 # Override global port
"#, port,
temp_dir.path().join("content").display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display());
"#,
port,
temp_dir.path().join("content").display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display()
);
std::fs::write(&config_path, config_content).unwrap();
let mut server_process = std::process::Command::new(env!("CARGO_BIN_EXE_pollux"))
@ -288,7 +335,10 @@ port = 1970 # Override global port
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(500));
assert!(server_process.try_wait().unwrap().is_none(), "Server should start with host port override");
assert!(
server_process.try_wait().unwrap().is_none(),
"Server should start with host port override"
);
server_process.kill().unwrap();
}
@ -303,4 +353,4 @@ fn test_config_file_not_found() {
assert!(!output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(stderr.contains("Config file 'nonexistent.toml' not found"));
}
}

View file

@ -15,10 +15,7 @@ fn test_concurrent_requests_multiple_hosts() {
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();
std::fs::write(root_dir.join("index.gmi"), format!("Welcome to {}", host)).unwrap();
host_roots.push(root_dir);
}
@ -26,23 +23,28 @@ fn test_concurrent_requests_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#"
let mut config_content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
"#, port);
"#,
port
);
for (i, host) in hosts.iter().enumerate() {
config_content.push_str(&format!(r#"
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()));
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();
@ -65,9 +67,20 @@ key = "{}"
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);
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
});
@ -97,7 +110,8 @@ fn test_mixed_valid_invalid_hostnames() {
// 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#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
@ -106,10 +120,11 @@ root = "{}"
cert = "{}"
key = "{}"
"#,
port,
root_dir.display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display());
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
@ -123,8 +138,16 @@ key = "{}"
// 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);
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![
@ -135,8 +158,14 @@ key = "{}"
];
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);
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();
@ -153,7 +182,8 @@ fn test_load_performance_basic() {
let config_path = temp_dir.path().join("config.toml");
let port = 1971 + (std::process::id() % 1000) as u16;
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
@ -162,10 +192,11 @@ root = "{}"
cert = "{}"
key = "{}"
"#,
port,
root_dir.display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display());
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
@ -183,17 +214,30 @@ key = "{}"
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);
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);
tracing::debug!(
"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);
assert!(
avg_time < 100.0,
"Average request time too slow: {:.2}ms",
avg_time
);
server_process.kill().unwrap();
}
@ -222,7 +266,8 @@ fn test_full_request_lifecycle() {
let cert_path = temp_dir.path().join("cert.pem");
let key_path = temp_dir.path().join("key.pem");
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
@ -231,10 +276,11 @@ root = "{}"
cert = "{}"
key = "{}"
"#,
port,
root_dir.display(),
cert_path.display(),
key_path.display());
port,
root_dir.display(),
cert_path.display(),
key_path.display()
);
std::fs::write(&config_path, config_content).unwrap();
// Start server with TLS
@ -248,27 +294,64 @@ key = "{}"
// 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);
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);
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);
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);
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);
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();
}
@ -295,4 +378,3 @@ fn make_gemini_request(host: &str, port: u16, url: &str) -> String {
Err(e) => format!("Error: Failed to run Python client: {}", e),
}
}

View file

@ -1,7 +1,5 @@
mod common;
#[test]
fn test_per_host_content_isolation() {
let temp_dir = common::setup_test_environment();
@ -19,7 +17,8 @@ fn test_per_host_content_isolation() {
// Create config with two hosts
let config_path = temp_dir.path().join("config.toml");
let port = 1965 + (std::process::id() % 1000) as u16; // Use dynamic port
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
@ -33,13 +32,14 @@ root = "{}"
cert = "{}"
key = "{}"
"#,
port,
site1_root.display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display(),
site2_root.display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display());
port,
site1_root.display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display(),
site2_root.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
@ -54,13 +54,29 @@ key = "{}"
// Test site1.com serves its content
let response1 = make_gemini_request("127.0.0.1", port, "gemini://site1.com/");
assert!(response1.starts_with("20"), "Expected success for site1.com, got: {}", response1);
assert!(response1.contains("Welcome to Site 1"), "Should serve site1 content, got: {}", response1);
assert!(
response1.starts_with("20"),
"Expected success for site1.com, got: {}",
response1
);
assert!(
response1.contains("Welcome to Site 1"),
"Should serve site1 content, got: {}",
response1
);
// Test site2.org serves its content
let response2 = make_gemini_request("127.0.0.1", port, "gemini://site2.org/");
assert!(response2.starts_with("20"), "Expected success for site2.org, got: {}", response2);
assert!(response2.contains("Welcome to Site 2"), "Should serve site2 content, got: {}", response2);
assert!(
response2.starts_with("20"),
"Expected success for site2.org, got: {}",
response2
);
assert!(
response2.contains("Welcome to Site 2"),
"Should serve site2 content, got: {}",
response2
);
server_process.kill().unwrap();
}
@ -73,7 +89,11 @@ fn test_per_host_path_security() {
let site1_root = temp_dir.path().join("site1");
std::fs::create_dir(&site1_root).unwrap();
std::fs::create_dir(site1_root.join("subdir")).unwrap();
std::fs::write(site1_root.join("subdir").join("secret.gmi"), "Secret content").unwrap();
std::fs::write(
site1_root.join("subdir").join("secret.gmi"),
"Secret content",
)
.unwrap();
// Create config
let config_path = temp_dir.path().join("config.toml");
@ -81,7 +101,8 @@ fn test_per_host_path_security() {
let cert_path = temp_dir.path().join("cert.pem");
let key_path = temp_dir.path().join("key.pem");
let config_content = format!(r#"
let config_content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
@ -90,10 +111,11 @@ root = "{}"
cert = "{}"
key = "{}"
"#,
port,
site1_root.display(),
cert_path.display(),
key_path.display());
port,
site1_root.display(),
cert_path.display(),
key_path.display()
);
std::fs::write(&config_path, config_content).unwrap();
let mut server_process = std::process::Command::new(env!("CARGO_BIN_EXE_pollux"))
@ -106,12 +128,24 @@ key = "{}"
// Test path traversal attempt should be blocked
let response = make_gemini_request("127.0.0.1", port, "gemini://site1.com/../../../etc/passwd");
assert!(response.starts_with("51"), "Path traversal should be blocked, got: {}", response);
assert!(
response.starts_with("51"),
"Path traversal should be blocked, got: {}",
response
);
// Test valid subdirectory access should work
let response = make_gemini_request("127.0.0.1", port, "gemini://site1.com/subdir/secret.gmi");
assert!(response.starts_with("20"), "Valid subdirectory access should work, got: {}", response);
assert!(response.contains("Secret content"), "Should serve content from subdirectory, got: {}", response);
assert!(
response.starts_with("20"),
"Valid subdirectory access should work, got: {}",
response
);
assert!(
response.contains("Secret content"),
"Should serve content from subdirectory, got: {}",
response
);
server_process.kill().unwrap();
}
@ -128,4 +162,4 @@ fn make_gemini_request(host: &str, port: u16, url: &str) -> String {
.output()
.unwrap();
String::from_utf8(output.stdout).unwrap()
}
}

View file

@ -71,7 +71,8 @@ fn test_virtual_host_routing_multiple_hosts() {
// Create config with two hosts
let config_path = temp_dir.path().join("config.toml");
let content = format!(r#"
let content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
@ -85,20 +86,29 @@ root = "{}"
cert = "{}"
key = "{}"
"#,
port,
temp_dir.path().join("site1").display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display(),
temp_dir.path().join("site2").display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display());
port,
temp_dir.path().join("site1").display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display(),
temp_dir.path().join("site2").display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display()
);
std::fs::write(&config_path, content).unwrap();
// Create host-specific content
std::fs::create_dir_all(temp_dir.path().join("site1")).unwrap();
std::fs::create_dir_all(temp_dir.path().join("site2")).unwrap();
std::fs::write(temp_dir.path().join("site1").join("index.gmi"), "# Site 1 Content\n").unwrap();
std::fs::write(temp_dir.path().join("site2").join("index.gmi"), "# Site 2 Content\n").unwrap();
std::fs::write(
temp_dir.path().join("site1").join("index.gmi"),
"# Site 1 Content\n",
)
.unwrap();
std::fs::write(
temp_dir.path().join("site2").join("index.gmi"),
"# Site 2 Content\n",
)
.unwrap();
// Use the same certs for both hosts (server uses first cert anyway)
@ -114,11 +124,19 @@ key = "{}"
// Test request to site1.com with TLS
let response1 = make_gemini_request("127.0.0.1", port, "gemini://site1.com/index.gmi");
assert!(response1.starts_with("20"), "Expected success response for site1.com, got: {}", response1);
assert!(
response1.starts_with("20"),
"Expected success response for site1.com, got: {}",
response1
);
// Test request to site2.org
let response2 = make_gemini_request("127.0.0.1", port, "gemini://site2.org/index.gmi");
assert!(response2.starts_with("20"), "Expected success response for site2.org, got: {}", response2);
assert!(
response2.starts_with("20"),
"Expected success response for site2.org, got: {}",
response2
);
server_process.kill().unwrap();
}
@ -132,7 +150,8 @@ fn test_virtual_host_routing_known_hostname() {
// Config with only one host
let config_path = temp_dir.path().join("config.toml");
let content = format!(r#"
let content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
@ -141,10 +160,11 @@ root = "{}"
cert = "{}"
key = "{}"
"#,
port,
temp_dir.path().join("content").display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display());
port,
temp_dir.path().join("content").display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display()
);
std::fs::write(&config_path, content).unwrap();
// Start server with TLS
@ -159,7 +179,11 @@ key = "{}"
// Test request to unknown hostname
let response = make_gemini_request("127.0.0.1", port, "gemini://unknown.com/index.gmi");
assert!(response.starts_with("53"), "Should return status 53 for unknown hostname, got: {}", response);
assert!(
response.starts_with("53"),
"Should return status 53 for unknown hostname, got: {}",
response
);
server_process.kill().unwrap();
}
@ -173,7 +197,8 @@ fn test_virtual_host_routing_malformed_url() {
// Config with one host
let config_path = temp_dir.path().join("config.toml");
let content = format!(r#"
let content = format!(
r#"
bind_host = "127.0.0.1"
port = {}
@ -182,10 +207,11 @@ root = "{}"
cert = "{}"
key = "{}"
"#,
port,
temp_dir.path().join("content").display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display());
port,
temp_dir.path().join("content").display(),
temp_dir.path().join("cert.pem").display(),
temp_dir.path().join("key.pem").display()
);
std::fs::write(&config_path, content).unwrap();
// Start server with TLS
@ -200,7 +226,11 @@ key = "{}"
// Test malformed URL (wrong protocol)
let response = make_gemini_request("127.0.0.1", port, "http://example.com/index.gmi");
assert!(response.starts_with("59"), "Should return status 59 for malformed URL, got: {}", response);
assert!(
response.starts_with("59"),
"Should return status 59 for malformed URL, got: {}",
response
);
server_process.kill().unwrap();
}
}