1use crate::builder::MockServerBuilder;
4use crate::stub::ResponseStub;
5use crate::{Error, Result};
6use axum::Router;
7use mockforge_core::config::{RouteConfig, RouteResponseConfig};
8use mockforge_core::{Config, ServerConfig};
9use serde_json::Value;
10use std::net::SocketAddr;
11use tokio::task::JoinHandle;
12
13#[derive(Debug)]
15pub struct MockServer {
16 port: u16,
17 address: SocketAddr,
18 config: ServerConfig,
19 server_handle: Option<JoinHandle<()>>,
20 shutdown_tx: Option<tokio::sync::oneshot::Sender<()>>,
21 routes: Vec<RouteConfig>,
22}
23
24impl MockServer {
25 #[must_use]
27 pub const fn new() -> MockServerBuilder {
28 MockServerBuilder::new()
29 }
30
31 pub(crate) async fn from_config(
33 server_config: ServerConfig,
34 _core_config: Config,
35 ) -> Result<Self> {
36 let port = server_config.http.port;
37 let host = server_config.http.host.clone();
38
39 let address: SocketAddr = format!("{host}:{port}")
40 .parse()
41 .map_err(|e| Error::InvalidConfig(format!("Invalid address: {e}")))?;
42
43 Ok(Self {
44 port,
45 address,
46 config: server_config,
47 server_handle: None,
48 shutdown_tx: None,
49 routes: Vec::new(),
50 })
51 }
52
53 pub async fn start(&mut self) -> Result<()> {
55 if self.server_handle.is_some() {
56 return Err(Error::ServerAlreadyStarted(self.port));
57 }
58
59 let router = self.build_simple_router();
61
62 let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
64 self.shutdown_tx = Some(shutdown_tx);
65
66 let address = self.address;
67
68 let server_handle = tokio::spawn(async move {
70 let listener = match tokio::net::TcpListener::bind(address).await {
71 Ok(l) => l,
72 Err(e) => {
73 tracing::error!("Failed to bind to {}: {}", address, e);
74 return;
75 }
76 };
77
78 tracing::info!("MockForge SDK server listening on {}", address);
79
80 axum::serve(listener, router)
81 .with_graceful_shutdown(async move {
82 let _ = shutdown_rx.await;
83 })
84 .await
85 .expect("Server error");
86 });
87
88 self.server_handle = Some(server_handle);
89
90 self.wait_for_ready().await?;
92
93 Ok(())
94 }
95
96 async fn wait_for_ready(&self) -> Result<()> {
98 let max_attempts = 50;
99 let delay = tokio::time::Duration::from_millis(100);
100
101 for attempt in 0..max_attempts {
102 let client = reqwest::Client::builder()
104 .timeout(tokio::time::Duration::from_millis(100))
105 .build()
106 .map_err(|e| Error::General(format!("Failed to create HTTP client: {e}")))?;
107
108 match client.get(format!("{}/health", self.url())).send().await {
109 Ok(response) if response.status().is_success() => return Ok(()),
110 _ => {
111 if attempt < max_attempts - 1 {
112 tokio::time::sleep(delay).await;
113 }
114 }
115 }
116 }
117
118 Err(Error::General(format!(
119 "Server failed to become ready within {}ms",
120 max_attempts * delay.as_millis() as u32
121 )))
122 }
123
124 fn build_simple_router(&self) -> Router {
126 use axum::http::StatusCode;
127 use axum::routing::{delete, get, post, put};
128 use axum::{response::IntoResponse, Json};
129
130 let mut router = Router::new();
131
132 for route_config in &self.routes {
133 let status = route_config.response.status;
134 let body = route_config.response.body.clone();
135 let headers = route_config.response.headers.clone();
136
137 let handler = move || {
138 let body = body.clone();
139 let headers = headers.clone();
140 async move {
141 let mut response = Json(body).into_response();
142 *response.status_mut() = StatusCode::from_u16(status).unwrap();
143
144 for (key, value) in headers {
145 if let Ok(header_name) = axum::http::HeaderName::from_bytes(key.as_bytes())
146 {
147 if let Ok(header_value) = axum::http::HeaderValue::from_str(&value) {
148 response.headers_mut().insert(header_name, header_value);
149 }
150 }
151 }
152
153 response
154 }
155 };
156
157 let path = &route_config.path;
158
159 router = match route_config.method.to_uppercase().as_str() {
160 "GET" => router.route(path, get(handler)),
161 "POST" => router.route(path, post(handler)),
162 "PUT" => router.route(path, put(handler)),
163 "DELETE" => router.route(path, delete(handler)),
164 _ => router,
165 };
166 }
167
168 router
169 }
170
171 pub async fn stop(mut self) -> Result<()> {
173 if let Some(shutdown_tx) = self.shutdown_tx.take() {
174 let _ = shutdown_tx.send(());
175 }
176
177 if let Some(handle) = self.server_handle.take() {
178 let _ = handle.await;
179 }
180
181 Ok(())
182 }
183
184 pub async fn stub_response(
186 &mut self,
187 method: impl Into<String>,
188 path: impl Into<String>,
189 body: Value,
190 ) -> Result<()> {
191 let stub = ResponseStub::new(method, path, body);
192 self.add_stub(stub).await
193 }
194
195 pub async fn add_stub(&mut self, stub: ResponseStub) -> Result<()> {
197 let route_config = RouteConfig {
198 path: stub.path.clone(),
199 method: stub.method,
200 request: None,
201 response: RouteResponseConfig {
202 status: stub.status,
203 headers: stub.headers,
204 body: Some(stub.body),
205 },
206 fault_injection: None,
207 latency: None,
208 };
209
210 self.routes.push(route_config);
211
212 Ok(())
213 }
214
215 pub async fn clear_stubs(&mut self) -> Result<()> {
217 self.routes.clear();
218 Ok(())
219 }
220
221 #[must_use]
223 pub const fn port(&self) -> u16 {
224 self.port
225 }
226
227 #[must_use]
229 pub fn url(&self) -> String {
230 format!("http://{}", self.address)
231 }
232
233 #[must_use]
235 pub const fn is_running(&self) -> bool {
236 self.server_handle.is_some()
237 }
238}
239
240impl Default for MockServer {
241 fn default() -> Self {
242 Self {
243 port: 0,
244 address: "127.0.0.1:0".parse().unwrap(),
245 config: ServerConfig::default(),
246 server_handle: None,
247 shutdown_tx: None,
248 routes: Vec::new(),
249 }
250 }
251}
252
253impl Drop for MockServer {
255 fn drop(&mut self) {
256 if let Some(shutdown_tx) = self.shutdown_tx.take() {
257 let _ = shutdown_tx.send(());
258 }
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265 use serde_json::json;
266
267 #[test]
268 fn test_mock_server_new() {
269 let builder = MockServer::new();
270 assert_eq!(std::mem::size_of_val(&builder), std::mem::size_of::<MockServerBuilder>());
272 }
273
274 #[test]
275 fn test_mock_server_default() {
276 let server = MockServer::default();
277 assert_eq!(server.port, 0);
278 assert!(!server.is_running());
279 assert!(server.routes.is_empty());
280 }
281
282 #[test]
283 fn test_mock_server_port() {
284 let server = MockServer::default();
285 assert_eq!(server.port(), 0);
286 }
287
288 #[test]
289 fn test_mock_server_url() {
290 let mut server = MockServer::default();
291 server.port = 8080;
292 server.address = "127.0.0.1:8080".parse().unwrap();
293 assert_eq!(server.url(), "http://127.0.0.1:8080");
294 }
295
296 #[test]
297 fn test_mock_server_is_running_false() {
298 let server = MockServer::default();
299 assert!(!server.is_running());
300 }
301
302 #[tokio::test]
303 async fn test_from_config_valid() {
304 let server_config = ServerConfig::default();
305 let core_config = Config::default();
306
307 let result = MockServer::from_config(server_config, core_config).await;
308 assert!(result.is_ok());
309
310 let server = result.unwrap();
311 assert!(!server.is_running());
312 assert!(server.routes.is_empty());
313 }
314
315 #[tokio::test]
316 async fn test_from_config_invalid_address() {
317 let mut server_config = ServerConfig::default();
318 server_config.http.host = "invalid host with spaces".to_string();
319 let core_config = Config::default();
320
321 let result = MockServer::from_config(server_config, core_config).await;
322 assert!(result.is_err());
323 match result {
324 Err(Error::InvalidConfig(msg)) => {
325 assert!(msg.contains("Invalid address"));
326 }
327 _ => panic!("Expected InvalidConfig error"),
328 }
329 }
330
331 #[tokio::test]
332 async fn test_add_stub() {
333 let mut server = MockServer::default();
334 let stub = ResponseStub::new("GET", "/api/test", json!({"test": true}));
335
336 let result = server.add_stub(stub.clone()).await;
337 assert!(result.is_ok());
338 assert_eq!(server.routes.len(), 1);
339
340 let route = &server.routes[0];
341 assert_eq!(route.path, "/api/test");
342 assert_eq!(route.method, "GET");
343 assert_eq!(route.response.status, 200);
344 }
345
346 #[tokio::test]
347 async fn test_add_stub_with_custom_status() {
348 let mut server = MockServer::default();
349 let stub = ResponseStub::new("POST", "/api/create", json!({"created": true})).status(201);
350
351 let result = server.add_stub(stub).await;
352 assert!(result.is_ok());
353 assert_eq!(server.routes.len(), 1);
354
355 let route = &server.routes[0];
356 assert_eq!(route.response.status, 201);
357 }
358
359 #[tokio::test]
360 async fn test_add_stub_with_headers() {
361 let mut server = MockServer::default();
362 let stub = ResponseStub::new("GET", "/api/test", json!({}))
363 .header("Content-Type", "application/json")
364 .header("X-Custom", "value");
365
366 let result = server.add_stub(stub).await;
367 assert!(result.is_ok());
368
369 let route = &server.routes[0];
370 assert_eq!(
371 route.response.headers.get("Content-Type"),
372 Some(&"application/json".to_string())
373 );
374 assert_eq!(route.response.headers.get("X-Custom"), Some(&"value".to_string()));
375 }
376
377 #[tokio::test]
378 async fn test_stub_response() {
379 let mut server = MockServer::default();
380
381 let result = server.stub_response("GET", "/api/users", json!({"users": []})).await;
382 assert!(result.is_ok());
383 assert_eq!(server.routes.len(), 1);
384
385 let route = &server.routes[0];
386 assert_eq!(route.path, "/api/users");
387 assert_eq!(route.method, "GET");
388 }
389
390 #[tokio::test]
391 async fn test_clear_stubs() {
392 let mut server = MockServer::default();
393
394 server.stub_response("GET", "/api/test1", json!({})).await.unwrap();
395 server.stub_response("POST", "/api/test2", json!({})).await.unwrap();
396 assert_eq!(server.routes.len(), 2);
397
398 let result = server.clear_stubs().await;
399 assert!(result.is_ok());
400 assert_eq!(server.routes.len(), 0);
401 }
402
403 #[tokio::test]
404 async fn test_multiple_stubs() {
405 let mut server = MockServer::default();
406
407 server.stub_response("GET", "/api/users", json!({"users": []})).await.unwrap();
408 server
409 .stub_response("POST", "/api/users", json!({"created": true}))
410 .await
411 .unwrap();
412 server
413 .stub_response("DELETE", "/api/users/1", json!({"deleted": true}))
414 .await
415 .unwrap();
416
417 assert_eq!(server.routes.len(), 3);
418
419 assert_eq!(server.routes[0].method, "GET");
420 assert_eq!(server.routes[1].method, "POST");
421 assert_eq!(server.routes[2].method, "DELETE");
422 }
423
424 #[test]
425 fn test_build_simple_router_empty() {
426 let server = MockServer::default();
427 let router = server.build_simple_router();
428 assert_eq!(std::mem::size_of_val(&router), std::mem::size_of::<Router>());
430 }
431
432 #[tokio::test]
433 async fn test_build_simple_router_with_routes() {
434 let mut server = MockServer::default();
435 server.stub_response("GET", "/test", json!({"test": true})).await.unwrap();
436 server.stub_response("POST", "/create", json!({"created": true})).await.unwrap();
437
438 let router = server.build_simple_router();
439 assert_eq!(std::mem::size_of_val(&router), std::mem::size_of::<Router>());
441 }
442
443 #[tokio::test]
444 async fn test_start_server_already_started() {
445 let mut server = MockServer::default();
446 server.port = 0; server.address = "127.0.0.1:0".parse().unwrap();
448
449 let result = server.start().await;
451 assert!(result.is_ok());
452 assert!(server.is_running());
453
454 let result2 = server.start().await;
456 assert!(result2.is_err());
457 match result2 {
458 Err(Error::ServerAlreadyStarted(_)) => (),
459 _ => panic!("Expected ServerAlreadyStarted error"),
460 }
461
462 let _ = server.stop().await;
464 }
465
466 #[test]
467 fn test_server_debug_format() {
468 let server = MockServer::default();
469 let debug_str = format!("{server:?}");
470 assert!(debug_str.contains("MockServer"));
471 }
472
473 #[tokio::test]
474 async fn test_route_config_conversion() {
475 let mut server = MockServer::default();
476 let stub = ResponseStub::new("PUT", "/api/update", json!({"updated": true}))
477 .status(200)
478 .header("X-Version", "1.0");
479
480 server.add_stub(stub).await.unwrap();
481
482 let route = &server.routes[0];
483 assert_eq!(route.path, "/api/update");
484 assert_eq!(route.method, "PUT");
485 assert_eq!(route.response.status, 200);
486 assert_eq!(route.response.headers.get("X-Version"), Some(&"1.0".to_string()));
487 assert!(route.response.body.is_some());
488 assert_eq!(route.response.body, Some(json!({"updated": true})));
489 }
490
491 #[tokio::test]
492 async fn test_server_with_different_methods() {
493 let mut server = MockServer::default();
494
495 server.stub_response("GET", "/test", json!({})).await.unwrap();
496 server.stub_response("POST", "/test", json!({})).await.unwrap();
497 server.stub_response("PUT", "/test", json!({})).await.unwrap();
498 server.stub_response("DELETE", "/test", json!({})).await.unwrap();
499 server.stub_response("PATCH", "/test", json!({})).await.unwrap();
500
501 assert_eq!(server.routes.len(), 5);
502
503 let methods: Vec<_> = server.routes.iter().map(|r| r.method.as_str()).collect();
505 assert!(methods.contains(&"GET"));
506 assert!(methods.contains(&"POST"));
507 assert!(methods.contains(&"PUT"));
508 assert!(methods.contains(&"DELETE"));
509 assert!(methods.contains(&"PATCH"));
510 }
511
512 #[tokio::test]
513 async fn test_server_url_format() {
514 let mut server = MockServer::default();
515 server.port = 3000;
516 server.address = "127.0.0.1:3000".parse().unwrap();
517
518 let url = server.url();
519 assert_eq!(url, "http://127.0.0.1:3000");
520 assert!(url.starts_with("http://"));
521 }
522
523 #[tokio::test]
524 async fn test_server_with_ipv6_address() {
525 let mut server = MockServer::default();
526 server.port = 8080;
527 server.address = "[::1]:8080".parse().unwrap();
528
529 let url = server.url();
530 assert_eq!(url, "http://[::1]:8080");
531 }
532}