use reinhardt_http::Handler;
use reinhardt_http::{Request, Response};
use reinhardt_server::HttpServer;
use std::sync::Arc;
use tokio::net::TcpListener;
use tokio::task::JoinHandle;
pub async fn spawn_test_server(handler: Arc<dyn Handler>) -> (String, JoinHandle<()>) {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let url = format!("http://{}", addr);
let server = HttpServer::new(handler);
let handle = tokio::spawn(async move {
loop {
match listener.accept().await {
Ok((stream, socket_addr)) => {
let handler_clone = server.handler();
tokio::spawn(async move {
if let Err(e) =
HttpServer::handle_connection(stream, socket_addr, handler_clone, None)
.await
{
eprintln!("Error handling connection: {:?}", e);
}
});
}
Err(e) => {
eprintln!("Error accepting connection: {:?}", e);
break;
}
}
}
});
(url, handle)
}
pub async fn shutdown_test_server(handle: JoinHandle<()>) {
handle.abort();
}
pub struct EchoPathHandler;
#[async_trait::async_trait]
impl Handler for EchoPathHandler {
async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
let path = request.path().to_string();
Ok(Response::ok().with_body(path))
}
}
pub struct StatusCodeHandler;
#[async_trait::async_trait]
impl Handler for StatusCodeHandler {
async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
match request.path() {
"/200" => Ok(Response::ok().with_body("OK")),
"/404" => Ok(Response::not_found().with_body("Not Found")),
"/500" => Ok(Response::internal_server_error().with_body("Internal Server Error")),
_ => Ok(Response::ok().with_body("Default")),
}
}
}
pub struct MethodEchoHandler;
#[async_trait::async_trait]
impl Handler for MethodEchoHandler {
async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
let method = request.method.as_str().to_string();
Ok(Response::ok().with_body(method))
}
}
pub struct DelayedHandler {
pub delay_ms: u64,
pub response_body: String,
}
#[async_trait::async_trait]
impl Handler for DelayedHandler {
async fn handle(&self, _request: Request) -> reinhardt_core::exception::Result<Response> {
tokio::time::sleep(std::time::Duration::from_millis(self.delay_ms)).await;
Ok(Response::ok().with_body(self.response_body.clone()))
}
}
pub struct BodyEchoHandler;
#[async_trait::async_trait]
impl Handler for BodyEchoHandler {
async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
let body = request.read_body()?;
Ok(Response::ok().with_body(body))
}
}
pub struct LargeResponseHandler {
pub size_kb: usize,
}
#[async_trait::async_trait]
impl Handler for LargeResponseHandler {
async fn handle(&self, _request: Request) -> reinhardt_core::exception::Result<Response> {
let data = "x".repeat(self.size_kb * 1024);
Ok(Response::ok().with_body(data))
}
}
pub struct RouterHandler;
#[async_trait::async_trait]
impl Handler for RouterHandler {
async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
let path = request.uri.path();
match path {
"/" => Ok(Response::ok().with_body("Home")),
"/api" => Ok(Response::ok().with_body(r#"{"status": "ok"}"#)),
"/notfound" => Ok(Response::not_found().with_body("Not Found")),
_ => Ok(Response::not_found().with_body("Unknown path")),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
fn test_request() -> Request {
Request::builder().uri("/test").build().unwrap()
}
#[rstest]
#[case(100)]
#[case(200)]
#[tokio::test]
async fn delayed_handler_actually_delays(#[case] delay_ms: u64) {
let handler = DelayedHandler {
delay_ms,
response_body: "delayed".to_string(),
};
let start = tokio::time::Instant::now();
let response = handler.handle(test_request()).await.unwrap();
let elapsed = start.elapsed();
assert!(
elapsed.as_millis() >= u128::from(delay_ms),
"Expected at least {}ms delay, but elapsed was {}ms",
delay_ms,
elapsed.as_millis()
);
assert_eq!(String::from_utf8_lossy(&response.body), "delayed");
}
#[rstest]
#[tokio::test]
async fn delayed_handler_zero_delay_returns_immediately() {
let handler = DelayedHandler {
delay_ms: 0,
response_body: "instant".to_string(),
};
let start = tokio::time::Instant::now();
let response = handler.handle(test_request()).await.unwrap();
let elapsed = start.elapsed();
assert!(
elapsed.as_millis() < 50,
"Zero delay should return almost immediately, but took {}ms",
elapsed.as_millis()
);
assert_eq!(String::from_utf8_lossy(&response.body), "instant");
}
}