mockforge_data/
mock_server.rs

1//! Mock Server Mode Implementation
2//!
3//! This module provides MSW-style mock server capabilities that can serve
4//! generated mock data based on OpenAPI specifications.
5
6use crate::mock_generator::{MockDataGenerator, MockDataResult, MockGeneratorConfig, MockResponse};
7use crate::{Error, Result};
8use axum::{
9    extract::Query,
10    http::{HeaderMap, StatusCode},
11    response::Json,
12    routing::get,
13    Router,
14};
15use serde_json::{json, Value};
16use std::collections::HashMap;
17use std::net::SocketAddr;
18use std::sync::Arc;
19use tokio::net::TcpListener;
20use tracing::info;
21
22/// Configuration for the mock server
23#[derive(Debug, Clone)]
24pub struct MockServerConfig {
25    /// Port to run the server on
26    pub port: u16,
27    /// Host to bind to
28    pub host: String,
29    /// OpenAPI specification
30    pub openapi_spec: Value,
31    /// Mock data generator configuration
32    pub generator_config: MockGeneratorConfig,
33    /// Whether to enable CORS
34    pub enable_cors: bool,
35    /// Custom response delays (in milliseconds)
36    pub response_delays: HashMap<String, u64>,
37    /// Whether to log all requests
38    pub log_requests: bool,
39}
40
41impl Default for MockServerConfig {
42    fn default() -> Self {
43        Self {
44            port: 3000,
45            host: "127.0.0.1".to_string(),
46            openapi_spec: json!({}),
47            generator_config: MockGeneratorConfig::default(),
48            enable_cors: true,
49            response_delays: HashMap::new(),
50            log_requests: true,
51        }
52    }
53}
54
55impl MockServerConfig {
56    /// Create a new mock server configuration
57    pub fn new(openapi_spec: Value) -> Self {
58        Self {
59            openapi_spec,
60            ..Default::default()
61        }
62    }
63
64    /// Set the port
65    pub fn port(mut self, port: u16) -> Self {
66        self.port = port;
67        self
68    }
69
70    /// Set the host
71    pub fn host(mut self, host: String) -> Self {
72        self.host = host;
73        self
74    }
75
76    /// Set the generator configuration
77    pub fn generator_config(mut self, config: MockGeneratorConfig) -> Self {
78        self.generator_config = config;
79        self
80    }
81
82    /// Enable or disable CORS
83    pub fn enable_cors(mut self, enabled: bool) -> Self {
84        self.enable_cors = enabled;
85        self
86    }
87
88    /// Add a response delay for a specific endpoint
89    pub fn response_delay(mut self, endpoint: String, delay_ms: u64) -> Self {
90        self.response_delays.insert(endpoint, delay_ms);
91        self
92    }
93
94    /// Enable or disable request logging
95    pub fn log_requests(mut self, enabled: bool) -> Self {
96        self.log_requests = enabled;
97        self
98    }
99}
100
101/// Mock server that serves generated data based on OpenAPI specifications
102#[derive(Debug)]
103pub struct MockServer {
104    /// Server configuration
105    config: MockServerConfig,
106    /// Generated mock data
107    mock_data: Arc<MockDataResult>,
108    /// Route handlers
109    handlers: HashMap<String, MockResponse>,
110}
111
112impl MockServer {
113    /// Create a new mock server
114    pub fn new(config: MockServerConfig) -> Result<Self> {
115        info!("Creating mock server with OpenAPI specification");
116
117        // Generate mock data from the OpenAPI spec
118        let mut generator = MockDataGenerator::with_config(config.generator_config.clone());
119        let mock_data = generator.generate_from_openapi_spec(&config.openapi_spec)?;
120
121        // Create handlers map from generated responses
122        let mut handlers = HashMap::new();
123        for (endpoint, response) in &mock_data.responses {
124            handlers.insert(endpoint.clone(), response.clone());
125        }
126
127        Ok(Self {
128            config,
129            mock_data: Arc::new(mock_data),
130            handlers,
131        })
132    }
133
134    /// Start the mock server
135    pub async fn start(self) -> Result<()> {
136        let config = self.config.clone();
137        let app = self.create_router();
138        let addr = SocketAddr::from(([127, 0, 0, 1], config.port));
139
140        info!("Starting mock server on {}", addr);
141
142        let listener = TcpListener::bind(addr)
143            .await
144            .map_err(|e| Error::generic(format!("Failed to bind to {}: {}", addr, e)))?;
145
146        axum::serve(listener, app)
147            .await
148            .map_err(|e| Error::generic(format!("Server error: {}", e)))?;
149
150        Ok(())
151    }
152
153    /// Create the Axum router with all endpoints
154    fn create_router(self) -> Router {
155        let mock_data = Arc::clone(&self.mock_data);
156        let config = Arc::new(self.config);
157        let handlers = Arc::new(self.handlers);
158
159        Router::new()
160            // Add all OpenAPI endpoints dynamically
161            .route("/", get(Self::root_handler))
162            .route("/health", get(Self::health_handler))
163            .route("/openapi.json", get(Self::openapi_handler))
164            .route("/mock-data", get(Self::mock_data_handler))
165            // Dynamic routes will be added based on OpenAPI spec
166            .with_state(MockServerState {
167                mock_data,
168                config,
169                handlers,
170            })
171    }
172
173    /// Root handler - returns API information
174    async fn root_handler() -> Json<Value> {
175        Json(json!({
176            "name": "MockForge Mock Server",
177            "version": "1.0.0",
178            "description": "Mock server powered by MockForge",
179            "endpoints": {
180                "/health": "Health check endpoint",
181                "/openapi.json": "OpenAPI specification",
182                "/mock-data": "Generated mock data"
183            }
184        }))
185    }
186
187    /// Health check handler
188    async fn health_handler() -> Json<Value> {
189        Json(json!({
190            "status": "healthy",
191            "timestamp": chrono::Utc::now().to_rfc3339(),
192            "service": "mockforge-mock-server"
193        }))
194    }
195
196    /// OpenAPI specification handler
197    async fn openapi_handler(
198        axum::extract::State(state): axum::extract::State<MockServerState>,
199    ) -> Json<Value> {
200        Json(serde_json::to_value(&state.mock_data.spec_info).unwrap_or(json!({})))
201    }
202
203    /// Mock data handler - returns all generated mock data
204    async fn mock_data_handler(
205        axum::extract::State(state): axum::extract::State<MockServerState>,
206    ) -> Json<Value> {
207        Json(json!({
208            "schemas": state.mock_data.schemas,
209            "responses": state.mock_data.responses,
210            "warnings": state.mock_data.warnings
211        }))
212    }
213
214    /// Generic endpoint handler that serves mock data based on the request
215    ///
216    /// This handler is kept for future use when implementing generic mock server
217    /// functionality that doesn't require pre-defined routes.
218    ///
219    /// TODO: Integrate into mock server when generic routing is implemented
220    #[allow(dead_code)] // TODO: Remove when generic handler is integrated
221    async fn generic_handler(
222        axum::extract::State(state): axum::extract::State<MockServerState>,
223        method: axum::http::Method,
224        path: axum::extract::Path<String>,
225        query: Query<HashMap<String, String>>,
226        _headers: HeaderMap,
227    ) -> std::result::Result<Json<Value>, StatusCode> {
228        let endpoint_key = format!("{} /{}", method.as_str().to_uppercase(), path.as_str());
229
230        // Log request if enabled
231        if state.config.log_requests {
232            info!("Handling request: {} with query: {:?}", endpoint_key, query);
233        }
234
235        // Apply response delay if configured
236        if let Some(delay) = state.config.response_delays.get(&endpoint_key) {
237            tokio::time::sleep(tokio::time::Duration::from_millis(*delay)).await;
238        }
239
240        // Find matching handler
241        if let Some(response) = state.handlers.get(&endpoint_key) {
242            Ok(Json(response.body.clone()))
243        } else {
244            // Try to find a similar endpoint (for path parameters)
245            let similar_endpoint = state
246                .handlers
247                .keys()
248                .find(|key| Self::endpoints_match(key, &endpoint_key))
249                .cloned();
250
251            if let Some(endpoint) = similar_endpoint {
252                if let Some(response) = state.handlers.get(&endpoint) {
253                    Ok(Json(response.body.clone()))
254                } else {
255                    Err(StatusCode::NOT_FOUND)
256                }
257            } else {
258                // Return a generic mock response
259                let generic_response = json!({
260                    "message": "Mock response",
261                    "endpoint": endpoint_key,
262                    "timestamp": chrono::Utc::now().to_rfc3339(),
263                    "data": {}
264                });
265                Ok(Json(generic_response))
266            }
267        }
268    }
269
270    /// Check if two endpoints match (handles path parameters)
271    ///
272    /// TODO: Integrate into route matching system when advanced path parameter matching is implemented
273    #[allow(dead_code)] // TODO: Remove when route matching system is updated
274    pub fn endpoints_match(pattern: &str, request: &str) -> bool {
275        // Simple pattern matching - in a real implementation,
276        // you'd want more sophisticated path parameter matching
277        let pattern_parts: Vec<&str> = pattern.split(' ').collect();
278        let request_parts: Vec<&str> = request.split(' ').collect();
279
280        if pattern_parts.len() != request_parts.len() {
281            return false;
282        }
283
284        for (pattern_part, request_part) in pattern_parts.iter().zip(request_parts.iter()) {
285            if pattern_part != request_part && !pattern_part.contains(":") {
286                return false;
287            }
288        }
289
290        true
291    }
292}
293
294/// State shared across all handlers
295#[derive(Debug, Clone)]
296struct MockServerState {
297    mock_data: Arc<MockDataResult>,
298    // These fields are reserved for future mock server configuration
299    // TODO: Integrate config and handlers when full mock server implementation is completed
300    #[allow(dead_code)] // TODO: Remove when config integration is complete
301    config: Arc<MockServerConfig>,
302    #[allow(dead_code)] // TODO: Remove when handler registry is integrated
303    handlers: Arc<HashMap<String, MockResponse>>,
304}
305
306/// Builder for creating mock servers
307#[derive(Debug)]
308pub struct MockServerBuilder {
309    config: MockServerConfig,
310}
311
312impl MockServerBuilder {
313    /// Create a new mock server builder
314    pub fn new(openapi_spec: Value) -> Self {
315        Self {
316            config: MockServerConfig::new(openapi_spec),
317        }
318    }
319
320    /// Set the port
321    pub fn port(mut self, port: u16) -> Self {
322        self.config = self.config.port(port);
323        self
324    }
325
326    /// Set the host
327    pub fn host(mut self, host: String) -> Self {
328        self.config = self.config.host(host);
329        self
330    }
331
332    /// Set the generator configuration
333    pub fn generator_config(mut self, config: MockGeneratorConfig) -> Self {
334        self.config = self.config.generator_config(config);
335        self
336    }
337
338    /// Enable or disable CORS
339    pub fn enable_cors(mut self, enabled: bool) -> Self {
340        self.config = self.config.enable_cors(enabled);
341        self
342    }
343
344    /// Add a response delay for a specific endpoint
345    pub fn response_delay(mut self, endpoint: String, delay_ms: u64) -> Self {
346        self.config = self.config.response_delay(endpoint, delay_ms);
347        self
348    }
349
350    /// Enable or disable request logging
351    pub fn log_requests(mut self, enabled: bool) -> Self {
352        self.config = self.config.log_requests(enabled);
353        self
354    }
355
356    /// Build the mock server
357    pub fn build(self) -> Result<MockServer> {
358        MockServer::new(self.config)
359    }
360}
361
362/// Quick function to start a mock server
363pub async fn start_mock_server(openapi_spec: Value, port: u16) -> Result<()> {
364    let server = MockServerBuilder::new(openapi_spec).port(port).build()?;
365
366    server.start().await
367}
368
369/// Quick function to start a mock server with custom configuration
370pub async fn start_mock_server_with_config(config: MockServerConfig) -> Result<()> {
371    let server = MockServer::new(config)?;
372    server.start().await
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378    use serde_json::json;
379
380    #[test]
381    fn test_mock_server_config_default() {
382        let config = MockServerConfig::default();
383
384        assert_eq!(config.port, 3000);
385        assert_eq!(config.host, "127.0.0.1");
386        assert!(config.enable_cors);
387        assert!(config.log_requests);
388        assert!(config.response_delays.is_empty());
389    }
390
391    #[test]
392    fn test_mock_server_config_new() {
393        let spec = json!({
394            "openapi": "3.0.0",
395            "info": {
396                "title": "Test API",
397                "version": "1.0.0"
398            }
399        });
400
401        let config = MockServerConfig::new(spec);
402
403        assert_eq!(config.port, 3000);
404        assert_eq!(config.host, "127.0.0.1");
405        assert!(config.enable_cors);
406    }
407
408    #[test]
409    fn test_mock_server_config_builder_methods() {
410        let spec = json!({
411            "openapi": "3.0.0",
412            "info": {
413                "title": "Test API",
414                "version": "1.0.0"
415            }
416        });
417
418        let config = MockServerConfig::new(spec)
419            .port(8080)
420            .host("0.0.0.0".to_string())
421            .enable_cors(false)
422            .response_delay("/api/users".to_string(), 100)
423            .log_requests(false);
424
425        assert_eq!(config.port, 8080);
426        assert_eq!(config.host, "0.0.0.0");
427        assert!(!config.enable_cors);
428        assert!(!config.log_requests);
429        assert!(config.response_delays.contains_key("/api/users"));
430        assert_eq!(config.response_delays.get("/api/users"), Some(&100));
431    }
432
433    #[test]
434    fn test_mock_server_builder() {
435        let spec = json!({
436            "openapi": "3.0.0",
437            "info": {
438                "title": "Test API",
439                "version": "1.0.0"
440            }
441        });
442
443        let builder = MockServerBuilder::new(spec)
444            .port(8080)
445            .host("0.0.0.0".to_string())
446            .enable_cors(false);
447
448        assert_eq!(builder.config.port, 8080);
449        assert_eq!(builder.config.host, "0.0.0.0");
450        assert!(!builder.config.enable_cors);
451    }
452
453    #[test]
454    fn test_endpoints_match_exact() {
455        assert!(MockServer::endpoints_match("GET /api/users", "GET /api/users"));
456        assert!(!MockServer::endpoints_match("GET /api/users", "POST /api/users"));
457        assert!(!MockServer::endpoints_match("GET /api/users", "GET /api/products"));
458    }
459
460    #[test]
461    fn test_endpoints_match_with_params() {
462        // This is a simplified test - real path parameter matching would be more complex
463        assert!(MockServer::endpoints_match("GET /api/users/:id", "GET /api/users/123"));
464        assert!(MockServer::endpoints_match("GET /api/users/:id", "GET /api/users/abc"));
465    }
466
467    #[tokio::test]
468    async fn test_mock_server_creation() {
469        let spec = json!({
470            "openapi": "3.0.0",
471            "info": {
472                "title": "Test API",
473                "version": "1.0.0"
474            },
475            "paths": {
476                "/api/users": {
477                    "get": {
478                        "responses": {
479                            "200": {
480                                "description": "List of users",
481                                "content": {
482                                    "application/json": {
483                                        "schema": {
484                                            "type": "object",
485                                            "properties": {
486                                                "users": {
487                                                    "type": "array",
488                                                    "items": {
489                                                        "type": "object",
490                                                        "properties": {
491                                                            "id": {"type": "string"},
492                                                            "name": {"type": "string"},
493                                                            "email": {"type": "string"}
494                                                        }
495                                                    }
496                                                }
497                                            }
498                                        }
499                                    }
500                                }
501                            }
502                        }
503                    }
504                }
505            }
506        });
507
508        let config = MockServerConfig::new(spec);
509        let server = MockServer::new(config);
510
511        assert!(server.is_ok());
512    }
513}