Skip to main content

reinhardt_testkit/fixtures/
server.rs

1//! Server test fixtures with automatic graceful shutdown.
2//!
3//! This module provides rstest fixtures for testing HTTP servers with automatic
4//! cleanup via RAII pattern.
5
6// `RateLimitConfig` is deprecated in favor of the `RateLimitSettings` fragment;
7// these fixtures still build it directly during the 0.2 compatibility window.
8#![allow(deprecated)]
9
10use reinhardt_di::InjectionContext;
11use reinhardt_http::Handler;
12use reinhardt_http::{Request, Response};
13use reinhardt_server::{
14	HttpServer, RateLimitConfig, RateLimitHandler, ShutdownCoordinator, TimeoutHandler,
15};
16use reinhardt_urls::routers::ServerRouter as Router;
17use rstest::fixture;
18use std::net::SocketAddr;
19use std::sync::Arc;
20use std::time::Duration;
21use tokio::net::TcpListener;
22use tokio::task::JoinHandle;
23
24#[cfg(feature = "websockets")]
25use reinhardt_server::WebSocketServer;
26
27#[cfg(feature = "graphql")]
28use reinhardt_server::GraphQLHandler;
29
30/// Test server guard with automatic graceful shutdown.
31///
32/// This guard automatically performs graceful shutdown when dropped, ensuring
33/// proper cleanup of server resources even if the test panics.
34///
35/// # Examples
36///
37/// ```no_run
38/// use reinhardt_testkit::fixtures::*;
39/// use reinhardt_urls::routers::ServerRouter as Router;
40///
41/// #[tokio::test]
42/// async fn test_example() {
43///     let router = Router::new();
44///     let server = test_server_guard(router).await;
45///     let response = reqwest::get(&format!("{}/test", server.url))
46///         .await
47///         .unwrap();
48///     assert_eq!(response.status(), 200);
49///     // Automatic graceful shutdown when server goes out of scope
50/// }
51/// ```
52pub struct TestServerGuard {
53	/// Server URL (e.g., "http://127.0.0.1:12345")
54	pub url: String,
55	/// Shutdown coordinator for graceful shutdown
56	pub coordinator: Arc<ShutdownCoordinator>,
57	/// Server task handle
58	server_task: Option<JoinHandle<()>>,
59}
60
61impl TestServerGuard {
62	/// Create a new test server guard.
63	///
64	/// This function:
65	/// 1. Binds to a random port (127.0.0.1:0)
66	/// 2. Creates a ShutdownCoordinator
67	/// 3. Spawns the server task
68	/// 4. Probes the server port until it accepts connections
69	///
70	/// # Arguments
71	///
72	/// * `router` - Router to use for handling requests
73	async fn new(router: Router) -> Self {
74		let shutdown_timeout = Duration::from_secs(5);
75		// Bind to random port and keep the listener to avoid TOCTOU race
76		let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
77		let actual_addr = listener.local_addr().unwrap();
78		let url = format!("http://{}", actual_addr);
79
80		// Create shutdown coordinator
81		let coordinator = Arc::new(ShutdownCoordinator::new(shutdown_timeout));
82
83		// Spawn server using the already-bound listener to avoid port race
84		let server_coordinator = (*coordinator).clone();
85		let handler: Arc<dyn Handler> = Arc::new(router);
86		let server = HttpServer::new(handler);
87		let mut shutdown_rx = server_coordinator.subscribe();
88		let server_task = tokio::spawn(async move {
89			loop {
90				tokio::select! {
91					result = listener.accept() => {
92						match result {
93							Ok((stream, socket_addr)) => {
94								let handler_clone = server.handler();
95								tokio::spawn(async move {
96									if let Err(e) =
97										HttpServer::handle_connection(stream, socket_addr, handler_clone, None)
98											.await
99									{
100										eprintln!("Error handling connection: {:?}", e);
101									}
102								});
103							}
104							Err(e) => {
105								eprintln!("Error accepting connection: {:?}", e);
106								break;
107							}
108						}
109					}
110					_ = shutdown_rx.recv() => {
111						break;
112					}
113				}
114			}
115		});
116
117		// Probe server readiness with TCP connect attempts instead of fixed sleep.
118		// This avoids flaky failures when the system is under heavy load.
119		wait_for_server_ready(actual_addr)
120			.await
121			.expect("Test server failed to become ready");
122
123		Self {
124			url,
125			coordinator,
126			server_task: Some(server_task),
127		}
128	}
129}
130
131impl Drop for TestServerGuard {
132	fn drop(&mut self) {
133		// Trigger shutdown signal
134		self.coordinator.shutdown();
135
136		// Abort the server task
137		// The ShutdownCoordinator will handle graceful shutdown,
138		// but we need to ensure the task is terminated
139		if let Some(task) = self.server_task.take() {
140			task.abort();
141		}
142	}
143}
144
145/// Create a test server guard with the given router.
146///
147/// This is a helper function (not an rstest fixture) that creates a test server
148/// with automatic graceful shutdown. Use it directly in your tests.
149///
150/// # Examples
151///
152/// ```no_run
153/// use reinhardt_testkit::fixtures::*;
154/// use reinhardt_urls::routers::ServerRouter as Router;
155///
156/// #[tokio::test]
157/// async fn test_server() {
158///     let router = Router::new();
159///     let server = test_server_guard(router).await;
160///     let response = reqwest::get(&format!("{}/hello", server.url))
161///         .await
162///         .unwrap();
163///     assert_eq!(response.status(), 200);
164///     // Automatic cleanup on drop
165/// }
166/// ```
167pub async fn test_server_guard(router: Router) -> TestServerGuard {
168	TestServerGuard::new(router).await
169}
170
171// ============================================================================
172// Basic Test Handlers
173// ============================================================================
174
175/// Basic handler for testing purposes that returns "OK"
176#[derive(Clone)]
177pub struct BasicHandler;
178
179#[async_trait::async_trait]
180impl Handler for BasicHandler {
181	async fn handle(&self, _request: Request) -> reinhardt_core::exception::Result<Response> {
182		Ok(Response::ok().with_body("OK"))
183	}
184}
185
186// ============================================================================
187// Client Fixtures
188// ============================================================================
189
190/// HTTP client fixture for testing HTTP requests
191///
192/// # Examples
193///
194/// ```no_run
195/// use reinhardt_testkit::fixtures::*;
196/// use rstest::*;
197///
198/// #[rstest]
199/// #[tokio::test]
200/// async fn test_with_client(http_client: reqwest::Client) {
201///     let response = http_client
202///         .get("http://localhost:8080/api/test")
203///         .send()
204///         .await
205///         .unwrap();
206///     assert_eq!(response.status(), 200);
207/// }
208/// ```
209#[fixture]
210pub fn http_client() -> reqwest::Client {
211	reqwest::Client::builder()
212		.timeout(Duration::from_secs(10))
213		.build()
214		.expect("Failed to create HTTP client")
215}
216// ============================================================================
217// HTTP/1.1 Server Fixtures
218// ============================================================================
219
220/// HTTP/1.1 test server fixture
221///
222/// # Examples
223///
224/// ```no_run
225/// use reinhardt_testkit::fixtures::*;
226/// use rstest::*;
227///
228/// #[rstest]
229/// #[tokio::test]
230/// async fn test_http1_server(#[future] http1_server: TestServer) {
231///     let server = http1_server.await;
232///     let client = reqwest::Client::new();
233///     let response = client.get(&server.url).send().await.unwrap();
234///     assert_eq!(response.status(), 200);
235/// }
236/// ```
237#[fixture]
238pub async fn http1_server() -> TestServer {
239	let handler = Arc::new(BasicHandler);
240	TestServer::builder()
241		.handler(handler)
242		.build()
243		.await
244		.expect("Failed to create HTTP/1.1 server")
245}
246
247// ============================================================================
248// HTTP/2 Server Fixtures
249// ============================================================================
250
251/// HTTP/2 test server fixture
252///
253/// # Examples
254///
255/// ```no_run
256/// use reinhardt_testkit::fixtures::*;
257/// use rstest::*;
258///
259/// #[rstest]
260/// #[tokio::test]
261/// async fn test_http2_server(#[future] http2_server: TestServer) {
262///     let server = http2_server.await;
263///     // Test with HTTP/2 client
264/// }
265/// ```
266#[fixture]
267pub async fn http2_server() -> TestServer {
268	let handler = Arc::new(BasicHandler);
269	TestServer::builder()
270		.handler(handler)
271		.http2(true)
272		.build()
273		.await
274		.expect("Failed to create HTTP/2 server")
275}
276
277// ============================================================================
278// Middleware Server Fixtures
279// ============================================================================
280
281/// Server fixture with timeout middleware
282///
283/// Default timeout: 5 seconds
284///
285/// # Examples
286///
287/// ```no_run
288/// use reinhardt_testkit::fixtures::*;
289/// use rstest::*;
290///
291/// #[rstest]
292/// #[tokio::test]
293/// async fn test_timeout(#[future] server_with_timeout: TestServer) {
294///     let server = server_with_timeout.await;
295///     // Timeout test
296/// }
297/// ```
298#[fixture]
299pub async fn server_with_timeout(
300	#[default(Duration::from_secs(5))] timeout: Duration,
301) -> TestServer {
302	let handler = Arc::new(BasicHandler);
303	let timeout_handler = Arc::new(TimeoutHandler::new(handler, timeout));
304	TestServer::builder()
305		.handler(timeout_handler)
306		.build()
307		.await
308		.expect("Failed to create server with timeout")
309}
310
311/// Server fixture with rate limit middleware
312///
313/// Default rate limit: 100 requests/minute
314///
315/// # Examples
316///
317/// ```no_run
318/// use reinhardt_testkit::fixtures::*;
319/// use rstest::*;
320///
321/// #[rstest]
322/// #[tokio::test]
323/// async fn test_rate_limit(#[future] server_with_rate_limit: TestServer) {
324///     let server = server_with_rate_limit.await;
325///     // Rate limit test
326/// }
327/// ```
328#[fixture]
329pub async fn server_with_rate_limit(#[default(100)] limit: u32) -> TestServer {
330	let handler = Arc::new(BasicHandler);
331	let config = RateLimitConfig::per_minute(limit as usize);
332	let rate_limit_handler = Arc::new(RateLimitHandler::new(handler, config));
333	TestServer::builder()
334		.handler(rate_limit_handler)
335		.build()
336		.await
337		.expect("Failed to create server with rate limit")
338}
339
340/// Server fixture with middleware chain (Timeout + RateLimit)
341///
342/// # Examples
343///
344/// ```no_run
345/// use reinhardt_testkit::fixtures::*;
346/// use rstest::*;
347///
348/// #[rstest]
349/// #[tokio::test]
350/// async fn test_middleware_chain(#[future] server_with_middleware_chain: TestServer) {
351///     let server = server_with_middleware_chain.await;
352///     // Middleware chain test
353/// }
354/// ```
355#[fixture]
356pub async fn server_with_middleware_chain() -> TestServer {
357	let handler = Arc::new(BasicHandler);
358	let timeout_handler = Arc::new(TimeoutHandler::new(handler, Duration::from_secs(5)));
359	let config = RateLimitConfig::per_minute(100);
360	let rate_limit_handler = Arc::new(RateLimitHandler::new(timeout_handler, config));
361
362	TestServer::builder()
363		.handler(rate_limit_handler)
364		.build()
365		.await
366		.expect("Failed to create server with middleware chain")
367}
368
369/// Server fixture with DI context
370///
371/// # Examples
372///
373/// ```no_run
374/// use reinhardt_testkit::fixtures::*;
375/// use rstest::*;
376///
377/// #[rstest]
378/// #[tokio::test]
379/// async fn test_di_context(#[future] server_with_di: (TestServer, Arc<InjectionContext>)) {
380///     let (server, di_context) = server_with_di.await;
381///     // DI context test
382/// }
383/// ```
384#[fixture]
385pub async fn server_with_di() -> (TestServer, Arc<InjectionContext>) {
386	use reinhardt_di::SingletonScope;
387
388	let handler = Arc::new(BasicHandler);
389	let di_context = Arc::new(InjectionContext::builder(Arc::new(SingletonScope::new())).build());
390
391	let server = TestServer::builder()
392		.handler(handler)
393		.di_context(di_context.clone())
394		.build()
395		.await
396		.expect("Failed to create server with DI context");
397
398	(server, di_context)
399}
400
401// ============================================================================
402// WebSocket Server Fixtures (feature: websocket)
403// ============================================================================
404
405#[cfg(feature = "websockets")]
406/// WebSocket-enabled server fixture
407///
408/// # Examples
409///
410/// ```no_run
411/// use reinhardt_testkit::fixtures::*;
412/// use rstest::*;
413///
414/// #[rstest]
415/// #[tokio::test]
416/// async fn test_websocket_server(#[future] websocket_server: TestServer) {
417///     let server = websocket_server.await;
418///     // WebSocket test
419/// }
420/// ```
421#[fixture]
422pub async fn websocket_server() -> TestServer {
423	use reinhardt_server::WebSocketHandler;
424
425	#[derive(Clone)]
426	struct EchoHandler;
427
428	#[async_trait::async_trait]
429	impl WebSocketHandler for EchoHandler {
430		async fn handle_message(&self, message: String) -> Result<String, String> {
431			Ok(message) // Echo back
432		}
433
434		async fn on_connect(&self) {}
435		async fn on_disconnect(&self) {}
436	}
437
438	let ws_handler = Arc::new(EchoHandler);
439	TestServer::builder()
440		.websocket_handler(ws_handler)
441		.build()
442		.await
443		.expect("Failed to create WebSocket server")
444}
445
446#[cfg(feature = "websockets")]
447/// WebSocket client fixture
448///
449/// # Examples
450///
451/// ```no_run
452/// use reinhardt_testkit::fixtures::*;
453/// use rstest::*;
454///
455/// #[rstest]
456/// #[tokio::test]
457/// async fn test_websocket_client(websocket_client: tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>) {
458///     // WebSocket client test
459/// }
460/// ```
461#[fixture]
462pub async fn websocket_client(
463	#[from(websocket_server)]
464	#[future]
465	server: TestServer,
466) -> tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>> {
467	let server = server.await;
468	let ws_url = server.url.replace("http://", "ws://");
469	let (ws_stream, _) = tokio_tungstenite::connect_async(&ws_url)
470		.await
471		.expect("Failed to connect WebSocket");
472	ws_stream
473}
474
475// ============================================================================
476// GraphQL Server Fixtures (feature: graphql)
477// ============================================================================
478
479#[cfg(feature = "graphql")]
480/// GraphQL-enabled server fixture
481///
482/// # Examples
483///
484/// ```no_run
485/// use reinhardt_testkit::fixtures::*;
486/// use rstest::*;
487///
488/// #[rstest]
489/// #[tokio::test]
490/// async fn test_graphql_server(#[future] graphql_server: TestServer) {
491///     let server = graphql_server.await;
492///     // GraphQL test
493/// }
494/// ```
495#[cfg(feature = "graphql")]
496#[fixture]
497pub async fn graphql_server() -> TestServer {
498	use async_graphql::{EmptyMutation, EmptySubscription, Object, Schema};
499
500	struct Query;
501
502	#[Object]
503	impl Query {
504		async fn hello(&self) -> &'static str {
505			"Hello, GraphQL!"
506		}
507	}
508
509	let schema = Schema::build(Query, EmptyMutation, EmptySubscription).finish();
510	let graphql_handler = Arc::new(GraphQLHandler::new(schema));
511
512	TestServer::builder()
513		.handler(graphql_handler)
514		.build()
515		.await
516		.expect("Failed to create GraphQL server")
517}
518
519// ============================================================================
520// TestServer Structure with Builder Pattern
521// ============================================================================
522
523/// Test server with automatic graceful shutdown
524pub struct TestServer {
525	/// Server URL (e.g., "http://127.0.0.1:12345")
526	pub url: String,
527	/// Server address
528	pub addr: SocketAddr,
529	/// Shutdown coordinator
530	pub coordinator: Arc<ShutdownCoordinator>,
531	/// Server task handle
532	server_task: Option<JoinHandle<()>>,
533}
534
535impl TestServer {
536	/// Create a new TestServerBuilder
537	pub fn builder() -> TestServerBuilder {
538		TestServerBuilder::new()
539	}
540}
541
542impl Drop for TestServer {
543	fn drop(&mut self) {
544		// Trigger shutdown signal
545		self.coordinator.shutdown();
546
547		// Abort the server task
548		if let Some(task) = self.server_task.take() {
549			task.abort();
550		}
551	}
552}
553
554/// Builder for TestServer
555pub struct TestServerBuilder {
556	handler: Option<Arc<dyn Handler>>,
557	#[cfg(feature = "websockets")]
558	websocket_handler: Option<Arc<dyn reinhardt_server::WebSocketHandler>>,
559	di_context: Option<Arc<InjectionContext>>,
560	http2: bool,
561	shutdown_timeout: Duration,
562}
563
564impl TestServerBuilder {
565	fn new() -> Self {
566		Self {
567			handler: None,
568			#[cfg(feature = "websockets")]
569			websocket_handler: None,
570			di_context: None,
571			http2: false,
572			shutdown_timeout: Duration::from_secs(5),
573		}
574	}
575
576	/// Set the handler for HTTP requests
577	pub fn handler(mut self, handler: Arc<dyn Handler>) -> Self {
578		self.handler = Some(handler);
579		self
580	}
581
582	#[cfg(feature = "websockets")]
583	/// Set the WebSocket handler
584	pub fn websocket_handler(
585		mut self,
586		handler: Arc<dyn reinhardt_server::WebSocketHandler>,
587	) -> Self {
588		self.websocket_handler = Some(handler);
589		self
590	}
591
592	/// Set the DI context
593	pub fn di_context(mut self, context: Arc<InjectionContext>) -> Self {
594		self.di_context = Some(context);
595		self
596	}
597
598	/// Enable HTTP/2
599	pub fn http2(mut self, enabled: bool) -> Self {
600		self.http2 = enabled;
601		self
602	}
603
604	/// Set shutdown timeout
605	pub fn shutdown_timeout(mut self, timeout: Duration) -> Self {
606		self.shutdown_timeout = timeout;
607		self
608	}
609
610	/// Build the TestServer
611	pub async fn build(self) -> Result<TestServer, Box<dyn std::error::Error>> {
612		// Bind to random port and keep the listener to avoid TOCTOU race
613		let listener = TcpListener::bind("127.0.0.1:0").await?;
614		let actual_addr = listener.local_addr()?;
615		let url = format!("http://{}", actual_addr);
616
617		// Create shutdown coordinator
618		let coordinator = Arc::new(ShutdownCoordinator::new(self.shutdown_timeout));
619
620		// Spawn server based on configuration
621		let server_coordinator = (*coordinator).clone();
622
623		#[cfg(feature = "websockets")]
624		let websocket_handler = self.websocket_handler;
625
626		let handler = self.handler;
627		let di_context = self.di_context;
628		let http2 = self.http2;
629
630		let server_task = tokio::spawn(async move {
631			// For WebSocket and HTTP/2 servers, we must drop the listener and re-bind
632			// because their APIs only accept SocketAddr. This has a small TOCTOU window
633			// but these server types are rarely used in parallel tests.
634			#[cfg(feature = "websockets")]
635			if let Some(ws_handler) = websocket_handler {
636				drop(listener);
637				let server = WebSocketServer::from_arc(ws_handler);
638				let _ = server
639					.listen_with_shutdown(actual_addr, server_coordinator)
640					.await;
641				return;
642			}
643
644			if let Some(h) = handler {
645				if http2 {
646					drop(listener);
647					let server = reinhardt_server::Http2Server::new(h);
648					let _ = server
649						.listen_with_shutdown(actual_addr, server_coordinator)
650						.await;
651				} else {
652					// Use the already-bound listener directly to avoid TOCTOU race
653					let server = HttpServer::new(h);
654					let mut shutdown_rx = server_coordinator.subscribe();
655					loop {
656						tokio::select! {
657							result = listener.accept() => {
658								match result {
659									Ok((stream, socket_addr)) => {
660										let handler_clone = server.handler();
661										let di_ctx = di_context.clone();
662										tokio::spawn(async move {
663											if let Err(e) =
664												HttpServer::handle_connection(stream, socket_addr, handler_clone, di_ctx)
665													.await
666											{
667												eprintln!("Error handling connection: {:?}", e);
668											}
669										});
670									}
671									Err(e) => {
672										eprintln!("Error accepting connection: {:?}", e);
673										break;
674									}
675								}
676							}
677							_ = shutdown_rx.recv() => {
678								break;
679							}
680						}
681					}
682				}
683			}
684		});
685
686		// Probe server readiness with TCP connect attempts instead of fixed sleep.
687		// This avoids flaky failures when the system is under heavy load.
688		wait_for_server_ready(actual_addr)
689			.await
690			.expect("Test server failed to become ready");
691
692		Ok(TestServer {
693			url,
694			addr: actual_addr,
695			coordinator,
696			server_task: Some(server_task),
697		})
698	}
699}
700
701// ============================================================================
702// Server Readiness Probe
703// ============================================================================
704
705/// Maximum number of TCP readiness probe attempts
706const SERVER_READY_MAX_ATTEMPTS: u32 = 20;
707
708/// Interval between TCP readiness probe attempts
709const SERVER_READY_PROBE_INTERVAL_MS: u64 = 50;
710
711/// Probe the server address with TCP connects until it accepts a connection.
712///
713/// This replaces a fixed `sleep(100ms)` with an active readiness check,
714/// eliminating flaky test failures caused by slow server startup under load.
715///
716/// # Errors
717///
718/// Returns an error if the server does not accept a TCP connection within
719/// the configured number of probe attempts.
720async fn wait_for_server_ready(addr: SocketAddr) -> Result<(), std::io::Error> {
721	for attempt in 1..=SERVER_READY_MAX_ATTEMPTS {
722		// Try to establish a TCP connection to verify the server is accepting
723		match tokio::net::TcpStream::connect(addr).await {
724			Ok(_) => return Ok(()),
725			Err(_) if attempt < SERVER_READY_MAX_ATTEMPTS => {
726				tokio::time::sleep(Duration::from_millis(SERVER_READY_PROBE_INTERVAL_MS)).await;
727			}
728			Err(e) => {
729				return Err(std::io::Error::new(
730					std::io::ErrorKind::TimedOut,
731					format!(
732						"Server at {} not ready after {} attempts: {}",
733						addr, SERVER_READY_MAX_ATTEMPTS, e
734					),
735				));
736			}
737		}
738	}
739
740	Err(std::io::Error::new(
741		std::io::ErrorKind::TimedOut,
742		format!(
743			"Server at {} not ready after {} attempts",
744			addr, SERVER_READY_MAX_ATTEMPTS
745		),
746	))
747}
748
749#[cfg(test)]
750mod tests {
751	use super::*;
752	use rstest::*;
753
754	#[rstest]
755	#[tokio::test]
756	async fn test_basic_handler_returns_ok() {
757		// Arrange
758		let handler = BasicHandler;
759		let request = Request::builder()
760			.method(hyper::Method::GET)
761			.uri("/")
762			.build()
763			.expect("Failed to build request");
764
765		// Act
766		let response = handler.handle(request).await;
767
768		// Assert
769		assert!(response.is_ok(), "Expected Ok response from BasicHandler");
770		let resp = response.unwrap();
771		assert_eq!(resp.status, hyper::StatusCode::OK);
772	}
773
774	#[rstest]
775	#[tokio::test]
776	async fn test_test_server_guard_starts() {
777		// Arrange
778		let router = Router::new();
779
780		// Act
781		let server = test_server_guard(router).await;
782
783		// Assert
784		assert!(
785			server.url.starts_with("http://127.0.0.1:"),
786			"Expected URL to start with 'http://127.0.0.1:', got: {}",
787			server.url
788		);
789	}
790
791	#[rstest]
792	#[tokio::test]
793	async fn test_test_server_builder_default() {
794		// Arrange
795		let handler: Arc<dyn Handler> = Arc::new(BasicHandler);
796
797		// Act
798		let result = TestServer::builder().handler(handler).build().await;
799
800		// Assert
801		assert!(
802			result.is_ok(),
803			"Expected TestServer::builder().handler().build() to succeed"
804		);
805	}
806
807	#[rstest]
808	#[tokio::test]
809	async fn test_test_server_url_format() {
810		// Arrange
811		let handler: Arc<dyn Handler> = Arc::new(BasicHandler);
812
813		// Act
814		let server = TestServer::builder()
815			.handler(handler)
816			.build()
817			.await
818			.expect("Failed to build TestServer");
819
820		// Assert
821		assert!(
822			server.url.starts_with("http://127.0.0.1:"),
823			"Expected URL format 'http://127.0.0.1:<port>', got: {}",
824			server.url
825		);
826		assert!(
827			server.addr.port() > 0,
828			"Expected non-zero port, got: {}",
829			server.addr.port()
830		);
831	}
832
833	#[rstest]
834	#[tokio::test]
835	async fn test_test_server_responds_to_request() {
836		// Arrange
837		let handler: Arc<dyn Handler> = Arc::new(BasicHandler);
838		let server = TestServer::builder()
839			.handler(handler)
840			.build()
841			.await
842			.expect("Failed to build TestServer");
843		let client = reqwest::Client::new();
844
845		// Act
846		let response = client.get(&server.url).send().await;
847
848		// Assert
849		assert!(response.is_ok(), "Expected GET request to succeed");
850		let resp = response.unwrap();
851		assert_eq!(resp.status(), reqwest::StatusCode::OK);
852	}
853
854	#[rstest]
855	fn test_http_client_fixture() {
856		// Arrange & Act
857		let client = http_client();
858
859		// Assert
860		// Verify client was created successfully by making a type assertion
861		let _: &reqwest::Client = &client;
862	}
863
864	#[rstest]
865	#[tokio::test]
866	async fn test_test_server_shutdown_timeout() {
867		// Arrange
868		let handler: Arc<dyn Handler> = Arc::new(BasicHandler);
869		let custom_timeout = Duration::from_secs(10);
870
871		// Act
872		let result = TestServer::builder()
873			.handler(handler)
874			.shutdown_timeout(custom_timeout)
875			.build()
876			.await;
877
878		// Assert
879		assert!(
880			result.is_ok(),
881			"Expected TestServer with custom shutdown timeout to build successfully"
882		);
883	}
884
885	#[rstest]
886	#[tokio::test]
887	async fn test_wait_for_server_ready() {
888		// Arrange
889		let listener = TcpListener::bind("127.0.0.1:0")
890			.await
891			.expect("Failed to bind listener");
892		let addr = listener.local_addr().expect("Failed to get local addr");
893
894		// Act
895		let result = wait_for_server_ready(addr).await;
896
897		// Assert
898		assert!(
899			result.is_ok(),
900			"Expected wait_for_server_ready to succeed for a bound address"
901		);
902	}
903}