1use crate::server::MockServer;
4use crate::{Error, Result};
5use mockforge_core::{Config, FailureConfig, LatencyProfile, ProxyConfig, ServerConfig};
6use std::net::TcpListener;
7use std::path::PathBuf;
8
9pub struct MockServerBuilder {
11 port: Option<u16>,
12 host: Option<String>,
13 config_file: Option<PathBuf>,
14 openapi_spec: Option<PathBuf>,
15 latency_profile: Option<LatencyProfile>,
16 failure_config: Option<FailureConfig>,
17 proxy_config: Option<ProxyConfig>,
18 enable_admin: bool,
19 admin_port: Option<u16>,
20 auto_port: bool,
21 port_range: Option<(u16, u16)>,
22}
23
24impl Default for MockServerBuilder {
25 fn default() -> Self {
26 Self::new()
27 }
28}
29
30impl MockServerBuilder {
31 #[must_use]
33 pub const fn new() -> Self {
34 Self {
35 port: None,
36 host: None,
37 config_file: None,
38 openapi_spec: None,
39 latency_profile: None,
40 failure_config: None,
41 proxy_config: None,
42 enable_admin: false,
43 admin_port: None,
44 auto_port: false,
45 port_range: None,
46 }
47 }
48
49 #[must_use]
51 pub const fn port(mut self, port: u16) -> Self {
52 self.port = Some(port);
53 self.auto_port = false;
54 self
55 }
56
57 #[must_use]
64 pub const fn auto_port(mut self) -> Self {
65 self.auto_port = true;
66 self.port = None;
67 self
68 }
69
70 #[must_use]
78 pub const fn port_range(mut self, start: u16, end: u16) -> Self {
79 self.port_range = Some((start, end));
80 self
81 }
82
83 pub fn host(mut self, host: impl Into<String>) -> Self {
85 self.host = Some(host.into());
86 self
87 }
88
89 pub fn config_file(mut self, path: impl Into<PathBuf>) -> Self {
91 self.config_file = Some(path.into());
92 self
93 }
94
95 pub fn openapi_spec(mut self, path: impl Into<PathBuf>) -> Self {
97 self.openapi_spec = Some(path.into());
98 self
99 }
100
101 #[must_use]
103 pub fn latency(mut self, profile: LatencyProfile) -> Self {
104 self.latency_profile = Some(profile);
105 self
106 }
107
108 #[must_use]
110 pub fn failures(mut self, config: FailureConfig) -> Self {
111 self.failure_config = Some(config);
112 self
113 }
114
115 #[must_use]
117 pub fn proxy(mut self, config: ProxyConfig) -> Self {
118 self.proxy_config = Some(config);
119 self
120 }
121
122 #[must_use]
124 pub const fn admin(mut self, enabled: bool) -> Self {
125 self.enable_admin = enabled;
126 self
127 }
128
129 #[must_use]
131 pub const fn admin_port(mut self, port: u16) -> Self {
132 self.admin_port = Some(port);
133 self
134 }
135
136 pub async fn start(self) -> Result<MockServer> {
138 let mut config = if let Some(config_file) = self.config_file {
140 mockforge_core::load_config(&config_file)
141 .await
142 .map_err(|e| Error::InvalidConfig(e.to_string()))?
143 } else {
144 ServerConfig::default()
145 };
146
147 if self.auto_port {
149 if let Some((start, end)) = self.port_range {
150 let port = find_available_port(start, end)?;
152 config.http.port = port;
153 } else {
154 config.http.port = 0;
157 }
158 } else if let Some(port) = self.port {
159 config.http.port = port;
160 }
161
162 if let Some(host) = self.host {
164 config.http.host = host;
165 }
166 if let Some(spec_path) = self.openapi_spec {
167 config.http.openapi_spec = Some(spec_path.to_string_lossy().to_string());
168 }
169
170 let mut core_config = Config::default();
172
173 if let Some(latency) = self.latency_profile {
174 core_config.latency_enabled = true;
175 core_config.default_latency = latency;
176 }
177
178 if let Some(failures) = self.failure_config {
179 core_config.failures_enabled = true;
180 core_config.failure_config = Some(failures);
181 }
182
183 if let Some(proxy) = self.proxy_config {
184 core_config.proxy = Some(proxy);
185 }
186
187 let mut server = MockServer::from_config(config, core_config).await?;
189 server.start().await?;
190 Ok(server)
191 }
192}
193
194fn is_port_available(port: u16) -> bool {
201 TcpListener::bind(("127.0.0.1", port)).is_ok()
202}
203
204fn find_available_port(start: u16, end: u16) -> Result<u16> {
217 if start >= end {
219 return Err(Error::InvalidConfig(format!(
220 "Invalid port range: start ({start}) must be less than end ({end})"
221 )));
222 }
223
224 for port in start..=end {
226 if is_port_available(port) {
227 return Ok(port);
228 }
229 }
230
231 Err(Error::PortDiscoveryFailed(format!(
232 "No available ports found in range {start}-{end}"
233 )))
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use std::path::PathBuf;
240
241 #[test]
242 fn test_builder_new() {
243 let builder = MockServerBuilder::new();
244 assert!(builder.port.is_none());
245 assert!(builder.host.is_none());
246 assert!(!builder.enable_admin);
247 assert!(!builder.auto_port);
248 }
249
250 #[test]
251 fn test_builder_default() {
252 let builder = MockServerBuilder::default();
253 assert!(builder.port.is_none());
254 assert!(builder.host.is_none());
255 }
256
257 #[test]
258 fn test_builder_port() {
259 let builder = MockServerBuilder::new().port(8080);
260 assert_eq!(builder.port, Some(8080));
261 assert!(!builder.auto_port);
262 }
263
264 #[test]
265 fn test_builder_auto_port() {
266 let builder = MockServerBuilder::new().auto_port();
267 assert!(builder.auto_port);
268 assert!(builder.port.is_none());
269 }
270
271 #[test]
272 fn test_builder_auto_port_overrides_manual_port() {
273 let builder = MockServerBuilder::new().port(8080).auto_port();
274 assert!(builder.auto_port);
275 assert!(builder.port.is_none());
276 }
277
278 #[test]
279 fn test_builder_manual_port_overrides_auto_port() {
280 let builder = MockServerBuilder::new().auto_port().port(8080);
281 assert!(!builder.auto_port);
282 assert_eq!(builder.port, Some(8080));
283 }
284
285 #[test]
286 fn test_builder_port_range() {
287 let builder = MockServerBuilder::new().port_range(30000, 31000);
288 assert_eq!(builder.port_range, Some((30000, 31000)));
289 }
290
291 #[test]
292 fn test_builder_host() {
293 let builder = MockServerBuilder::new().host("0.0.0.0");
294 assert_eq!(builder.host, Some("0.0.0.0".to_string()));
295 }
296
297 #[test]
298 fn test_builder_config_file() {
299 let builder = MockServerBuilder::new().config_file("/path/to/config.yaml");
300 assert_eq!(builder.config_file, Some(PathBuf::from("/path/to/config.yaml")));
301 }
302
303 #[test]
304 fn test_builder_openapi_spec() {
305 let builder = MockServerBuilder::new().openapi_spec("/path/to/spec.yaml");
306 assert_eq!(builder.openapi_spec, Some(PathBuf::from("/path/to/spec.yaml")));
307 }
308
309 #[test]
310 fn test_builder_latency() {
311 let latency = LatencyProfile::new(100, 0);
312 let builder = MockServerBuilder::new().latency(latency);
313 assert!(builder.latency_profile.is_some());
314 }
315
316 #[test]
317 fn test_builder_failures() {
318 let failures = FailureConfig {
319 global_error_rate: 0.1,
320 default_status_codes: vec![500, 503],
321 ..Default::default()
322 };
323 let builder = MockServerBuilder::new().failures(failures);
324 assert!(builder.failure_config.is_some());
325 }
326
327 #[test]
328 fn test_builder_proxy() {
329 let proxy = ProxyConfig {
330 enabled: true,
331 target_url: Some("http://example.com".to_string()),
332 ..Default::default()
333 };
334 let builder = MockServerBuilder::new().proxy(proxy);
335 assert!(builder.proxy_config.is_some());
336 }
337
338 #[test]
339 fn test_builder_admin() {
340 let builder = MockServerBuilder::new().admin(true);
341 assert!(builder.enable_admin);
342 }
343
344 #[test]
345 fn test_builder_admin_port() {
346 let builder = MockServerBuilder::new().admin_port(9090);
347 assert_eq!(builder.admin_port, Some(9090));
348 }
349
350 #[test]
351 fn test_builder_fluent_chaining() {
352 let latency = LatencyProfile::new(50, 0);
353 let failures = FailureConfig {
354 global_error_rate: 0.05,
355 default_status_codes: vec![500],
356 ..Default::default()
357 };
358
359 let builder = MockServerBuilder::new()
360 .port(8080)
361 .host("localhost")
362 .latency(latency)
363 .failures(failures)
364 .admin(true)
365 .admin_port(9090);
366
367 assert_eq!(builder.port, Some(8080));
368 assert_eq!(builder.host, Some("localhost".to_string()));
369 assert!(builder.latency_profile.is_some());
370 assert!(builder.failure_config.is_some());
371 assert!(builder.enable_admin);
372 assert_eq!(builder.admin_port, Some(9090));
373 }
374
375 #[test]
376 fn test_is_port_available_unbound_port() {
377 assert!(is_port_available(0));
379 }
380
381 #[test]
382 fn test_is_port_available_bound_port() {
383 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
385 let addr = listener.local_addr().unwrap();
386 let port = addr.port();
387
388 assert!(!is_port_available(port));
390 }
391
392 #[test]
393 fn test_find_available_port_success() {
394 let result = find_available_port(30000, 35000);
396 assert!(result.is_ok());
397 let port = result.unwrap();
398 assert!(port >= 30000 && port <= 35000);
399 }
400
401 #[test]
402 fn test_find_available_port_invalid_range_equal() {
403 let result = find_available_port(8080, 8080);
404 assert!(result.is_err());
405 match result {
406 Err(Error::InvalidConfig(msg)) => {
407 assert!(msg.contains("Invalid port range"));
408 assert!(msg.contains("8080"));
409 }
410 _ => panic!("Expected InvalidConfig error"),
411 }
412 }
413
414 #[test]
415 fn test_find_available_port_invalid_range_reversed() {
416 let result = find_available_port(9000, 8000);
417 assert!(result.is_err());
418 match result {
419 Err(Error::InvalidConfig(msg)) => {
420 assert!(msg.contains("Invalid port range"));
421 }
422 _ => panic!("Expected InvalidConfig error"),
423 }
424 }
425
426 #[test]
427 fn test_find_available_port_no_ports_available() {
428 let port1 = 40000;
430 let port2 = 40001;
431 let _listener1 = TcpListener::bind(("127.0.0.1", port1)).ok();
432 let _listener2 = TcpListener::bind(("127.0.0.1", port2)).ok();
433
434 if _listener1.is_some() && _listener2.is_some() {
436 let result = find_available_port(port1, port2);
437 assert!(result.is_err());
438 match result {
439 Err(Error::PortDiscoveryFailed(msg)) => {
440 assert!(msg.contains("No available ports"));
441 assert!(msg.contains("40000"));
442 assert!(msg.contains("40001"));
443 }
444 _ => panic!("Expected PortDiscoveryFailed error"),
445 }
446 }
447 }
448
449 #[test]
450 fn test_find_available_port_single_port_range() {
451 let result = find_available_port(45000, 45001);
453 assert!(result.is_ok());
454 let port = result.unwrap();
455 assert!(port == 45000 || port == 45001);
456 }
457
458 #[test]
459 fn test_builder_multiple_config_sources() {
460 let builder = MockServerBuilder::new()
461 .config_file("/path/to/config.yaml")
462 .openapi_spec("/path/to/spec.yaml")
463 .port(8080)
464 .host("localhost");
465
466 assert!(builder.config_file.is_some());
467 assert!(builder.openapi_spec.is_some());
468 assert_eq!(builder.port, Some(8080));
469 assert_eq!(builder.host, Some("localhost".to_string()));
470 }
471
472 #[test]
473 fn test_builder_with_all_features() {
474 let latency = LatencyProfile::new(100, 0);
475 let failures = FailureConfig {
476 global_error_rate: 0.1,
477 default_status_codes: vec![500, 503],
478 ..Default::default()
479 };
480 let proxy = ProxyConfig {
481 enabled: true,
482 target_url: Some("http://backend.com".to_string()),
483 ..Default::default()
484 };
485
486 let builder = MockServerBuilder::new()
487 .port(8080)
488 .host("0.0.0.0")
489 .config_file("/config.yaml")
490 .openapi_spec("/spec.yaml")
491 .latency(latency)
492 .failures(failures)
493 .proxy(proxy)
494 .admin(true)
495 .admin_port(9090);
496
497 assert!(builder.port.is_some());
498 assert!(builder.host.is_some());
499 assert!(builder.config_file.is_some());
500 assert!(builder.openapi_spec.is_some());
501 assert!(builder.latency_profile.is_some());
502 assert!(builder.failure_config.is_some());
503 assert!(builder.proxy_config.is_some());
504 assert!(builder.enable_admin);
505 assert!(builder.admin_port.is_some());
506 }
507
508 #[test]
509 fn test_builder_port_range_default() {
510 let builder = MockServerBuilder::new().auto_port();
511 assert!(builder.port_range.is_none());
513 }
514
515 #[test]
516 fn test_builder_port_range_custom() {
517 let builder = MockServerBuilder::new().auto_port().port_range(40000, 50000);
518 assert_eq!(builder.port_range, Some((40000, 50000)));
519 }
520}