mockforge_http/
lib.rs

1//! # MockForge HTTP
2//!
3//! HTTP/REST API mocking library for MockForge.
4//!
5//! This crate provides HTTP-specific functionality for creating mock REST APIs,
6//! including OpenAPI integration, request validation, AI-powered response generation,
7//! and management endpoints.
8//!
9//! ## Overview
10//!
11//! MockForge HTTP enables you to:
12//!
13//! - **Serve OpenAPI specs**: Automatically generate mock endpoints from OpenAPI/Swagger
14//! - **Validate requests**: Enforce schema validation with configurable modes
15//! - **AI-powered responses**: Generate intelligent responses using LLMs
16//! - **Management API**: Real-time monitoring, configuration, and control
17//! - **Request logging**: Comprehensive HTTP request/response logging
18//! - **Metrics collection**: Track performance and usage statistics
19//! - **Server-Sent Events**: Stream logs and metrics to clients
20//!
21//! ## Quick Start
22//!
23//! ### Basic HTTP Server from OpenAPI
24//!
25//! ```rust,no_run
26//! use axum::Router;
27//! use mockforge_core::openapi_routes::ValidationMode;
28//! use mockforge_core::ValidationOptions;
29//! use mockforge_http::build_router;
30//!
31//! #[tokio::main]
32//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
33//!     // Build router from OpenAPI specification
34//!     let router = build_router(
35//!         Some("./api-spec.json".to_string()),
36//!         Some(ValidationOptions {
37//!             request_mode: ValidationMode::Enforce,
38//!             ..ValidationOptions::default()
39//!         }),
40//!         None,
41//!     ).await;
42//!
43//!     // Start the server
44//!     let addr: std::net::SocketAddr = "0.0.0.0:3000".parse()?;
45//!     let listener = tokio::net::TcpListener::bind(addr).await?;
46//!     axum::serve(listener, router).await?;
47//!
48//!     Ok(())
49//! }
50//! ```
51//!
52//! ### With Management API
53//!
54//! Enable real-time monitoring and configuration:
55//!
56//! ```rust,no_run
57//! use mockforge_http::{management_router, ManagementState};
58//!
59//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
60//! let state = ManagementState::new(None, None, 3000);
61//!
62//! // Build management router
63//! let mgmt_router = management_router(state);
64//!
65//! // Mount under your main router
66//! let app = axum::Router::new()
67//!     .nest("/__mockforge", mgmt_router);
68//! # Ok(())
69//! # }
70//! ```
71//!
72//! ### AI-Powered Responses
73//!
74//! Generate intelligent responses based on request context:
75//!
76//! ```rust,no_run
77//! use mockforge_data::intelligent_mock::{IntelligentMockConfig, ResponseMode};
78//! use mockforge_http::{process_response_with_ai, AiResponseConfig};
79//! use serde_json::json;
80//!
81//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
82//! let ai_config = AiResponseConfig {
83//!     intelligent: Some(
84//!         IntelligentMockConfig::new(ResponseMode::Intelligent)
85//!             .with_prompt("Generate realistic user data".to_string()),
86//!     ),
87//!     drift: None,
88//! };
89//!
90//! let response = process_response_with_ai(
91//!     Some(json!({"name": "Alice"})),
92//!     ai_config
93//!         .intelligent
94//!         .clone()
95//!         .map(serde_json::to_value)
96//!         .transpose()?,
97//!     ai_config
98//!         .drift
99//!         .clone()
100//!         .map(serde_json::to_value)
101//!         .transpose()?,
102//! )
103//! .await?;
104//! # Ok(())
105//! # }
106//! ```
107//!
108//! ## Key Features
109//!
110//! ### OpenAPI Integration
111//! - Automatic endpoint generation from specs
112//! - Request/response validation
113//! - Schema-based mock data generation
114//!
115//! ### Management & Monitoring
116//! - [`management`]: REST API for server control and monitoring
117//! - [`management_ws`]: WebSocket API for real-time updates
118//! - [`sse`]: Server-Sent Events for log streaming
119//! - [`request_logging`]: Comprehensive request/response logging
120//! - [`metrics_middleware`]: Performance metrics collection
121//!
122//! ### Advanced Features
123//! - [`ai_handler`]: AI-powered response generation
124//! - [`auth`]: Authentication and authorization
125//! - [`chain_handlers`]: Multi-step request workflows
126//! - [`latency_profiles`]: Configurable latency simulation
127//! - [`replay_listing`]: Fixture management
128//!
129//! ## Middleware
130//!
131//! MockForge HTTP includes several middleware layers:
132//!
133//! - **Request Tracing**: [`http_tracing_middleware`] - Distributed tracing integration
134//! - **Metrics Collection**: [`metrics_middleware`] - Prometheus-compatible metrics
135//! - **Operation Metadata**: [`op_middleware`] - OpenAPI operation tracking
136//!
137//! ## Management API Endpoints
138//!
139//! When using the management router, these endpoints are available:
140//!
141//! - `GET /health` - Health check
142//! - `GET /stats` - Server statistics
143//! - `GET /logs` - Request logs (SSE stream)
144//! - `GET /metrics` - Performance metrics
145//! - `GET /fixtures` - List available fixtures
146//! - `POST /config/*` - Update configuration
147//!
148//! ## Examples
149//!
150//! See the [examples directory](https://github.com/SaaSy-Solutions/mockforge/tree/main/examples)
151//! for complete working examples.
152//!
153//! ## Related Crates
154//!
155//! - [`mockforge-core`](https://docs.rs/mockforge-core): Core mocking functionality
156//! - [`mockforge-data`](https://docs.rs/mockforge-data): Synthetic data generation
157//! - [`mockforge-plugin-core`](https://docs.rs/mockforge-plugin-core): Plugin development
158//!
159//! ## Documentation
160//!
161//! - [MockForge Book](https://docs.mockforge.dev/)
162//! - [HTTP Mocking Guide](https://docs.mockforge.dev/user-guide/http-mocking.html)
163//! - [API Reference](https://docs.rs/mockforge-http)
164
165pub mod ai_handler;
166pub mod auth;
167pub mod chain_handlers;
168/// Contract diff middleware for automatic request capture
169pub mod contract_diff_middleware;
170pub mod coverage;
171/// File generation service for creating mock PDF, CSV, JSON files
172pub mod file_generator;
173/// File serving for generated mock files
174pub mod file_server;
175/// Kubernetes-native health check endpoints (liveness, readiness, startup probes)
176pub mod health;
177pub mod http_tracing_middleware;
178/// Latency profile configuration for HTTP request simulation
179pub mod latency_profiles;
180/// Management API for server control and monitoring
181pub mod management;
182/// WebSocket-based management API for real-time updates
183pub mod management_ws;
184pub mod metrics_middleware;
185pub mod middleware;
186pub mod op_middleware;
187/// Browser/Mobile Proxy Server
188pub mod proxy_server;
189/// Quick mock generation utilities
190pub mod quick_mock;
191/// RAG-powered AI response generation
192pub mod rag_ai_generator;
193/// Replay listing and fixture management
194pub mod replay_listing;
195pub mod request_logging;
196/// Specification import API for OpenAPI and AsyncAPI
197pub mod spec_import;
198/// Server-Sent Events for streaming logs and metrics
199pub mod sse;
200/// State machine API for scenario state machines
201pub mod state_machine_api;
202/// TLS/HTTPS support
203pub mod tls;
204/// Token response utilities
205pub mod token_response;
206/// UI Builder API for low-code mock endpoint creation
207pub mod ui_builder;
208/// Verification API for request verification
209pub mod verification;
210
211// Re-export AI handler utilities
212pub use ai_handler::{process_response_with_ai, AiResponseConfig, AiResponseHandler};
213// Re-export health check utilities
214pub use health::{HealthManager, ServiceStatus};
215
216// Re-export management API utilities
217pub use management::{
218    management_router, management_router_with_ui_builder, ManagementState, MockConfig,
219    ServerConfig, ServerStats,
220};
221
222// Re-export UI Builder utilities
223pub use ui_builder::{create_ui_builder_router, EndpointConfig, UIBuilderState};
224
225// Re-export management WebSocket utilities
226pub use management_ws::{ws_management_router, MockEvent, WsManagementState};
227
228// Re-export verification API utilities
229pub use verification::verification_router;
230
231// Re-export metrics middleware
232pub use metrics_middleware::collect_http_metrics;
233
234// Re-export tracing middleware
235pub use http_tracing_middleware::http_tracing_middleware;
236
237// Re-export coverage utilities
238pub use coverage::{calculate_coverage, CoverageReport, MethodCoverage, RouteCoverage};
239
240use axum::middleware::from_fn_with_state;
241use axum::{extract::State, response::Json, Router};
242use mockforge_core::failure_injection::{FailureConfig, FailureInjector};
243use mockforge_core::latency::LatencyInjector;
244use mockforge_core::openapi::OpenApiSpec;
245use mockforge_core::openapi_routes::OpenApiRouteRegistry;
246use mockforge_core::openapi_routes::ValidationOptions;
247use tower_http::cors::{Any, CorsLayer};
248
249use mockforge_core::LatencyProfile;
250#[cfg(feature = "data-faker")]
251use mockforge_data::provider::register_core_faker_provider;
252use std::collections::HashMap;
253use std::ffi::OsStr;
254use std::path::Path;
255use tokio::fs;
256use tokio::sync::RwLock;
257use tracing::*;
258
259/// Route info for storing in state
260#[derive(Clone)]
261pub struct RouteInfo {
262    /// HTTP method (GET, POST, PUT, etc.)
263    pub method: String,
264    /// API path pattern (e.g., "/api/users/{id}")
265    pub path: String,
266    /// OpenAPI operation ID if available
267    pub operation_id: Option<String>,
268    /// Operation summary from OpenAPI spec
269    pub summary: Option<String>,
270    /// Operation description from OpenAPI spec
271    pub description: Option<String>,
272    /// List of parameter names for this route
273    pub parameters: Vec<String>,
274}
275
276/// Shared state for tracking OpenAPI routes
277#[derive(Clone)]
278pub struct HttpServerState {
279    /// List of registered routes from OpenAPI spec
280    pub routes: Vec<RouteInfo>,
281    /// Optional global rate limiter for request throttling
282    pub rate_limiter: Option<std::sync::Arc<crate::middleware::rate_limit::GlobalRateLimiter>>,
283    /// Production headers to add to all responses (for deceptive deploy)
284    pub production_headers: Option<std::sync::Arc<std::collections::HashMap<String, String>>>,
285}
286
287impl Default for HttpServerState {
288    fn default() -> Self {
289        Self::new()
290    }
291}
292
293impl HttpServerState {
294    /// Create a new empty HTTP server state
295    pub fn new() -> Self {
296        Self {
297            routes: Vec::new(),
298            rate_limiter: None,
299            production_headers: None,
300        }
301    }
302
303    /// Create HTTP server state with pre-configured routes
304    pub fn with_routes(routes: Vec<RouteInfo>) -> Self {
305        Self {
306            routes,
307            rate_limiter: None,
308            production_headers: None,
309        }
310    }
311
312    /// Add a rate limiter to the HTTP server state
313    pub fn with_rate_limiter(
314        mut self,
315        rate_limiter: std::sync::Arc<crate::middleware::rate_limit::GlobalRateLimiter>,
316    ) -> Self {
317        self.rate_limiter = Some(rate_limiter);
318        self
319    }
320
321    /// Add production headers to the HTTP server state
322    pub fn with_production_headers(
323        mut self,
324        headers: std::sync::Arc<std::collections::HashMap<String, String>>,
325    ) -> Self {
326        self.production_headers = Some(headers);
327        self
328    }
329}
330
331/// Handler to return OpenAPI routes information
332async fn get_routes_handler(State(state): State<HttpServerState>) -> Json<serde_json::Value> {
333    let route_info: Vec<serde_json::Value> = state
334        .routes
335        .iter()
336        .map(|route| {
337            serde_json::json!({
338                "method": route.method,
339                "path": route.path,
340                "operation_id": route.operation_id,
341                "summary": route.summary,
342                "description": route.description,
343                "parameters": route.parameters
344            })
345        })
346        .collect();
347
348    Json(serde_json::json!({
349        "routes": route_info,
350        "total": state.routes.len()
351    }))
352}
353
354/// Build the base HTTP router, optionally from an OpenAPI spec.
355pub async fn build_router(
356    spec_path: Option<String>,
357    options: Option<ValidationOptions>,
358    failure_config: Option<FailureConfig>,
359) -> Router {
360    build_router_with_multi_tenant(
361        spec_path,
362        options,
363        failure_config,
364        None,
365        None,
366        None,
367        None,
368        None,
369        None,
370        None,
371    )
372    .await
373}
374
375/// Apply CORS middleware to the router based on configuration
376fn apply_cors_middleware(
377    app: Router,
378    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
379) -> Router {
380    use http::Method;
381    use tower_http::cors::AllowOrigin;
382
383    if let Some(config) = cors_config {
384        if !config.enabled {
385            return app;
386        }
387
388        let mut cors_layer = CorsLayer::new();
389
390        // Configure allowed origins
391        if config.allowed_origins.contains(&"*".to_string()) {
392            cors_layer = cors_layer.allow_origin(Any);
393        } else if !config.allowed_origins.is_empty() {
394            // Try to parse each origin, fallback to permissive if parsing fails
395            let origins: Vec<_> = config
396                .allowed_origins
397                .iter()
398                .filter_map(|origin| {
399                    origin.parse::<http::HeaderValue>().ok().map(|hv| AllowOrigin::exact(hv))
400                })
401                .collect();
402
403            if origins.is_empty() {
404                // If no valid origins, use permissive for development
405                warn!("No valid CORS origins configured, using permissive CORS");
406                cors_layer = cors_layer.allow_origin(Any);
407            } else {
408                // Use the first origin as exact match (tower-http limitation)
409                // For multiple origins, we'd need a custom implementation
410                if origins.len() == 1 {
411                    cors_layer = cors_layer.allow_origin(origins[0].clone());
412                } else {
413                    // Multiple origins - use permissive for now
414                    warn!(
415                        "Multiple CORS origins configured, using permissive CORS. \
416                        Consider using '*' for all origins."
417                    );
418                    cors_layer = cors_layer.allow_origin(Any);
419                }
420            }
421        } else {
422            // No origins specified, use permissive for development
423            cors_layer = cors_layer.allow_origin(Any);
424        }
425
426        // Configure allowed methods
427        if !config.allowed_methods.is_empty() {
428            let methods: Vec<Method> =
429                config.allowed_methods.iter().filter_map(|m| m.parse().ok()).collect();
430            if !methods.is_empty() {
431                cors_layer = cors_layer.allow_methods(methods);
432            }
433        } else {
434            // Default to common HTTP methods
435            cors_layer = cors_layer.allow_methods([
436                Method::GET,
437                Method::POST,
438                Method::PUT,
439                Method::DELETE,
440                Method::PATCH,
441                Method::OPTIONS,
442            ]);
443        }
444
445        // Configure allowed headers
446        if !config.allowed_headers.is_empty() {
447            let headers: Vec<_> = config
448                .allowed_headers
449                .iter()
450                .filter_map(|h| h.parse::<http::HeaderName>().ok())
451                .collect();
452            if !headers.is_empty() {
453                cors_layer = cors_layer.allow_headers(headers);
454            }
455        } else {
456            // Default headers
457            cors_layer =
458                cors_layer.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION]);
459        }
460
461        // Allow credentials and expose common headers
462        cors_layer = cors_layer.allow_credentials(true);
463
464        info!("CORS middleware enabled with configured settings");
465        app.layer(cors_layer)
466    } else {
467        // No CORS config provided - use permissive CORS for development
468        debug!("No CORS config provided, using permissive CORS for development");
469        app.layer(CorsLayer::permissive())
470    }
471}
472
473/// Build the base HTTP router with multi-tenant workspace support
474#[allow(clippy::too_many_arguments)]
475pub async fn build_router_with_multi_tenant(
476    spec_path: Option<String>,
477    options: Option<ValidationOptions>,
478    failure_config: Option<FailureConfig>,
479    multi_tenant_config: Option<mockforge_core::MultiTenantConfig>,
480    _route_configs: Option<Vec<mockforge_core::config::RouteConfig>>,
481    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
482    ai_generator: Option<
483        std::sync::Arc<dyn mockforge_core::openapi::response::AiGenerator + Send + Sync>,
484    >,
485    smtp_registry: Option<std::sync::Arc<dyn std::any::Any + Send + Sync>>,
486    mockai: Option<
487        std::sync::Arc<tokio::sync::RwLock<mockforge_core::intelligent_behavior::MockAI>>,
488    >,
489    deceptive_deploy_config: Option<mockforge_core::config::DeceptiveDeployConfig>,
490) -> Router {
491    use std::time::Instant;
492
493    let startup_start = Instant::now();
494
495    // Set up the basic router
496    let mut app = Router::new();
497
498    // Initialize rate limiter with default configuration
499    // Can be customized via environment variables or config
500    let mut rate_limit_config = crate::middleware::RateLimitConfig {
501        requests_per_minute: std::env::var("MOCKFORGE_RATE_LIMIT_RPM")
502            .ok()
503            .and_then(|v| v.parse().ok())
504            .unwrap_or(1000),
505        burst: std::env::var("MOCKFORGE_RATE_LIMIT_BURST")
506            .ok()
507            .and_then(|v| v.parse().ok())
508            .unwrap_or(2000),
509        per_ip: true,
510        per_endpoint: false,
511    };
512
513    // Apply deceptive deploy configuration if enabled
514    let mut final_cors_config = cors_config;
515    let mut production_headers: Option<std::sync::Arc<std::collections::HashMap<String, String>>> =
516        None;
517    // Auth config from deceptive deploy OAuth (if configured)
518    let mut deceptive_deploy_auth_config: Option<mockforge_core::config::AuthConfig> = None;
519
520    if let Some(deploy_config) = &deceptive_deploy_config {
521        if deploy_config.enabled {
522            info!("Deceptive deploy mode enabled - applying production-like configuration");
523
524            // Override CORS config if provided
525            if let Some(prod_cors) = &deploy_config.cors {
526                final_cors_config = Some(mockforge_core::config::HttpCorsConfig {
527                    enabled: true,
528                    allowed_origins: prod_cors.allowed_origins.clone(),
529                    allowed_methods: prod_cors.allowed_methods.clone(),
530                    allowed_headers: prod_cors.allowed_headers.clone(),
531                });
532                info!("Applied production-like CORS configuration");
533            }
534
535            // Override rate limit config if provided
536            if let Some(prod_rate_limit) = &deploy_config.rate_limit {
537                rate_limit_config = crate::middleware::RateLimitConfig {
538                    requests_per_minute: prod_rate_limit.requests_per_minute,
539                    burst: prod_rate_limit.burst,
540                    per_ip: prod_rate_limit.per_ip,
541                    per_endpoint: false,
542                };
543                info!(
544                    "Applied production-like rate limiting: {} req/min, burst: {}",
545                    prod_rate_limit.requests_per_minute, prod_rate_limit.burst
546                );
547            }
548
549            // Set production headers
550            if !deploy_config.headers.is_empty() {
551                let headers_map: std::collections::HashMap<String, String> =
552                    deploy_config.headers.clone();
553                production_headers = Some(std::sync::Arc::new(headers_map));
554                info!("Configured {} production headers", deploy_config.headers.len());
555            }
556
557            // Integrate OAuth config from deceptive deploy
558            if let Some(prod_oauth) = &deploy_config.oauth {
559                let oauth2_config: mockforge_core::config::OAuth2Config = prod_oauth.clone().into();
560                deceptive_deploy_auth_config = Some(mockforge_core::config::AuthConfig {
561                    oauth2: Some(oauth2_config),
562                    ..Default::default()
563                });
564                info!("Applied production-like OAuth configuration for deceptive deploy");
565            }
566        }
567    }
568
569    let rate_limiter =
570        std::sync::Arc::new(crate::middleware::GlobalRateLimiter::new(rate_limit_config.clone()));
571
572    let mut state = HttpServerState::new().with_rate_limiter(rate_limiter.clone());
573
574    // Add production headers to state if configured
575    if let Some(headers) = production_headers.clone() {
576        state = state.with_production_headers(headers);
577    }
578
579    // Clone spec_path for later use
580    let spec_path_for_mgmt = spec_path.clone();
581
582    // If an OpenAPI spec is provided, integrate it
583    if let Some(spec_path) = spec_path {
584        tracing::debug!("Processing OpenAPI spec path: {}", spec_path);
585
586        // Measure OpenAPI spec loading
587        let spec_load_start = Instant::now();
588        match OpenApiSpec::from_file(&spec_path).await {
589            Ok(openapi) => {
590                let spec_load_duration = spec_load_start.elapsed();
591                info!(
592                    "Successfully loaded OpenAPI spec from {} (took {:?})",
593                    spec_path, spec_load_duration
594                );
595
596                // Measure route registry creation
597                tracing::debug!("Creating OpenAPI route registry...");
598                let registry_start = Instant::now();
599                let registry = if let Some(opts) = options {
600                    tracing::debug!("Using custom validation options");
601                    OpenApiRouteRegistry::new_with_options(openapi, opts)
602                } else {
603                    tracing::debug!("Using environment-based options");
604                    OpenApiRouteRegistry::new_with_env(openapi)
605                };
606                let registry_duration = registry_start.elapsed();
607                info!(
608                    "Created OpenAPI route registry with {} routes (took {:?})",
609                    registry.routes().len(),
610                    registry_duration
611                );
612
613                // Measure route extraction
614                let extract_start = Instant::now();
615                let route_info: Vec<RouteInfo> = registry
616                    .routes()
617                    .iter()
618                    .map(|route| RouteInfo {
619                        method: route.method.clone(),
620                        path: route.path.clone(),
621                        operation_id: route.operation.operation_id.clone(),
622                        summary: route.operation.summary.clone(),
623                        description: route.operation.description.clone(),
624                        parameters: route.parameters.clone(),
625                    })
626                    .collect();
627                state.routes = route_info;
628                let extract_duration = extract_start.elapsed();
629                debug!("Extracted route information (took {:?})", extract_duration);
630
631                // Measure overrides loading
632                let overrides = if std::env::var("MOCKFORGE_HTTP_OVERRIDES_GLOB").is_ok() {
633                    tracing::debug!("Loading overrides from environment variable");
634                    let overrides_start = Instant::now();
635                    match mockforge_core::Overrides::load_from_globs(&[]).await {
636                        Ok(overrides) => {
637                            let overrides_duration = overrides_start.elapsed();
638                            info!(
639                                "Loaded {} override rules (took {:?})",
640                                overrides.rules().len(),
641                                overrides_duration
642                            );
643                            Some(overrides)
644                        }
645                        Err(e) => {
646                            tracing::warn!("Failed to load overrides: {}", e);
647                            None
648                        }
649                    }
650                } else {
651                    None
652                };
653
654                // Measure router building
655                let router_build_start = Instant::now();
656                let overrides_enabled = overrides.is_some();
657                let openapi_router = if let Some(mockai_instance) = &mockai {
658                    tracing::debug!("Building router with MockAI support");
659                    registry.build_router_with_mockai(Some(mockai_instance.clone()))
660                } else if let Some(ai_generator) = &ai_generator {
661                    tracing::debug!("Building router with AI generator support");
662                    registry.build_router_with_ai(Some(ai_generator.clone()))
663                } else if let Some(failure_config) = &failure_config {
664                    tracing::debug!("Building router with failure injection and overrides");
665                    let failure_injector = FailureInjector::new(Some(failure_config.clone()), true);
666                    registry.build_router_with_injectors_and_overrides(
667                        LatencyInjector::default(),
668                        Some(failure_injector),
669                        overrides,
670                        overrides_enabled,
671                    )
672                } else {
673                    tracing::debug!("Building router with overrides");
674                    registry.build_router_with_injectors_and_overrides(
675                        LatencyInjector::default(),
676                        None,
677                        overrides,
678                        overrides_enabled,
679                    )
680                };
681                let router_build_duration = router_build_start.elapsed();
682                debug!("Built OpenAPI router (took {:?})", router_build_duration);
683
684                tracing::debug!("Merging OpenAPI router with main router");
685                app = app.merge(openapi_router);
686                tracing::debug!("Router built successfully");
687            }
688            Err(e) => {
689                warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec_path, e);
690            }
691        }
692    }
693
694    // Add basic health check endpoint
695    app = app.route(
696        "/health",
697        axum::routing::get(|| async {
698            use mockforge_core::server_utils::health::HealthStatus;
699            {
700                // HealthStatus should always serialize, but handle errors gracefully
701                match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
702                    Ok(value) => axum::Json(value),
703                    Err(e) => {
704                        // Log error but return a simple healthy response
705                        tracing::error!("Failed to serialize health status: {}", e);
706                        axum::Json(serde_json::json!({
707                            "status": "healthy",
708                            "service": "mockforge-http",
709                            "uptime_seconds": 0
710                        }))
711                    }
712                }
713            }
714        }),
715    )
716    // Add SSE endpoints
717    .merge(sse::sse_router())
718    // Add file serving endpoints for generated mock files
719    .merge(file_server::file_serving_router());
720
721    // Clone state for routes_router since we'll use it for middleware too
722    let state_for_routes = state.clone();
723
724    // Create a router with state for the routes and coverage endpoints
725    let routes_router = Router::new()
726        .route("/__mockforge/routes", axum::routing::get(get_routes_handler))
727        .route("/__mockforge/coverage", axum::routing::get(coverage::get_coverage_handler))
728        .with_state(state_for_routes);
729
730    // Merge the routes router with the main app
731    app = app.merge(routes_router);
732
733    // Add static coverage UI
734    // Determine the path to the coverage.html file
735    let coverage_html_path = std::env::var("MOCKFORGE_COVERAGE_UI_PATH")
736        .unwrap_or_else(|_| "crates/mockforge-http/static/coverage.html".to_string());
737
738    // Check if the file exists before serving it
739    if std::path::Path::new(&coverage_html_path).exists() {
740        app = app.nest_service(
741            "/__mockforge/coverage.html",
742            tower_http::services::ServeFile::new(&coverage_html_path),
743        );
744        debug!("Serving coverage UI from: {}", coverage_html_path);
745    } else {
746        debug!(
747            "Coverage UI file not found at: {}. Skipping static file serving.",
748            coverage_html_path
749        );
750    }
751
752    // Add management API endpoints
753    let mut management_state = ManagementState::new(None, spec_path_for_mgmt, 3000); // Port will be updated when we know the actual port
754
755    // Create WebSocket state and connect it to management state
756    use std::sync::Arc;
757    let ws_state = WsManagementState::new();
758    let ws_broadcast = Arc::new(ws_state.tx.clone());
759    let management_state = management_state.with_ws_broadcast(ws_broadcast);
760
761    // Note: ProxyConfig not available in this build function path
762    // Migration endpoints will work once ProxyConfig is passed to build_router_with_chains_and_multi_tenant
763
764    #[cfg(feature = "smtp")]
765    let management_state = {
766        if let Some(smtp_reg) = smtp_registry {
767            match smtp_reg.downcast::<mockforge_smtp::SmtpSpecRegistry>() {
768                Ok(smtp_reg) => management_state.with_smtp_registry(smtp_reg),
769                Err(e) => {
770                    error!(
771                        "Invalid SMTP registry type passed to HTTP management state: {:?}",
772                        e.type_id()
773                    );
774                    management_state
775                }
776            }
777        } else {
778            management_state
779        }
780    };
781    #[cfg(not(feature = "smtp"))]
782    let management_state = management_state;
783    #[cfg(not(feature = "smtp"))]
784    let _ = smtp_registry;
785    app = app.nest("/__mockforge/api", management_router(management_state));
786
787    // Add verification API endpoint
788    app = app.merge(verification_router());
789
790    // Add management WebSocket endpoint
791    app = app.nest("/__mockforge/ws", ws_management_router(ws_state));
792
793    // Add request logging middleware to capture all requests
794    app = app.layer(axum::middleware::from_fn(request_logging::log_http_requests));
795
796    // Add contract diff middleware for automatic request capture
797    // This captures requests for contract diff analysis (after logging)
798    app = app.layer(axum::middleware::from_fn(contract_diff_middleware::capture_for_contract_diff));
799
800    // Add rate limiting middleware (before logging to rate limit early)
801    app = app.layer(from_fn_with_state(state.clone(), crate::middleware::rate_limit_middleware));
802
803    // Add production headers middleware if configured
804    if state.production_headers.is_some() {
805        app = app.layer(from_fn_with_state(
806            state.clone(),
807            crate::middleware::production_headers_middleware,
808        ));
809    }
810
811    // Add authentication middleware if OAuth is configured via deceptive deploy
812    if let Some(auth_config) = deceptive_deploy_auth_config {
813        use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
814        use std::collections::HashMap;
815        use std::sync::Arc;
816        use tokio::sync::RwLock;
817
818        // Create OAuth2 client if configured
819        let oauth2_client = if let Some(oauth2_config) = &auth_config.oauth2 {
820            match create_oauth2_client(oauth2_config) {
821                Ok(client) => Some(client),
822                Err(e) => {
823                    warn!("Failed to create OAuth2 client from deceptive deploy config: {}", e);
824                    None
825                }
826            }
827        } else {
828            None
829        };
830
831        // Create auth state
832        let auth_state = AuthState {
833            config: auth_config,
834            spec: None, // OpenAPI spec not available in this context
835            oauth2_client,
836            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
837        };
838
839        // Apply auth middleware
840        app = app.layer(axum::middleware::from_fn_with_state(auth_state, auth_middleware));
841        info!("Applied OAuth authentication middleware from deceptive deploy configuration");
842    }
843
844    // Add CORS middleware (use final_cors_config which may be overridden by deceptive deploy)
845    app = apply_cors_middleware(app, final_cors_config);
846
847    // Add workspace routing middleware if multi-tenant is enabled
848    if let Some(mt_config) = multi_tenant_config {
849        if mt_config.enabled {
850            use mockforge_core::{MultiTenantWorkspaceRegistry, WorkspaceRouter};
851            use std::sync::Arc;
852
853            info!(
854                "Multi-tenant mode enabled with {} routing strategy",
855                match mt_config.routing_strategy {
856                    mockforge_core::RoutingStrategy::Path => "path-based",
857                    mockforge_core::RoutingStrategy::Port => "port-based",
858                    mockforge_core::RoutingStrategy::Both => "hybrid",
859                }
860            );
861
862            // Create the multi-tenant workspace registry
863            let mut registry = MultiTenantWorkspaceRegistry::new(mt_config.clone());
864
865            // Register the default workspace before wrapping in Arc
866            let default_workspace =
867                mockforge_core::Workspace::new(mt_config.default_workspace.clone());
868            if let Err(e) =
869                registry.register_workspace(mt_config.default_workspace.clone(), default_workspace)
870            {
871                warn!("Failed to register default workspace: {}", e);
872            } else {
873                info!("Registered default workspace: '{}'", mt_config.default_workspace);
874            }
875
876            // Auto-discover and register workspaces if configured
877            if mt_config.auto_discover {
878                if let Some(config_dir) = &mt_config.config_directory {
879                    let config_path = Path::new(config_dir);
880                    if config_path.exists() && config_path.is_dir() {
881                        match fs::read_dir(config_path).await {
882                            Ok(mut entries) => {
883                                while let Ok(Some(entry)) = entries.next_entry().await {
884                                    let path = entry.path();
885                                    if path.extension() == Some(OsStr::new("yaml")) {
886                                        match fs::read_to_string(&path).await {
887                                            Ok(content) => {
888                                                match serde_yaml::from_str::<
889                                                    mockforge_core::Workspace,
890                                                >(
891                                                    &content
892                                                ) {
893                                                    Ok(workspace) => {
894                                                        if let Err(e) = registry.register_workspace(
895                                                            workspace.id.clone(),
896                                                            workspace,
897                                                        ) {
898                                                            warn!("Failed to register auto-discovered workspace from {:?}: {}", path, e);
899                                                        } else {
900                                                            info!("Auto-registered workspace from {:?}", path);
901                                                        }
902                                                    }
903                                                    Err(e) => {
904                                                        warn!("Failed to parse workspace from {:?}: {}", path, e);
905                                                    }
906                                                }
907                                            }
908                                            Err(e) => {
909                                                warn!(
910                                                    "Failed to read workspace file {:?}: {}",
911                                                    path, e
912                                                );
913                                            }
914                                        }
915                                    }
916                                }
917                            }
918                            Err(e) => {
919                                warn!("Failed to read config directory {:?}: {}", config_path, e);
920                            }
921                        }
922                    } else {
923                        warn!(
924                            "Config directory {:?} does not exist or is not a directory",
925                            config_path
926                        );
927                    }
928                }
929            }
930
931            // Wrap registry in Arc for shared access
932            let registry = Arc::new(registry);
933
934            // Create workspace router and wrap the app with workspace middleware
935            let _workspace_router = WorkspaceRouter::new(registry);
936
937            // Note: The actual middleware integration would need to be implemented
938            // in the WorkspaceRouter to work with Axum's middleware system
939            info!("Workspace routing middleware initialized for HTTP server");
940        }
941    }
942
943    let total_startup_duration = startup_start.elapsed();
944    info!("HTTP router startup completed (total time: {:?})", total_startup_duration);
945
946    app
947}
948
949/// Build the base HTTP router with authentication and latency support
950pub async fn build_router_with_auth_and_latency(
951    _spec_path: Option<String>,
952    _options: Option<()>,
953    _auth_config: Option<mockforge_core::config::AuthConfig>,
954    _latency_injector: Option<LatencyInjector>,
955) -> Router {
956    // For now, just use the basic router. Full auth and latency support can be added later.
957    build_router(None, None, None).await
958}
959
960/// Build the base HTTP router with latency injection support
961pub async fn build_router_with_latency(
962    _spec_path: Option<String>,
963    _options: Option<ValidationOptions>,
964    _latency_injector: Option<LatencyInjector>,
965) -> Router {
966    // For now, fall back to basic router since injectors are complex to implement
967    build_router(None, None, None).await
968}
969
970/// Build the base HTTP router with authentication support
971pub async fn build_router_with_auth(
972    spec_path: Option<String>,
973    options: Option<ValidationOptions>,
974    auth_config: Option<mockforge_core::config::AuthConfig>,
975) -> Router {
976    use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
977    use std::sync::Arc;
978
979    // If richer faker is available, register provider once (idempotent)
980    #[cfg(feature = "data-faker")]
981    {
982        register_core_faker_provider();
983    }
984
985    // Set up authentication state
986    let spec = if let Some(spec_path) = &spec_path {
987        match mockforge_core::openapi::OpenApiSpec::from_file(&spec_path).await {
988            Ok(spec) => Some(Arc::new(spec)),
989            Err(e) => {
990                warn!("Failed to load OpenAPI spec for auth: {}", e);
991                None
992            }
993        }
994    } else {
995        None
996    };
997
998    // Create OAuth2 client if configured
999    let oauth2_client = if let Some(auth_config) = &auth_config {
1000        if let Some(oauth2_config) = &auth_config.oauth2 {
1001            match create_oauth2_client(oauth2_config) {
1002                Ok(client) => Some(client),
1003                Err(e) => {
1004                    warn!("Failed to create OAuth2 client: {}", e);
1005                    None
1006                }
1007            }
1008        } else {
1009            None
1010        }
1011    } else {
1012        None
1013    };
1014
1015    let auth_state = AuthState {
1016        config: auth_config.unwrap_or_default(),
1017        spec,
1018        oauth2_client,
1019        introspection_cache: Arc::new(RwLock::new(HashMap::new())),
1020    };
1021
1022    // Set up the basic router with auth state
1023    let mut app = Router::new().with_state(auth_state.clone());
1024
1025    // If an OpenAPI spec is provided, integrate it
1026    if let Some(spec_path) = spec_path {
1027        match OpenApiSpec::from_file(&spec_path).await {
1028            Ok(openapi) => {
1029                info!("Loaded OpenAPI spec from {}", spec_path);
1030                let registry = if let Some(opts) = options {
1031                    OpenApiRouteRegistry::new_with_options(openapi, opts)
1032                } else {
1033                    OpenApiRouteRegistry::new_with_env(openapi)
1034                };
1035
1036                app = registry.build_router();
1037            }
1038            Err(e) => {
1039                warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec_path, e);
1040            }
1041        }
1042    }
1043
1044    // Add basic health check endpoint
1045    app = app.route(
1046        "/health",
1047        axum::routing::get(|| async {
1048            use mockforge_core::server_utils::health::HealthStatus;
1049            {
1050                // HealthStatus should always serialize, but handle errors gracefully
1051                match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
1052                    Ok(value) => axum::Json(value),
1053                    Err(e) => {
1054                        // Log error but return a simple healthy response
1055                        tracing::error!("Failed to serialize health status: {}", e);
1056                        axum::Json(serde_json::json!({
1057                            "status": "healthy",
1058                            "service": "mockforge-http",
1059                            "uptime_seconds": 0
1060                        }))
1061                    }
1062                }
1063            }
1064        }),
1065    )
1066    // Add SSE endpoints
1067    .merge(sse::sse_router())
1068    // Add file serving endpoints for generated mock files
1069    .merge(file_server::file_serving_router())
1070    // Add authentication middleware (before logging)
1071    .layer(axum::middleware::from_fn_with_state(auth_state.clone(), auth_middleware))
1072    // Add request logging middleware
1073    .layer(axum::middleware::from_fn(request_logging::log_http_requests));
1074
1075    app
1076}
1077
1078/// Serve a provided router on the given port.
1079pub async fn serve_router(
1080    port: u16,
1081    app: Router,
1082) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1083    serve_router_with_tls(port, app, None).await
1084}
1085
1086/// Serve a provided router on the given port with optional TLS support.
1087pub async fn serve_router_with_tls(
1088    port: u16,
1089    app: Router,
1090    tls_config: Option<mockforge_core::config::HttpTlsConfig>,
1091) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1092    use std::net::SocketAddr;
1093
1094    let addr = mockforge_core::wildcard_socket_addr(port);
1095
1096    if let Some(ref tls) = tls_config {
1097        if tls.enabled {
1098            info!("HTTPS listening on {}", addr);
1099            return serve_with_tls(addr, app, tls).await;
1100        }
1101    }
1102
1103    info!("HTTP listening on {}", addr);
1104
1105    let listener = tokio::net::TcpListener::bind(addr).await.map_err(|e| {
1106        format!(
1107            "Failed to bind HTTP server to port {}: {}\n\
1108             Hint: The port may already be in use. Try using a different port with --http-port or check if another process is using this port with: lsof -i :{} or netstat -tulpn | grep {}",
1109            port, e, port, port
1110        )
1111    })?;
1112
1113    axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await?;
1114    Ok(())
1115}
1116
1117/// Serve router with TLS/HTTPS support
1118///
1119/// Note: This is a simplified implementation. For production use, consider using
1120/// a reverse proxy (nginx) for TLS termination, or use axum-server crate.
1121/// This implementation validates TLS configuration but recommends using a reverse proxy.
1122async fn serve_with_tls(
1123    addr: std::net::SocketAddr,
1124    _app: Router,
1125    tls_config: &mockforge_core::config::HttpTlsConfig,
1126) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1127    // Validate TLS configuration by attempting to load certificates
1128    let _acceptor = tls::load_tls_acceptor(tls_config)?;
1129
1130    // For now, return an informative error suggesting reverse proxy usage
1131    // Full TLS implementation with axum requires axum-server or similar
1132    Err(format!(
1133        "TLS/HTTPS support is configured but requires a reverse proxy (nginx) for production use.\n\
1134         Certificate validation passed: {} and {}\n\
1135         For native TLS support, please use a reverse proxy or wait for axum-server integration.\n\
1136         You can configure nginx with TLS termination pointing to the HTTP server on port {}.",
1137        tls_config.cert_file,
1138        tls_config.key_file,
1139        addr.port()
1140    )
1141    .into())
1142}
1143
1144/// Backwards-compatible start that builds + serves the base router.
1145pub async fn start(
1146    port: u16,
1147    spec_path: Option<String>,
1148    options: Option<ValidationOptions>,
1149) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1150    start_with_latency(port, spec_path, options, None).await
1151}
1152
1153/// Start HTTP server with authentication and latency support
1154pub async fn start_with_auth_and_latency(
1155    port: u16,
1156    spec_path: Option<String>,
1157    options: Option<ValidationOptions>,
1158    auth_config: Option<mockforge_core::config::AuthConfig>,
1159    latency_profile: Option<LatencyProfile>,
1160) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1161    start_with_auth_and_injectors(port, spec_path, options, auth_config, latency_profile, None)
1162        .await
1163}
1164
1165/// Start HTTP server with authentication and injectors support
1166pub async fn start_with_auth_and_injectors(
1167    port: u16,
1168    spec_path: Option<String>,
1169    options: Option<ValidationOptions>,
1170    auth_config: Option<mockforge_core::config::AuthConfig>,
1171    _latency_profile: Option<LatencyProfile>,
1172    _failure_injector: Option<mockforge_core::FailureInjector>,
1173) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1174    // For now, ignore latency and failure injectors and just use auth
1175    let app = build_router_with_auth(spec_path, options, auth_config).await;
1176    serve_router(port, app).await
1177}
1178
1179/// Start HTTP server with latency injection support
1180pub async fn start_with_latency(
1181    port: u16,
1182    spec_path: Option<String>,
1183    options: Option<ValidationOptions>,
1184    latency_profile: Option<LatencyProfile>,
1185) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1186    let latency_injector =
1187        latency_profile.map(|profile| LatencyInjector::new(profile, Default::default()));
1188
1189    let app = build_router_with_latency(spec_path, options, latency_injector).await;
1190    serve_router(port, app).await
1191}
1192
1193/// Build the base HTTP router with chaining support
1194pub async fn build_router_with_chains(
1195    spec_path: Option<String>,
1196    options: Option<ValidationOptions>,
1197    circling_config: Option<mockforge_core::request_chaining::ChainConfig>,
1198) -> Router {
1199    build_router_with_chains_and_multi_tenant(
1200        spec_path,
1201        options,
1202        circling_config,
1203        None,
1204        None,
1205        None,
1206        None,
1207        None,
1208        None,
1209        None,
1210        false,
1211        None, // health_manager
1212        None, // mockai
1213        None, // deceptive_deploy_config
1214        None, // proxy_config
1215    )
1216    .await
1217}
1218
1219/// Build the base HTTP router with chaining and multi-tenant support
1220#[allow(clippy::too_many_arguments)]
1221pub async fn build_router_with_chains_and_multi_tenant(
1222    spec_path: Option<String>,
1223    options: Option<ValidationOptions>,
1224    _circling_config: Option<mockforge_core::request_chaining::ChainConfig>,
1225    multi_tenant_config: Option<mockforge_core::MultiTenantConfig>,
1226    _route_configs: Option<Vec<mockforge_core::config::RouteConfig>>,
1227    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
1228    _ai_generator: Option<
1229        std::sync::Arc<dyn mockforge_core::openapi::response::AiGenerator + Send + Sync>,
1230    >,
1231    smtp_registry: Option<std::sync::Arc<dyn std::any::Any + Send + Sync>>,
1232    mqtt_broker: Option<std::sync::Arc<dyn std::any::Any + Send + Sync>>,
1233    traffic_shaper: Option<mockforge_core::traffic_shaping::TrafficShaper>,
1234    traffic_shaping_enabled: bool,
1235    health_manager: Option<std::sync::Arc<health::HealthManager>>,
1236    _mockai: Option<
1237        std::sync::Arc<tokio::sync::RwLock<mockforge_core::intelligent_behavior::MockAI>>,
1238    >,
1239    deceptive_deploy_config: Option<mockforge_core::config::DeceptiveDeployConfig>,
1240    proxy_config: Option<mockforge_core::proxy::config::ProxyConfig>,
1241) -> Router {
1242    use crate::latency_profiles::LatencyProfiles;
1243    use crate::op_middleware::Shared;
1244    use mockforge_core::Overrides;
1245
1246    let _shared = Shared {
1247        profiles: LatencyProfiles::default(),
1248        overrides: Overrides::default(),
1249        failure_injector: None,
1250        traffic_shaper,
1251        overrides_enabled: false,
1252        traffic_shaping_enabled,
1253    };
1254
1255    // Start with basic router
1256    let mut app = Router::new();
1257    let mut include_default_health = true;
1258
1259    // If an OpenAPI spec is provided, integrate it
1260    if let Some(ref spec) = spec_path {
1261        match OpenApiSpec::from_file(&spec).await {
1262            Ok(openapi) => {
1263                info!("Loaded OpenAPI spec from {}", spec);
1264                let registry = if let Some(opts) = options {
1265                    OpenApiRouteRegistry::new_with_options(openapi, opts)
1266                } else {
1267                    OpenApiRouteRegistry::new_with_env(openapi)
1268                };
1269                if registry
1270                    .routes()
1271                    .iter()
1272                    .any(|route| route.method == "GET" && route.path == "/health")
1273                {
1274                    include_default_health = false;
1275                }
1276                // Use MockAI if available, otherwise use standard router
1277                let spec_router = if let Some(ref mockai_instance) = _mockai {
1278                    tracing::debug!("Building router with MockAI support");
1279                    registry.build_router_with_mockai(Some(mockai_instance.clone()))
1280                } else {
1281                    registry.build_router()
1282                };
1283                app = app.merge(spec_router);
1284            }
1285            Err(e) => {
1286                warn!("Failed to load OpenAPI spec from {:?}: {}. Starting without OpenAPI integration.", spec_path, e);
1287            }
1288        }
1289    }
1290
1291    // Add health check endpoints
1292    if let Some(health) = health_manager {
1293        // Use comprehensive health check router with all probe endpoints
1294        app = app.merge(health::health_router(health));
1295        info!(
1296            "Health check endpoints enabled: /health, /health/live, /health/ready, /health/startup"
1297        );
1298    } else if include_default_health {
1299        // Fallback to basic health endpoint for backwards compatibility
1300        app = app.route(
1301            "/health",
1302            axum::routing::get(|| async {
1303                use mockforge_core::server_utils::health::HealthStatus;
1304                {
1305                    // HealthStatus should always serialize, but handle errors gracefully
1306                    match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
1307                        Ok(value) => axum::Json(value),
1308                        Err(e) => {
1309                            // Log error but return a simple healthy response
1310                            tracing::error!("Failed to serialize health status: {}", e);
1311                            axum::Json(serde_json::json!({
1312                                "status": "healthy",
1313                                "service": "mockforge-http",
1314                                "uptime_seconds": 0
1315                            }))
1316                        }
1317                    }
1318                }
1319            }),
1320        );
1321    }
1322
1323    app = app.merge(sse::sse_router());
1324    // Add file serving endpoints for generated mock files
1325    app = app.merge(file_server::file_serving_router());
1326
1327    // Add management API endpoints
1328    let mut management_state = ManagementState::new(None, spec_path, 3000); // Port will be updated when we know the actual port
1329
1330    // Create WebSocket state and connect it to management state
1331    use std::sync::Arc;
1332    let ws_state = WsManagementState::new();
1333    let ws_broadcast = Arc::new(ws_state.tx.clone());
1334    let management_state = management_state.with_ws_broadcast(ws_broadcast);
1335
1336    // Add proxy config to management state if available
1337    let management_state = if let Some(proxy_cfg) = proxy_config {
1338        use tokio::sync::RwLock;
1339        let proxy_config_arc = Arc::new(RwLock::new(proxy_cfg));
1340        management_state.with_proxy_config(proxy_config_arc)
1341    } else {
1342        management_state
1343    };
1344
1345    #[cfg(feature = "smtp")]
1346    let management_state = {
1347        if let Some(smtp_reg) = smtp_registry {
1348            match smtp_reg.downcast::<mockforge_smtp::SmtpSpecRegistry>() {
1349                Ok(smtp_reg) => management_state.with_smtp_registry(smtp_reg),
1350                Err(e) => {
1351                    error!(
1352                        "Invalid SMTP registry type passed to HTTP management state: {:?}",
1353                        e.type_id()
1354                    );
1355                    management_state
1356                }
1357            }
1358        } else {
1359            management_state
1360        }
1361    };
1362    #[cfg(not(feature = "smtp"))]
1363    let management_state = {
1364        let _ = smtp_registry;
1365        management_state
1366    };
1367    #[cfg(feature = "mqtt")]
1368    let management_state = {
1369        if let Some(broker) = mqtt_broker {
1370            match broker.downcast::<mockforge_mqtt::MqttBroker>() {
1371                Ok(broker) => management_state.with_mqtt_broker(broker),
1372                Err(e) => {
1373                    error!(
1374                        "Invalid MQTT broker passed to HTTP management state: {:?}",
1375                        e.type_id()
1376                    );
1377                    management_state
1378                }
1379            }
1380        } else {
1381            management_state
1382        }
1383    };
1384    #[cfg(not(feature = "mqtt"))]
1385    let management_state = {
1386        let _ = mqtt_broker;
1387        management_state
1388    };
1389    app = app.nest("/__mockforge/api", management_router(management_state));
1390
1391    // Add verification API endpoint
1392    app = app.merge(verification_router());
1393
1394    // Add management WebSocket endpoint
1395    app = app.nest("/__mockforge/ws", ws_management_router(ws_state));
1396
1397    // Add workspace routing middleware if multi-tenant is enabled
1398    if let Some(mt_config) = multi_tenant_config {
1399        if mt_config.enabled {
1400            use mockforge_core::{MultiTenantWorkspaceRegistry, WorkspaceRouter};
1401            use std::sync::Arc;
1402
1403            info!(
1404                "Multi-tenant mode enabled with {} routing strategy",
1405                match mt_config.routing_strategy {
1406                    mockforge_core::RoutingStrategy::Path => "path-based",
1407                    mockforge_core::RoutingStrategy::Port => "port-based",
1408                    mockforge_core::RoutingStrategy::Both => "hybrid",
1409                }
1410            );
1411
1412            // Create the multi-tenant workspace registry
1413            let mut registry = MultiTenantWorkspaceRegistry::new(mt_config.clone());
1414
1415            // Register the default workspace before wrapping in Arc
1416            let default_workspace =
1417                mockforge_core::Workspace::new(mt_config.default_workspace.clone());
1418            if let Err(e) =
1419                registry.register_workspace(mt_config.default_workspace.clone(), default_workspace)
1420            {
1421                warn!("Failed to register default workspace: {}", e);
1422            } else {
1423                info!("Registered default workspace: '{}'", mt_config.default_workspace);
1424            }
1425
1426            // Wrap registry in Arc for shared access
1427            let registry = Arc::new(registry);
1428
1429            // Create workspace router
1430            let _workspace_router = WorkspaceRouter::new(registry);
1431            info!("Workspace routing middleware initialized for HTTP server");
1432        }
1433    }
1434
1435    // Apply deceptive deploy configuration if enabled
1436    let mut final_cors_config = cors_config;
1437    let mut production_headers: Option<std::sync::Arc<std::collections::HashMap<String, String>>> =
1438        None;
1439    // Auth config from deceptive deploy OAuth (if configured)
1440    let mut deceptive_deploy_auth_config: Option<mockforge_core::config::AuthConfig> = None;
1441    let mut rate_limit_config = crate::middleware::RateLimitConfig {
1442        requests_per_minute: std::env::var("MOCKFORGE_RATE_LIMIT_RPM")
1443            .ok()
1444            .and_then(|v| v.parse().ok())
1445            .unwrap_or(1000),
1446        burst: std::env::var("MOCKFORGE_RATE_LIMIT_BURST")
1447            .ok()
1448            .and_then(|v| v.parse().ok())
1449            .unwrap_or(2000),
1450        per_ip: true,
1451        per_endpoint: false,
1452    };
1453
1454    if let Some(deploy_config) = &deceptive_deploy_config {
1455        if deploy_config.enabled {
1456            info!("Deceptive deploy mode enabled - applying production-like configuration");
1457
1458            // Override CORS config if provided
1459            if let Some(prod_cors) = &deploy_config.cors {
1460                final_cors_config = Some(mockforge_core::config::HttpCorsConfig {
1461                    enabled: true,
1462                    allowed_origins: prod_cors.allowed_origins.clone(),
1463                    allowed_methods: prod_cors.allowed_methods.clone(),
1464                    allowed_headers: prod_cors.allowed_headers.clone(),
1465                });
1466                info!("Applied production-like CORS configuration");
1467            }
1468
1469            // Override rate limit config if provided
1470            if let Some(prod_rate_limit) = &deploy_config.rate_limit {
1471                rate_limit_config = crate::middleware::RateLimitConfig {
1472                    requests_per_minute: prod_rate_limit.requests_per_minute,
1473                    burst: prod_rate_limit.burst,
1474                    per_ip: prod_rate_limit.per_ip,
1475                    per_endpoint: false,
1476                };
1477                info!(
1478                    "Applied production-like rate limiting: {} req/min, burst: {}",
1479                    prod_rate_limit.requests_per_minute, prod_rate_limit.burst
1480                );
1481            }
1482
1483            // Set production headers
1484            if !deploy_config.headers.is_empty() {
1485                let headers_map: std::collections::HashMap<String, String> =
1486                    deploy_config.headers.clone();
1487                production_headers = Some(std::sync::Arc::new(headers_map));
1488                info!("Configured {} production headers", deploy_config.headers.len());
1489            }
1490
1491            // Integrate OAuth config from deceptive deploy
1492            if let Some(prod_oauth) = &deploy_config.oauth {
1493                let oauth2_config: mockforge_core::config::OAuth2Config = prod_oauth.clone().into();
1494                deceptive_deploy_auth_config = Some(mockforge_core::config::AuthConfig {
1495                    oauth2: Some(oauth2_config),
1496                    ..Default::default()
1497                });
1498                info!("Applied production-like OAuth configuration for deceptive deploy");
1499            }
1500        }
1501    }
1502
1503    // Initialize rate limiter and state
1504    let rate_limiter =
1505        std::sync::Arc::new(crate::middleware::GlobalRateLimiter::new(rate_limit_config.clone()));
1506
1507    let mut state = HttpServerState::new().with_rate_limiter(rate_limiter.clone());
1508
1509    // Add production headers to state if configured
1510    if let Some(headers) = production_headers.clone() {
1511        state = state.with_production_headers(headers);
1512    }
1513
1514    // Add rate limiting middleware
1515    app = app.layer(from_fn_with_state(state.clone(), crate::middleware::rate_limit_middleware));
1516
1517    // Add production headers middleware if configured
1518    if state.production_headers.is_some() {
1519        app = app.layer(from_fn_with_state(
1520            state.clone(),
1521            crate::middleware::production_headers_middleware,
1522        ));
1523    }
1524
1525    // Add authentication middleware if OAuth is configured via deceptive deploy
1526    if let Some(auth_config) = deceptive_deploy_auth_config {
1527        use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
1528        use std::collections::HashMap;
1529        use std::sync::Arc;
1530        use tokio::sync::RwLock;
1531
1532        // Create OAuth2 client if configured
1533        let oauth2_client = if let Some(oauth2_config) = &auth_config.oauth2 {
1534            match create_oauth2_client(oauth2_config) {
1535                Ok(client) => Some(client),
1536                Err(e) => {
1537                    warn!("Failed to create OAuth2 client from deceptive deploy config: {}", e);
1538                    None
1539                }
1540            }
1541        } else {
1542            None
1543        };
1544
1545        // Create auth state
1546        let auth_state = AuthState {
1547            config: auth_config,
1548            spec: None, // OpenAPI spec not available in this context
1549            oauth2_client,
1550            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
1551        };
1552
1553        // Apply auth middleware
1554        app = app.layer(axum::middleware::from_fn_with_state(auth_state, auth_middleware));
1555        info!("Applied OAuth authentication middleware from deceptive deploy configuration");
1556    }
1557
1558    // Add contract diff middleware for automatic request capture
1559    // This captures requests for contract diff analysis
1560    app = app.layer(axum::middleware::from_fn(contract_diff_middleware::capture_for_contract_diff));
1561
1562    // Add CORS middleware (use final_cors_config which may be overridden by deceptive deploy)
1563    app = apply_cors_middleware(app, final_cors_config);
1564
1565    app
1566}
1567
1568// Note: start_with_traffic_shaping function removed due to compilation issues
1569// Use build_router_with_traffic_shaping_and_multi_tenant directly instead
1570
1571#[test]
1572fn test_route_info_clone() {
1573    let route = RouteInfo {
1574        method: "POST".to_string(),
1575        path: "/users".to_string(),
1576        operation_id: Some("createUser".to_string()),
1577        summary: None,
1578        description: None,
1579        parameters: vec![],
1580    };
1581
1582    let cloned = route.clone();
1583    assert_eq!(route.method, cloned.method);
1584    assert_eq!(route.path, cloned.path);
1585    assert_eq!(route.operation_id, cloned.operation_id);
1586}
1587
1588#[test]
1589fn test_http_server_state_new() {
1590    let state = HttpServerState::new();
1591    assert_eq!(state.routes.len(), 0);
1592}
1593
1594#[test]
1595fn test_http_server_state_with_routes() {
1596    let routes = vec![
1597        RouteInfo {
1598            method: "GET".to_string(),
1599            path: "/users".to_string(),
1600            operation_id: Some("getUsers".to_string()),
1601            summary: None,
1602            description: None,
1603            parameters: vec![],
1604        },
1605        RouteInfo {
1606            method: "POST".to_string(),
1607            path: "/users".to_string(),
1608            operation_id: Some("createUser".to_string()),
1609            summary: None,
1610            description: None,
1611            parameters: vec![],
1612        },
1613    ];
1614
1615    let state = HttpServerState::with_routes(routes.clone());
1616    assert_eq!(state.routes.len(), 2);
1617    assert_eq!(state.routes[0].method, "GET");
1618    assert_eq!(state.routes[1].method, "POST");
1619}
1620
1621#[test]
1622fn test_http_server_state_clone() {
1623    let routes = vec![RouteInfo {
1624        method: "GET".to_string(),
1625        path: "/test".to_string(),
1626        operation_id: None,
1627        summary: None,
1628        description: None,
1629        parameters: vec![],
1630    }];
1631
1632    let state = HttpServerState::with_routes(routes);
1633    let cloned = state.clone();
1634
1635    assert_eq!(state.routes.len(), cloned.routes.len());
1636    assert_eq!(state.routes[0].method, cloned.routes[0].method);
1637}
1638
1639#[tokio::test]
1640async fn test_build_router_without_openapi() {
1641    let _router = build_router(None, None, None).await;
1642    // Should succeed without OpenAPI spec
1643}
1644
1645#[tokio::test]
1646async fn test_build_router_with_nonexistent_spec() {
1647    let _router = build_router(Some("/nonexistent/spec.yaml".to_string()), None, None).await;
1648    // Should succeed but log a warning
1649}
1650
1651#[tokio::test]
1652async fn test_build_router_with_auth_and_latency() {
1653    let _router = build_router_with_auth_and_latency(None, None, None, None).await;
1654    // Should succeed without parameters
1655}
1656
1657#[tokio::test]
1658async fn test_build_router_with_latency() {
1659    let _router = build_router_with_latency(None, None, None).await;
1660    // Should succeed without parameters
1661}
1662
1663#[tokio::test]
1664async fn test_build_router_with_auth() {
1665    let _router = build_router_with_auth(None, None, None).await;
1666    // Should succeed without parameters
1667}
1668
1669#[tokio::test]
1670async fn test_build_router_with_chains() {
1671    let _router = build_router_with_chains(None, None, None).await;
1672    // Should succeed without parameters
1673}
1674
1675#[test]
1676fn test_route_info_with_all_fields() {
1677    let route = RouteInfo {
1678        method: "PUT".to_string(),
1679        path: "/users/{id}".to_string(),
1680        operation_id: Some("updateUser".to_string()),
1681        summary: Some("Update user".to_string()),
1682        description: Some("Updates an existing user".to_string()),
1683        parameters: vec!["id".to_string(), "body".to_string()],
1684    };
1685
1686    assert!(route.operation_id.is_some());
1687    assert!(route.summary.is_some());
1688    assert!(route.description.is_some());
1689    assert_eq!(route.parameters.len(), 2);
1690}
1691
1692#[test]
1693fn test_route_info_with_minimal_fields() {
1694    let route = RouteInfo {
1695        method: "DELETE".to_string(),
1696        path: "/users/{id}".to_string(),
1697        operation_id: None,
1698        summary: None,
1699        description: None,
1700        parameters: vec![],
1701    };
1702
1703    assert!(route.operation_id.is_none());
1704    assert!(route.summary.is_none());
1705    assert!(route.description.is_none());
1706    assert_eq!(route.parameters.len(), 0);
1707}
1708
1709#[test]
1710fn test_http_server_state_empty_routes() {
1711    let state = HttpServerState::with_routes(vec![]);
1712    assert_eq!(state.routes.len(), 0);
1713}
1714
1715#[test]
1716fn test_http_server_state_multiple_routes() {
1717    let routes = vec![
1718        RouteInfo {
1719            method: "GET".to_string(),
1720            path: "/users".to_string(),
1721            operation_id: Some("listUsers".to_string()),
1722            summary: Some("List all users".to_string()),
1723            description: None,
1724            parameters: vec![],
1725        },
1726        RouteInfo {
1727            method: "GET".to_string(),
1728            path: "/users/{id}".to_string(),
1729            operation_id: Some("getUser".to_string()),
1730            summary: Some("Get a user".to_string()),
1731            description: None,
1732            parameters: vec!["id".to_string()],
1733        },
1734        RouteInfo {
1735            method: "POST".to_string(),
1736            path: "/users".to_string(),
1737            operation_id: Some("createUser".to_string()),
1738            summary: Some("Create a user".to_string()),
1739            description: None,
1740            parameters: vec!["body".to_string()],
1741        },
1742    ];
1743
1744    let state = HttpServerState::with_routes(routes);
1745    assert_eq!(state.routes.len(), 3);
1746
1747    // Verify different HTTP methods
1748    let methods: Vec<&str> = state.routes.iter().map(|r| r.method.as_str()).collect();
1749    assert!(methods.contains(&"GET"));
1750    assert!(methods.contains(&"POST"));
1751}
1752
1753#[test]
1754fn test_http_server_state_with_rate_limiter() {
1755    use std::sync::Arc;
1756
1757    let config = crate::middleware::RateLimitConfig::default();
1758    let rate_limiter = Arc::new(crate::middleware::GlobalRateLimiter::new(config));
1759
1760    let state = HttpServerState::new().with_rate_limiter(rate_limiter);
1761
1762    assert!(state.rate_limiter.is_some());
1763    assert_eq!(state.routes.len(), 0);
1764}
1765
1766#[tokio::test]
1767async fn test_build_router_includes_rate_limiter() {
1768    let _router = build_router(None, None, None).await;
1769    // Router should be created successfully with rate limiter initialized
1770}