mockforge_http/
lib.rs

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