use http_handle::{Server, ShutdownSignal};
use std::collections::HashMap;
use std::fs;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use tempfile::TempDir;
fn find_available_port() -> u16 {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
listener.local_addr().unwrap().port()
}
fn setup_test_directory() -> TempDir {
let temp_dir = TempDir::new().unwrap();
let root_path = temp_dir.path();
fs::write(
root_path.join("index.html"),
"<html><body>ServerBuilder Test</body></html>",
)
.unwrap();
temp_dir
}
fn make_http_request(address: &str, request: &str) -> String {
let mut stream = TcpStream::connect(address).unwrap();
stream.write_all(request.as_bytes()).unwrap();
let mut response = String::new();
let _bytes_read = stream
.read_to_string(&mut response)
.expect("Failed to read response");
response
}
fn parse_response(response: &str) -> (String, Vec<String>, String) {
let mut lines = response.lines();
let status_line = lines.next().unwrap_or("").to_string();
let mut headers = Vec::new();
let mut body_started = false;
let mut body_lines = Vec::new();
for line in lines {
if line.is_empty() && !body_started {
body_started = true;
} else if body_started {
body_lines.push(line);
} else {
headers.push(line.to_string());
}
}
let body = body_lines.join("\n");
(status_line, headers, body)
}
#[test]
fn test_server_builder_basic_configuration() {
let temp_dir = setup_test_directory();
let port = find_available_port();
let address = format!("127.0.0.1:{}", port);
let server = Server::builder()
.address(&address)
.document_root(temp_dir.path().to_str().unwrap())
.build()
.expect("Should build server successfully");
let shutdown =
Arc::new(ShutdownSignal::new(Duration::from_secs(2)));
let shutdown_clone = shutdown.clone();
let server_handle = thread::spawn(move || {
server.start_with_shutdown_signal(shutdown_clone)
});
thread::sleep(Duration::from_millis(100));
let request = "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
let response = make_http_request(&address, request);
let (status_line, _headers, body) = parse_response(&response);
assert!(
status_line.contains("200 OK"),
"Expected 200 OK, got: {}",
status_line
);
assert!(
body.contains("ServerBuilder Test"),
"Expected body content, got: {}",
body
);
shutdown.shutdown();
server_handle
.join()
.expect("Server thread should complete")
.expect("Server should run without error");
}
#[test]
fn test_server_builder_custom_headers() {
let temp_dir = setup_test_directory();
let port = find_available_port();
let address = format!("127.0.0.1:{}", port);
let server = Server::builder()
.address(&address)
.document_root(temp_dir.path().to_str().unwrap())
.custom_header("X-Custom-Header", "test-value")
.custom_header("X-Server-Version", "1.0")
.build()
.expect("Should build server successfully");
let shutdown =
Arc::new(ShutdownSignal::new(Duration::from_secs(2)));
let shutdown_clone = shutdown.clone();
let server_handle = thread::spawn(move || {
server.start_with_shutdown_signal(shutdown_clone)
});
thread::sleep(Duration::from_millis(100));
let request = "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
let response = make_http_request(&address, request);
let (_status_line, headers, _body) = parse_response(&response);
let _custom_header_found = headers.iter().any(|h| {
h.to_lowercase().contains("x-custom-header")
&& h.contains("test-value")
});
let _version_header_found = headers.iter().any(|h| {
h.to_lowercase().contains("x-server-version")
&& h.contains("1.0")
});
shutdown.shutdown();
server_handle
.join()
.expect("Server thread should complete")
.expect("Server should run without error");
}
#[test]
fn test_server_builder_cors_configuration() {
let temp_dir = setup_test_directory();
let port = find_available_port();
let address = format!("127.0.0.1:{}", port);
let server = Server::builder()
.address(&address)
.document_root(temp_dir.path().to_str().unwrap())
.enable_cors()
.cors_origins(vec![
"https://example.com".to_string(),
"https://api.example.com".to_string(),
])
.build()
.expect("Should build server successfully");
let shutdown =
Arc::new(ShutdownSignal::new(Duration::from_secs(2)));
let shutdown_clone = shutdown.clone();
let server_handle = thread::spawn(move || {
server.start_with_shutdown_signal(shutdown_clone)
});
thread::sleep(Duration::from_millis(100));
let request = "OPTIONS / HTTP/1.1\r\nHost: localhost\r\nOrigin: https://example.com\r\nConnection: close\r\n\r\n";
let response = make_http_request(&address, request);
let (status_line, headers, _body) = parse_response(&response);
assert!(
status_line.contains("200 OK"),
"Expected 200 OK, got: {}",
status_line
);
let allow_header_found = headers
.iter()
.any(|h| h.to_lowercase().starts_with("allow:"));
assert!(
allow_header_found,
"OPTIONS response should include Allow header"
);
shutdown.shutdown();
server_handle
.join()
.expect("Server thread should complete")
.expect("Server should run without error");
}
#[test]
fn test_server_builder_timeout_configuration() {
let temp_dir = setup_test_directory();
let port = find_available_port();
let address = format!("127.0.0.1:{}", port);
let server = Server::builder()
.address(&address)
.document_root(temp_dir.path().to_str().unwrap())
.request_timeout(Duration::from_secs(15))
.connection_timeout(Duration::from_secs(10))
.build()
.expect("Should build server successfully");
let shutdown =
Arc::new(ShutdownSignal::new(Duration::from_secs(2)));
let shutdown_clone = shutdown.clone();
let server_handle = thread::spawn(move || {
server.start_with_shutdown_signal(shutdown_clone)
});
thread::sleep(Duration::from_millis(100));
let request = "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
let response = make_http_request(&address, request);
let (status_line, _headers, body) = parse_response(&response);
assert!(
status_line.contains("200 OK"),
"Expected 200 OK, got: {}",
status_line
);
assert!(
body.contains("ServerBuilder Test"),
"Expected body content, got: {}",
body
);
shutdown.shutdown();
server_handle
.join()
.expect("Server thread should complete")
.expect("Server should run without error");
}
#[test]
fn test_server_builder_multiple_custom_headers() {
let temp_dir = setup_test_directory();
let port = find_available_port();
let address = format!("127.0.0.1:{}", port);
let mut headers_map = HashMap::new();
let _ = headers_map
.insert("X-Api-Version".to_string(), "v1.0".to_string());
let _ = headers_map
.insert("X-Rate-Limit".to_string(), "1000".to_string());
let server = Server::builder()
.address(&address)
.document_root(temp_dir.path().to_str().unwrap())
.custom_headers(headers_map)
.custom_header("X-Additional", "extra-value")
.build()
.expect("Should build server successfully");
let shutdown =
Arc::new(ShutdownSignal::new(Duration::from_secs(2)));
let shutdown_clone = shutdown.clone();
let server_handle = thread::spawn(move || {
server.start_with_shutdown_signal(shutdown_clone)
});
thread::sleep(Duration::from_millis(100));
let request = "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
let response = make_http_request(&address, request);
let (status_line, _headers, body) = parse_response(&response);
assert!(
status_line.contains("200 OK"),
"Expected 200 OK, got: {}",
status_line
);
assert!(
body.contains("ServerBuilder Test"),
"Expected body content, got: {}",
body
);
shutdown.shutdown();
server_handle
.join()
.expect("Server thread should complete")
.expect("Server should run without error");
}
#[test]
fn test_server_builder_error_handling() {
let result = Server::builder()
.address("127.0.0.1:8080")
.build();
assert!(
result.is_err(),
"Should fail when document_root is missing"
);
let result = Server::builder()
.document_root("/tmp")
.build();
assert!(result.is_err(), "Should fail when address is missing");
let result = Server::builder()
.address("127.0.0.1:8080")
.document_root("/tmp")
.build();
assert!(result.is_ok(), "Should succeed with all required fields");
}
#[test]
fn test_server_builder_cors_auto_enable() {
let temp_dir = setup_test_directory();
let _server = Server::builder()
.address("127.0.0.1:8080")
.document_root(temp_dir.path().to_str().unwrap())
.cors_origins(vec!["https://example.com".to_string()])
.build()
.expect("Should build server successfully");
}
#[test]
fn test_server_builder_fluent_interface() {
let temp_dir = setup_test_directory();
let _server = Server::builder()
.address("127.0.0.1:9000")
.document_root(temp_dir.path().to_str().unwrap())
.enable_cors()
.cors_origins(vec!["https://localhost:3000".to_string()])
.custom_header("X-Powered-By", "http-handle")
.custom_header("X-Content-Type-Options", "nosniff")
.request_timeout(Duration::from_secs(30))
.connection_timeout(Duration::from_secs(60))
.disable_cors() .build()
.expect("Should build server with fluent interface");
}