Skip to main content

mockforge_http/
lib.rs

1// TODO: remove once mockforge-intelligence, mockforge-proxy, etc. crates are extracted
2// Deprecation allowances are now scoped to individual handler files
3// (handlers/*.rs, middleware/drift_tracking.rs, rag_ai_generator.rs,
4// management/ai_gen.rs) and to specific build_router functions below,
5// rather than being crate-wide.
6
7//! # MockForge HTTP
8//!
9//! HTTP/REST API mocking library for MockForge.
10//!
11//! This crate provides HTTP-specific functionality for creating mock REST APIs,
12//! including OpenAPI integration, request validation, AI-powered response generation,
13//! and management endpoints.
14//!
15//! ## Overview
16//!
17//! MockForge HTTP enables you to:
18//!
19//! - **Serve OpenAPI specs**: Automatically generate mock endpoints from OpenAPI/Swagger
20//! - **Validate requests**: Enforce schema validation with configurable modes
21//! - **AI-powered responses**: Generate intelligent responses using LLMs
22//! - **Management API**: Real-time monitoring, configuration, and control
23//! - **Request logging**: Comprehensive HTTP request/response logging
24//! - **Metrics collection**: Track performance and usage statistics
25//! - **Server-Sent Events**: Stream logs and metrics to clients
26//!
27//! ## Quick Start
28//!
29//! ### Basic HTTP Server from OpenAPI
30//!
31//! ```rust,no_run
32//! use axum::Router;
33//! use mockforge_openapi::openapi_routes::ValidationMode;
34//! use mockforge_openapi::openapi_routes::ValidationOptions;
35//! use mockforge_http::build_router;
36//!
37//! #[tokio::main]
38//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
39//!     // Build router from OpenAPI specification
40//!     let router = build_router(
41//!         Some("./api-spec.json".to_string()),
42//!         Some(ValidationOptions {
43//!             request_mode: ValidationMode::Enforce,
44//!             ..ValidationOptions::default()
45//!         }),
46//!         None,
47//!     ).await;
48//!
49//!     // Start the server
50//!     let addr: std::net::SocketAddr = "0.0.0.0:3000".parse()?;
51//!     let listener = tokio::net::TcpListener::bind(addr).await?;
52//!     axum::serve(listener, router).await?;
53//!
54//!     Ok(())
55//! }
56//! ```
57//!
58//! ### With Management API
59//!
60//! Enable real-time monitoring and configuration:
61//!
62//! ```rust,no_run
63//! use mockforge_http::{management_router, ManagementState};
64//!
65//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
66//! let state = ManagementState::new(None, None, 3000);
67//!
68//! // Build management router
69//! let mgmt_router = management_router(state);
70//!
71//! // Mount under your main router
72//! let app = axum::Router::new()
73//!     .nest("/__mockforge", mgmt_router);
74//! # Ok(())
75//! # }
76//! ```
77//!
78//! ### AI-Powered Responses
79//!
80//! Generate intelligent responses based on request context:
81//!
82//! ```rust,no_run
83//! use mockforge_data::intelligent_mock::{IntelligentMockConfig, ResponseMode};
84//! use mockforge_http::{process_response_with_ai, AiResponseConfig};
85//! use serde_json::json;
86//!
87//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
88//! let ai_config = AiResponseConfig {
89//!     intelligent: Some(
90//!         IntelligentMockConfig::new(ResponseMode::Intelligent)
91//!             .with_prompt("Generate realistic user data".to_string()),
92//!     ),
93//!     drift: None,
94//! };
95//!
96//! let response = process_response_with_ai(
97//!     Some(json!({"name": "Alice"})),
98//!     ai_config
99//!         .intelligent
100//!         .clone()
101//!         .map(serde_json::to_value)
102//!         .transpose()?,
103//!     ai_config
104//!         .drift
105//!         .clone()
106//!         .map(serde_json::to_value)
107//!         .transpose()?,
108//! )
109//! .await?;
110//! # Ok(())
111//! # }
112//! ```
113//!
114//! ## Key Features
115//!
116//! ### OpenAPI Integration
117//! - Automatic endpoint generation from specs
118//! - Request/response validation
119//! - Schema-based mock data generation
120//!
121//! ### Management & Monitoring
122//! - [`management`]: REST API for server control and monitoring
123//! - [`management_ws`]: WebSocket API for real-time updates
124//! - [`sse`]: Server-Sent Events for log streaming
125//! - [`request_logging`]: Comprehensive request/response logging
126//! - [`metrics_middleware`]: Performance metrics collection
127//!
128//! ### Advanced Features
129//! - [`ai_handler`]: AI-powered response generation
130//! - [`auth`]: Authentication and authorization
131//! - [`chain_handlers`]: Multi-step request workflows
132//! - [`latency_profiles`]: Configurable latency simulation
133//! - [`replay_listing`]: Fixture management
134//!
135//! ## Middleware
136//!
137//! MockForge HTTP includes several middleware layers:
138//!
139//! - **Request Tracing**: [`http_tracing_middleware`] - Distributed tracing integration
140//! - **Metrics Collection**: [`metrics_middleware`] - Prometheus-compatible metrics
141//! - **Operation Metadata**: [`op_middleware`] - OpenAPI operation tracking
142//!
143//! ## Management API Endpoints
144//!
145//! When using the management router, these endpoints are available:
146//!
147//! - `GET /health` - Health check
148//! - `GET /stats` - Server statistics
149//! - `GET /logs` - Request logs (SSE stream)
150//! - `GET /metrics` - Performance metrics
151//! - `GET /fixtures` - List available fixtures
152//! - `POST /config/*` - Update configuration
153//!
154//! ## Examples
155//!
156//! See the [examples directory](https://github.com/SaaSy-Solutions/mockforge/tree/main/examples)
157//! for complete working examples.
158//!
159//! ## Related Crates
160//!
161//! - [`mockforge-core`](https://docs.rs/mockforge-core): Core mocking functionality
162//! - [`mockforge-data`](https://docs.rs/mockforge-data): Synthetic data generation
163//! - [`mockforge-plugin-core`](https://docs.rs/mockforge-plugin-core): Plugin development
164//!
165//! ## Documentation
166//!
167//! - [MockForge Book](https://docs.mockforge.dev/)
168//! - [HTTP Mocking Guide](https://docs.mockforge.dev/user-guide/http-mocking.html)
169//! - [API Reference](https://docs.rs/mockforge-http)
170
171pub mod ai_handler;
172pub mod auth;
173pub mod chain_handlers;
174/// Cross-protocol consistency engine integration for HTTP
175pub mod consistency;
176/// Contract diff middleware for automatic request capture
177pub mod contract_diff_middleware;
178pub mod coverage;
179pub mod database;
180/// File generation service for creating mock PDF, CSV, JSON files
181pub mod file_generator;
182/// File serving for generated mock files
183pub mod file_server;
184/// Kubernetes-native health check endpoints (liveness, readiness, startup probes)
185pub mod health;
186pub mod http_tracing_middleware;
187/// Latency profile configuration for HTTP request simulation
188pub mod latency_profiles;
189/// Management API for server control and monitoring
190pub mod management;
191/// WebSocket-based management API for real-time updates
192pub mod management_ws;
193pub mod metrics_middleware;
194pub mod middleware;
195pub mod op_middleware;
196/// Unified protocol server lifecycle implementation
197pub mod protocol_server;
198/// Browser/Mobile Proxy Server
199pub mod proxy_server;
200/// Quick mock generation utilities
201pub mod quick_mock;
202/// RAG-powered AI response generation
203pub mod rag_ai_generator;
204/// Replay listing and fixture management
205pub mod replay_listing;
206pub mod request_logging;
207/// Specification import API for OpenAPI and AsyncAPI
208pub mod spec_import;
209/// Server-Sent Events for streaming logs and metrics
210pub mod sse;
211/// State machine API for scenario state machines
212pub mod state_machine_api;
213/// TLS/HTTPS support
214pub mod tls;
215/// Token response utilities
216pub mod token_response;
217/// UI Builder API for low-code mock endpoint creation
218pub mod ui_builder;
219/// Verification API for request verification
220pub mod verification;
221
222// Access review handlers
223pub mod handlers;
224
225// Re-export AI handler utilities
226pub use ai_handler::{process_response_with_ai, AiResponseConfig, AiResponseHandler};
227// Re-export health check utilities
228pub use health::{HealthManager, ServiceStatus};
229
230// Re-export management API utilities
231pub use management::{
232    management_router, management_router_with_ui_builder, ManagementState, MockConfig,
233    ServerConfig, ServerStats,
234};
235
236// Re-export UI Builder utilities
237pub use ui_builder::{create_ui_builder_router, EndpointConfig, UIBuilderState};
238
239// Re-export management WebSocket utilities
240pub use management_ws::{ws_management_router, MockEvent, WsManagementState};
241
242// Re-export verification API utilities
243pub use verification::verification_router;
244
245// Re-export metrics middleware
246pub use metrics_middleware::collect_http_metrics;
247
248// Re-export tracing middleware
249pub use http_tracing_middleware::http_tracing_middleware;
250
251// Re-export coverage utilities
252pub use coverage::{calculate_coverage, CoverageReport, MethodCoverage, RouteCoverage};
253
254/// Helper function to load persona from config file
255/// Tries to load from common config locations: config.yaml, mockforge.yaml, tools/mockforge/config.yaml
256async fn load_persona_from_config() -> Option<Arc<Persona>> {
257    use mockforge_core::config::load_config;
258
259    // Try common config file locations
260    let config_paths = [
261        "config.yaml",
262        "mockforge.yaml",
263        "tools/mockforge/config.yaml",
264        "../tools/mockforge/config.yaml",
265    ];
266
267    for path in &config_paths {
268        if let Ok(config) = load_config(path).await {
269            // Access intelligent_behavior through mockai config
270            // Note: Config structure is mockai.intelligent_behavior.personas
271            if let Some(persona) = config.mockai.intelligent_behavior.personas.get_active_persona()
272            {
273                tracing::info!(
274                    "Loaded active persona '{}' from config file: {}",
275                    persona.name,
276                    path
277                );
278                return Some(Arc::new(persona.clone()));
279            } else {
280                tracing::debug!(
281                    "No active persona found in config file: {} (personas count: {})",
282                    path,
283                    config.mockai.intelligent_behavior.personas.personas.len()
284                );
285            }
286        } else {
287            tracing::debug!("Could not load config from: {}", path);
288        }
289    }
290
291    tracing::debug!("No persona found in config files, persona-based generation will be disabled");
292    None
293}
294
295use axum::extract::State;
296use axum::middleware::from_fn_with_state;
297use axum::response::Json;
298use axum::Router;
299use mockforge_chaos::core_failure_injection::{FailureConfig, FailureInjector};
300use mockforge_core::intelligent_behavior::config::Persona;
301use mockforge_foundation::latency::LatencyInjector;
302use mockforge_openapi::openapi_routes::OpenApiRouteRegistry;
303use mockforge_openapi::openapi_routes::ValidationOptions;
304use mockforge_openapi::OpenApiSpec;
305use std::sync::Arc;
306use tower_http::cors::{Any, CorsLayer};
307
308#[cfg(feature = "data-faker")]
309use mockforge_data::provider::register_core_faker_provider;
310use mockforge_foundation::latency::LatencyProfile;
311use std::collections::HashMap;
312use std::ffi::OsStr;
313use std::path::Path;
314use tokio::fs;
315use tokio::sync::RwLock;
316use tracing::*;
317
318/// Route info for storing in state
319#[derive(Clone)]
320pub struct RouteInfo {
321    /// HTTP method (GET, POST, PUT, etc.)
322    pub method: String,
323    /// API path pattern (e.g., "/api/users/{id}")
324    pub path: String,
325    /// OpenAPI operation ID if available
326    pub operation_id: Option<String>,
327    /// Operation summary from OpenAPI spec
328    pub summary: Option<String>,
329    /// Operation description from OpenAPI spec
330    pub description: Option<String>,
331    /// List of parameter names for this route
332    pub parameters: Vec<String>,
333}
334
335/// Shared state for tracking OpenAPI routes
336#[derive(Clone)]
337pub struct HttpServerState {
338    /// List of registered routes from OpenAPI spec
339    pub routes: Vec<RouteInfo>,
340    /// Optional global rate limiter for request throttling
341    pub rate_limiter: Option<Arc<middleware::rate_limit::GlobalRateLimiter>>,
342    /// Production headers to add to all responses (for deceptive deploy)
343    pub production_headers: Option<Arc<HashMap<String, String>>>,
344}
345
346impl Default for HttpServerState {
347    fn default() -> Self {
348        Self::new()
349    }
350}
351
352impl HttpServerState {
353    /// Create a new empty HTTP server state
354    pub fn new() -> Self {
355        Self {
356            routes: Vec::new(),
357            rate_limiter: None,
358            production_headers: None,
359        }
360    }
361
362    /// Create HTTP server state with pre-configured routes
363    pub fn with_routes(routes: Vec<RouteInfo>) -> Self {
364        Self {
365            routes,
366            rate_limiter: None,
367            production_headers: None,
368        }
369    }
370
371    /// Add a rate limiter to the HTTP server state
372    pub fn with_rate_limiter(
373        mut self,
374        rate_limiter: Arc<middleware::rate_limit::GlobalRateLimiter>,
375    ) -> Self {
376        self.rate_limiter = Some(rate_limiter);
377        self
378    }
379
380    /// Add production headers to the HTTP server state
381    pub fn with_production_headers(mut self, headers: Arc<HashMap<String, String>>) -> Self {
382        self.production_headers = Some(headers);
383        self
384    }
385}
386
387/// Handler to return OpenAPI routes information
388async fn get_routes_handler(State(state): State<HttpServerState>) -> Json<serde_json::Value> {
389    let route_info: Vec<serde_json::Value> = state
390        .routes
391        .iter()
392        .map(|route| {
393            serde_json::json!({
394                "method": route.method,
395                "path": route.path,
396                "operation_id": route.operation_id,
397                "summary": route.summary,
398                "description": route.description,
399                "parameters": route.parameters
400            })
401        })
402        .collect();
403
404    Json(serde_json::json!({
405        "routes": route_info,
406        "total": state.routes.len()
407    }))
408}
409
410/// Handler to serve the Scalar API docs page
411async fn get_docs_handler() -> axum::response::Html<&'static str> {
412    axum::response::Html(include_str!("../static/docs.html"))
413}
414
415/// Build the base HTTP router, optionally from an OpenAPI spec.
416pub async fn build_router(
417    spec_path: Option<String>,
418    options: Option<ValidationOptions>,
419    failure_config: Option<FailureConfig>,
420) -> Router {
421    build_router_with_multi_tenant(
422        spec_path,
423        options,
424        failure_config,
425        None,
426        None,
427        None,
428        None,
429        None,
430        None,
431        None,
432    )
433    .await
434}
435
436/// Apply CORS middleware to the router based on configuration
437fn apply_cors_middleware(
438    app: Router,
439    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
440) -> Router {
441    use http::Method;
442    use tower_http::cors::AllowOrigin;
443
444    if let Some(config) = cors_config {
445        if !config.enabled {
446            return app;
447        }
448
449        let mut cors_layer = CorsLayer::new();
450        let is_wildcard_origin;
451
452        // Configure allowed origins
453        if config.allowed_origins.contains(&"*".to_string()) {
454            cors_layer = cors_layer.allow_origin(Any);
455            is_wildcard_origin = true;
456        } else if !config.allowed_origins.is_empty() {
457            // Try to parse each origin, fallback to permissive if parsing fails
458            let origins: Vec<_> = config
459                .allowed_origins
460                .iter()
461                .filter_map(|origin| {
462                    origin.parse::<http::HeaderValue>().ok().map(AllowOrigin::exact)
463                })
464                .collect();
465
466            if origins.is_empty() {
467                // If no valid origins, use permissive for development
468                warn!("No valid CORS origins configured, using permissive CORS");
469                cors_layer = cors_layer.allow_origin(Any);
470                is_wildcard_origin = true;
471            } else {
472                // Use the first origin as exact match (tower-http limitation)
473                // For multiple origins, we'd need a custom implementation
474                if origins.len() == 1 {
475                    cors_layer = cors_layer.allow_origin(origins[0].clone());
476                    is_wildcard_origin = false;
477                } else {
478                    // Multiple origins - use permissive for now
479                    warn!(
480                        "Multiple CORS origins configured, using permissive CORS. \
481                        Consider using '*' for all origins."
482                    );
483                    cors_layer = cors_layer.allow_origin(Any);
484                    is_wildcard_origin = true;
485                }
486            }
487        } else {
488            // No origins specified, use permissive for development
489            cors_layer = cors_layer.allow_origin(Any);
490            is_wildcard_origin = true;
491        }
492
493        // Configure allowed methods
494        if !config.allowed_methods.is_empty() {
495            let methods: Vec<Method> =
496                config.allowed_methods.iter().filter_map(|m| m.parse().ok()).collect();
497            if !methods.is_empty() {
498                cors_layer = cors_layer.allow_methods(methods);
499            }
500        } else {
501            // Default to common HTTP methods
502            cors_layer = cors_layer.allow_methods([
503                Method::GET,
504                Method::POST,
505                Method::PUT,
506                Method::DELETE,
507                Method::PATCH,
508                Method::OPTIONS,
509            ]);
510        }
511
512        // Configure allowed headers
513        if !config.allowed_headers.is_empty() {
514            let headers: Vec<_> = config
515                .allowed_headers
516                .iter()
517                .filter_map(|h| h.parse::<http::HeaderName>().ok())
518                .collect();
519            if !headers.is_empty() {
520                cors_layer = cors_layer.allow_headers(headers);
521            }
522        } else {
523            // Default headers
524            cors_layer =
525                cors_layer.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION]);
526        }
527
528        // Configure credentials - cannot allow credentials with wildcard origin
529        // Determine if credentials should be allowed
530        // Cannot allow credentials with wildcard origin per CORS spec
531        let should_allow_credentials = if is_wildcard_origin {
532            // Wildcard origin - credentials must be false
533            false
534        } else {
535            // Specific origins - use config value (defaults to false)
536            config.allow_credentials
537        };
538
539        cors_layer = cors_layer.allow_credentials(should_allow_credentials);
540
541        info!(
542            "CORS middleware enabled with configured settings (credentials: {})",
543            should_allow_credentials
544        );
545        app.layer(cors_layer)
546    } else {
547        // No CORS config provided - use permissive CORS for development
548        // Note: permissive() allows credentials, but since it uses wildcard origin,
549        // we need to disable credentials to avoid CORS spec violation
550        debug!("No CORS config provided, using permissive CORS for development");
551        // Create a permissive CORS layer but disable credentials to avoid CORS spec violation
552        // (cannot combine credentials with wildcard origin)
553        app.layer(CorsLayer::permissive().allow_credentials(false))
554    }
555}
556
557/// Build the base HTTP router with multi-tenant workspace support
558#[allow(clippy::too_many_arguments)]
559#[allow(deprecated)] // uses MockAI, MultiTenantWorkspaceRegistry, WorkspaceRouter, Workspace (all stay in core)
560pub async fn build_router_with_multi_tenant(
561    spec_path: Option<String>,
562    options: Option<ValidationOptions>,
563    failure_config: Option<FailureConfig>,
564    multi_tenant_config: Option<mockforge_foundation::multi_tenant_types::MultiTenantConfig>,
565    _route_configs: Option<Vec<mockforge_core::config::RouteConfig>>,
566    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
567    ai_generator: Option<Arc<dyn mockforge_openapi::response::AiGenerator + Send + Sync>>,
568    smtp_registry: Option<Arc<dyn std::any::Any + Send + Sync>>,
569    mockai: Option<Arc<RwLock<mockforge_core::intelligent_behavior::MockAI>>>,
570    deceptive_deploy_config: Option<mockforge_core::config::DeceptiveDeployConfig>,
571) -> Router {
572    use std::time::Instant;
573
574    let startup_start = Instant::now();
575
576    // Set up the basic router
577    let mut app = Router::new();
578
579    // Initialize rate limiter with default configuration
580    // Can be customized via environment variables or config
581    let mut rate_limit_config = middleware::RateLimitConfig {
582        requests_per_minute: std::env::var("MOCKFORGE_RATE_LIMIT_RPM")
583            .ok()
584            .and_then(|v| v.parse().ok())
585            .unwrap_or(1000),
586        burst: std::env::var("MOCKFORGE_RATE_LIMIT_BURST")
587            .ok()
588            .and_then(|v| v.parse().ok())
589            .unwrap_or(2000),
590        per_ip: true,
591        per_endpoint: false,
592    };
593
594    // Apply deceptive deploy configuration if enabled
595    let mut final_cors_config = cors_config;
596    let mut production_headers: Option<std::sync::Arc<HashMap<String, String>>> = None;
597    // Auth config from deceptive deploy OAuth (if configured)
598    let mut deceptive_deploy_auth_config: Option<mockforge_core::config::AuthConfig> = None;
599
600    if let Some(deploy_config) = &deceptive_deploy_config {
601        if deploy_config.enabled {
602            info!("Deceptive deploy mode enabled - applying production-like configuration");
603
604            // Override CORS config if provided
605            if let Some(prod_cors) = &deploy_config.cors {
606                final_cors_config = Some(mockforge_core::config::HttpCorsConfig {
607                    enabled: true,
608                    allowed_origins: prod_cors.allowed_origins.clone(),
609                    allowed_methods: prod_cors.allowed_methods.clone(),
610                    allowed_headers: prod_cors.allowed_headers.clone(),
611                    allow_credentials: prod_cors.allow_credentials,
612                });
613                info!("Applied production-like CORS configuration");
614            }
615
616            // Override rate limit config if provided
617            if let Some(prod_rate_limit) = &deploy_config.rate_limit {
618                rate_limit_config = middleware::RateLimitConfig {
619                    requests_per_minute: prod_rate_limit.requests_per_minute,
620                    burst: prod_rate_limit.burst,
621                    per_ip: prod_rate_limit.per_ip,
622                    per_endpoint: false,
623                };
624                info!(
625                    "Applied production-like rate limiting: {} req/min, burst: {}",
626                    prod_rate_limit.requests_per_minute, prod_rate_limit.burst
627                );
628            }
629
630            // Set production headers
631            if !deploy_config.headers.is_empty() {
632                let headers_map: HashMap<String, String> = deploy_config.headers.clone();
633                production_headers = Some(std::sync::Arc::new(headers_map));
634                info!("Configured {} production headers", deploy_config.headers.len());
635            }
636
637            // Integrate OAuth config from deceptive deploy
638            if let Some(prod_oauth) = &deploy_config.oauth {
639                let oauth2_config: mockforge_core::config::OAuth2Config = prod_oauth.clone().into();
640                deceptive_deploy_auth_config = Some(mockforge_core::config::AuthConfig {
641                    oauth2: Some(oauth2_config),
642                    ..Default::default()
643                });
644                info!("Applied production-like OAuth configuration for deceptive deploy");
645            }
646        }
647    }
648
649    let rate_limiter =
650        std::sync::Arc::new(middleware::GlobalRateLimiter::new(rate_limit_config.clone()));
651
652    let mut state = HttpServerState::new().with_rate_limiter(rate_limiter.clone());
653
654    // Add production headers to state if configured
655    if let Some(headers) = production_headers.clone() {
656        state = state.with_production_headers(headers);
657    }
658
659    // Clone spec_path for later use
660    let spec_path_for_mgmt = spec_path.clone();
661
662    // If an OpenAPI spec is provided, integrate it
663    if let Some(spec_path) = spec_path {
664        tracing::debug!("Processing OpenAPI spec path: {}", spec_path);
665
666        // Measure OpenAPI spec loading
667        let spec_load_start = Instant::now();
668        match OpenApiSpec::from_file(&spec_path).await {
669            Ok(openapi) => {
670                let spec_load_duration = spec_load_start.elapsed();
671                info!(
672                    "Successfully loaded OpenAPI spec from {} (took {:?})",
673                    spec_path, spec_load_duration
674                );
675
676                // Measure route registry creation
677                tracing::debug!("Creating OpenAPI route registry...");
678                let registry_start = Instant::now();
679
680                // Try to load persona from config if available
681                let persona = load_persona_from_config().await;
682
683                let registry = if let Some(opts) = options {
684                    tracing::debug!("Using custom validation options");
685                    if let Some(ref persona) = persona {
686                        tracing::info!("Using persona '{}' for route generation", persona.name);
687                    }
688                    OpenApiRouteRegistry::new_with_options_and_persona(openapi, opts, persona)
689                } else {
690                    tracing::debug!("Using environment-based options");
691                    if let Some(ref persona) = persona {
692                        tracing::info!("Using persona '{}' for route generation", persona.name);
693                    }
694                    OpenApiRouteRegistry::new_with_env_and_persona(openapi, persona)
695                };
696                let registry_duration = registry_start.elapsed();
697                info!(
698                    "Created OpenAPI route registry with {} routes (took {:?})",
699                    registry.routes().len(),
700                    registry_duration
701                );
702
703                // Measure route extraction
704                let extract_start = Instant::now();
705                let route_info: Vec<RouteInfo> = registry
706                    .routes()
707                    .iter()
708                    .map(|route| RouteInfo {
709                        method: route.method.clone(),
710                        path: route.path.clone(),
711                        operation_id: route.operation.operation_id.clone(),
712                        summary: route.operation.summary.clone(),
713                        description: route.operation.description.clone(),
714                        parameters: route.parameters.clone(),
715                    })
716                    .collect();
717                state.routes = route_info;
718                let extract_duration = extract_start.elapsed();
719                debug!("Extracted route information (took {:?})", extract_duration);
720
721                // Measure overrides loading
722                let overrides = if std::env::var("MOCKFORGE_HTTP_OVERRIDES_GLOB").is_ok() {
723                    tracing::debug!("Loading overrides from environment variable");
724                    let overrides_start = Instant::now();
725                    match mockforge_core::Overrides::load_from_globs(&[]).await {
726                        Ok(overrides) => {
727                            let overrides_duration = overrides_start.elapsed();
728                            info!(
729                                "Loaded {} override rules (took {:?})",
730                                overrides.rules().len(),
731                                overrides_duration
732                            );
733                            Some(overrides)
734                        }
735                        Err(e) => {
736                            tracing::warn!("Failed to load overrides: {}", e);
737                            None
738                        }
739                    }
740                } else {
741                    None
742                };
743
744                // Measure router building
745                let router_build_start = Instant::now();
746                let overrides_enabled = overrides.is_some();
747                let response_rewriter: Option<
748                    std::sync::Arc<dyn mockforge_openapi::response_rewriter::ResponseRewriter>,
749                > = Some(std::sync::Arc::new(
750                    mockforge_core::openapi_rewriter::CoreResponseRewriter::new(overrides),
751                ));
752                let openapi_router = if let Some(mockai_instance) = &mockai {
753                    tracing::debug!("Building router with MockAI support");
754                    registry.build_router_with_mockai(Some(mockai_instance.clone()))
755                } else if let Some(ai_generator) = &ai_generator {
756                    tracing::debug!("Building router with AI generator support");
757                    registry.build_router_with_ai(Some(ai_generator.clone()))
758                } else if let Some(failure_config) = &failure_config {
759                    tracing::debug!("Building router with failure injection and overrides");
760                    let failure_injector = FailureInjector::new(Some(failure_config.clone()), true);
761                    registry.build_router_with_injectors_and_overrides(
762                        LatencyInjector::default(),
763                        Some(failure_injector),
764                        response_rewriter,
765                        overrides_enabled,
766                    )
767                } else {
768                    tracing::debug!("Building router with overrides");
769                    registry.build_router_with_injectors_and_overrides(
770                        LatencyInjector::default(),
771                        None,
772                        response_rewriter,
773                        overrides_enabled,
774                    )
775                };
776                let router_build_duration = router_build_start.elapsed();
777                debug!("Built OpenAPI router (took {:?})", router_build_duration);
778
779                tracing::debug!("Merging OpenAPI router with main router");
780                app = app.merge(openapi_router);
781                tracing::debug!("Router built successfully");
782            }
783            Err(e) => {
784                warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec_path, e);
785            }
786        }
787    }
788
789    // Add basic health check endpoint
790    app = app.route(
791        "/health",
792        axum::routing::get(|| async {
793            use mockforge_core::server_utils::health::HealthStatus;
794            {
795                // HealthStatus should always serialize, but handle errors gracefully
796                match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
797                    Ok(value) => Json(value),
798                    Err(e) => {
799                        // Log error but return a simple healthy response
800                        tracing::error!("Failed to serialize health status: {}", e);
801                        Json(serde_json::json!({
802                            "status": "healthy",
803                            "service": "mockforge-http",
804                            "uptime_seconds": 0
805                        }))
806                    }
807                }
808            }
809        }),
810    )
811    // Add SSE endpoints
812    .merge(sse::sse_router())
813    // Add file serving endpoints for generated mock files
814    .merge(file_server::file_serving_router());
815
816    // Clone state for routes_router since we'll use it for middleware too
817    let state_for_routes = state.clone();
818
819    // Create a router with state for the routes and coverage endpoints
820    let routes_router = Router::new()
821        .route("/__mockforge/routes", axum::routing::get(get_routes_handler))
822        .route("/__mockforge/coverage", axum::routing::get(coverage::get_coverage_handler))
823        .with_state(state_for_routes);
824
825    // Merge the routes router with the main app
826    app = app.merge(routes_router);
827
828    // Add API docs page (Scalar-powered interactive explorer)
829    app = app.route("/__mockforge/docs", axum::routing::get(get_docs_handler));
830
831    // Add static coverage UI
832    // Determine the path to the coverage.html file
833    let coverage_html_path = std::env::var("MOCKFORGE_COVERAGE_UI_PATH")
834        .unwrap_or_else(|_| "crates/mockforge-http/static/coverage.html".to_string());
835
836    // Check if the file exists before serving it
837    if Path::new(&coverage_html_path).exists() {
838        app = app.nest_service(
839            "/__mockforge/coverage.html",
840            tower_http::services::ServeFile::new(&coverage_html_path),
841        );
842        debug!("Serving coverage UI from: {}", coverage_html_path);
843    } else {
844        debug!(
845            "Coverage UI file not found at: {}. Skipping static file serving.",
846            coverage_html_path
847        );
848    }
849
850    // Add management API endpoints
851    // Load spec for ManagementState so /__mockforge/api/spec can serve it
852    let mgmt_spec = if let Some(ref sp) = spec_path_for_mgmt {
853        match OpenApiSpec::from_file(sp).await {
854            Ok(s) => Some(Arc::new(s)),
855            Err(e) => {
856                debug!("Failed to load OpenAPI spec for management API: {}", e);
857                None
858            }
859        }
860    } else {
861        None
862    };
863    let mgmt_port = std::env::var("PORT")
864        .or_else(|_| std::env::var("MOCKFORGE_HTTP_PORT"))
865        .ok()
866        .and_then(|p| p.parse().ok())
867        .unwrap_or(3000);
868    let management_state = ManagementState::new(mgmt_spec, spec_path_for_mgmt, mgmt_port);
869
870    // Create WebSocket state and connect it to management state
871    use std::sync::Arc;
872    let ws_state = WsManagementState::new();
873    let ws_broadcast = Arc::new(ws_state.tx.clone());
874    let management_state = management_state.with_ws_broadcast(ws_broadcast);
875
876    // Note: ProxyConfig not available in this build function path
877    // Migration endpoints will work once ProxyConfig is passed to build_router_with_chains_and_multi_tenant
878
879    #[cfg(feature = "smtp")]
880    let management_state = {
881        if let Some(smtp_reg) = smtp_registry {
882            match smtp_reg.downcast::<mockforge_smtp::SmtpSpecRegistry>() {
883                Ok(smtp_reg) => management_state.with_smtp_registry(smtp_reg),
884                Err(e) => {
885                    error!(
886                        "Invalid SMTP registry type passed to HTTP management state: {:?}",
887                        e.type_id()
888                    );
889                    management_state
890                }
891            }
892        } else {
893            management_state
894        }
895    };
896    #[cfg(not(feature = "smtp"))]
897    let management_state = management_state;
898    #[cfg(not(feature = "smtp"))]
899    let _ = smtp_registry;
900    let management_state_for_fallback = management_state.clone();
901    app = app.nest("/__mockforge/api", management_router(management_state));
902    // Serve any request that the rest of the router doesn't handle as a dynamic
903    // mock lookup. Lets the Node/Rust SDK register stubs via the management
904    // API and have requests actually reach them. Falls through to 404 when
905    // no mock matches.
906    app = app.fallback_service(
907        axum::routing::any(crate::management::dynamic_mock_fallback)
908            .with_state(management_state_for_fallback),
909    );
910
911    // Add verification API endpoint
912    app = app.merge(verification_router());
913
914    // Add OIDC well-known endpoints
915    use crate::auth::oidc::oidc_router;
916    app = app.merge(oidc_router());
917
918    // Add access review API if enabled
919    {
920        use mockforge_core::security::get_global_access_review_service;
921        if let Some(service) = get_global_access_review_service().await {
922            use crate::handlers::access_review::{access_review_router, AccessReviewState};
923            let review_state = AccessReviewState { service };
924            app = app.nest("/api/v1/security/access-reviews", access_review_router(review_state));
925            debug!("Access review API mounted at /api/v1/security/access-reviews");
926        }
927    }
928
929    // Add privileged access API if enabled
930    {
931        use mockforge_core::security::get_global_privileged_access_manager;
932        if let Some(manager) = get_global_privileged_access_manager().await {
933            use crate::handlers::privileged_access::{
934                privileged_access_router, PrivilegedAccessState,
935            };
936            let privileged_state = PrivilegedAccessState { manager };
937            app = app.nest(
938                "/api/v1/security/privileged-access",
939                privileged_access_router(privileged_state),
940            );
941            debug!("Privileged access API mounted at /api/v1/security/privileged-access");
942        }
943    }
944
945    // Add change management API if enabled
946    {
947        use mockforge_core::security::get_global_change_management_engine;
948        if let Some(engine) = get_global_change_management_engine().await {
949            use crate::handlers::change_management::{
950                change_management_router, ChangeManagementState,
951            };
952            let change_state = ChangeManagementState { engine };
953            app = app.nest("/api/v1/change-management", change_management_router(change_state));
954            debug!("Change management API mounted at /api/v1/change-management");
955        }
956    }
957
958    // Add risk assessment API if enabled
959    {
960        use mockforge_core::security::get_global_risk_assessment_engine;
961        if let Some(engine) = get_global_risk_assessment_engine().await {
962            use crate::handlers::risk_assessment::{risk_assessment_router, RiskAssessmentState};
963            let risk_state = RiskAssessmentState { engine };
964            app = app.nest("/api/v1/security", risk_assessment_router(risk_state));
965            debug!("Risk assessment API mounted at /api/v1/security/risks");
966        }
967    }
968
969    // Add token lifecycle API
970    {
971        use crate::auth::token_lifecycle::TokenLifecycleManager;
972        use crate::handlers::token_lifecycle::{token_lifecycle_router, TokenLifecycleState};
973        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
974        let lifecycle_state = TokenLifecycleState {
975            manager: lifecycle_manager,
976        };
977        app = app.nest("/api/v1/auth", token_lifecycle_router(lifecycle_state));
978        debug!("Token lifecycle API mounted at /api/v1/auth");
979    }
980
981    // Add OAuth2 server endpoints
982    {
983        use crate::auth::oidc::load_oidc_state;
984        use crate::auth::token_lifecycle::TokenLifecycleManager;
985        use crate::handlers::oauth2_server::{oauth2_server_router, OAuth2ServerState};
986        // Load OIDC state from configuration (environment variables or config file)
987        let oidc_state = Arc::new(RwLock::new(load_oidc_state()));
988        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
989        let oauth2_state = OAuth2ServerState {
990            oidc_state,
991            lifecycle_manager,
992            auth_codes: Arc::new(RwLock::new(HashMap::new())),
993            refresh_tokens: Arc::new(RwLock::new(HashMap::new())),
994        };
995        app = app.merge(oauth2_server_router(oauth2_state));
996        debug!("OAuth2 server endpoints mounted at /oauth2/authorize and /oauth2/token");
997    }
998
999    // Add consent screen endpoints
1000    {
1001        use crate::auth::oidc::load_oidc_state;
1002        use crate::auth::risk_engine::RiskEngine;
1003        use crate::auth::token_lifecycle::TokenLifecycleManager;
1004        use crate::handlers::consent::{consent_router, ConsentState};
1005        use crate::handlers::oauth2_server::OAuth2ServerState;
1006        // Load OIDC state from configuration (environment variables or config file)
1007        let oidc_state = Arc::new(RwLock::new(load_oidc_state()));
1008        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
1009        let oauth2_state = OAuth2ServerState {
1010            oidc_state: oidc_state.clone(),
1011            lifecycle_manager: lifecycle_manager.clone(),
1012            auth_codes: Arc::new(RwLock::new(HashMap::new())),
1013            refresh_tokens: Arc::new(RwLock::new(HashMap::new())),
1014        };
1015        let risk_engine = Arc::new(RiskEngine::default());
1016        let consent_state = ConsentState {
1017            oauth2_state,
1018            risk_engine,
1019        };
1020        app = app.merge(consent_router(consent_state));
1021        debug!("Consent screen endpoints mounted at /consent");
1022    }
1023
1024    // Add risk simulation API
1025    {
1026        use crate::auth::risk_engine::RiskEngine;
1027        use crate::handlers::risk_simulation::{risk_simulation_router, RiskSimulationState};
1028        let risk_engine = Arc::new(RiskEngine::default());
1029        let risk_state = RiskSimulationState { risk_engine };
1030        app = app.nest("/api/v1/auth", risk_simulation_router(risk_state));
1031        debug!("Risk simulation API mounted at /api/v1/auth/risk");
1032    }
1033
1034    // Add management WebSocket endpoint
1035    app = app.nest("/__mockforge/ws", ws_management_router(ws_state));
1036
1037    // Add request logging middleware to capture all requests
1038    app = app.layer(axum::middleware::from_fn(request_logging::log_http_requests));
1039
1040    // Add security middleware for security event tracking (after logging, before contract diff)
1041    app = app.layer(axum::middleware::from_fn(middleware::security_middleware));
1042
1043    // Add contract diff middleware for automatic request capture
1044    // This captures requests for contract diff analysis (after logging)
1045    app = app.layer(axum::middleware::from_fn(contract_diff_middleware::capture_for_contract_diff));
1046
1047    // Add rate limiting middleware (before logging to rate limit early)
1048    app = app.layer(from_fn_with_state(state.clone(), middleware::rate_limit_middleware));
1049
1050    // Add production headers middleware if configured
1051    if state.production_headers.is_some() {
1052        app =
1053            app.layer(from_fn_with_state(state.clone(), middleware::production_headers_middleware));
1054    }
1055
1056    // Add authentication middleware if OAuth is configured via deceptive deploy
1057    if let Some(auth_config) = deceptive_deploy_auth_config {
1058        use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
1059        use std::collections::HashMap;
1060        use std::sync::Arc;
1061        use tokio::sync::RwLock;
1062
1063        // Create OAuth2 client if configured
1064        let oauth2_client = if let Some(oauth2_config) = &auth_config.oauth2 {
1065            match create_oauth2_client(oauth2_config) {
1066                Ok(client) => Some(client),
1067                Err(e) => {
1068                    warn!("Failed to create OAuth2 client from deceptive deploy config: {}", e);
1069                    None
1070                }
1071            }
1072        } else {
1073            None
1074        };
1075
1076        // Create auth state
1077        let auth_state = AuthState {
1078            config: auth_config,
1079            spec: None, // OpenAPI spec not available in this context
1080            oauth2_client,
1081            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
1082        };
1083
1084        // Apply auth middleware
1085        app = app.layer(from_fn_with_state(auth_state, auth_middleware));
1086        info!("Applied OAuth authentication middleware from deceptive deploy configuration");
1087    }
1088
1089    // Add CORS middleware (use final_cors_config which may be overridden by deceptive deploy)
1090    app = apply_cors_middleware(app, final_cors_config);
1091
1092    // Add workspace routing middleware if multi-tenant is enabled
1093    if let Some(mt_config) = multi_tenant_config {
1094        if mt_config.enabled {
1095            use mockforge_core::{MultiTenantWorkspaceRegistry, WorkspaceRouter};
1096            use std::sync::Arc;
1097
1098            info!(
1099                "Multi-tenant mode enabled with {} routing strategy",
1100                match mt_config.routing_strategy {
1101                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Path => "path-based",
1102                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Port => "port-based",
1103                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Both => "hybrid",
1104                }
1105            );
1106
1107            // Create the multi-tenant workspace registry
1108            let mut registry = MultiTenantWorkspaceRegistry::new(mt_config.clone());
1109
1110            // Register the default workspace before wrapping in Arc
1111            let default_workspace =
1112                mockforge_core::Workspace::new(mt_config.default_workspace.clone());
1113            if let Err(e) =
1114                registry.register_workspace(mt_config.default_workspace.clone(), default_workspace)
1115            {
1116                warn!("Failed to register default workspace: {}", e);
1117            } else {
1118                info!("Registered default workspace: '{}'", mt_config.default_workspace);
1119            }
1120
1121            // Auto-discover and register workspaces if configured
1122            if mt_config.auto_discover {
1123                if let Some(config_dir) = &mt_config.config_directory {
1124                    let config_path = Path::new(config_dir);
1125                    if config_path.exists() && config_path.is_dir() {
1126                        match fs::read_dir(config_path).await {
1127                            Ok(mut entries) => {
1128                                while let Ok(Some(entry)) = entries.next_entry().await {
1129                                    let path = entry.path();
1130                                    if path.extension() == Some(OsStr::new("yaml")) {
1131                                        match fs::read_to_string(&path).await {
1132                                            Ok(content) => {
1133                                                match serde_yaml::from_str::<
1134                                                    mockforge_core::Workspace,
1135                                                >(
1136                                                    &content
1137                                                ) {
1138                                                    Ok(workspace) => {
1139                                                        if let Err(e) = registry.register_workspace(
1140                                                            workspace.id.clone(),
1141                                                            workspace,
1142                                                        ) {
1143                                                            warn!("Failed to register auto-discovered workspace from {:?}: {}", path, e);
1144                                                        } else {
1145                                                            info!("Auto-registered workspace from {:?}", path);
1146                                                        }
1147                                                    }
1148                                                    Err(e) => {
1149                                                        warn!("Failed to parse workspace from {:?}: {}", path, e);
1150                                                    }
1151                                                }
1152                                            }
1153                                            Err(e) => {
1154                                                warn!(
1155                                                    "Failed to read workspace file {:?}: {}",
1156                                                    path, e
1157                                                );
1158                                            }
1159                                        }
1160                                    }
1161                                }
1162                            }
1163                            Err(e) => {
1164                                warn!("Failed to read config directory {:?}: {}", config_path, e);
1165                            }
1166                        }
1167                    } else {
1168                        warn!(
1169                            "Config directory {:?} does not exist or is not a directory",
1170                            config_path
1171                        );
1172                    }
1173                }
1174            }
1175
1176            // Wrap registry in Arc for shared access
1177            let registry = Arc::new(registry);
1178
1179            // Create workspace router and wrap the app with workspace middleware
1180            let _workspace_router = WorkspaceRouter::new(registry);
1181
1182            // Note: The actual middleware integration would need to be implemented
1183            // in the WorkspaceRouter to work with Axum's middleware system
1184            info!("Workspace routing middleware initialized for HTTP server");
1185        }
1186    }
1187
1188    let total_startup_duration = startup_start.elapsed();
1189    info!("HTTP router startup completed (total time: {:?})", total_startup_duration);
1190
1191    app
1192}
1193
1194/// Build the base HTTP router with authentication and latency support
1195pub async fn build_router_with_auth_and_latency(
1196    spec_path: Option<String>,
1197    _options: Option<()>,
1198    auth_config: Option<mockforge_core::config::AuthConfig>,
1199    latency_injector: Option<LatencyInjector>,
1200) -> Router {
1201    // Build auth router first, then layer latency on top
1202    let mut app = build_router_with_auth(spec_path.clone(), None, auth_config).await;
1203
1204    // Apply latency injection middleware if configured
1205    if let Some(injector) = latency_injector {
1206        let injector = Arc::new(injector);
1207        app = app.layer(axum::middleware::from_fn(move |req, next: axum::middleware::Next| {
1208            let injector = injector.clone();
1209            async move {
1210                let _ = injector.inject_latency(&[]).await;
1211                next.run(req).await
1212            }
1213        }));
1214    }
1215
1216    app
1217}
1218
1219/// Build the base HTTP router with latency injection support
1220pub async fn build_router_with_latency(
1221    spec_path: Option<String>,
1222    options: Option<ValidationOptions>,
1223    latency_injector: Option<LatencyInjector>,
1224) -> Router {
1225    if let Some(spec) = &spec_path {
1226        match OpenApiSpec::from_file(spec).await {
1227            Ok(openapi) => {
1228                let registry = if let Some(opts) = options {
1229                    OpenApiRouteRegistry::new_with_options(openapi, opts)
1230                } else {
1231                    OpenApiRouteRegistry::new_with_env(openapi)
1232                };
1233
1234                if let Some(injector) = latency_injector {
1235                    return registry.build_router_with_latency(injector);
1236                } else {
1237                    return registry.build_router();
1238                }
1239            }
1240            Err(e) => {
1241                warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec, e);
1242            }
1243        }
1244    }
1245
1246    build_router(None, None, None).await
1247}
1248
1249/// Build the base HTTP router with authentication support
1250pub async fn build_router_with_auth(
1251    spec_path: Option<String>,
1252    options: Option<ValidationOptions>,
1253    auth_config: Option<mockforge_core::config::AuthConfig>,
1254) -> Router {
1255    use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
1256    use std::sync::Arc;
1257
1258    // If richer faker is available, register provider once (idempotent)
1259    #[cfg(feature = "data-faker")]
1260    {
1261        register_core_faker_provider();
1262    }
1263
1264    // Set up authentication state
1265    let spec = if let Some(spec_path) = &spec_path {
1266        match OpenApiSpec::from_file(&spec_path).await {
1267            Ok(spec) => Some(Arc::new(spec)),
1268            Err(e) => {
1269                warn!("Failed to load OpenAPI spec for auth: {}", e);
1270                None
1271            }
1272        }
1273    } else {
1274        None
1275    };
1276
1277    // Create OAuth2 client if configured
1278    let oauth2_client = if let Some(auth_config) = &auth_config {
1279        if let Some(oauth2_config) = &auth_config.oauth2 {
1280            match create_oauth2_client(oauth2_config) {
1281                Ok(client) => Some(client),
1282                Err(e) => {
1283                    warn!("Failed to create OAuth2 client: {}", e);
1284                    None
1285                }
1286            }
1287        } else {
1288            None
1289        }
1290    } else {
1291        None
1292    };
1293
1294    let auth_state = AuthState {
1295        config: auth_config.unwrap_or_default(),
1296        spec,
1297        oauth2_client,
1298        introspection_cache: Arc::new(RwLock::new(HashMap::new())),
1299    };
1300
1301    // Set up the basic router with auth state
1302    let mut app = Router::new().with_state(auth_state.clone());
1303
1304    // If an OpenAPI spec is provided, integrate it
1305    if let Some(spec_path) = spec_path {
1306        match OpenApiSpec::from_file(&spec_path).await {
1307            Ok(openapi) => {
1308                info!("Loaded OpenAPI spec from {}", spec_path);
1309                let registry = if let Some(opts) = options {
1310                    OpenApiRouteRegistry::new_with_options(openapi, opts)
1311                } else {
1312                    OpenApiRouteRegistry::new_with_env(openapi)
1313                };
1314
1315                app = registry.build_router();
1316            }
1317            Err(e) => {
1318                warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec_path, e);
1319            }
1320        }
1321    }
1322
1323    // Add basic health check endpoint
1324    app = app.route(
1325        "/health",
1326        axum::routing::get(|| async {
1327            use mockforge_core::server_utils::health::HealthStatus;
1328            {
1329                // HealthStatus should always serialize, but handle errors gracefully
1330                match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
1331                    Ok(value) => Json(value),
1332                    Err(e) => {
1333                        // Log error but return a simple healthy response
1334                        tracing::error!("Failed to serialize health status: {}", e);
1335                        Json(serde_json::json!({
1336                            "status": "healthy",
1337                            "service": "mockforge-http",
1338                            "uptime_seconds": 0
1339                        }))
1340                    }
1341                }
1342            }
1343        }),
1344    )
1345    // Add SSE endpoints
1346    .merge(sse::sse_router())
1347    // Add file serving endpoints for generated mock files
1348    .merge(file_server::file_serving_router())
1349    // Add authentication middleware (before logging)
1350    .layer(from_fn_with_state(auth_state.clone(), auth_middleware))
1351    // Add request logging middleware
1352    .layer(axum::middleware::from_fn(request_logging::log_http_requests));
1353
1354    app
1355}
1356
1357/// Serve a provided router on the given port.
1358pub async fn serve_router(
1359    port: u16,
1360    app: Router,
1361) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1362    serve_router_with_tls(port, app, None).await
1363}
1364
1365/// Serve a provided router on the given port with optional TLS support.
1366pub async fn serve_router_with_tls(
1367    port: u16,
1368    app: Router,
1369    tls_config: Option<mockforge_core::config::HttpTlsConfig>,
1370) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1371    serve_router_with_tls_notify(port, app, tls_config, None).await
1372}
1373
1374/// Serve a provided router on the given port with optional TLS support, notifying
1375/// the caller via `bound_port_tx` with the actual OS-assigned port once the
1376/// listener has bound. This matters when the requested `port` is `0` (ephemeral),
1377/// because callers such as the `@mockforge-dev/sdk` need to learn the real port
1378/// to connect to it. On the TLS path, `bound_port_tx` is currently ignored and
1379/// the requested `port` is reported verbatim — callers that need dynamic TLS
1380/// ports should bind on a known port.
1381pub async fn serve_router_with_tls_notify(
1382    port: u16,
1383    app: Router,
1384    tls_config: Option<mockforge_core::config::HttpTlsConfig>,
1385    bound_port_tx: Option<tokio::sync::oneshot::Sender<u16>>,
1386) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1387    use std::net::SocketAddr;
1388
1389    let addr = mockforge_core::wildcard_socket_addr(port);
1390
1391    if let Some(ref tls) = tls_config {
1392        if tls.enabled {
1393            info!("HTTPS listening on {}", addr);
1394            if let Some(tx) = bound_port_tx {
1395                let _ = tx.send(port);
1396            }
1397            return serve_with_tls(addr, app, tls).await;
1398        }
1399    }
1400
1401    let listener = tokio::net::TcpListener::bind(addr).await.map_err(|e| {
1402        format!(
1403            "Failed to bind HTTP server to port {}: {}\n\
1404             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 {}",
1405            port, e, port, port
1406        )
1407    })?;
1408
1409    let actual_port = listener.local_addr().map(|a| a.port()).unwrap_or(port);
1410    info!("HTTP listening on {}", listener.local_addr().unwrap_or(addr));
1411    if let Some(tx) = bound_port_tx {
1412        let _ = tx.send(actual_port);
1413    }
1414
1415    // Wrap the Router with OData URI rewrite.
1416    // Router::layer() only applies to matched routes, so we must wrap at the service level
1417    // to rewrite URIs BEFORE route matching occurs.
1418    let odata_app = tower::ServiceBuilder::new()
1419        .layer(mockforge_core::odata_rewrite::ODataRewriteLayer)
1420        .service(app);
1421    let make_svc = axum::ServiceExt::<axum::http::Request<axum::body::Body>>::into_make_service_with_connect_info::<SocketAddr>(odata_app);
1422    axum::serve(listener, make_svc).await?;
1423    Ok(())
1424}
1425
1426/// Serve router with TLS/HTTPS support using axum-server
1427///
1428/// This function implements native TLS serving using axum-server and tokio-rustls.
1429/// It supports standard TLS and mutual TLS (mTLS) based on the configuration.
1430async fn serve_with_tls(
1431    addr: std::net::SocketAddr,
1432    app: Router,
1433    tls_config: &mockforge_core::config::HttpTlsConfig,
1434) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1435    use axum_server::tls_rustls::RustlsConfig;
1436    use std::net::SocketAddr;
1437
1438    // Initialize the rustls crypto provider (must be called before TLS operations)
1439    tls::init_crypto_provider();
1440
1441    info!("Loading TLS configuration for HTTPS server");
1442
1443    // Load TLS server configuration (supports mTLS)
1444    let server_config = tls::load_tls_server_config(tls_config)?;
1445
1446    // Create RustlsConfig from the ServerConfig
1447    // Note: axum-server's RustlsConfig can be created from a ServerConfig
1448    let rustls_config = RustlsConfig::from_config(server_config);
1449
1450    info!("Starting HTTPS server on {}", addr);
1451
1452    // Wrap the Router with OData URI rewrite (same as the non-TLS path).
1453    // Router::layer() only applies to matched routes, so we must wrap at the service level
1454    // to rewrite URIs BEFORE route matching occurs.
1455    let odata_app = tower::ServiceBuilder::new()
1456        .layer(mockforge_core::odata_rewrite::ODataRewriteLayer)
1457        .service(app);
1458    let make_svc = axum::ServiceExt::<axum::http::Request<axum::body::Body>>::into_make_service_with_connect_info::<SocketAddr>(odata_app);
1459
1460    // Serve with TLS using axum-server
1461    axum_server::bind_rustls(addr, rustls_config)
1462        .serve(make_svc)
1463        .await
1464        .map_err(|e| format!("HTTPS server error: {}", e).into())
1465}
1466
1467/// Backwards-compatible start that builds + serves the base router.
1468pub async fn start(
1469    port: u16,
1470    spec_path: Option<String>,
1471    options: Option<ValidationOptions>,
1472) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1473    start_with_latency(port, spec_path, options, None).await
1474}
1475
1476/// Start HTTP server with authentication and latency support
1477pub async fn start_with_auth_and_latency(
1478    port: u16,
1479    spec_path: Option<String>,
1480    options: Option<ValidationOptions>,
1481    auth_config: Option<mockforge_core::config::AuthConfig>,
1482    latency_profile: Option<LatencyProfile>,
1483) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1484    start_with_auth_and_injectors(port, spec_path, options, auth_config, latency_profile, None)
1485        .await
1486}
1487
1488/// Start HTTP server with authentication and injectors support
1489pub async fn start_with_auth_and_injectors(
1490    port: u16,
1491    spec_path: Option<String>,
1492    options: Option<ValidationOptions>,
1493    auth_config: Option<mockforge_core::config::AuthConfig>,
1494    _latency_profile: Option<LatencyProfile>,
1495    _failure_injector: Option<FailureInjector>,
1496) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1497    // For now, ignore latency and failure injectors and just use auth
1498    let app = build_router_with_auth(spec_path, options, auth_config).await;
1499    serve_router(port, app).await
1500}
1501
1502/// Start HTTP server with latency injection support
1503pub async fn start_with_latency(
1504    port: u16,
1505    spec_path: Option<String>,
1506    options: Option<ValidationOptions>,
1507    latency_profile: Option<LatencyProfile>,
1508) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1509    let latency_injector =
1510        latency_profile.map(|profile| LatencyInjector::new(profile, Default::default()));
1511
1512    let app = build_router_with_latency(spec_path, options, latency_injector).await;
1513    serve_router(port, app).await
1514}
1515
1516/// Build the base HTTP router with chaining support
1517pub async fn build_router_with_chains(
1518    spec_path: Option<String>,
1519    options: Option<ValidationOptions>,
1520    circling_config: Option<mockforge_core::request_chaining::ChainConfig>,
1521) -> Router {
1522    build_router_with_chains_and_multi_tenant(
1523        spec_path,
1524        options,
1525        circling_config,
1526        None,
1527        None,
1528        None,
1529        None,
1530        None,
1531        None,
1532        None,
1533        false,
1534        None, // health_manager
1535        None, // mockai
1536        None, // deceptive_deploy_config
1537        None, // proxy_config
1538    )
1539    .await
1540}
1541
1542// Template expansion is now handled by mockforge_core::template_expansion
1543// which is explicitly isolated from the templating module to avoid Send issues
1544
1545/// Helper function to apply route chaos injection (fault injection and latency)
1546/// This is extracted to avoid capturing RouteChaosInjector in the closure, which causes Send issues
1547/// Uses mockforge-route-chaos crate which is isolated from mockforge-core to avoid Send issues
1548/// Uses the RouteChaosInjectorTrait from mockforge-core to avoid circular dependency
1549async fn apply_route_chaos(
1550    injector: Option<&dyn mockforge_core::priority_handler::RouteChaosInjectorTrait>,
1551    method: &http::Method,
1552    uri: &http::Uri,
1553) -> Option<axum::response::Response> {
1554    use axum::http::StatusCode;
1555    use axum::response::IntoResponse;
1556
1557    if let Some(injector) = injector {
1558        // Check for fault injection first
1559        if let Some(fault_response) = injector.get_fault_response(method, uri) {
1560            // Return fault response
1561            let mut response = Json(serde_json::json!({
1562                "error": fault_response.error_message,
1563                "fault_type": fault_response.fault_type,
1564            }))
1565            .into_response();
1566            *response.status_mut() = StatusCode::from_u16(fault_response.status_code)
1567                .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
1568            return Some(response);
1569        }
1570
1571        // Inject latency if configured (this is async and may delay the request)
1572        if let Err(e) = injector.inject_latency(method, uri).await {
1573            tracing::warn!("Failed to inject latency: {}", e);
1574        }
1575    }
1576
1577    None // No fault response, processing should continue
1578}
1579
1580/// Build the base HTTP router with chaining and multi-tenant support
1581#[allow(clippy::too_many_arguments)]
1582#[allow(deprecated)] // uses core engines (DriftBudgetEngine, ThreatAnalyzer, Forecaster, ProtocolContractRegistry, MockAI, MultiTenantWorkspaceRegistry, etc.) that stay in core
1583pub async fn build_router_with_chains_and_multi_tenant(
1584    spec_path: Option<String>,
1585    options: Option<ValidationOptions>,
1586    _circling_config: Option<mockforge_core::request_chaining::ChainConfig>,
1587    multi_tenant_config: Option<mockforge_foundation::multi_tenant_types::MultiTenantConfig>,
1588    route_configs: Option<Vec<mockforge_core::config::RouteConfig>>,
1589    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
1590    _ai_generator: Option<Arc<dyn mockforge_openapi::response::AiGenerator + Send + Sync>>,
1591    smtp_registry: Option<Arc<dyn std::any::Any + Send + Sync>>,
1592    mqtt_broker: Option<Arc<dyn std::any::Any + Send + Sync>>,
1593    traffic_shaper: Option<mockforge_core::traffic_shaping::TrafficShaper>,
1594    traffic_shaping_enabled: bool,
1595    health_manager: Option<Arc<HealthManager>>,
1596    _mockai: Option<Arc<RwLock<mockforge_core::intelligent_behavior::MockAI>>>,
1597    deceptive_deploy_config: Option<mockforge_core::config::DeceptiveDeployConfig>,
1598    proxy_config: Option<mockforge_proxy::config::ProxyConfig>,
1599) -> Router {
1600    use crate::latency_profiles::LatencyProfiles;
1601    use crate::op_middleware::Shared;
1602    use mockforge_core::Overrides;
1603
1604    // Extract template expansion setting before options is moved (used in OpenAPI routes and custom routes)
1605    let template_expand =
1606        options.as_ref().map(|o| o.response_template_expand).unwrap_or_else(|| {
1607            std::env::var("MOCKFORGE_RESPONSE_TEMPLATE_EXPAND")
1608                .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
1609                .unwrap_or(false)
1610        });
1611
1612    let _shared = Shared {
1613        profiles: LatencyProfiles::default(),
1614        overrides: Overrides::default(),
1615        failure_injector: None,
1616        traffic_shaper,
1617        overrides_enabled: false,
1618        traffic_shaping_enabled,
1619    };
1620
1621    // Start with basic router
1622    let mut app = Router::new();
1623    let mut include_default_health = true;
1624    let mut captured_routes: Vec<RouteInfo> = Vec::new();
1625
1626    // If an OpenAPI spec is provided, integrate it
1627    if let Some(ref spec) = spec_path {
1628        match OpenApiSpec::from_file(&spec).await {
1629            Ok(openapi) => {
1630                info!("Loaded OpenAPI spec from {}", spec);
1631
1632                // Try to load persona from config if available
1633                let persona = load_persona_from_config().await;
1634
1635                let mut registry = if let Some(opts) = options {
1636                    tracing::debug!("Using custom validation options");
1637                    if let Some(ref persona) = persona {
1638                        tracing::info!("Using persona '{}' for route generation", persona.name);
1639                    }
1640                    OpenApiRouteRegistry::new_with_options_and_persona(openapi, opts, persona)
1641                } else {
1642                    tracing::debug!("Using environment-based options");
1643                    if let Some(ref persona) = persona {
1644                        tracing::info!("Using persona '{}' for route generation", persona.name);
1645                    }
1646                    OpenApiRouteRegistry::new_with_env_and_persona(openapi, persona)
1647                };
1648
1649                // Load custom fixtures if enabled
1650                let fixtures_dir = std::env::var("MOCKFORGE_FIXTURES_DIR")
1651                    .unwrap_or_else(|_| "/app/fixtures".to_string());
1652                let custom_fixtures_enabled = std::env::var("MOCKFORGE_CUSTOM_FIXTURES_ENABLED")
1653                    .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
1654                    .unwrap_or(true); // Enabled by default
1655
1656                if custom_fixtures_enabled {
1657                    use mockforge_openapi::CustomFixtureLoader;
1658                    use std::path::PathBuf;
1659                    use std::sync::Arc;
1660
1661                    let fixtures_path = PathBuf::from(&fixtures_dir);
1662                    let mut custom_loader = CustomFixtureLoader::new(fixtures_path, true);
1663
1664                    if let Err(e) = custom_loader.load_fixtures().await {
1665                        tracing::warn!("Failed to load custom fixtures: {}", e);
1666                    } else {
1667                        tracing::info!("Custom fixtures loaded from {}", fixtures_dir);
1668                        registry = registry.with_custom_fixture_loader(Arc::new(custom_loader));
1669                    }
1670                }
1671
1672                if registry
1673                    .routes()
1674                    .iter()
1675                    .any(|route| route.method == "GET" && route.path == "/health")
1676                {
1677                    include_default_health = false;
1678                }
1679                // Capture route info for the /__mockforge/routes endpoint
1680                captured_routes = registry
1681                    .routes()
1682                    .iter()
1683                    .map(|r| RouteInfo {
1684                        method: r.method.clone(),
1685                        path: r.path.clone(),
1686                        operation_id: r.operation.operation_id.clone(),
1687                        summary: r.operation.summary.clone(),
1688                        description: r.operation.description.clone(),
1689                        parameters: r.parameters.clone(),
1690                    })
1691                    .collect();
1692
1693                // Store routes in the global route store so the admin server
1694                // can serve them without proxying back to the HTTP server.
1695                {
1696                    let global_routes: Vec<mockforge_core::request_logger::GlobalRouteInfo> =
1697                        captured_routes
1698                            .iter()
1699                            .map(|r| mockforge_core::request_logger::GlobalRouteInfo {
1700                                method: r.method.clone(),
1701                                path: r.path.clone(),
1702                                operation_id: r.operation_id.clone(),
1703                                summary: r.summary.clone(),
1704                                description: r.description.clone(),
1705                                parameters: r.parameters.clone(),
1706                            })
1707                            .collect();
1708                    mockforge_core::request_logger::set_global_routes(global_routes);
1709                    tracing::info!("Stored {} routes in global route store", captured_routes.len());
1710                }
1711
1712                // Use MockAI if available, otherwise use standard router
1713                let spec_router = if let Some(ref mockai_instance) = _mockai {
1714                    tracing::debug!("Building router with MockAI support");
1715                    registry.build_router_with_mockai(Some(mockai_instance.clone()))
1716                } else {
1717                    registry.build_router()
1718                };
1719                app = app.merge(spec_router);
1720            }
1721            Err(e) => {
1722                warn!("Failed to load OpenAPI spec from {:?}: {}. Starting without OpenAPI integration.", spec_path, e);
1723            }
1724        }
1725    }
1726
1727    // Register custom routes from config with advanced routing features
1728    // Create RouteChaosInjector for advanced fault injection and latency
1729    // Store as trait object to avoid circular dependency (RouteChaosInjectorTrait is in mockforge-core)
1730    let route_chaos_injector: Option<
1731        std::sync::Arc<dyn mockforge_core::priority_handler::RouteChaosInjectorTrait>,
1732    > = if let Some(ref route_configs) = route_configs {
1733        if !route_configs.is_empty() {
1734            // Convert to the type expected by RouteChaosInjector
1735            // Note: Both use the same mockforge-core, but we need to ensure type compatibility
1736            let route_configs_converted: Vec<mockforge_core::config::RouteConfig> =
1737                route_configs.to_vec();
1738            match mockforge_route_chaos::RouteChaosInjector::new(route_configs_converted) {
1739                Ok(injector) => {
1740                    info!(
1741                        "Initialized advanced routing features for {} route(s)",
1742                        route_configs.len()
1743                    );
1744                    // RouteChaosInjector implements RouteChaosInjectorTrait, so we can cast it
1745                    // The trait is implemented in mockforge-route-chaos/src/lib.rs
1746                    Some(std::sync::Arc::new(injector)
1747                        as std::sync::Arc<
1748                            dyn mockforge_core::priority_handler::RouteChaosInjectorTrait,
1749                        >)
1750                }
1751                Err(e) => {
1752                    warn!(
1753                        "Failed to initialize advanced routing features: {}. Using basic routing.",
1754                        e
1755                    );
1756                    None
1757                }
1758            }
1759        } else {
1760            None
1761        }
1762    } else {
1763        None
1764    };
1765
1766    if let Some(route_configs) = route_configs {
1767        use axum::http::StatusCode;
1768        use axum::response::IntoResponse;
1769
1770        if !route_configs.is_empty() {
1771            info!("Registering {} custom route(s) from config", route_configs.len());
1772        }
1773
1774        let injector = route_chaos_injector.clone();
1775        for route_config in route_configs {
1776            let status = route_config.response.status;
1777            let body = route_config.response.body.clone();
1778            let headers = route_config.response.headers.clone();
1779            let path = route_config.path.clone();
1780            let method = route_config.method.clone();
1781
1782            // Create handler that returns the configured response with template expansion
1783            // Supports both basic templates ({{uuid}}, {{now}}) and request-aware templates
1784            // ({{request.query.name}}, {{request.path.id}}, {{request.headers.name}})
1785            // Register route using `any()` since we need full Request access for template expansion
1786            let expected_method = method.to_uppercase();
1787            // Clone Arc for the closure - Arc is Send-safe
1788            // Note: RouteChaosInjector is marked as Send+Sync via unsafe impl, but the compiler
1789            // is conservative because rng() is available in the rand crate, even though we use thread_rng()
1790            let injector_clone = injector.clone();
1791            app = app.route(
1792                &path,
1793                #[allow(clippy::non_send_fields_in_send_ty)]
1794                axum::routing::any(move |req: http::Request<axum::body::Body>| {
1795                    let body = body.clone();
1796                    let headers = headers.clone();
1797                    let expand = template_expand;
1798                    let expected = expected_method.clone();
1799                    let status_code = status;
1800                    // Clone Arc again for the async block
1801                    let injector_for_chaos = injector_clone.clone();
1802
1803                    async move {
1804                        // Check if request method matches expected method
1805                        if req.method().as_str() != expected.as_str() {
1806                            // Return 405 Method Not Allowed for wrong method
1807                            return axum::response::Response::builder()
1808                                .status(StatusCode::METHOD_NOT_ALLOWED)
1809                                .header("Allow", &expected)
1810                                .body(axum::body::Body::empty())
1811                                .unwrap()
1812                                .into_response();
1813                        }
1814
1815                        // Apply advanced routing features (fault injection and latency) if available
1816                        // Use helper function to avoid capturing RouteChaosInjector in closure
1817                        // Pass the Arc as a reference to the helper function
1818                        if let Some(fault_response) = apply_route_chaos(
1819                            injector_for_chaos.as_deref(),
1820                            req.method(),
1821                            req.uri(),
1822                        )
1823                        .await
1824                        {
1825                            return fault_response;
1826                        }
1827
1828                        // Create JSON response from body, or empty object if None
1829                        let mut body_value = body.unwrap_or(serde_json::json!({}));
1830
1831                        // Apply template expansion if enabled
1832                        // Use mockforge-template-expansion crate which is completely isolated
1833                        // from mockforge-core to avoid Send issues (no rng() in dependency chain)
1834                        if expand {
1835                            use mockforge_template_expansion::RequestContext;
1836                            use serde_json::Value;
1837                            use std::collections::HashMap;
1838
1839                            // Extract request data for template expansion
1840                            let method = req.method().to_string();
1841                            let path = req.uri().path().to_string();
1842
1843                            // Extract query parameters
1844                            let query_params: HashMap<String, Value> = req
1845                                .uri()
1846                                .query()
1847                                .map(|q| {
1848                                    url::form_urlencoded::parse(q.as_bytes())
1849                                        .into_owned()
1850                                        .map(|(k, v)| (k, Value::String(v)))
1851                                        .collect()
1852                                })
1853                                .unwrap_or_default();
1854
1855                            // Extract headers
1856                            let headers: HashMap<String, Value> = req
1857                                .headers()
1858                                .iter()
1859                                .map(|(k, v)| {
1860                                    (
1861                                        k.to_string(),
1862                                        Value::String(v.to_str().unwrap_or_default().to_string()),
1863                                    )
1864                                })
1865                                .collect();
1866
1867                            // Create RequestContext for expand_prompt_template
1868                            // Using RequestContext from mockforge-template-expansion (not mockforge-core)
1869                            // to avoid bringing rng() into scope
1870                            let context = RequestContext {
1871                                method,
1872                                path,
1873                                query_params,
1874                                headers,
1875                                body: None, // Body extraction would require reading the request stream
1876                                path_params: HashMap::new(),
1877                                multipart_fields: HashMap::new(),
1878                                multipart_files: HashMap::new(),
1879                            };
1880
1881                            // Perform template expansion in spawn_blocking to ensure Send safety
1882                            // The template expansion crate is completely isolated from mockforge-core
1883                            // and doesn't have rng() in its dependency chain
1884                            let body_value_clone = body_value.clone();
1885                            let context_clone = context.clone();
1886                            body_value = match tokio::task::spawn_blocking(move || {
1887                                mockforge_template_expansion::expand_templates_in_json(
1888                                    body_value_clone,
1889                                    &context_clone,
1890                                )
1891                            })
1892                            .await
1893                            {
1894                                Ok(result) => result,
1895                                Err(_) => body_value, // Fallback to original on error
1896                            };
1897                        }
1898
1899                        let mut response = Json(body_value).into_response();
1900
1901                        // Set status code
1902                        *response.status_mut() =
1903                            StatusCode::from_u16(status_code).unwrap_or(StatusCode::OK);
1904
1905                        // Add custom headers
1906                        for (key, value) in headers {
1907                            if let Ok(header_name) = http::HeaderName::from_bytes(key.as_bytes()) {
1908                                if let Ok(header_value) = http::HeaderValue::from_str(&value) {
1909                                    response.headers_mut().insert(header_name, header_value);
1910                                }
1911                            }
1912                        }
1913
1914                        response
1915                    }
1916                }),
1917            );
1918
1919            debug!("Registered route: {} {}", method, path);
1920        }
1921    }
1922
1923    // Add health check endpoints
1924    if let Some(health) = health_manager {
1925        // Use comprehensive health check router with all probe endpoints
1926        app = app.merge(health::health_router(health));
1927        info!(
1928            "Health check endpoints enabled: /health, /health/live, /health/ready, /health/startup"
1929        );
1930    } else if include_default_health {
1931        // Fallback to basic health endpoint for backwards compatibility
1932        app = app.route(
1933            "/health",
1934            axum::routing::get(|| async {
1935                use mockforge_core::server_utils::health::HealthStatus;
1936                {
1937                    // HealthStatus should always serialize, but handle errors gracefully
1938                    match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
1939                        Ok(value) => Json(value),
1940                        Err(e) => {
1941                            // Log error but return a simple healthy response
1942                            tracing::error!("Failed to serialize health status: {}", e);
1943                            Json(serde_json::json!({
1944                                "status": "healthy",
1945                                "service": "mockforge-http",
1946                                "uptime_seconds": 0
1947                            }))
1948                        }
1949                    }
1950                }
1951            }),
1952        );
1953    }
1954
1955    app = app.merge(sse::sse_router());
1956    // Add file serving endpoints for generated mock files
1957    app = app.merge(file_server::file_serving_router());
1958
1959    // Add management API endpoints
1960    // Load spec for ManagementState so /__mockforge/api/spec can serve it
1961    let mgmt_spec = if let Some(ref sp) = spec_path {
1962        match OpenApiSpec::from_file(sp).await {
1963            Ok(s) => Some(Arc::new(s)),
1964            Err(e) => {
1965                debug!("Failed to load OpenAPI spec for management API: {}", e);
1966                None
1967            }
1968        }
1969    } else {
1970        None
1971    };
1972    let spec_path_clone = spec_path.clone();
1973    let mgmt_port = std::env::var("PORT")
1974        .or_else(|_| std::env::var("MOCKFORGE_HTTP_PORT"))
1975        .ok()
1976        .and_then(|p| p.parse().ok())
1977        .unwrap_or(3000);
1978    let management_state = ManagementState::new(mgmt_spec, spec_path_clone, mgmt_port);
1979
1980    // Create WebSocket state and connect it to management state
1981    use std::sync::Arc;
1982    let ws_state = WsManagementState::new();
1983    let ws_broadcast = Arc::new(ws_state.tx.clone());
1984    let management_state = management_state.with_ws_broadcast(ws_broadcast);
1985
1986    // Add proxy config to management state if available
1987    let management_state = if let Some(proxy_cfg) = proxy_config {
1988        use tokio::sync::RwLock;
1989        let proxy_config_arc = Arc::new(RwLock::new(proxy_cfg));
1990        management_state.with_proxy_config(proxy_config_arc)
1991    } else {
1992        management_state
1993    };
1994
1995    #[cfg(feature = "smtp")]
1996    let management_state = {
1997        if let Some(smtp_reg) = smtp_registry {
1998            match smtp_reg.downcast::<mockforge_smtp::SmtpSpecRegistry>() {
1999                Ok(smtp_reg) => management_state.with_smtp_registry(smtp_reg),
2000                Err(e) => {
2001                    error!(
2002                        "Invalid SMTP registry type passed to HTTP management state: {:?}",
2003                        e.type_id()
2004                    );
2005                    management_state
2006                }
2007            }
2008        } else {
2009            management_state
2010        }
2011    };
2012    #[cfg(not(feature = "smtp"))]
2013    let management_state = {
2014        let _ = smtp_registry;
2015        management_state
2016    };
2017    #[cfg(feature = "mqtt")]
2018    let management_state = {
2019        if let Some(broker) = mqtt_broker {
2020            match broker.downcast::<mockforge_mqtt::MqttBroker>() {
2021                Ok(broker) => management_state.with_mqtt_broker(broker),
2022                Err(e) => {
2023                    error!(
2024                        "Invalid MQTT broker passed to HTTP management state: {:?}",
2025                        e.type_id()
2026                    );
2027                    management_state
2028                }
2029            }
2030        } else {
2031            management_state
2032        }
2033    };
2034    #[cfg(not(feature = "mqtt"))]
2035    let management_state = {
2036        let _ = mqtt_broker;
2037        management_state
2038    };
2039    let management_state_for_fallback = management_state.clone();
2040    app = app.nest("/__mockforge/api", management_router(management_state));
2041    // Dynamic-mock fallback; see identical block earlier in this file.
2042    app = app.fallback_service(
2043        axum::routing::any(crate::management::dynamic_mock_fallback)
2044            .with_state(management_state_for_fallback),
2045    );
2046
2047    // Add verification API endpoint
2048    app = app.merge(verification_router());
2049
2050    // Add OIDC well-known endpoints
2051    use crate::auth::oidc::oidc_router;
2052    app = app.merge(oidc_router());
2053
2054    // Add access review API if enabled
2055    {
2056        use mockforge_core::security::get_global_access_review_service;
2057        if let Some(service) = get_global_access_review_service().await {
2058            use crate::handlers::access_review::{access_review_router, AccessReviewState};
2059            let review_state = AccessReviewState { service };
2060            app = app.nest("/api/v1/security/access-reviews", access_review_router(review_state));
2061            debug!("Access review API mounted at /api/v1/security/access-reviews");
2062        }
2063    }
2064
2065    // Add privileged access API if enabled
2066    {
2067        use mockforge_core::security::get_global_privileged_access_manager;
2068        if let Some(manager) = get_global_privileged_access_manager().await {
2069            use crate::handlers::privileged_access::{
2070                privileged_access_router, PrivilegedAccessState,
2071            };
2072            let privileged_state = PrivilegedAccessState { manager };
2073            app = app.nest(
2074                "/api/v1/security/privileged-access",
2075                privileged_access_router(privileged_state),
2076            );
2077            debug!("Privileged access API mounted at /api/v1/security/privileged-access");
2078        }
2079    }
2080
2081    // Add change management API if enabled
2082    {
2083        use mockforge_core::security::get_global_change_management_engine;
2084        if let Some(engine) = get_global_change_management_engine().await {
2085            use crate::handlers::change_management::{
2086                change_management_router, ChangeManagementState,
2087            };
2088            let change_state = ChangeManagementState { engine };
2089            app = app.nest("/api/v1/change-management", change_management_router(change_state));
2090            debug!("Change management API mounted at /api/v1/change-management");
2091        }
2092    }
2093
2094    // Add risk assessment API if enabled
2095    {
2096        use mockforge_core::security::get_global_risk_assessment_engine;
2097        if let Some(engine) = get_global_risk_assessment_engine().await {
2098            use crate::handlers::risk_assessment::{risk_assessment_router, RiskAssessmentState};
2099            let risk_state = RiskAssessmentState { engine };
2100            app = app.nest("/api/v1/security", risk_assessment_router(risk_state));
2101            debug!("Risk assessment API mounted at /api/v1/security/risks");
2102        }
2103    }
2104
2105    // Add token lifecycle API
2106    {
2107        use crate::auth::token_lifecycle::TokenLifecycleManager;
2108        use crate::handlers::token_lifecycle::{token_lifecycle_router, TokenLifecycleState};
2109        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
2110        let lifecycle_state = TokenLifecycleState {
2111            manager: lifecycle_manager,
2112        };
2113        app = app.nest("/api/v1/auth", token_lifecycle_router(lifecycle_state));
2114        debug!("Token lifecycle API mounted at /api/v1/auth");
2115    }
2116
2117    // Add OAuth2 server endpoints
2118    {
2119        use crate::auth::oidc::load_oidc_state;
2120        use crate::auth::token_lifecycle::TokenLifecycleManager;
2121        use crate::handlers::oauth2_server::{oauth2_server_router, OAuth2ServerState};
2122        // Load OIDC state from configuration (environment variables or config file)
2123        let oidc_state = Arc::new(RwLock::new(load_oidc_state()));
2124        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
2125        let oauth2_state = OAuth2ServerState {
2126            oidc_state,
2127            lifecycle_manager,
2128            auth_codes: Arc::new(RwLock::new(HashMap::new())),
2129            refresh_tokens: Arc::new(RwLock::new(HashMap::new())),
2130        };
2131        app = app.merge(oauth2_server_router(oauth2_state));
2132        debug!("OAuth2 server endpoints mounted at /oauth2/authorize and /oauth2/token");
2133    }
2134
2135    // Add consent screen endpoints
2136    {
2137        use crate::auth::oidc::load_oidc_state;
2138        use crate::auth::risk_engine::RiskEngine;
2139        use crate::auth::token_lifecycle::TokenLifecycleManager;
2140        use crate::handlers::consent::{consent_router, ConsentState};
2141        use crate::handlers::oauth2_server::OAuth2ServerState;
2142        // Load OIDC state from configuration (environment variables or config file)
2143        let oidc_state = Arc::new(RwLock::new(load_oidc_state()));
2144        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
2145        let oauth2_state = OAuth2ServerState {
2146            oidc_state: oidc_state.clone(),
2147            lifecycle_manager: lifecycle_manager.clone(),
2148            auth_codes: Arc::new(RwLock::new(HashMap::new())),
2149            refresh_tokens: Arc::new(RwLock::new(HashMap::new())),
2150        };
2151        let risk_engine = Arc::new(RiskEngine::default());
2152        let consent_state = ConsentState {
2153            oauth2_state,
2154            risk_engine,
2155        };
2156        app = app.merge(consent_router(consent_state));
2157        debug!("Consent screen endpoints mounted at /consent");
2158    }
2159
2160    // Add risk simulation API
2161    {
2162        use crate::auth::risk_engine::RiskEngine;
2163        use crate::handlers::risk_simulation::{risk_simulation_router, RiskSimulationState};
2164        let risk_engine = Arc::new(RiskEngine::default());
2165        let risk_state = RiskSimulationState { risk_engine };
2166        app = app.nest("/api/v1/auth", risk_simulation_router(risk_state));
2167        debug!("Risk simulation API mounted at /api/v1/auth/risk");
2168    }
2169
2170    // Initialize database connection (optional)
2171    let database = {
2172        use crate::database::Database;
2173        let database_url = std::env::var("DATABASE_URL").ok();
2174        match Database::connect_optional(database_url.as_deref()).await {
2175            Ok(db) => {
2176                if db.is_connected() {
2177                    // Run migrations if database is connected
2178                    if let Err(e) = db.migrate_if_connected().await {
2179                        warn!("Failed to run database migrations: {}", e);
2180                    } else {
2181                        info!("Database connected and migrations applied");
2182                    }
2183                }
2184                Some(db)
2185            }
2186            Err(e) => {
2187                warn!("Failed to connect to database: {}. Continuing without database support.", e);
2188                None
2189            }
2190        }
2191    };
2192
2193    // Add drift budget and incident management endpoints
2194    // Initialize shared components for drift tracking and protocol contracts
2195    let (drift_engine, incident_manager, drift_config) = {
2196        use mockforge_core::contract_drift::{DriftBudgetConfig, DriftBudgetEngine};
2197        use mockforge_core::incidents::{IncidentManager, IncidentStore};
2198        use std::sync::Arc;
2199
2200        // Initialize drift budget engine with default config
2201        let drift_config = DriftBudgetConfig::default();
2202        let drift_engine = Arc::new(DriftBudgetEngine::new(drift_config.clone()));
2203
2204        // Initialize incident store and manager
2205        let incident_store = Arc::new(IncidentStore::default());
2206        let incident_manager = Arc::new(IncidentManager::new(incident_store.clone()));
2207
2208        (drift_engine, incident_manager, drift_config)
2209    };
2210
2211    {
2212        use crate::handlers::drift_budget::{drift_budget_router, DriftBudgetState};
2213        use crate::middleware::drift_tracking::DriftTrackingState;
2214        use mockforge_contracts::consumer_contracts::{
2215            ConsumerBreakingChangeDetector, UsageRecorder,
2216        };
2217        use mockforge_core::ai_contract_diff::ContractDiffAnalyzer;
2218        use std::sync::Arc;
2219
2220        // Initialize usage recorder and consumer detector
2221        let usage_recorder = Arc::new(UsageRecorder::default());
2222        let consumer_detector =
2223            Arc::new(ConsumerBreakingChangeDetector::new(usage_recorder.clone()));
2224
2225        // Initialize contract diff analyzer if enabled
2226        let diff_analyzer = if drift_config.enabled {
2227            match ContractDiffAnalyzer::new(
2228                mockforge_core::ai_contract_diff::ContractDiffConfig::default(),
2229            ) {
2230                Ok(analyzer) => Some(Arc::new(analyzer)),
2231                Err(e) => {
2232                    warn!("Failed to create contract diff analyzer: {}", e);
2233                    None
2234                }
2235            }
2236        } else {
2237            None
2238        };
2239
2240        // Get OpenAPI spec if available
2241        // Note: Load from spec_path if available, or leave as None for manual configuration.
2242        let spec = if let Some(ref spec_path) = spec_path {
2243            match OpenApiSpec::from_file(spec_path).await {
2244                Ok(s) => Some(Arc::new(s)),
2245                Err(e) => {
2246                    debug!("Failed to load OpenAPI spec for drift tracking: {}", e);
2247                    None
2248                }
2249            }
2250        } else {
2251            None
2252        };
2253
2254        // Create drift tracking state
2255        let drift_tracking_state = DriftTrackingState {
2256            diff_analyzer,
2257            spec,
2258            drift_engine: drift_engine.clone(),
2259            incident_manager: incident_manager.clone(),
2260            usage_recorder,
2261            consumer_detector,
2262            enabled: drift_config.enabled,
2263        };
2264
2265        // Add response body buffering middleware (before drift tracking)
2266        app = app.layer(axum::middleware::from_fn(middleware::buffer_response_middleware));
2267
2268        // Add drift tracking middleware (after response buffering)
2269        // Use a wrapper that inserts state into extensions before calling the middleware
2270        let drift_tracking_state_clone = drift_tracking_state.clone();
2271        app = app.layer(axum::middleware::from_fn(
2272            move |mut req: axum::extract::Request, next: axum::middleware::Next| {
2273                let state = drift_tracking_state_clone.clone();
2274                async move {
2275                    // Insert state into extensions if not already present
2276                    if req.extensions().get::<DriftTrackingState>().is_none() {
2277                        req.extensions_mut().insert(state);
2278                    }
2279                    // Call the middleware function
2280                    middleware::drift_tracking::drift_tracking_middleware_with_extensions(req, next)
2281                        .await
2282                }
2283            },
2284        ));
2285
2286        let drift_state = DriftBudgetState {
2287            engine: drift_engine.clone(),
2288            incident_manager: incident_manager.clone(),
2289            gitops_handler: None, // Can be initialized later if GitOps is configured
2290        };
2291
2292        app = app.merge(drift_budget_router(drift_state));
2293        debug!("Drift budget and incident management endpoints mounted at /api/v1/drift");
2294    }
2295
2296    // Add pipeline management endpoints (MockOps)
2297    #[cfg(feature = "pipelines")]
2298    {
2299        use crate::handlers::pipelines::{pipeline_router, PipelineState};
2300
2301        let pipeline_state = PipelineState::new();
2302        app = app.merge(pipeline_router(pipeline_state));
2303        debug!("Pipeline management endpoints mounted at /api/v1/pipelines");
2304    }
2305
2306    // Add governance endpoints (forecasting, semantic drift, threat modeling, contract health)
2307    {
2308        use crate::handlers::contract_health::{contract_health_router, ContractHealthState};
2309        use crate::handlers::forecasting::{forecasting_router, ForecastingState};
2310        use crate::handlers::semantic_drift::{semantic_drift_router, SemanticDriftState};
2311        use crate::handlers::threat_modeling::{threat_modeling_router, ThreatModelingState};
2312        use mockforge_contracts::contract_drift::forecasting::{Forecaster, ForecastingConfig};
2313        use mockforge_core::contract_drift::threat_modeling::ThreatAnalyzer;
2314        use mockforge_core::incidents::semantic_manager::SemanticIncidentManager;
2315        use mockforge_foundation::threat_modeling_types::ThreatModelingConfig;
2316        use std::sync::Arc;
2317
2318        // Initialize forecasting
2319        let forecasting_config = ForecastingConfig::default();
2320        let forecaster = Arc::new(Forecaster::new(forecasting_config));
2321        let forecasting_state = ForecastingState {
2322            forecaster,
2323            database: database.clone(),
2324        };
2325
2326        // Initialize semantic drift manager
2327        let semantic_manager = Arc::new(SemanticIncidentManager::new());
2328        let semantic_state = SemanticDriftState {
2329            manager: semantic_manager,
2330            database: database.clone(),
2331        };
2332
2333        // Initialize threat analyzer
2334        let threat_config = ThreatModelingConfig::default();
2335        let threat_analyzer = match ThreatAnalyzer::new(threat_config) {
2336            Ok(analyzer) => Arc::new(analyzer),
2337            Err(e) => {
2338                warn!("Failed to create threat analyzer: {}. Using default.", e);
2339                Arc::new(ThreatAnalyzer::new(ThreatModelingConfig::default()).unwrap_or_else(
2340                    |_| {
2341                        // Fallback to a minimal config if default also fails
2342                        ThreatAnalyzer::new(ThreatModelingConfig {
2343                            enabled: false,
2344                            ..Default::default()
2345                        })
2346                        .expect("Failed to create fallback threat analyzer")
2347                    },
2348                ))
2349            }
2350        };
2351        // Load webhook configs from ServerConfig
2352        let mut webhook_configs = Vec::new();
2353        let config_paths = [
2354            "config.yaml",
2355            "mockforge.yaml",
2356            "tools/mockforge/config.yaml",
2357            "../tools/mockforge/config.yaml",
2358        ];
2359
2360        for path in &config_paths {
2361            if let Ok(config) = mockforge_core::config::load_config(path).await {
2362                if !config.incidents.webhooks.is_empty() {
2363                    webhook_configs = config.incidents.webhooks.clone();
2364                    info!("Loaded {} webhook configs from config: {}", webhook_configs.len(), path);
2365                    break;
2366                }
2367            }
2368        }
2369
2370        if webhook_configs.is_empty() {
2371            debug!("No webhook configs found in config files, using empty list");
2372        }
2373
2374        let threat_state = ThreatModelingState {
2375            analyzer: threat_analyzer,
2376            webhook_configs,
2377            database: database.clone(),
2378        };
2379
2380        // Initialize contract health state
2381        let contract_health_state = ContractHealthState {
2382            incident_manager: incident_manager.clone(),
2383            semantic_manager: Arc::new(SemanticIncidentManager::new()),
2384            database: database.clone(),
2385        };
2386
2387        // Register routers
2388        app = app.merge(forecasting_router(forecasting_state));
2389        debug!("Forecasting endpoints mounted at /api/v1/forecasts");
2390
2391        app = app.merge(semantic_drift_router(semantic_state));
2392        debug!("Semantic drift endpoints mounted at /api/v1/semantic-drift");
2393
2394        app = app.merge(threat_modeling_router(threat_state));
2395        debug!("Threat modeling endpoints mounted at /api/v1/threats");
2396
2397        app = app.merge(contract_health_router(contract_health_state));
2398        debug!("Contract health endpoints mounted at /api/v1/contract-health");
2399    }
2400
2401    // Add protocol contracts endpoints with fitness registry initialization
2402    {
2403        use crate::handlers::protocol_contracts::{
2404            protocol_contracts_router, ProtocolContractState,
2405        };
2406        use mockforge_core::contract_drift::{
2407            ConsumerImpactAnalyzer, FitnessFunctionRegistry, ProtocolContractRegistry,
2408        };
2409        use std::sync::Arc;
2410        use tokio::sync::RwLock;
2411
2412        // Initialize protocol contract registry
2413        let contract_registry = Arc::new(RwLock::new(ProtocolContractRegistry::new()));
2414
2415        // Initialize fitness function registry and load from config
2416        let mut fitness_registry = FitnessFunctionRegistry::new();
2417
2418        // Try to load config and populate fitness rules
2419        let config_paths = [
2420            "config.yaml",
2421            "mockforge.yaml",
2422            "tools/mockforge/config.yaml",
2423            "../tools/mockforge/config.yaml",
2424        ];
2425
2426        let mut config_loaded = false;
2427        for path in &config_paths {
2428            if let Ok(config) = mockforge_core::config::load_config(path).await {
2429                if !config.contracts.fitness_rules.is_empty() {
2430                    if let Err(e) =
2431                        fitness_registry.load_from_config(&config.contracts.fitness_rules)
2432                    {
2433                        warn!("Failed to load fitness rules from config {}: {}", path, e);
2434                    } else {
2435                        info!(
2436                            "Loaded {} fitness rules from config: {}",
2437                            config.contracts.fitness_rules.len(),
2438                            path
2439                        );
2440                        config_loaded = true;
2441                        break;
2442                    }
2443                }
2444            }
2445        }
2446
2447        if !config_loaded {
2448            debug!("No fitness rules found in config files, using empty registry");
2449        }
2450
2451        let fitness_registry = Arc::new(RwLock::new(fitness_registry));
2452
2453        // Reuse drift engine and incident manager from drift budget section
2454
2455        // Initialize consumer impact analyzer
2456        let consumer_mapping_registry =
2457            mockforge_core::contract_drift::ConsumerMappingRegistry::new();
2458        let consumer_analyzer =
2459            Arc::new(RwLock::new(ConsumerImpactAnalyzer::new(consumer_mapping_registry)));
2460
2461        let protocol_state = ProtocolContractState {
2462            registry: contract_registry,
2463            drift_engine: Some(drift_engine.clone()),
2464            incident_manager: Some(incident_manager.clone()),
2465            fitness_registry: Some(fitness_registry),
2466            consumer_analyzer: Some(consumer_analyzer),
2467        };
2468
2469        app = app.nest("/api/v1/contracts", protocol_contracts_router(protocol_state));
2470        debug!("Protocol contracts endpoints mounted at /api/v1/contracts");
2471    }
2472
2473    // Add behavioral cloning middleware (optional - applies learned behavior to requests)
2474    #[cfg(feature = "behavioral-cloning")]
2475    {
2476        use crate::middleware::behavioral_cloning::BehavioralCloningMiddlewareState;
2477        use std::path::PathBuf;
2478
2479        // Determine database path (defaults to ./recordings.db)
2480        let db_path = std::env::var("RECORDER_DATABASE_PATH")
2481            .ok()
2482            .map(PathBuf::from)
2483            .or_else(|| std::env::current_dir().ok().map(|p| p.join("recordings.db")));
2484
2485        let bc_middleware_state = if let Some(path) = db_path {
2486            BehavioralCloningMiddlewareState::with_database_path(path)
2487        } else {
2488            BehavioralCloningMiddlewareState::new()
2489        };
2490
2491        // Only enable if BEHAVIORAL_CLONING_ENABLED is set to true
2492        let enabled = std::env::var("BEHAVIORAL_CLONING_ENABLED")
2493            .ok()
2494            .and_then(|v| v.parse::<bool>().ok())
2495            .unwrap_or(false);
2496
2497        if enabled {
2498            let bc_state_clone = bc_middleware_state.clone();
2499            app = app.layer(axum::middleware::from_fn(
2500                move |mut req: axum::extract::Request, next: axum::middleware::Next| {
2501                    let state = bc_state_clone.clone();
2502                    async move {
2503                        // Insert state into extensions if not already present
2504                        if req.extensions().get::<BehavioralCloningMiddlewareState>().is_none() {
2505                            req.extensions_mut().insert(state);
2506                        }
2507                        // Call the middleware function
2508                        crate::middleware::behavioral_cloning::behavioral_cloning_middleware(
2509                            req, next,
2510                        )
2511                        .await
2512                    }
2513                },
2514            ));
2515            debug!("Behavioral cloning middleware enabled (applies learned behavior to requests)");
2516        }
2517    }
2518
2519    // Add consumer contracts endpoints
2520    {
2521        use crate::handlers::consumer_contracts::{
2522            consumer_contracts_router, ConsumerContractsState,
2523        };
2524        use mockforge_contracts::consumer_contracts::{
2525            ConsumerBreakingChangeDetector, ConsumerRegistry, UsageRecorder,
2526        };
2527        use std::sync::Arc;
2528
2529        // Initialize consumer registry
2530        let registry = Arc::new(ConsumerRegistry::default());
2531
2532        // Initialize usage recorder
2533        let usage_recorder = Arc::new(UsageRecorder::default());
2534
2535        // Initialize breaking change detector
2536        let detector = Arc::new(ConsumerBreakingChangeDetector::new(usage_recorder.clone()));
2537
2538        let consumer_state = ConsumerContractsState {
2539            registry,
2540            usage_recorder,
2541            detector,
2542            violations: Arc::new(RwLock::new(HashMap::new())),
2543        };
2544
2545        app = app.merge(consumer_contracts_router(consumer_state));
2546        debug!("Consumer contracts endpoints mounted at /api/v1/consumers");
2547    }
2548
2549    // Add behavioral cloning endpoints
2550    #[cfg(feature = "behavioral-cloning")]
2551    {
2552        use crate::handlers::behavioral_cloning::{
2553            behavioral_cloning_router, BehavioralCloningState,
2554        };
2555        use std::path::PathBuf;
2556
2557        // Determine database path (defaults to ./recordings.db)
2558        let db_path = std::env::var("RECORDER_DATABASE_PATH")
2559            .ok()
2560            .map(PathBuf::from)
2561            .or_else(|| std::env::current_dir().ok().map(|p| p.join("recordings.db")));
2562
2563        let bc_state = if let Some(path) = db_path {
2564            BehavioralCloningState::with_database_path(path)
2565        } else {
2566            BehavioralCloningState::new()
2567        };
2568
2569        app = app.merge(behavioral_cloning_router(bc_state));
2570        debug!("Behavioral cloning endpoints mounted at /api/v1/behavioral-cloning");
2571    }
2572
2573    // Add consistency engine and cross-protocol state management
2574    {
2575        use crate::consistency::{ConsistencyMiddlewareState, HttpAdapter};
2576        use crate::handlers::consistency::{consistency_router, ConsistencyState};
2577        use mockforge_core::consistency::ConsistencyEngine;
2578        use std::sync::Arc;
2579
2580        // Initialize consistency engine
2581        let consistency_engine = Arc::new(ConsistencyEngine::new());
2582
2583        // Create and register HTTP adapter
2584        let http_adapter = Arc::new(HttpAdapter::new(consistency_engine.clone()));
2585        consistency_engine.register_adapter(http_adapter.clone()).await;
2586
2587        // Create consistency state for handlers
2588        let consistency_state = ConsistencyState {
2589            engine: consistency_engine.clone(),
2590        };
2591
2592        // Create X-Ray state first (needed for middleware)
2593        use crate::handlers::xray::XRayState;
2594        let xray_state = Arc::new(XRayState {
2595            engine: consistency_engine.clone(),
2596            request_contexts: std::sync::Arc::new(RwLock::new(HashMap::new())),
2597        });
2598
2599        // Create consistency middleware state
2600        let consistency_middleware_state = ConsistencyMiddlewareState {
2601            engine: consistency_engine.clone(),
2602            adapter: http_adapter,
2603            xray_state: Some(xray_state.clone()),
2604        };
2605
2606        // Add consistency middleware (before other middleware to inject state early)
2607        let consistency_middleware_state_clone = consistency_middleware_state.clone();
2608        app = app.layer(axum::middleware::from_fn(
2609            move |mut req: axum::extract::Request, next: axum::middleware::Next| {
2610                let state = consistency_middleware_state_clone.clone();
2611                async move {
2612                    // Insert state into extensions if not already present
2613                    if req.extensions().get::<ConsistencyMiddlewareState>().is_none() {
2614                        req.extensions_mut().insert(state);
2615                    }
2616                    // Call the middleware function
2617                    consistency::middleware::consistency_middleware(req, next).await
2618                }
2619            },
2620        ));
2621
2622        // Add consistency API endpoints
2623        app = app.merge(consistency_router(consistency_state));
2624        debug!("Consistency engine initialized and endpoints mounted at /api/v1/consistency");
2625
2626        // Add fidelity score endpoints
2627        {
2628            use crate::handlers::fidelity::{fidelity_router, FidelityState};
2629            let fidelity_state = FidelityState::new();
2630            app = app.merge(fidelity_router(fidelity_state));
2631            debug!("Fidelity score endpoints mounted at /api/v1/workspace/:workspace_id/fidelity");
2632        }
2633
2634        // Add scenario studio endpoints
2635        {
2636            use crate::handlers::scenario_studio::{scenario_studio_router, ScenarioStudioState};
2637            let scenario_studio_state = ScenarioStudioState::new();
2638            app = app.merge(scenario_studio_router(scenario_studio_state));
2639            debug!("Scenario Studio endpoints mounted at /api/v1/scenario-studio");
2640        }
2641
2642        // Add performance mode endpoints
2643        {
2644            use crate::handlers::performance::{performance_router, PerformanceState};
2645            let performance_state = PerformanceState::new();
2646            app = app.nest("/api/performance", performance_router(performance_state));
2647            debug!("Performance mode endpoints mounted at /api/performance");
2648        }
2649
2650        // Add world state endpoints
2651        {
2652            use crate::handlers::world_state::{world_state_router, WorldStateState};
2653            use mockforge_world_state::WorldStateEngine;
2654            use std::sync::Arc;
2655            use tokio::sync::RwLock;
2656
2657            let world_state_engine = Arc::new(RwLock::new(WorldStateEngine::new()));
2658            let world_state_state = WorldStateState {
2659                engine: world_state_engine,
2660            };
2661            app = app.nest("/api/world-state", world_state_router().with_state(world_state_state));
2662            debug!("World state endpoints mounted at /api/world-state");
2663        }
2664
2665        // Add snapshot management endpoints
2666        {
2667            use crate::handlers::snapshots::{snapshot_router, SnapshotState};
2668            use mockforge_core::snapshots::SnapshotManager;
2669            use std::path::PathBuf;
2670
2671            let snapshot_dir = std::env::var("MOCKFORGE_SNAPSHOT_DIR").ok().map(PathBuf::from);
2672            let snapshot_manager = Arc::new(SnapshotManager::new(snapshot_dir));
2673
2674            let snapshot_state = SnapshotState {
2675                manager: snapshot_manager,
2676                consistency_engine: Some(consistency_engine.clone()),
2677                workspace_persistence: None, // Can be initialized later if workspace persistence is available
2678                vbr_engine: None, // Can be initialized when VBR engine is available in server state
2679                recorder: None,   // Can be initialized when Recorder is available in server state
2680            };
2681
2682            app = app.merge(snapshot_router(snapshot_state));
2683            debug!("Snapshot management endpoints mounted at /api/v1/snapshots");
2684
2685            // Add X-Ray API endpoints for browser extension
2686            {
2687                use crate::handlers::xray::xray_router;
2688                app = app.merge(xray_router((*xray_state).clone()));
2689                debug!("X-Ray API endpoints mounted at /api/v1/xray");
2690            }
2691        }
2692
2693        // Add A/B testing endpoints and middleware
2694        {
2695            use crate::handlers::ab_testing::{ab_testing_router, ABTestingState};
2696            use crate::middleware::ab_testing::ab_testing_middleware;
2697
2698            let ab_testing_state = ABTestingState::new();
2699
2700            // Add A/B testing middleware (before other response middleware)
2701            let ab_testing_state_clone = ab_testing_state.clone();
2702            app = app.layer(axum::middleware::from_fn(
2703                move |mut req: axum::extract::Request, next: axum::middleware::Next| {
2704                    let state = ab_testing_state_clone.clone();
2705                    async move {
2706                        // Insert state into extensions if not already present
2707                        if req.extensions().get::<ABTestingState>().is_none() {
2708                            req.extensions_mut().insert(state);
2709                        }
2710                        // Call the middleware function
2711                        ab_testing_middleware(req, next).await
2712                    }
2713                },
2714            ));
2715
2716            // Add A/B testing API endpoints
2717            app = app.merge(ab_testing_router(ab_testing_state));
2718            debug!("A/B testing endpoints mounted at /api/v1/ab-tests");
2719        }
2720    }
2721
2722    // Add PR generation endpoints (optional - only if configured)
2723    {
2724        use crate::handlers::pr_generation::{pr_generation_router, PRGenerationState};
2725        use mockforge_core::pr_generation::{PRGenerator, PRProvider};
2726        use std::sync::Arc;
2727
2728        // Load PR generation config from environment or use default
2729        let pr_config = mockforge_core::pr_generation::PRGenerationConfig::from_env();
2730
2731        let generator = if pr_config.enabled && pr_config.token.is_some() {
2732            let token = pr_config.token.as_ref().unwrap().clone();
2733            let generator = match pr_config.provider {
2734                PRProvider::GitHub => PRGenerator::new_github(
2735                    pr_config.owner.clone(),
2736                    pr_config.repo.clone(),
2737                    token,
2738                    pr_config.base_branch.clone(),
2739                ),
2740                PRProvider::GitLab => PRGenerator::new_gitlab(
2741                    pr_config.owner.clone(),
2742                    pr_config.repo.clone(),
2743                    token,
2744                    pr_config.base_branch.clone(),
2745                ),
2746            };
2747            Some(Arc::new(generator))
2748        } else {
2749            None
2750        };
2751
2752        let pr_state = PRGenerationState {
2753            generator: generator.clone(),
2754        };
2755
2756        app = app.merge(pr_generation_router(pr_state));
2757        if generator.is_some() {
2758            debug!(
2759                "PR generation endpoints mounted at /api/v1/pr (configured for {:?})",
2760                pr_config.provider
2761            );
2762        } else {
2763            debug!("PR generation endpoints mounted at /api/v1/pr (not configured - set GITHUB_TOKEN/GITLAB_TOKEN and PR_REPO_OWNER/PR_REPO_NAME)");
2764        }
2765    }
2766
2767    // Add management WebSocket endpoint
2768    app = app.nest("/__mockforge/ws", ws_management_router(ws_state));
2769
2770    // Add workspace routing middleware if multi-tenant is enabled
2771    if let Some(mt_config) = multi_tenant_config {
2772        if mt_config.enabled {
2773            use mockforge_core::{MultiTenantWorkspaceRegistry, WorkspaceRouter};
2774            use std::sync::Arc;
2775
2776            info!(
2777                "Multi-tenant mode enabled with {} routing strategy",
2778                match mt_config.routing_strategy {
2779                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Path => "path-based",
2780                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Port => "port-based",
2781                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Both => "hybrid",
2782                }
2783            );
2784
2785            // Create the multi-tenant workspace registry
2786            let mut registry = MultiTenantWorkspaceRegistry::new(mt_config.clone());
2787
2788            // Register the default workspace before wrapping in Arc
2789            let default_workspace =
2790                mockforge_core::Workspace::new(mt_config.default_workspace.clone());
2791            if let Err(e) =
2792                registry.register_workspace(mt_config.default_workspace.clone(), default_workspace)
2793            {
2794                warn!("Failed to register default workspace: {}", e);
2795            } else {
2796                info!("Registered default workspace: '{}'", mt_config.default_workspace);
2797            }
2798
2799            // Wrap registry in Arc for shared access
2800            let registry = Arc::new(registry);
2801
2802            // Create workspace router
2803            let _workspace_router = WorkspaceRouter::new(registry);
2804            info!("Workspace routing middleware initialized for HTTP server");
2805        }
2806    }
2807
2808    // Apply deceptive deploy configuration if enabled
2809    let mut final_cors_config = cors_config;
2810    let mut production_headers: Option<std::sync::Arc<HashMap<String, String>>> = None;
2811    // Auth config from deceptive deploy OAuth (if configured)
2812    let mut deceptive_deploy_auth_config: Option<mockforge_core::config::AuthConfig> = None;
2813    let mut rate_limit_config = middleware::RateLimitConfig {
2814        requests_per_minute: std::env::var("MOCKFORGE_RATE_LIMIT_RPM")
2815            .ok()
2816            .and_then(|v| v.parse().ok())
2817            .unwrap_or(1000),
2818        burst: std::env::var("MOCKFORGE_RATE_LIMIT_BURST")
2819            .ok()
2820            .and_then(|v| v.parse().ok())
2821            .unwrap_or(2000),
2822        per_ip: true,
2823        per_endpoint: false,
2824    };
2825
2826    if let Some(deploy_config) = &deceptive_deploy_config {
2827        if deploy_config.enabled {
2828            info!("Deceptive deploy mode enabled - applying production-like configuration");
2829
2830            // Override CORS config if provided
2831            if let Some(prod_cors) = &deploy_config.cors {
2832                final_cors_config = Some(mockforge_core::config::HttpCorsConfig {
2833                    enabled: true,
2834                    allowed_origins: prod_cors.allowed_origins.clone(),
2835                    allowed_methods: prod_cors.allowed_methods.clone(),
2836                    allowed_headers: prod_cors.allowed_headers.clone(),
2837                    allow_credentials: prod_cors.allow_credentials,
2838                });
2839                info!("Applied production-like CORS configuration");
2840            }
2841
2842            // Override rate limit config if provided
2843            if let Some(prod_rate_limit) = &deploy_config.rate_limit {
2844                rate_limit_config = middleware::RateLimitConfig {
2845                    requests_per_minute: prod_rate_limit.requests_per_minute,
2846                    burst: prod_rate_limit.burst,
2847                    per_ip: prod_rate_limit.per_ip,
2848                    per_endpoint: false,
2849                };
2850                info!(
2851                    "Applied production-like rate limiting: {} req/min, burst: {}",
2852                    prod_rate_limit.requests_per_minute, prod_rate_limit.burst
2853                );
2854            }
2855
2856            // Set production headers
2857            if !deploy_config.headers.is_empty() {
2858                let headers_map: HashMap<String, String> = deploy_config.headers.clone();
2859                production_headers = Some(std::sync::Arc::new(headers_map));
2860                info!("Configured {} production headers", deploy_config.headers.len());
2861            }
2862
2863            // Integrate OAuth config from deceptive deploy
2864            if let Some(prod_oauth) = &deploy_config.oauth {
2865                let oauth2_config: mockforge_core::config::OAuth2Config = prod_oauth.clone().into();
2866                deceptive_deploy_auth_config = Some(mockforge_core::config::AuthConfig {
2867                    oauth2: Some(oauth2_config),
2868                    ..Default::default()
2869                });
2870                info!("Applied production-like OAuth configuration for deceptive deploy");
2871            }
2872        }
2873    }
2874
2875    // Initialize rate limiter and state
2876    let rate_limiter =
2877        std::sync::Arc::new(middleware::GlobalRateLimiter::new(rate_limit_config.clone()));
2878
2879    let mut state = HttpServerState::new().with_rate_limiter(rate_limiter.clone());
2880
2881    // Add production headers to state if configured
2882    if let Some(headers) = production_headers.clone() {
2883        state = state.with_production_headers(headers);
2884    }
2885
2886    // Add rate limiting middleware
2887    app = app.layer(from_fn_with_state(state.clone(), middleware::rate_limit_middleware));
2888
2889    // Add production headers middleware if configured
2890    if state.production_headers.is_some() {
2891        app =
2892            app.layer(from_fn_with_state(state.clone(), middleware::production_headers_middleware));
2893    }
2894
2895    // Add authentication middleware if OAuth is configured via deceptive deploy
2896    if let Some(auth_config) = deceptive_deploy_auth_config {
2897        use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
2898        use std::collections::HashMap;
2899        use std::sync::Arc;
2900        use tokio::sync::RwLock;
2901
2902        // Create OAuth2 client if configured
2903        let oauth2_client = if let Some(oauth2_config) = &auth_config.oauth2 {
2904            match create_oauth2_client(oauth2_config) {
2905                Ok(client) => Some(client),
2906                Err(e) => {
2907                    warn!("Failed to create OAuth2 client from deceptive deploy config: {}", e);
2908                    None
2909                }
2910            }
2911        } else {
2912            None
2913        };
2914
2915        // Create auth state
2916        let auth_state = AuthState {
2917            config: auth_config,
2918            spec: None, // OpenAPI spec not available in this context
2919            oauth2_client,
2920            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
2921        };
2922
2923        // Apply auth middleware
2924        app = app.layer(from_fn_with_state(auth_state, auth_middleware));
2925        info!("Applied OAuth authentication middleware from deceptive deploy configuration");
2926    }
2927
2928    // Add runtime daemon 404 detection middleware (if enabled)
2929    #[cfg(feature = "runtime-daemon")]
2930    {
2931        use mockforge_runtime_daemon::{AutoGenerator, NotFoundDetector, RuntimeDaemonConfig};
2932        use std::sync::Arc;
2933
2934        // Load runtime daemon config from environment
2935        let daemon_config = RuntimeDaemonConfig::from_env();
2936
2937        if daemon_config.enabled {
2938            info!("Runtime daemon enabled - auto-creating mocks from 404s");
2939
2940            // Determine management API URL (default to localhost:3000)
2941            let management_api_url =
2942                std::env::var("MOCKFORGE_MANAGEMENT_API_URL").unwrap_or_else(|_| {
2943                    let port =
2944                        std::env::var("MOCKFORGE_HTTP_PORT").unwrap_or_else(|_| "3000".to_string());
2945                    format!("http://localhost:{}", port)
2946                });
2947
2948            // Create auto-generator
2949            let generator = Arc::new(AutoGenerator::new(daemon_config.clone(), management_api_url));
2950
2951            // Create detector and set generator
2952            let detector = NotFoundDetector::new(daemon_config.clone());
2953            detector.set_generator(generator).await;
2954
2955            // Add middleware layer
2956            let detector_clone = detector.clone();
2957            app = app.layer(axum::middleware::from_fn(
2958                move |req: axum::extract::Request, next: axum::middleware::Next| {
2959                    let detector = detector_clone.clone();
2960                    async move { detector.detect_and_auto_create(req, next).await }
2961                },
2962            ));
2963
2964            debug!("Runtime daemon 404 detection middleware added");
2965        }
2966    }
2967
2968    // Add /__mockforge/routes endpoint so the admin UI can discover registered routes
2969    {
2970        let routes_state = HttpServerState::with_routes(captured_routes);
2971        let routes_router = Router::new()
2972            .route("/__mockforge/routes", axum::routing::get(get_routes_handler))
2973            .with_state(routes_state);
2974        app = app.merge(routes_router);
2975    }
2976
2977    // Add API docs page (Scalar-powered interactive explorer)
2978    app = app.route("/__mockforge/docs", axum::routing::get(get_docs_handler));
2979
2980    // Note: OData URI rewrite is applied at the service level in serve_router_with_tls()
2981    // because Router::layer() only applies to matched routes, not unmatched ones.
2982
2983    // Add request logging middleware to capture all requests for the admin dashboard
2984    app = app.layer(axum::middleware::from_fn(request_logging::log_http_requests));
2985
2986    // Add contract diff middleware for automatic request capture
2987    // This captures requests for contract diff analysis
2988    app = app.layer(axum::middleware::from_fn(contract_diff_middleware::capture_for_contract_diff));
2989
2990    // Add CORS middleware (use final_cors_config which may be overridden by deceptive deploy)
2991    app = apply_cors_middleware(app, final_cors_config);
2992
2993    app
2994}
2995
2996// Note: start_with_traffic_shaping function removed due to compilation issues
2997// Use build_router_with_traffic_shaping_and_multi_tenant directly instead
2998
2999#[test]
3000fn test_route_info_clone() {
3001    let route = RouteInfo {
3002        method: "POST".to_string(),
3003        path: "/users".to_string(),
3004        operation_id: Some("createUser".to_string()),
3005        summary: None,
3006        description: None,
3007        parameters: vec![],
3008    };
3009
3010    let cloned = route.clone();
3011    assert_eq!(route.method, cloned.method);
3012    assert_eq!(route.path, cloned.path);
3013    assert_eq!(route.operation_id, cloned.operation_id);
3014}
3015
3016#[test]
3017fn test_http_server_state_new() {
3018    let state = HttpServerState::new();
3019    assert_eq!(state.routes.len(), 0);
3020}
3021
3022#[test]
3023fn test_http_server_state_with_routes() {
3024    let routes = vec![
3025        RouteInfo {
3026            method: "GET".to_string(),
3027            path: "/users".to_string(),
3028            operation_id: Some("getUsers".to_string()),
3029            summary: None,
3030            description: None,
3031            parameters: vec![],
3032        },
3033        RouteInfo {
3034            method: "POST".to_string(),
3035            path: "/users".to_string(),
3036            operation_id: Some("createUser".to_string()),
3037            summary: None,
3038            description: None,
3039            parameters: vec![],
3040        },
3041    ];
3042
3043    let state = HttpServerState::with_routes(routes.clone());
3044    assert_eq!(state.routes.len(), 2);
3045    assert_eq!(state.routes[0].method, "GET");
3046    assert_eq!(state.routes[1].method, "POST");
3047}
3048
3049#[test]
3050fn test_http_server_state_clone() {
3051    let routes = vec![RouteInfo {
3052        method: "GET".to_string(),
3053        path: "/test".to_string(),
3054        operation_id: None,
3055        summary: None,
3056        description: None,
3057        parameters: vec![],
3058    }];
3059
3060    let state = HttpServerState::with_routes(routes);
3061    let cloned = state.clone();
3062
3063    assert_eq!(state.routes.len(), cloned.routes.len());
3064    assert_eq!(state.routes[0].method, cloned.routes[0].method);
3065}
3066
3067#[tokio::test]
3068async fn test_build_router_without_openapi() {
3069    let _router = build_router(None, None, None).await;
3070    // Should succeed without OpenAPI spec
3071}
3072
3073#[tokio::test]
3074async fn test_build_router_with_nonexistent_spec() {
3075    let _router = build_router(Some("/nonexistent/spec.yaml".to_string()), None, None).await;
3076    // Should succeed but log a warning
3077}
3078
3079#[tokio::test]
3080async fn test_build_router_with_auth_and_latency() {
3081    let _router = build_router_with_auth_and_latency(None, None, None, None).await;
3082    // Should succeed without parameters
3083}
3084
3085#[tokio::test]
3086async fn test_build_router_with_latency() {
3087    let _router = build_router_with_latency(None, None, None).await;
3088    // Should succeed without parameters
3089}
3090
3091#[tokio::test]
3092async fn test_build_router_with_auth() {
3093    let _router = build_router_with_auth(None, None, None).await;
3094    // Should succeed without parameters
3095}
3096
3097#[tokio::test]
3098async fn test_build_router_with_chains() {
3099    let _router = build_router_with_chains(None, None, None).await;
3100    // Should succeed without parameters
3101}
3102
3103#[test]
3104fn test_route_info_with_all_fields() {
3105    let route = RouteInfo {
3106        method: "PUT".to_string(),
3107        path: "/users/{id}".to_string(),
3108        operation_id: Some("updateUser".to_string()),
3109        summary: Some("Update user".to_string()),
3110        description: Some("Updates an existing user".to_string()),
3111        parameters: vec!["id".to_string(), "body".to_string()],
3112    };
3113
3114    assert!(route.operation_id.is_some());
3115    assert!(route.summary.is_some());
3116    assert!(route.description.is_some());
3117    assert_eq!(route.parameters.len(), 2);
3118}
3119
3120#[test]
3121fn test_route_info_with_minimal_fields() {
3122    let route = RouteInfo {
3123        method: "DELETE".to_string(),
3124        path: "/users/{id}".to_string(),
3125        operation_id: None,
3126        summary: None,
3127        description: None,
3128        parameters: vec![],
3129    };
3130
3131    assert!(route.operation_id.is_none());
3132    assert!(route.summary.is_none());
3133    assert!(route.description.is_none());
3134    assert_eq!(route.parameters.len(), 0);
3135}
3136
3137#[test]
3138fn test_http_server_state_empty_routes() {
3139    let state = HttpServerState::with_routes(vec![]);
3140    assert_eq!(state.routes.len(), 0);
3141}
3142
3143#[test]
3144fn test_http_server_state_multiple_routes() {
3145    let routes = vec![
3146        RouteInfo {
3147            method: "GET".to_string(),
3148            path: "/users".to_string(),
3149            operation_id: Some("listUsers".to_string()),
3150            summary: Some("List all users".to_string()),
3151            description: None,
3152            parameters: vec![],
3153        },
3154        RouteInfo {
3155            method: "GET".to_string(),
3156            path: "/users/{id}".to_string(),
3157            operation_id: Some("getUser".to_string()),
3158            summary: Some("Get a user".to_string()),
3159            description: None,
3160            parameters: vec!["id".to_string()],
3161        },
3162        RouteInfo {
3163            method: "POST".to_string(),
3164            path: "/users".to_string(),
3165            operation_id: Some("createUser".to_string()),
3166            summary: Some("Create a user".to_string()),
3167            description: None,
3168            parameters: vec!["body".to_string()],
3169        },
3170    ];
3171
3172    let state = HttpServerState::with_routes(routes);
3173    assert_eq!(state.routes.len(), 3);
3174
3175    // Verify different HTTP methods
3176    let methods: Vec<&str> = state.routes.iter().map(|r| r.method.as_str()).collect();
3177    assert!(methods.contains(&"GET"));
3178    assert!(methods.contains(&"POST"));
3179}
3180
3181#[test]
3182fn test_http_server_state_with_rate_limiter() {
3183    use std::sync::Arc;
3184
3185    let config = crate::middleware::RateLimitConfig::default();
3186    let rate_limiter = Arc::new(crate::middleware::GlobalRateLimiter::new(config));
3187
3188    let state = HttpServerState::new().with_rate_limiter(rate_limiter);
3189
3190    assert!(state.rate_limiter.is_some());
3191    assert_eq!(state.routes.len(), 0);
3192}
3193
3194#[tokio::test]
3195async fn test_build_router_includes_rate_limiter() {
3196    let _router = build_router(None, None, None).await;
3197    // Router should be created successfully with rate limiter initialized
3198}