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