reinhardt_test/server.rs
1//! HTTP server test utilities
2//!
3//! This module provides utilities for testing HTTP servers, including
4//! spawning test servers and various test handler implementations.
5
6use reinhardt_http::Handler;
7use reinhardt_http::{Request, Response};
8use reinhardt_server::HttpServer;
9use std::sync::Arc;
10use tokio::net::TcpListener;
11use tokio::task::JoinHandle;
12
13/// Spawns a test server on a random available port
14///
15/// # Arguments
16///
17/// * `handler` - The handler to use for the test server
18///
19/// # Returns
20///
21/// Returns a tuple containing:
22/// * The server URL (e.g., "http://127.0.0.1:12345")
23/// * A JoinHandle to the running server task
24///
25/// # Example
26///
27/// ```no_run
28/// use reinhardt_test::server::{spawn_test_server, EchoPathHandler};
29/// use std::sync::Arc;
30///
31/// # async fn example() {
32/// let handler = Arc::new(EchoPathHandler);
33/// let (url, handle) = spawn_test_server(handler).await;
34/// println!("Server running at: {}", url);
35/// # }
36/// ```
37pub async fn spawn_test_server(handler: Arc<dyn Handler>) -> (String, JoinHandle<()>) {
38 // Bind to port 0 to get a random available port
39 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
40 let addr = listener.local_addr().unwrap();
41 let url = format!("http://{}", addr);
42
43 // Create server
44 let server = HttpServer::new(handler);
45
46 // Spawn server in background task
47 let handle = tokio::spawn(async move {
48 // Accept connections manually since we need to use our existing listener
49 loop {
50 match listener.accept().await {
51 Ok((stream, socket_addr)) => {
52 let handler_clone = server.handler();
53 tokio::spawn(async move {
54 if let Err(e) =
55 HttpServer::handle_connection(stream, socket_addr, handler_clone, None)
56 .await
57 {
58 eprintln!("Error handling connection: {:?}", e);
59 }
60 });
61 }
62 Err(e) => {
63 eprintln!("Error accepting connection: {:?}", e);
64 break;
65 }
66 }
67 }
68 });
69
70 // Give the server a moment to start
71
72 (url, handle)
73}
74
75/// Shuts down a test server gracefully
76///
77/// # Arguments
78///
79/// * `handle` - The JoinHandle returned by [`spawn_test_server`]
80///
81/// # Example
82///
83/// ```no_run
84/// use reinhardt_test::server::{spawn_test_server, shutdown_test_server, EchoPathHandler};
85/// use std::sync::Arc;
86///
87/// # async fn example() {
88/// let handler = Arc::new(EchoPathHandler);
89/// let (url, handle) = spawn_test_server(handler).await;
90/// // ... perform tests ...
91/// shutdown_test_server(handle).await;
92/// # }
93/// ```
94pub async fn shutdown_test_server(handle: JoinHandle<()>) {
95 handle.abort();
96 // Give it a moment to clean up
97}
98
99/// Simple test handler that echoes the request path
100///
101/// Returns the request path as the response body.
102///
103/// # Example
104///
105/// ```no_run
106/// use reinhardt_test::server::{spawn_test_server, EchoPathHandler};
107/// use std::sync::Arc;
108///
109/// # async fn example() {
110/// let handler = Arc::new(EchoPathHandler);
111/// let (url, handle) = spawn_test_server(handler).await;
112/// // A request to "/test/path" will return "test/path"
113/// # }
114/// ```
115pub struct EchoPathHandler;
116
117#[async_trait::async_trait]
118impl Handler for EchoPathHandler {
119 async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
120 let path = request.path().to_string();
121 Ok(Response::ok().with_body(path))
122 }
123}
124
125/// Test handler that returns specific status codes based on path
126///
127/// Responds with different HTTP status codes based on the request path:
128/// * `/200` - Returns 200 OK
129/// * `/404` - Returns 404 Not Found
130/// * `/500` - Returns 500 Internal Server Error
131/// * Other paths - Returns 200 OK with "Default" body
132///
133/// # Example
134///
135/// ```no_run
136/// use reinhardt_test::server::{spawn_test_server, StatusCodeHandler};
137/// use std::sync::Arc;
138///
139/// # async fn example() {
140/// let handler = Arc::new(StatusCodeHandler);
141/// let (url, handle) = spawn_test_server(handler).await;
142/// // A request to "/404" will return 404 Not Found
143/// # }
144/// ```
145pub struct StatusCodeHandler;
146
147#[async_trait::async_trait]
148impl Handler for StatusCodeHandler {
149 async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
150 match request.path() {
151 "/200" => Ok(Response::ok().with_body("OK")),
152 "/404" => Ok(Response::not_found().with_body("Not Found")),
153 "/500" => Ok(Response::internal_server_error().with_body("Internal Server Error")),
154 _ => Ok(Response::ok().with_body("Default")),
155 }
156 }
157}
158
159/// Test handler that echoes the request method
160///
161/// Returns the HTTP method (GET, POST, etc.) as the response body.
162///
163/// # Example
164///
165/// ```no_run
166/// use reinhardt_test::server::{spawn_test_server, MethodEchoHandler};
167/// use std::sync::Arc;
168///
169/// # async fn example() {
170/// let handler = Arc::new(MethodEchoHandler);
171/// let (url, handle) = spawn_test_server(handler).await;
172/// // A GET request will return "GET" as the response body
173/// # }
174/// ```
175pub struct MethodEchoHandler;
176
177#[async_trait::async_trait]
178impl Handler for MethodEchoHandler {
179 async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
180 let method = request.method.as_str().to_string();
181 Ok(Response::ok().with_body(method))
182 }
183}
184
185/// Test handler with configurable delay
186///
187/// Useful for testing timeouts and async behavior. The handler
188/// waits for a specified duration before returning a response.
189///
190/// # Fields
191///
192/// * `delay_ms` - Delay in milliseconds before responding
193/// * `response_body` - The body to return in the response
194///
195/// # Example
196///
197/// ```no_run
198/// use reinhardt_test::server::{spawn_test_server, DelayedHandler};
199/// use std::sync::Arc;
200///
201/// # async fn example() {
202/// let handler = Arc::new(DelayedHandler {
203/// delay_ms: 100,
204/// response_body: "Delayed response".to_string(),
205/// });
206/// let (url, handle) = spawn_test_server(handler).await;
207/// // Responses will be delayed by 100ms
208/// # }
209/// ```
210pub struct DelayedHandler {
211 pub delay_ms: u64,
212 pub response_body: String,
213}
214
215#[async_trait::async_trait]
216impl Handler for DelayedHandler {
217 async fn handle(&self, _request: Request) -> reinhardt_core::exception::Result<Response> {
218 tokio::time::sleep(std::time::Duration::from_millis(self.delay_ms)).await;
219 Ok(Response::ok().with_body(self.response_body.clone()))
220 }
221}
222
223/// Test handler that echoes the request body
224///
225/// Returns the request body as the response body.
226///
227/// # Example
228///
229/// ```no_run
230/// use reinhardt_test::server::{spawn_test_server, BodyEchoHandler};
231/// use std::sync::Arc;
232///
233/// # async fn example() {
234/// let handler = Arc::new(BodyEchoHandler);
235/// let (url, handle) = spawn_test_server(handler).await;
236/// // A POST request with body "test data" will return "test data"
237/// # }
238/// ```
239pub struct BodyEchoHandler;
240
241#[async_trait::async_trait]
242impl Handler for BodyEchoHandler {
243 async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
244 let body = request.read_body()?;
245 Ok(Response::ok().with_body(body))
246 }
247}
248
249/// Test handler that returns a large response
250///
251/// Useful for testing response size limits and memory handling.
252///
253/// # Fields
254///
255/// * `size_kb` - Size of the response in kilobytes
256///
257/// # Example
258///
259/// ```no_run
260/// use reinhardt_test::server::{spawn_test_server, LargeResponseHandler};
261/// use std::sync::Arc;
262///
263/// # async fn example() {
264/// let handler = Arc::new(LargeResponseHandler {
265/// size_kb: 1024, // 1MB response
266/// });
267/// let (url, handle) = spawn_test_server(handler).await;
268/// // Responses will be 1MB of repeated 'x' characters
269/// # }
270/// ```
271pub struct LargeResponseHandler {
272 pub size_kb: usize,
273}
274
275#[async_trait::async_trait]
276impl Handler for LargeResponseHandler {
277 async fn handle(&self, _request: Request) -> reinhardt_core::exception::Result<Response> {
278 let data = "x".repeat(self.size_kb * 1024);
279 Ok(Response::ok().with_body(data))
280 }
281}
282
283/// Test handler that returns different responses based on path
284///
285/// A simple router-like handler for testing routing behavior:
286/// * `/` - Returns "Home"
287/// * `/api` - Returns JSON `{"status": "ok"}`
288/// * `/notfound` - Returns 404 with "Not Found"
289/// * Other paths - Returns 404 with "Unknown path"
290///
291/// # Example
292///
293/// ```no_run
294/// use reinhardt_test::server::{spawn_test_server, RouterHandler};
295/// use std::sync::Arc;
296///
297/// # async fn example() {
298/// let handler = Arc::new(RouterHandler);
299/// let (url, handle) = spawn_test_server(handler).await;
300/// // A request to "/" will return "Home"
301/// // A request to "/api" will return JSON
302/// # }
303/// ```
304pub struct RouterHandler;
305
306#[async_trait::async_trait]
307impl Handler for RouterHandler {
308 async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
309 let path = request.uri.path();
310
311 match path {
312 "/" => Ok(Response::ok().with_body("Home")),
313 "/api" => Ok(Response::ok().with_body(r#"{"status": "ok"}"#)),
314 "/notfound" => Ok(Response::not_found().with_body("Not Found")),
315 _ => Ok(Response::not_found().with_body("Unknown path")),
316 }
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use rstest::rstest;
324
325 fn test_request() -> Request {
326 Request::builder().uri("/test").build().unwrap()
327 }
328
329 #[rstest]
330 #[case(100)]
331 #[case(200)]
332 #[tokio::test]
333 async fn delayed_handler_actually_delays(#[case] delay_ms: u64) {
334 // Arrange
335 let handler = DelayedHandler {
336 delay_ms,
337 response_body: "delayed".to_string(),
338 };
339
340 // Act
341 let start = tokio::time::Instant::now();
342 let response = handler.handle(test_request()).await.unwrap();
343 let elapsed = start.elapsed();
344
345 // Assert
346 assert!(
347 elapsed.as_millis() >= u128::from(delay_ms),
348 "Expected at least {}ms delay, but elapsed was {}ms",
349 delay_ms,
350 elapsed.as_millis()
351 );
352 assert_eq!(String::from_utf8_lossy(&response.body), "delayed");
353 }
354
355 #[rstest]
356 #[tokio::test]
357 async fn delayed_handler_zero_delay_returns_immediately() {
358 // Arrange
359 let handler = DelayedHandler {
360 delay_ms: 0,
361 response_body: "instant".to_string(),
362 };
363
364 // Act
365 let start = tokio::time::Instant::now();
366 let response = handler.handle(test_request()).await.unwrap();
367 let elapsed = start.elapsed();
368
369 // Assert
370 assert!(
371 elapsed.as_millis() < 50,
372 "Zero delay should return almost immediately, but took {}ms",
373 elapsed.as_millis()
374 );
375 assert_eq!(String::from_utf8_lossy(&response.body), "instant");
376 }
377}