1pub mod ai_handler;
166pub mod auth;
167pub mod chain_handlers;
168pub mod coverage;
169pub mod http_tracing_middleware;
170pub mod latency_profiles;
171pub mod management;
172pub mod management_ws;
173pub mod metrics_middleware;
174pub mod middleware;
175pub mod op_middleware;
176pub mod quick_mock;
177pub mod rag_ai_generator;
178pub mod replay_listing;
179pub mod request_logging;
180pub mod spec_import;
181pub mod sse;
182pub mod token_response;
183pub mod ui_builder;
184
185pub use ai_handler::{process_response_with_ai, AiResponseConfig, AiResponseHandler};
187
188pub use management::{
190 management_router, management_router_with_ui_builder, ManagementState, MockConfig,
191 ServerConfig, ServerStats,
192};
193
194pub use ui_builder::{create_ui_builder_router, EndpointConfig, UIBuilderState};
196
197pub use management_ws::{ws_management_router, MockEvent, WsManagementState};
199
200pub use metrics_middleware::collect_http_metrics;
202
203pub use http_tracing_middleware::http_tracing_middleware;
205
206pub use coverage::{calculate_coverage, CoverageReport, MethodCoverage, RouteCoverage};
208
209use axum::middleware::from_fn_with_state;
210use axum::{extract::State, response::Json, Router};
211use mockforge_core::failure_injection::{FailureConfig, FailureInjector};
212use mockforge_core::latency::LatencyInjector;
213use mockforge_core::openapi::OpenApiSpec;
214use mockforge_core::openapi_routes::OpenApiRouteRegistry;
215use mockforge_core::openapi_routes::ValidationOptions;
216
217use mockforge_core::LatencyProfile;
218#[cfg(feature = "data-faker")]
219use mockforge_data::provider::register_core_faker_provider;
220use std::collections::HashMap;
221use std::ffi::OsStr;
222use std::path::Path;
223use tokio::fs;
224use tokio::sync::RwLock;
225use tracing::*;
226
227#[derive(Clone)]
229pub struct RouteInfo {
230 pub method: String,
231 pub path: String,
232 pub operation_id: Option<String>,
233 pub summary: Option<String>,
234 pub description: Option<String>,
235 pub parameters: Vec<String>,
236}
237
238#[derive(Clone)]
240pub struct HttpServerState {
241 pub routes: Vec<RouteInfo>,
242 pub rate_limiter: Option<std::sync::Arc<crate::middleware::rate_limit::GlobalRateLimiter>>,
243}
244
245impl Default for HttpServerState {
246 fn default() -> Self {
247 Self::new()
248 }
249}
250
251impl HttpServerState {
252 pub fn new() -> Self {
253 Self {
254 routes: Vec::new(),
255 rate_limiter: None,
256 }
257 }
258
259 pub fn with_routes(routes: Vec<RouteInfo>) -> Self {
260 Self {
261 routes,
262 rate_limiter: None,
263 }
264 }
265
266 pub fn with_rate_limiter(
267 mut self,
268 rate_limiter: std::sync::Arc<crate::middleware::rate_limit::GlobalRateLimiter>,
269 ) -> Self {
270 self.rate_limiter = Some(rate_limiter);
271 self
272 }
273}
274
275async fn get_routes_handler(State(state): State<HttpServerState>) -> Json<serde_json::Value> {
277 let route_info: Vec<serde_json::Value> = state
278 .routes
279 .iter()
280 .map(|route| {
281 serde_json::json!({
282 "method": route.method,
283 "path": route.path,
284 "operation_id": route.operation_id,
285 "summary": route.summary,
286 "description": route.description,
287 "parameters": route.parameters
288 })
289 })
290 .collect();
291
292 Json(serde_json::json!({
293 "routes": route_info,
294 "total": state.routes.len()
295 }))
296}
297
298pub async fn build_router(
300 spec_path: Option<String>,
301 options: Option<ValidationOptions>,
302 failure_config: Option<FailureConfig>,
303) -> Router {
304 build_router_with_multi_tenant(spec_path, options, failure_config, None, None, None, None, None)
305 .await
306}
307
308#[allow(clippy::too_many_arguments)]
310pub async fn build_router_with_multi_tenant(
311 spec_path: Option<String>,
312 options: Option<ValidationOptions>,
313 failure_config: Option<FailureConfig>,
314 multi_tenant_config: Option<mockforge_core::MultiTenantConfig>,
315 _route_configs: Option<Vec<mockforge_core::config::RouteConfig>>,
316 _cors_config: Option<mockforge_core::config::HttpCorsConfig>,
317 ai_generator: Option<
318 std::sync::Arc<dyn mockforge_core::openapi::response::AiGenerator + Send + Sync>,
319 >,
320 smtp_registry: Option<std::sync::Arc<dyn std::any::Any + Send + Sync>>,
321) -> Router {
322 use std::time::Instant;
323
324 let startup_start = Instant::now();
325
326 let mut app = Router::new();
328
329 let rate_limit_config = crate::middleware::RateLimitConfig {
332 requests_per_minute: std::env::var("MOCKFORGE_RATE_LIMIT_RPM")
333 .ok()
334 .and_then(|v| v.parse().ok())
335 .unwrap_or(1000),
336 burst: std::env::var("MOCKFORGE_RATE_LIMIT_BURST")
337 .ok()
338 .and_then(|v| v.parse().ok())
339 .unwrap_or(2000),
340 per_ip: true,
341 per_endpoint: false,
342 };
343 let rate_limiter =
344 std::sync::Arc::new(crate::middleware::GlobalRateLimiter::new(rate_limit_config.clone()));
345
346 let mut state = HttpServerState::new().with_rate_limiter(rate_limiter.clone());
347
348 let spec_path_for_mgmt = spec_path.clone();
350
351 if let Some(spec_path) = spec_path {
353 tracing::debug!("Processing OpenAPI spec path: {}", spec_path);
354
355 let spec_load_start = Instant::now();
357 match OpenApiSpec::from_file(&spec_path).await {
358 Ok(openapi) => {
359 let spec_load_duration = spec_load_start.elapsed();
360 info!(
361 "Successfully loaded OpenAPI spec from {} (took {:?})",
362 spec_path, spec_load_duration
363 );
364
365 tracing::debug!("Creating OpenAPI route registry...");
367 let registry_start = Instant::now();
368 let registry = if let Some(opts) = options {
369 tracing::debug!("Using custom validation options");
370 OpenApiRouteRegistry::new_with_options(openapi, opts)
371 } else {
372 tracing::debug!("Using environment-based options");
373 OpenApiRouteRegistry::new_with_env(openapi)
374 };
375 let registry_duration = registry_start.elapsed();
376 info!(
377 "Created OpenAPI route registry with {} routes (took {:?})",
378 registry.routes().len(),
379 registry_duration
380 );
381
382 let extract_start = Instant::now();
384 let route_info: Vec<RouteInfo> = registry
385 .routes()
386 .iter()
387 .map(|route| RouteInfo {
388 method: route.method.clone(),
389 path: route.path.clone(),
390 operation_id: route.operation.operation_id.clone(),
391 summary: route.operation.summary.clone(),
392 description: route.operation.description.clone(),
393 parameters: route.parameters.clone(),
394 })
395 .collect();
396 state.routes = route_info;
397 let extract_duration = extract_start.elapsed();
398 debug!("Extracted route information (took {:?})", extract_duration);
399
400 let overrides = if std::env::var("MOCKFORGE_HTTP_OVERRIDES_GLOB").is_ok() {
402 tracing::debug!("Loading overrides from environment variable");
403 let overrides_start = Instant::now();
404 match mockforge_core::Overrides::load_from_globs(&[]).await {
405 Ok(overrides) => {
406 let overrides_duration = overrides_start.elapsed();
407 info!(
408 "Loaded {} override rules (took {:?})",
409 overrides.rules().len(),
410 overrides_duration
411 );
412 Some(overrides)
413 }
414 Err(e) => {
415 tracing::warn!("Failed to load overrides: {}", e);
416 None
417 }
418 }
419 } else {
420 None
421 };
422
423 let router_build_start = Instant::now();
425 let overrides_enabled = overrides.is_some();
426 let openapi_router = if let Some(ai_generator) = &ai_generator {
427 tracing::debug!("Building router with AI generator support");
428 registry.build_router_with_ai(Some(ai_generator.clone()))
429 } else if let Some(failure_config) = &failure_config {
430 tracing::debug!("Building router with failure injection and overrides");
431 let failure_injector = FailureInjector::new(Some(failure_config.clone()), true);
432 registry.build_router_with_injectors_and_overrides(
433 LatencyInjector::default(),
434 Some(failure_injector),
435 overrides,
436 overrides_enabled,
437 )
438 } else {
439 tracing::debug!("Building router with overrides");
440 registry.build_router_with_injectors_and_overrides(
441 LatencyInjector::default(),
442 None,
443 overrides,
444 overrides_enabled,
445 )
446 };
447 let router_build_duration = router_build_start.elapsed();
448 debug!("Built OpenAPI router (took {:?})", router_build_duration);
449
450 tracing::debug!("Merging OpenAPI router with main router");
451 app = app.merge(openapi_router);
452 tracing::debug!("Router built successfully");
453 }
454 Err(e) => {
455 warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec_path, e);
456 }
457 }
458 }
459
460 app = app.route(
462 "/health",
463 axum::routing::get(|| async {
464 use mockforge_core::server_utils::health::HealthStatus;
465 axum::Json(serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")).unwrap())
466 }),
467 )
468 .merge(sse::sse_router());
470
471 let state_for_routes = state.clone();
473
474 let routes_router = Router::new()
476 .route("/__mockforge/routes", axum::routing::get(get_routes_handler))
477 .route("/__mockforge/coverage", axum::routing::get(coverage::get_coverage_handler))
478 .with_state(state_for_routes);
479
480 app = app.merge(routes_router);
482
483 let coverage_html_path = std::env::var("MOCKFORGE_COVERAGE_UI_PATH")
486 .unwrap_or_else(|_| "crates/mockforge-http/static/coverage.html".to_string());
487
488 if std::path::Path::new(&coverage_html_path).exists() {
490 app = app.nest_service(
491 "/__mockforge/coverage.html",
492 tower_http::services::ServeFile::new(&coverage_html_path),
493 );
494 debug!("Serving coverage UI from: {}", coverage_html_path);
495 } else {
496 debug!(
497 "Coverage UI file not found at: {}. Skipping static file serving.",
498 coverage_html_path
499 );
500 }
501
502 let mut management_state = ManagementState::new(None, spec_path_for_mgmt, 3000); #[cfg(feature = "smtp")]
505 let mut management_state = {
506 if let Some(smtp_reg) = smtp_registry {
507 let smtp_reg = smtp_reg
508 .downcast::<mockforge_smtp::SmtpSpecRegistry>()
509 .expect("Invalid SMTP registry type passed to HTTP management state");
510 management_state.with_smtp_registry(smtp_reg)
511 } else {
512 management_state
513 }
514 };
515 #[cfg(not(feature = "smtp"))]
516 let mut management_state = management_state;
517 #[cfg(not(feature = "smtp"))]
518 let _ = smtp_registry;
519 app = app.nest("/__mockforge/api", management_router(management_state));
520
521 let ws_state = WsManagementState::new();
523 app = app.nest("/__mockforge/ws", ws_management_router(ws_state));
524
525 app = app.layer(axum::middleware::from_fn(request_logging::log_http_requests));
527
528 app = app.layer(from_fn_with_state(state, crate::middleware::rate_limit_middleware));
530
531 if let Some(mt_config) = multi_tenant_config {
533 if mt_config.enabled {
534 use mockforge_core::{MultiTenantWorkspaceRegistry, WorkspaceRouter};
535 use std::sync::Arc;
536
537 info!(
538 "Multi-tenant mode enabled with {} routing strategy",
539 match mt_config.routing_strategy {
540 mockforge_core::RoutingStrategy::Path => "path-based",
541 mockforge_core::RoutingStrategy::Port => "port-based",
542 mockforge_core::RoutingStrategy::Both => "hybrid",
543 }
544 );
545
546 let mut registry = MultiTenantWorkspaceRegistry::new(mt_config.clone());
548
549 let default_workspace =
551 mockforge_core::Workspace::new(mt_config.default_workspace.clone());
552 if let Err(e) =
553 registry.register_workspace(mt_config.default_workspace.clone(), default_workspace)
554 {
555 warn!("Failed to register default workspace: {}", e);
556 } else {
557 info!("Registered default workspace: '{}'", mt_config.default_workspace);
558 }
559
560 if mt_config.auto_discover {
562 if let Some(config_dir) = &mt_config.config_directory {
563 let config_path = Path::new(config_dir);
564 if config_path.exists() && config_path.is_dir() {
565 match fs::read_dir(config_path).await {
566 Ok(mut entries) => {
567 while let Ok(Some(entry)) = entries.next_entry().await {
568 let path = entry.path();
569 if path.extension() == Some(OsStr::new("yaml")) {
570 match fs::read_to_string(&path).await {
571 Ok(content) => {
572 match serde_yaml::from_str::<
573 mockforge_core::Workspace,
574 >(
575 &content
576 ) {
577 Ok(workspace) => {
578 if let Err(e) = registry.register_workspace(
579 workspace.id.clone(),
580 workspace,
581 ) {
582 warn!("Failed to register auto-discovered workspace from {:?}: {}", path, e);
583 } else {
584 info!("Auto-registered workspace from {:?}", path);
585 }
586 }
587 Err(e) => {
588 warn!("Failed to parse workspace from {:?}: {}", path, e);
589 }
590 }
591 }
592 Err(e) => {
593 warn!(
594 "Failed to read workspace file {:?}: {}",
595 path, e
596 );
597 }
598 }
599 }
600 }
601 }
602 Err(e) => {
603 warn!("Failed to read config directory {:?}: {}", config_path, e);
604 }
605 }
606 } else {
607 warn!(
608 "Config directory {:?} does not exist or is not a directory",
609 config_path
610 );
611 }
612 }
613 }
614
615 let registry = Arc::new(registry);
617
618 let _workspace_router = WorkspaceRouter::new(registry);
620
621 info!("Workspace routing middleware initialized for HTTP server");
624 }
625 }
626
627 let total_startup_duration = startup_start.elapsed();
628 info!("HTTP router startup completed (total time: {:?})", total_startup_duration);
629
630 app
631}
632
633pub async fn build_router_with_auth_and_latency(
635 _spec_path: Option<String>,
636 _options: Option<()>,
637 _auth_config: Option<mockforge_core::config::AuthConfig>,
638 _latency_injector: Option<LatencyInjector>,
639) -> Router {
640 build_router(None, None, None).await
642}
643
644pub async fn build_router_with_latency(
646 _spec_path: Option<String>,
647 _options: Option<ValidationOptions>,
648 _latency_injector: Option<LatencyInjector>,
649) -> Router {
650 build_router(None, None, None).await
652}
653
654pub async fn build_router_with_auth(
656 spec_path: Option<String>,
657 options: Option<ValidationOptions>,
658 auth_config: Option<mockforge_core::config::AuthConfig>,
659) -> Router {
660 use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
661 use std::sync::Arc;
662
663 #[cfg(feature = "data-faker")]
665 {
666 register_core_faker_provider();
667 }
668
669 let spec = if let Some(spec_path) = &spec_path {
671 match mockforge_core::openapi::OpenApiSpec::from_file(&spec_path).await {
672 Ok(spec) => Some(Arc::new(spec)),
673 Err(e) => {
674 warn!("Failed to load OpenAPI spec for auth: {}", e);
675 None
676 }
677 }
678 } else {
679 None
680 };
681
682 let oauth2_client = if let Some(auth_config) = &auth_config {
684 if let Some(oauth2_config) = &auth_config.oauth2 {
685 match create_oauth2_client(oauth2_config) {
686 Ok(client) => Some(client),
687 Err(e) => {
688 warn!("Failed to create OAuth2 client: {}", e);
689 None
690 }
691 }
692 } else {
693 None
694 }
695 } else {
696 None
697 };
698
699 let auth_state = AuthState {
700 config: auth_config.unwrap_or_default(),
701 spec,
702 oauth2_client,
703 introspection_cache: Arc::new(RwLock::new(HashMap::new())),
704 };
705
706 let mut app = Router::new().with_state(auth_state.clone());
708
709 if let Some(spec_path) = spec_path {
711 match OpenApiSpec::from_file(&spec_path).await {
712 Ok(openapi) => {
713 info!("Loaded OpenAPI spec from {}", spec_path);
714 let registry = if let Some(opts) = options {
715 OpenApiRouteRegistry::new_with_options(openapi, opts)
716 } else {
717 OpenApiRouteRegistry::new_with_env(openapi)
718 };
719
720 app = registry.build_router();
721 }
722 Err(e) => {
723 warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec_path, e);
724 }
725 }
726 }
727
728 app = app.route(
730 "/health",
731 axum::routing::get(|| async {
732 use mockforge_core::server_utils::health::HealthStatus;
733 axum::Json(serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")).unwrap())
734 }),
735 )
736 .merge(sse::sse_router())
738 .layer(axum::middleware::from_fn_with_state(auth_state.clone(), auth_middleware))
740 .layer(axum::middleware::from_fn(request_logging::log_http_requests));
742
743 app
744}
745
746pub async fn serve_router(
748 port: u16,
749 app: Router,
750) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
751 use std::net::SocketAddr;
752
753 let addr = mockforge_core::wildcard_socket_addr(port);
754 info!("HTTP listening on {}", addr);
755
756 let listener = tokio::net::TcpListener::bind(addr).await.map_err(|e| {
757 format!(
758 "Failed to bind HTTP server to port {}: {}\n\
759 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 {}",
760 port, e, port, port
761 )
762 })?;
763
764 axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await?;
765 Ok(())
766}
767
768pub async fn start(
770 port: u16,
771 spec_path: Option<String>,
772 options: Option<ValidationOptions>,
773) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
774 start_with_latency(port, spec_path, options, None).await
775}
776
777pub async fn start_with_auth_and_latency(
779 port: u16,
780 spec_path: Option<String>,
781 options: Option<ValidationOptions>,
782 auth_config: Option<mockforge_core::config::AuthConfig>,
783 latency_profile: Option<LatencyProfile>,
784) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
785 start_with_auth_and_injectors(port, spec_path, options, auth_config, latency_profile, None)
786 .await
787}
788
789pub async fn start_with_auth_and_injectors(
791 port: u16,
792 spec_path: Option<String>,
793 options: Option<ValidationOptions>,
794 auth_config: Option<mockforge_core::config::AuthConfig>,
795 _latency_profile: Option<LatencyProfile>,
796 _failure_injector: Option<mockforge_core::FailureInjector>,
797) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
798 let app = build_router_with_auth(spec_path, options, auth_config).await;
800 serve_router(port, app).await
801}
802
803pub async fn start_with_latency(
805 port: u16,
806 spec_path: Option<String>,
807 options: Option<ValidationOptions>,
808 latency_profile: Option<LatencyProfile>,
809) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
810 let latency_injector =
811 latency_profile.map(|profile| LatencyInjector::new(profile, Default::default()));
812
813 let app = build_router_with_latency(spec_path, options, latency_injector).await;
814 serve_router(port, app).await
815}
816
817pub async fn build_router_with_chains(
819 spec_path: Option<String>,
820 options: Option<ValidationOptions>,
821 circling_config: Option<mockforge_core::request_chaining::ChainConfig>,
822) -> Router {
823 build_router_with_chains_and_multi_tenant(
824 spec_path,
825 options,
826 circling_config,
827 None,
828 None,
829 None,
830 None,
831 None,
832 None,
833 None,
834 false,
835 )
836 .await
837}
838
839#[allow(clippy::too_many_arguments)]
841pub async fn build_router_with_chains_and_multi_tenant(
842 spec_path: Option<String>,
843 options: Option<ValidationOptions>,
844 _circling_config: Option<mockforge_core::request_chaining::ChainConfig>,
845 multi_tenant_config: Option<mockforge_core::MultiTenantConfig>,
846 _route_configs: Option<Vec<mockforge_core::config::RouteConfig>>,
847 _cors_config: Option<mockforge_core::config::HttpCorsConfig>,
848 _ai_generator: Option<
849 std::sync::Arc<dyn mockforge_core::openapi::response::AiGenerator + Send + Sync>,
850 >,
851 smtp_registry: Option<std::sync::Arc<dyn std::any::Any + Send + Sync>>,
852 mqtt_broker: Option<std::sync::Arc<dyn std::any::Any + Send + Sync>>,
853 traffic_shaper: Option<mockforge_core::traffic_shaping::TrafficShaper>,
854 traffic_shaping_enabled: bool,
855) -> Router {
856 use crate::latency_profiles::LatencyProfiles;
857 use crate::op_middleware::Shared;
858 use mockforge_core::Overrides;
859
860 let _shared = Shared {
861 profiles: LatencyProfiles::default(),
862 overrides: Overrides::default(),
863 failure_injector: None,
864 traffic_shaper,
865 overrides_enabled: false,
866 traffic_shaping_enabled,
867 };
868
869 let mut app = Router::new();
871 let mut include_default_health = true;
872
873 if let Some(ref spec) = spec_path {
875 match OpenApiSpec::from_file(&spec).await {
876 Ok(openapi) => {
877 info!("Loaded OpenAPI spec from {}", spec);
878 let registry = if let Some(opts) = options {
879 OpenApiRouteRegistry::new_with_options(openapi, opts)
880 } else {
881 OpenApiRouteRegistry::new_with_env(openapi)
882 };
883 if registry
884 .routes()
885 .iter()
886 .any(|route| route.method == "GET" && route.path == "/health")
887 {
888 include_default_health = false;
889 }
890 let spec_router = registry.build_router();
891 app = app.merge(spec_router);
892 }
893 Err(e) => {
894 warn!("Failed to load OpenAPI spec from {:?}: {}. Starting without OpenAPI integration.", spec_path, e);
895 }
896 }
897 }
898
899 if include_default_health {
900 app = app.route(
901 "/health",
902 axum::routing::get(|| async {
903 use mockforge_core::server_utils::health::HealthStatus;
904 axum::Json(
905 serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")).unwrap(),
906 )
907 }),
908 );
909 }
910
911 app = app.merge(sse::sse_router());
912
913 let management_state = ManagementState::new(None, spec_path, 3000); #[cfg(feature = "smtp")]
916 let management_state = {
917 if let Some(smtp_reg) = smtp_registry {
918 let smtp_reg = smtp_reg
919 .downcast::<mockforge_smtp::SmtpSpecRegistry>()
920 .expect("Invalid SMTP registry type passed to HTTP management state");
921 management_state.with_smtp_registry(smtp_reg)
922 } else {
923 management_state
924 }
925 };
926 #[cfg(not(feature = "smtp"))]
927 let management_state = {
928 let _ = smtp_registry;
929 management_state
930 };
931 #[cfg(feature = "mqtt")]
932 let management_state = {
933 if let Some(broker) = mqtt_broker {
934 let broker = broker
935 .downcast::<mockforge_mqtt::MqttBroker>()
936 .expect("Invalid MQTT broker passed to HTTP management state");
937 management_state.with_mqtt_broker(broker)
938 } else {
939 management_state
940 }
941 };
942 #[cfg(not(feature = "mqtt"))]
943 let management_state = {
944 let _ = mqtt_broker;
945 management_state
946 };
947 app = app.nest("/__mockforge/api", management_router(management_state));
948
949 if let Some(mt_config) = multi_tenant_config {
951 if mt_config.enabled {
952 use mockforge_core::{MultiTenantWorkspaceRegistry, WorkspaceRouter};
953 use std::sync::Arc;
954
955 info!(
956 "Multi-tenant mode enabled with {} routing strategy",
957 match mt_config.routing_strategy {
958 mockforge_core::RoutingStrategy::Path => "path-based",
959 mockforge_core::RoutingStrategy::Port => "port-based",
960 mockforge_core::RoutingStrategy::Both => "hybrid",
961 }
962 );
963
964 let mut registry = MultiTenantWorkspaceRegistry::new(mt_config.clone());
966
967 let default_workspace =
969 mockforge_core::Workspace::new(mt_config.default_workspace.clone());
970 if let Err(e) =
971 registry.register_workspace(mt_config.default_workspace.clone(), default_workspace)
972 {
973 warn!("Failed to register default workspace: {}", e);
974 } else {
975 info!("Registered default workspace: '{}'", mt_config.default_workspace);
976 }
977
978 let registry = Arc::new(registry);
980
981 let _workspace_router = WorkspaceRouter::new(registry);
983 info!("Workspace routing middleware initialized for HTTP server");
984 }
985 }
986
987 app
988}
989
990#[test]
994fn test_route_info_clone() {
995 let route = RouteInfo {
996 method: "POST".to_string(),
997 path: "/users".to_string(),
998 operation_id: Some("createUser".to_string()),
999 summary: None,
1000 description: None,
1001 parameters: vec![],
1002 };
1003
1004 let cloned = route.clone();
1005 assert_eq!(route.method, cloned.method);
1006 assert_eq!(route.path, cloned.path);
1007 assert_eq!(route.operation_id, cloned.operation_id);
1008}
1009
1010#[test]
1011fn test_http_server_state_new() {
1012 let state = HttpServerState::new();
1013 assert_eq!(state.routes.len(), 0);
1014}
1015
1016#[test]
1017fn test_http_server_state_with_routes() {
1018 let routes = vec![
1019 RouteInfo {
1020 method: "GET".to_string(),
1021 path: "/users".to_string(),
1022 operation_id: Some("getUsers".to_string()),
1023 summary: None,
1024 description: None,
1025 parameters: vec![],
1026 },
1027 RouteInfo {
1028 method: "POST".to_string(),
1029 path: "/users".to_string(),
1030 operation_id: Some("createUser".to_string()),
1031 summary: None,
1032 description: None,
1033 parameters: vec![],
1034 },
1035 ];
1036
1037 let state = HttpServerState::with_routes(routes.clone());
1038 assert_eq!(state.routes.len(), 2);
1039 assert_eq!(state.routes[0].method, "GET");
1040 assert_eq!(state.routes[1].method, "POST");
1041}
1042
1043#[test]
1044fn test_http_server_state_clone() {
1045 let routes = vec![RouteInfo {
1046 method: "GET".to_string(),
1047 path: "/test".to_string(),
1048 operation_id: None,
1049 summary: None,
1050 description: None,
1051 parameters: vec![],
1052 }];
1053
1054 let state = HttpServerState::with_routes(routes);
1055 let cloned = state.clone();
1056
1057 assert_eq!(state.routes.len(), cloned.routes.len());
1058 assert_eq!(state.routes[0].method, cloned.routes[0].method);
1059}
1060
1061#[tokio::test]
1062async fn test_build_router_without_openapi() {
1063 let _router = build_router(None, None, None).await;
1064 }
1066
1067#[tokio::test]
1068async fn test_build_router_with_nonexistent_spec() {
1069 let _router = build_router(Some("/nonexistent/spec.yaml".to_string()), None, None).await;
1070 }
1072
1073#[tokio::test]
1074async fn test_build_router_with_auth_and_latency() {
1075 let _router = build_router_with_auth_and_latency(None, None, None, None).await;
1076 }
1078
1079#[tokio::test]
1080async fn test_build_router_with_latency() {
1081 let _router = build_router_with_latency(None, None, None).await;
1082 }
1084
1085#[tokio::test]
1086async fn test_build_router_with_auth() {
1087 let _router = build_router_with_auth(None, None, None).await;
1088 }
1090
1091#[tokio::test]
1092async fn test_build_router_with_chains() {
1093 let _router = build_router_with_chains(None, None, None).await;
1094 }
1096
1097#[test]
1098fn test_route_info_with_all_fields() {
1099 let route = RouteInfo {
1100 method: "PUT".to_string(),
1101 path: "/users/{id}".to_string(),
1102 operation_id: Some("updateUser".to_string()),
1103 summary: Some("Update user".to_string()),
1104 description: Some("Updates an existing user".to_string()),
1105 parameters: vec!["id".to_string(), "body".to_string()],
1106 };
1107
1108 assert!(route.operation_id.is_some());
1109 assert!(route.summary.is_some());
1110 assert!(route.description.is_some());
1111 assert_eq!(route.parameters.len(), 2);
1112}
1113
1114#[test]
1115fn test_route_info_with_minimal_fields() {
1116 let route = RouteInfo {
1117 method: "DELETE".to_string(),
1118 path: "/users/{id}".to_string(),
1119 operation_id: None,
1120 summary: None,
1121 description: None,
1122 parameters: vec![],
1123 };
1124
1125 assert!(route.operation_id.is_none());
1126 assert!(route.summary.is_none());
1127 assert!(route.description.is_none());
1128 assert_eq!(route.parameters.len(), 0);
1129}
1130
1131#[test]
1132fn test_http_server_state_empty_routes() {
1133 let state = HttpServerState::with_routes(vec![]);
1134 assert_eq!(state.routes.len(), 0);
1135}
1136
1137#[test]
1138fn test_http_server_state_multiple_routes() {
1139 let routes = vec![
1140 RouteInfo {
1141 method: "GET".to_string(),
1142 path: "/users".to_string(),
1143 operation_id: Some("listUsers".to_string()),
1144 summary: Some("List all users".to_string()),
1145 description: None,
1146 parameters: vec![],
1147 },
1148 RouteInfo {
1149 method: "GET".to_string(),
1150 path: "/users/{id}".to_string(),
1151 operation_id: Some("getUser".to_string()),
1152 summary: Some("Get a user".to_string()),
1153 description: None,
1154 parameters: vec!["id".to_string()],
1155 },
1156 RouteInfo {
1157 method: "POST".to_string(),
1158 path: "/users".to_string(),
1159 operation_id: Some("createUser".to_string()),
1160 summary: Some("Create a user".to_string()),
1161 description: None,
1162 parameters: vec!["body".to_string()],
1163 },
1164 ];
1165
1166 let state = HttpServerState::with_routes(routes);
1167 assert_eq!(state.routes.len(), 3);
1168
1169 let methods: Vec<&str> = state.routes.iter().map(|r| r.method.as_str()).collect();
1171 assert!(methods.contains(&"GET"));
1172 assert!(methods.contains(&"POST"));
1173}
1174
1175#[test]
1176fn test_http_server_state_with_rate_limiter() {
1177 use std::sync::Arc;
1178
1179 let config = crate::middleware::RateLimitConfig::default();
1180 let rate_limiter = Arc::new(crate::middleware::GlobalRateLimiter::new(config));
1181
1182 let state = HttpServerState::new().with_rate_limiter(rate_limiter);
1183
1184 assert!(state.rate_limiter.is_some());
1185 assert_eq!(state.routes.len(), 0);
1186}
1187
1188#[tokio::test]
1189async fn test_build_router_includes_rate_limiter() {
1190 let _router = build_router(None, None, None).await;
1191 }