Skip to main content

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}