//! Test Configuration for Foxhunt HFT Trading System //! //! This module provides configuration utilities for test environments, //! including database setup, service configuration, and test parameters. use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::env; /// Test environment configuration #[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Default)] pub struct TestConfig { pub database: DatabaseConfig, pub services: ServiceConfig, pub performance: PerformanceConfig, pub market_data: MarketDataConfig, pub risk: RiskConfig, } /// Database configuration for tests #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DatabaseConfig { pub url: String, pub max_connections: u32, pub connection_timeout_ms: u64, pub query_timeout_ms: u64, pub enable_logging: bool, pub auto_migrate: bool, pub cleanup_on_drop: bool, } /// Service configuration for tests #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServiceConfig { pub trading_service: ServiceEndpoint, pub ml_training_service: ServiceEndpoint, pub backtesting_service: ServiceEndpoint, pub enable_mocks: bool, pub mock_latency_ms: u64, pub mock_failure_rate: f64, } /// Service endpoint configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServiceEndpoint { pub host: String, pub port: u16, pub use_tls: bool, pub timeout_ms: u64, pub max_retries: u32, } /// Performance testing configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PerformanceConfig { pub max_latency_ns: u64, pub min_throughput_ops_per_sec: f64, pub test_duration_secs: u64, pub warmup_duration_secs: u64, pub concurrent_operations: usize, pub stress_test_multiplier: f64, } /// Market data configuration for tests #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MarketDataConfig { pub default_symbols: Vec, pub tick_rate_hz: u32, pub price_precision: u32, pub volume_range: (u64, u64), pub volatility_range: (f64, f64), pub enable_realistic_data: bool, } /// Risk management configuration for tests #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RiskConfig { pub default_var_limit: Decimal, pub default_position_limit: Decimal, pub default_concentration_limit: Decimal, pub stress_test_scenarios: Vec, pub enable_circuit_breakers: bool, pub max_drawdown_limit: Decimal, } impl Default for DatabaseConfig { fn default() -> Self { Self { url: env::var("TEST_DATABASE_URL").unwrap_or_else(|_| { "postgresql://foxhunt_test:test_password@localhost:5432/foxhunt_test".to_string() }), max_connections: 10, connection_timeout_ms: 5000, query_timeout_ms: 30000, enable_logging: false, // Disable SQL logging in tests by default auto_migrate: true, cleanup_on_drop: true, } } } impl Default for ServiceConfig { fn default() -> Self { Self { trading_service: ServiceEndpoint { host: "localhost".to_string(), port: 8001, use_tls: false, timeout_ms: 5000, max_retries: 3, }, ml_training_service: ServiceEndpoint { host: "localhost".to_string(), port: 8002, use_tls: false, timeout_ms: 10000, max_retries: 3, }, backtesting_service: ServiceEndpoint { host: "localhost".to_string(), port: 8003, use_tls: false, timeout_ms: 30000, max_retries: 3, }, enable_mocks: true, // Enable mocks by default for unit tests mock_latency_ms: 1, mock_failure_rate: 0.0, // No failures by default } } } impl Default for PerformanceConfig { fn default() -> Self { Self { max_latency_ns: 50_000, // 50µs min_throughput_ops_per_sec: 1000.0, test_duration_secs: 10, warmup_duration_secs: 2, concurrent_operations: 10, stress_test_multiplier: 2.0, } } } impl Default for MarketDataConfig { fn default() -> Self { use super::ALL_TEST_SYMBOLS; Self { default_symbols: ALL_TEST_SYMBOLS.iter().map(|&s| s.to_string()).collect(), tick_rate_hz: 1000, // 1000 ticks per second price_precision: 2, // 2 decimal places volume_range: (100, 10000), volatility_range: (0.01, 0.05), // 1% to 5% daily volatility enable_realistic_data: true, } } } impl Default for RiskConfig { fn default() -> Self { Self { default_var_limit: Decimal::from(100000), // $100k default_position_limit: Decimal::from(1000000), // $1M default_concentration_limit: Decimal::new(25, 2), // 25% stress_test_scenarios: vec![ "market_crash".to_string(), "interest_rate_shock".to_string(), "liquidity_crisis".to_string(), ], enable_circuit_breakers: true, max_drawdown_limit: Decimal::new(20, 2), // 20% } } } impl TestConfig { /// Load configuration from environment variables and defaults pub fn from_env() -> Self { let mut config = Self::default(); // Override with environment variables if present if let Ok(db_url) = env::var("TEST_DATABASE_URL") { config.database.url = db_url; } if let Ok(max_connections) = env::var("TEST_DB_MAX_CONNECTIONS") { if let Ok(connections) = max_connections.parse() { config.database.max_connections = connections; } } if let Ok(enable_mocks) = env::var("TEST_ENABLE_MOCKS") { config.services.enable_mocks = enable_mocks.to_lowercase() == "true"; } if let Ok(max_latency) = env::var("TEST_MAX_LATENCY_NS") { if let Ok(latency) = max_latency.parse() { config.performance.max_latency_ns = latency; } } config } /// Create configuration for unit tests (fast, mocked) pub fn for_unit_tests() -> Self { Self { database: DatabaseConfig { max_connections: 5, connection_timeout_ms: 1000, query_timeout_ms: 5000, enable_logging: false, cleanup_on_drop: true, ..DatabaseConfig::default() }, services: ServiceConfig { enable_mocks: true, mock_latency_ms: 0, // No artificial latency for unit tests mock_failure_rate: 0.0, ..ServiceConfig::default() }, performance: PerformanceConfig { test_duration_secs: 1, warmup_duration_secs: 0, concurrent_operations: 1, ..PerformanceConfig::default() }, market_data: MarketDataConfig { tick_rate_hz: 10, // Low tick rate for fast tests ..MarketDataConfig::default() }, ..Self::default() } } /// Create configuration for integration tests (realistic) pub fn for_integration_tests() -> Self { Self { services: ServiceConfig { enable_mocks: false, // Use real services ..ServiceConfig::default() }, performance: PerformanceConfig { test_duration_secs: 30, warmup_duration_secs: 5, concurrent_operations: 50, ..PerformanceConfig::default() }, ..Self::default() } } /// Create configuration for performance tests (demanding) pub fn for_performance_tests() -> Self { Self { services: ServiceConfig { enable_mocks: false, ..ServiceConfig::default() }, performance: PerformanceConfig { max_latency_ns: 14_000, // 14µs target for HFT min_throughput_ops_per_sec: 10000.0, test_duration_secs: 60, warmup_duration_secs: 10, concurrent_operations: 100, stress_test_multiplier: 5.0, ..PerformanceConfig::default() }, market_data: MarketDataConfig { tick_rate_hz: 10000, // High frequency data ..MarketDataConfig::default() }, ..Self::default() } } /// Create configuration for stress tests (extreme) pub fn for_stress_tests() -> Self { Self { performance: PerformanceConfig { test_duration_secs: 300, // 5 minutes concurrent_operations: 1000, stress_test_multiplier: 10.0, ..PerformanceConfig::default() }, market_data: MarketDataConfig { tick_rate_hz: 50000, // Extreme tick rate volatility_range: (0.05, 0.20), // Higher volatility ..MarketDataConfig::default() }, ..Self::default() } } /// Validate configuration values pub fn validate(&self) -> Result<(), String> { if self.database.max_connections == 0 { return Err("Database max_connections must be greater than 0".to_string()); } if self.performance.max_latency_ns == 0 { return Err("Performance max_latency_ns must be greater than 0".to_string()); } if self.performance.min_throughput_ops_per_sec <= 0.0 { return Err("Performance min_throughput_ops_per_sec must be positive".to_string()); } if self.services.mock_failure_rate < 0.0 || self.services.mock_failure_rate > 1.0 { return Err("Services mock_failure_rate must be between 0.0 and 1.0".to_string()); } if self.market_data.tick_rate_hz == 0 { return Err("Market data tick_rate_hz must be greater than 0".to_string()); } if self.risk.default_var_limit <= Decimal::ZERO { return Err("Risk default_var_limit must be positive".to_string()); } if self.risk.default_concentration_limit <= Decimal::ZERO || self.risk.default_concentration_limit > Decimal::ONE { return Err("Risk default_concentration_limit must be between 0 and 1".to_string()); } Ok(()) } /// Get database URL with test database suffix if not already present pub fn get_test_database_url(&self) -> String { let url = &self.database.url; if url.contains("_test") { url.clone() } else { // Append _test to database name if let Some(last_slash) = url.rfind('/') { let (base, db_name) = url.split_at(last_slash + 1); format!("{}{}_test", base, db_name) } else { format!("{}_test", url) } } } /// Create environment variables map for child processes pub fn to_env_vars(&self) -> HashMap { let mut env_vars = HashMap::new(); env_vars.insert( "TEST_DATABASE_URL".to_string(), self.get_test_database_url(), ); env_vars.insert( "TEST_DB_MAX_CONNECTIONS".to_string(), self.database.max_connections.to_string(), ); env_vars.insert( "TEST_ENABLE_MOCKS".to_string(), self.services.enable_mocks.to_string(), ); env_vars.insert( "TEST_MAX_LATENCY_NS".to_string(), self.performance.max_latency_ns.to_string(), ); env_vars.insert( "TEST_MIN_THROUGHPUT".to_string(), self.performance.min_throughput_ops_per_sec.to_string(), ); env_vars.insert( "RUST_LOG".to_string(), if self.database.enable_logging { "debug" } else { "warn" } .to_string(), ); env_vars } } /// Test configuration builder for fluent configuration #[derive(Debug)] pub struct TestConfigBuilder { config: TestConfig, } impl TestConfigBuilder { pub fn new() -> Self { Self { config: TestConfig::default(), } } pub fn with_database_url(mut self, url: impl Into) -> Self { self.config.database.url = url.into(); self } pub fn with_max_connections(mut self, max_connections: u32) -> Self { self.config.database.max_connections = max_connections; self } pub fn with_mocks_enabled(mut self, enabled: bool) -> Self { self.config.services.enable_mocks = enabled; self } pub fn with_max_latency_ns(mut self, latency: u64) -> Self { self.config.performance.max_latency_ns = latency; self } pub fn with_test_duration(mut self, duration_secs: u64) -> Self { self.config.performance.test_duration_secs = duration_secs; self } pub fn with_concurrent_operations(mut self, operations: usize) -> Self { self.config.performance.concurrent_operations = operations; self } pub fn with_var_limit(mut self, limit: Decimal) -> Self { self.config.risk.default_var_limit = limit; self } pub fn build(self) -> Result { self.config.validate()?; Ok(self.config) } } impl Default for TestConfigBuilder { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_default_config() { let config = TestConfig::default(); assert!(config.validate().is_ok()); assert!(config.database.max_connections > 0); assert!(config.performance.max_latency_ns > 0); assert!(!config.market_data.default_symbols.is_empty()); } #[test] fn test_unit_test_config() { let config = TestConfig::for_unit_tests(); assert!(config.validate().is_ok()); assert!(config.services.enable_mocks); assert_eq!(config.services.mock_latency_ms, 0); assert_eq!(config.performance.test_duration_secs, 1); } #[test] fn test_performance_test_config() { let config = TestConfig::for_performance_tests(); assert!(config.validate().is_ok()); assert!(!config.services.enable_mocks); assert_eq!(config.performance.max_latency_ns, 14_000); assert!(config.performance.concurrent_operations >= 100); } #[test] fn test_config_builder() { let config = TestConfigBuilder::new() .with_max_connections(20) .with_mocks_enabled(false) .with_max_latency_ns(10_000) .build() .unwrap(); assert_eq!(config.database.max_connections, 20); assert!(!config.services.enable_mocks); assert_eq!(config.performance.max_latency_ns, 10_000); } #[test] fn test_config_validation() { let mut config = TestConfig::default(); // Test invalid max_connections config.database.max_connections = 0; assert!(config.validate().is_err()); // Test invalid failure rate config.database.max_connections = 10; config.services.mock_failure_rate = 1.5; assert!(config.validate().is_err()); // Test valid config config.services.mock_failure_rate = 0.1; assert!(config.validate().is_ok()); } #[test] fn test_test_database_url() { let config = TestConfig::default(); let test_url = config.get_test_database_url(); assert!(test_url.contains("_test")); // Test with already test database let mut config_with_test = config.clone(); config_with_test.database.url = "postgresql://user:pass@localhost/db_test".to_string(); let test_url2 = config_with_test.get_test_database_url(); assert_eq!(test_url2, config_with_test.database.url); } #[test] fn test_env_vars_generation() { let config = TestConfig::default(); let env_vars = config.to_env_vars(); assert!(env_vars.contains_key("TEST_DATABASE_URL")); assert!(env_vars.contains_key("TEST_DB_MAX_CONNECTIONS")); assert!(env_vars.contains_key("TEST_ENABLE_MOCKS")); assert!(env_vars.contains_key("RUST_LOG")); } }