//! Mock Services for Integration Testing //! //! This module provides comprehensive mock implementations of all trading system //! services for integration testing, including realistic latency simulation, //! configurable failure rates, and comprehensive response patterns. use std::collections::HashMap; use std::sync::{Arc, atomic::{AtomicU16, AtomicU64, Ordering}}; use std::time::{Duration, Instant}; use tokio::sync::{mpsc, RwLock, Mutex}; use uuid::Uuid; use serde_json::json; use chrono::{DateTime, Utc}; use tli::prelude::*; use crate::fixtures::*; pub mod mock_trading_service; pub mod mock_backtesting_service; pub mod mock_database; pub mod mock_ml_infrastructure; /// Mock trading service implementation for comprehensive integration testing /// /// Provides realistic simulation of the trading service including configurable /// latency, failure rates, order lifecycle management, and circuit breaker behavior. pub struct MockTradingService { /// TCP port the mock service is listening on port: u16, /// Handle to the background server task server_handle: Option>, /// Configured responses for specific order IDs order_responses: Arc>>, /// Configured risk rejections for testing risk management risk_rejections: Arc>>, /// Counter for testing failure sequences failure_sequence_count: Arc, /// Simulated order lifecycle progressions lifecycle_simulations: Arc>>>, /// Circuit breaker state for testing resilience circuit_breaker_status: Arc>, /// Overall trading system status trading_status: Arc>, /// Service configuration including latency and failure rates config: MockServiceConfig, } /// Configuration for mock service behavior /// /// Controls latency simulation, failure injection, chaos testing, /// and connection limits for realistic testing scenarios. #[derive(Debug, Clone)] pub struct MockServiceConfig { /// Simulated service latency in milliseconds pub latency_ms: u64, /// Failure injection rate (0.0 to 1.0) pub failure_rate: f64, /// Whether to enable chaos testing features pub enable_chaos: bool, /// Maximum number of concurrent connections pub max_connections: usize, } impl Default for MockServiceConfig { fn default() -> Self { Self { latency_ms: 10, failure_rate: 0.01, // 1% enable_chaos: false, max_connections: 100, } } } // OrderStatus now imported from canonical source use common::OrderStatus; /// Circuit breaker state for testing resilience patterns /// /// Tracks failure counts and circuit breaker state to validate /// proper handling of service degradation scenarios. #[derive(Debug, Clone)] pub struct CircuitBreakerStatus { /// Whether the circuit breaker is currently open pub is_open: bool, /// Number of consecutive failures pub failure_count: u32, /// Timestamp of the most recent failure pub last_failure_time: Option>, } impl Default for CircuitBreakerStatus { fn default() -> Self { Self { is_open: false, failure_count: 0, last_failure_time: None, } } } /// Overall trading system operational status /// /// Tracks system health, emergency stop state, heartbeat, /// and key operational metrics for monitoring and testing. #[derive(Debug, Clone)] pub struct TradingStatus { /// Whether the trading system is currently active pub is_active: bool, /// Whether emergency stop has been triggered pub emergency_stop_active: bool, /// Timestamp of the last system heartbeat pub last_heartbeat: DateTime, /// Number of currently active orders pub active_orders: u64, /// Total trading volume processed pub total_volume: Decimal, } impl Default for TradingStatus { fn default() -> Self { Self { is_active: true, emergency_stop_active: false, last_heartbeat: Utc::now(), active_orders: 0, total_volume: Decimal::ZERO, } } } /// Order submission request structure for testing /// /// Represents the structure of order submission requests /// used to test the trading service API. #[derive(Debug, Clone)] pub struct SubmitOrderRequest { /// Trading symbol (e.g., "AAPL", "BTCUSD") pub symbol: String, /// Order side as integer (Buy/Sell enum value) pub side: i32, /// Order type as integer (Market/Limit enum value) pub order_type: i32, /// Order quantity pub quantity: f64, /// Limit price (None for market orders) pub price: Option, /// Client-provided order identifier pub client_order_id: String, /// Additional order metadata pub metadata: HashMap, } /// Order submission response structure for testing /// /// Contains the result of an order submission including /// success status, assigned order ID, and execution timing. #[derive(Debug, Clone)] pub struct SubmitOrderResponse { /// Whether the order was successfully submitted pub success: bool, /// System-assigned order identifier pub order_id: String, /// Response message or error description pub message: String, /// Order processing time in nanoseconds pub execution_time_ns: u64, } /// Start backtest request structure #[derive(Debug, Clone)] pub struct StartBacktestRequest { pub backtest_id: String, pub enable_monitoring: bool, } /// Start backtest response structure #[derive(Debug, Clone)] pub struct StartBacktestResponse { pub success: bool, pub message: String, } impl MockTradingService { /// Create a new mock trading service with default configuration /// /// # Returns /// * `Ok(MockTradingService)` - Configured mock service ready to start /// /// * `Err(TliError)` - If port allocation or initialization failed pub async fn new() -> TliResult { Self::new_with_config(MockServiceConfig::default()).await } /// Create a new mock trading service with custom configuration /// /// # Arguments /// * `config` - Custom service configuration including latency and failure rates /// /// # Returns /// * `Ok(MockTradingService)` - Configured mock service /// /// * `Err(TliError)` - If port allocation failed pub async fn new_with_config(config: MockServiceConfig) -> TliResult { let port = TEST_PORT_MANAGER.allocate_port().await; Ok(Self { port, server_handle: None, order_responses: Arc::new(RwLock::new(HashMap::new())), risk_rejections: Arc::new(RwLock::new(HashMap::new())), failure_sequence_count: Arc::new(AtomicU64::new(0)), lifecycle_simulations: Arc::new(RwLock::new(HashMap::new())), circuit_breaker_status: Arc::new(RwLock::new(CircuitBreakerStatus::default())), trading_status: Arc::new(RwLock::new(TradingStatus::default())), config, }) } /// Get the TCP port number the mock service is bound to /// /// # Returns /// /// The port number allocated for this mock service pub fn port(&self) -> u16 { self.port } /// Configure a specific response for an order submission /// /// # Arguments /// * `client_order_id` - Client order ID to configure response for /// /// * `response` - The response to return for this order pub async fn configure_order_response(&self, client_order_id: &str, response: SubmitOrderResponse) { let mut responses = self.order_responses.write().await; responses.insert(client_order_id.to_string(), response); } /// Configure a risk rejection for testing risk management /// /// # Arguments /// * `client_order_id` - Order ID to reject /// /// * `reason` - Rejection reason message pub async fn configure_risk_rejection(&self, client_order_id: &str, reason: String) { let mut rejections = self.risk_rejections.write().await; rejections.insert(client_order_id.to_string(), reason); } /// Configure failure sequence for circuit breaker testing pub async fn configure_failure_sequence(&self, count: u64) { self.failure_sequence_count.store(count, Ordering::Relaxed); } /// Configure order lifecycle simulation pub async fn configure_lifecycle_simulation(&self, order_id: &str, statuses: Vec) { let mut simulations = self.lifecycle_simulations.write().await; simulations.insert(order_id.to_string(), statuses); } /// Start the mock trading service and begin accepting connections /// /// # Returns /// * `Ok(())` - Service started successfully /// /// * `Err(TliError)` - If service failed to start pub async fn start(&mut self) -> TliResult<()> { let port = self.port; let config = self.config.clone(); let order_responses = Arc::clone(&self.order_responses); let risk_rejections = Arc::clone(&self.risk_rejections); let failure_sequence = Arc::clone(&self.failure_sequence_count); let circuit_breaker = Arc::clone(&self.circuit_breaker_status); let trading_status = Arc::clone(&self.trading_status); let handle = tokio::spawn(async move { let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port)) .await .expect("Failed to bind mock trading service"); while let Ok((stream, _)) = listener.accept().await { let responses = Arc::clone(&order_responses); let rejections = Arc::clone(&risk_rejections); let failure_seq = Arc::clone(&failure_sequence); let cb_status = Arc::clone(&circuit_breaker); let trade_status = Arc::clone(&trading_status); let service_config = config.clone(); tokio::spawn(async move { Self::handle_connection(stream, responses, rejections, failure_seq, cb_status, trade_status, service_config).await; }); } }); self.server_handle = Some(handle); Ok(()) } /// Handle individual client connection async fn handle_connection( stream: tokio::net::TcpStream, order_responses: Arc>>, risk_rejections: Arc>>, failure_sequence: Arc, circuit_breaker: Arc>, trading_status: Arc>, config: MockServiceConfig, ) { // Simulate service latency if config.latency_ms > 0 { tokio::time::sleep(Duration::from_millis(config.latency_ms)).await; } // Simulate random failures if configured if config.enable_chaos && fastrand::f64() < config.failure_rate { return; // Drop connection to simulate failure } // Handle gRPC-style protocol simulation // In a real implementation, this would use tonic/gRPC // For testing, we'll simulate the essential behavior } /// Stop the mock service and release resources pub async fn stop(&mut self) { if let Some(handle) = self.server_handle.take() { handle.abort(); } TEST_PORT_MANAGER.release_port(self.port).await; } } impl Drop for MockTradingService { fn drop(&mut self) { if let Some(handle) = self.server_handle.take() { handle.abort(); } } } /// Mock backtesting service for integration testing pub struct MockBacktestingService { port: u16, server_handle: Option>, backtest_responses: Arc>>, ml_responses: Arc>>, ensemble_responses: Arc>>, config: MockServiceConfig, } /// Backtest result structure #[derive(Debug, Clone)] pub struct BacktestResult { pub backtest_id: String, pub strategy_name: String, pub total_return: f64, pub annualized_return: f64, pub max_drawdown: f64, pub sharpe_ratio: f64, pub total_trades: u64, pub win_rate: f64, pub avg_trade_return: f64, pub final_value: f64, pub execution_time_ms: u64, pub events_processed: u64, } /// ML backtest configuration #[derive(Debug, Clone)] pub struct MLBacktestConfig { pub model_name: String, pub expected_return: f64, pub expected_sharpe: f64, pub expected_trades: u64, } /// Ensemble results structure #[derive(Debug, Clone)] pub struct EnsembleResults { pub ensemble_return: f64, pub ensemble_sharpe: f64, pub individual_returns: Vec, pub individual_sharpes: Vec, pub diversification_benefit: f64, pub model_weights_final: Vec, pub rebalance_count: u32, } /// Create backtest request structure #[derive(Debug, Clone)] pub struct CreateBacktestRequest { pub name: String, pub strategy_type: String, pub symbol: String, pub start_date: i64, pub end_date: i64, pub initial_capital: f64, pub parameters: serde_json::Value, pub enable_real_time_monitoring: bool, } /// Create backtest response structure #[derive(Debug, Clone)] pub struct CreateBacktestResponse { pub success: bool, pub backtest_id: String, pub message: String, } impl MockBacktestingService { /// Create new mock backtesting service pub async fn new() -> TliResult { let port = TEST_PORT_MANAGER.allocate_port().await; Ok(Self { port, server_handle: None, backtest_responses: Arc::new(RwLock::new(HashMap::new())), ml_responses: Arc::new(RwLock::new(HashMap::new())), ensemble_responses: Arc::new(RwLock::new(HashMap::new())), config: MockServiceConfig::default(), }) } /// Get the port the mock service is running on pub fn port(&self) -> u16 { self.port } /// Configure backtest response pub async fn configure_backtest_response(&self, name: &str, result: BacktestResult) { let mut responses = self.backtest_responses.write().await; responses.insert(name.to_string(), result); } /// Configure ML backtest response pub async fn configure_ml_backtest_response( &self, name: &str, model_name: &str, expected_return: f64, expected_sharpe: f64, expected_trades: u64, ) { let mut responses = self.ml_responses.write().await; responses.insert(name.to_string(), MLBacktestConfig { model_name: model_name.to_string(), expected_return, expected_sharpe, expected_trades, }); } /// Configure ensemble response pub async fn configure_ensemble_response(&self, name: &str, results: EnsembleResults) { let mut responses = self.ensemble_responses.write().await; responses.insert(name.to_string(), results); } /// Stop the mock service pub async fn stop(&mut self) { if let Some(handle) = self.server_handle.take() { handle.abort(); } TEST_PORT_MANAGER.release_port(self.port).await; } } /// Test database manager for PostgreSQL integration testing pub struct TestDatabaseManager { pool: sqlx::PgPool, config: TestDatabaseConfig, } /// Test database configuration #[derive(Debug, Clone)] pub struct TestDatabaseConfig { pub database_url: String, pub max_connections: u32, pub enable_cleanup: bool, pub test_schema: String, } impl Default for TestDatabaseConfig { fn default() -> Self { Self { database_url: std::env::var("TEST_DATABASE_URL") .unwrap_or_else(|_| "postgresql://foxhunt_test:test_password@localhost:5432/foxhunt_test".to_string()), max_connections: 20, enable_cleanup: true, test_schema: "test_".to_string(), } } } impl TestDatabaseManager { /// Create new test database manager pub async fn new() -> TliResult { Self::new_with_config(TestDatabaseConfig::default()).await } /// Create new test database manager with custom configuration pub async fn new_with_config(config: TestDatabaseConfig) -> TliResult { let pool = sqlx::PgPool::connect(&config.database_url) .await .map_err(|e| TliError::DatabaseError(format!("Failed to connect to test database: {}", e)))?; Ok(Self { pool, config }) } /// Get connection pool pub fn get_pool(&self) -> &sqlx::PgPool { &self.pool } /// Verify order was persisted pub async fn verify_order_persisted(&self, order_id: &str) -> TliResult { let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM orders WHERE order_id = $1") .bind(order_id) .fetch_one(&self.pool) .await .map_err(|e| TliError::DatabaseError(format!("Failed to verify order persistence: {}", e)))?; Ok(count > 0) } /// Clean up test data pub async fn cleanup(&self) -> TliResult<()> { if self.config.enable_cleanup { let cleanup_queries = vec![ "DELETE FROM trading_events WHERE source LIKE '%test%'", "DELETE FROM orders WHERE client_order_id LIKE '%test%'", "DELETE FROM positions WHERE account_id LIKE '%test%'", "DELETE FROM risk_events WHERE account_id LIKE '%test%'", "DELETE FROM market_data WHERE symbol LIKE '%TEST%'", "DELETE FROM performance_metrics WHERE metric_name LIKE '%test%'", ]; for query in cleanup_queries { sqlx::query(query) .execute(&self.pool) .await .map_err(|e| TliError::DatabaseError(format!("Cleanup failed: {}", e)))?; } } Ok(()) } } /// Test database structure pub struct TestDatabase { manager: TestDatabaseManager, } impl TestDatabase { /// Create new test database pub async fn new() -> TliResult { let manager = TestDatabaseManager::new().await?; Ok(Self { manager }) } /// Verify order was persisted pub async fn verify_order_persisted(&self, order_id: &str) -> TliResult { self.manager.verify_order_persisted(order_id).await } } /// Test data provider for market data simulation pub struct TestDataProvider { historical_data: HashMap>, } /// Market data point structure #[derive(Debug, Clone)] pub struct MarketDataPoint { pub timestamp: DateTime, pub symbol: String, pub price: f64, pub volume: u64, pub bid: Option, pub ask: Option, } impl TestDataProvider { /// Create new test data provider pub async fn new() -> TliResult { let mut provider = Self { historical_data: HashMap::new(), }; // Generate sample data for common symbols provider.generate_sample_data("AAPL", 1000).await; provider.generate_sample_data("MSFT", 1000).await; provider.generate_sample_data("GOOGL", 1000).await; Ok(provider) } /// Generate sample market data async fn generate_sample_data(&mut self, symbol: &str, count: usize) { let mut data = Vec::new(); let mut price = 150.0; let start_time = Utc::now() - chrono::Duration::days(30); for i in 0..count { let timestamp = start_time + chrono::Duration::seconds(i as i64 * 60); // Simple random walk price += (fastrand::f64() - 0.5) * 2.0; price = price.max(100.0).min(200.0); // Keep price in reasonable range data.push(MarketDataPoint { timestamp, symbol: symbol.to_string(), price, volume: 1000 + fastrand::u64(0..10000), bid: Some(price - 0.01), ask: Some(price + 0.01), }); } self.historical_data.insert(symbol.to_string(), data); } /// Get historical data for symbol pub fn get_historical_data(&self, symbol: &str) -> Option<&Vec> { self.historical_data.get(symbol) } } /// ML testing infrastructure pub struct MLTestInfrastructure { mock_models: HashMap, } /// Mock ML model #[derive(Debug, Clone)] pub struct MockMLModel { pub name: String, pub inference_latency_ns: u64, pub accuracy: f64, pub memory_usage_mb: u64, } impl MLTestInfrastructure { /// Create new ML testing infrastructure pub async fn new() -> TliResult { let mut models = HashMap::new(); // Add mock models with realistic characteristics models.insert("TLOB".to_string(), MockMLModel { name: "TLOB".to_string(), inference_latency_ns: 15_000, // 15µs accuracy: 0.89, memory_usage_mb: 256, }); models.insert("MAMBA".to_string(), MockMLModel { name: "MAMBA".to_string(), inference_latency_ns: 25_000, // 25µs accuracy: 0.91, memory_usage_mb: 512, }); models.insert("TFT".to_string(), MockMLModel { name: "TFT".to_string(), inference_latency_ns: 35_000, // 35µs accuracy: 0.87, memory_usage_mb: 384, }); models.insert("DQN".to_string(), MockMLModel { name: "DQN".to_string(), inference_latency_ns: 20_000, // 20µs accuracy: 0.84, memory_usage_mb: 128, }); Ok(Self { mock_models: models, }) } /// Get mock model pub fn get_model(&self, name: &str) -> Option<&MockMLModel> { self.mock_models.get(name) } /// List available models pub fn list_models(&self) -> Vec<&str> { self.mock_models.keys().map(|s| s.as_str()).collect() } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_mock_trading_service() { let mut service = MockTradingService::new().await.unwrap(); assert!(service.port() > 0); service.configure_order_response("test_order", SubmitOrderResponse { success: true, order_id: "order_123".to_string(), message: "Success".to_string(), execution_time_ns: 15_000, }).await; service.stop().await; } #[tokio::test] async fn test_test_data_provider() { let provider = TestDataProvider::new().await.unwrap(); let aapl_data = provider.get_historical_data("AAPL").unwrap(); assert_eq!(aapl_data.len(), 1000); assert!(aapl_data[0].price > 0.0); } #[tokio::test] async fn test_ml_infrastructure() { let ml_infra = MLTestInfrastructure::new().await.unwrap(); let models = ml_infra.list_models(); assert!(models.contains(&"TLOB")); assert!(models.contains(&"MAMBA")); let tlob = ml_infra.get_model("TLOB").unwrap(); assert_eq!(tlob.name, "TLOB"); assert!(tlob.inference_latency_ns > 0); } }