//! Centralized mock implementations for integration testing //! //! This module provides reusable mock implementations for all three services //! (Trading, Backtesting, ML Training) that can be shared across test suites. //! Mocks are designed to be realistic and maintain behavioral consistency. use super::*; use std::collections::HashMap; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::{RwLock, broadcast, mpsc}; use uuid::Uuid; use serde_json::json; use tracing::{info, debug, warn}; /// Centralized mock service registry pub struct MockServiceRegistry { trading_service: Arc, backtesting_service: Arc, ml_training_service: Arc, tli_client: Arc, } impl MockServiceRegistry { pub fn new() -> Self { Self { trading_service: Arc::new(MockTradingService::new()), backtesting_service: Arc::new(MockBacktestingService::new()), ml_training_service: Arc::new(MockMLTrainingService::new()), tli_client: Arc::new(MockTLIClient::new()), } } pub fn trading_service(&self) -> Arc { self.trading_service.clone() } pub fn backtesting_service(&self) -> Arc { self.backtesting_service.clone() } pub fn ml_training_service(&self) -> Arc { self.ml_training_service.clone() } pub fn tli_client(&self) -> Arc { self.tli_client.clone() } /// Start all mock services pub async fn start_all(&self) -> TestResult<()> { self.trading_service.start().await?; self.backtesting_service.start().await?; self.ml_training_service.start().await?; self.tli_client.start().await?; Ok(()) } /// Stop all mock services pub async fn stop_all(&self) -> TestResult<()> { self.trading_service.stop().await?; self.backtesting_service.stop().await?; self.ml_training_service.stop().await?; self.tli_client.stop().await?; Ok(()) } } // ============================================================================ // Mock Trading Service // ============================================================================ /// Mock Trading Service with realistic behavior pub struct MockTradingService { state: Arc>, orders: Arc>>, positions: Arc>>, market_data_tx: broadcast::Sender, running: Arc>, } #[derive(Debug, Default)] struct TradingServiceState { connected_brokers: Vec, risk_limits: RiskLimits, trading_enabled: bool, } #[derive(Debug, Default)] struct RiskLimits { max_position_size: u64, max_order_value: f64, daily_loss_limit: f64, } #[derive(Debug, Clone)] pub struct MockOrder { pub id: String, pub symbol: String, pub side: OrderSide, pub quantity: u64, pub price: f64, pub status: OrderStatus, pub created_at: Instant, } #[derive(Debug, Clone)] pub struct MockPosition { pub symbol: String, pub quantity: i64, // Signed for long/short positions pub average_price: f64, pub unrealized_pnl: f64, } #[derive(Debug, Clone)] pub struct MockMarketData { pub symbol: String, pub bid: f64, pub ask: f64, pub last_price: f64, pub volume: u64, pub timestamp: Instant, } #[derive(Debug, Clone)] // OrderSide now imported from canonical source use common::OrderSide; // OrderStatus now imported from canonical source use common::OrderStatus; impl MockTradingService { pub fn new() -> Self { let (market_data_tx, _) = broadcast::channel(1000); Self { state: Arc::new(RwLock::new(TradingServiceState { connected_brokers: vec!["IBKR".to_string(), "ICMarkets".to_string()], risk_limits: RiskLimits { max_position_size: 10000, max_order_value: 100000.0, daily_loss_limit: 50000.0, }, trading_enabled: true, })), orders: Arc::new(RwLock::new(HashMap::new())), positions: Arc::new(RwLock::new(HashMap::new())), market_data_tx, running: Arc::new(RwLock::new(false)), } } pub async fn start(&self) -> TestResult<()> { info!("Starting Mock Trading Service"); *self.running.write().await = true; // Start market data generator self.start_market_data_generator().await; Ok(()) } pub async fn stop(&self) -> TestResult<()> { info!("Stopping Mock Trading Service"); *self.running.write().await = false; Ok(()) } pub async fn place_order(&self, request: PlaceOrderRequest) -> TestResult { let order_id = Uuid::new_v4().to_string(); // Validate order if request.quantity == 0 { return Ok(PlaceOrderResponse { success: false, order_id: String::new(), error_message: "Invalid quantity".to_string(), }); } let state = self.state.read().await; if request.quantity > state.risk_limits.max_position_size { return Ok(PlaceOrderResponse { success: false, order_id: String::new(), error_message: "Order exceeds position size limit".to_string(), }); } // Create order let order = MockOrder { id: order_id.clone(), symbol: request.symbol, side: request.side, quantity: request.quantity, price: request.price, status: OrderStatus::Pending, created_at: Instant::now(), }; // Store order self.orders.write().await.insert(order_id.clone(), order); // Simulate order processing tokio::time::sleep(Duration::from_millis(10)).await; Ok(PlaceOrderResponse { success: true, order_id, error_message: String::new(), }) } pub async fn get_order_status(&self, order_id: &str) -> TestResult { let orders = self.orders.read().await; if let Some(order) = orders.get(order_id) { Ok(OrderStatusResponse { order_id: order.id.clone(), symbol: order.symbol.clone(), side: order.side.clone(), quantity: order.quantity, price: order.price, status: order.status.clone(), filled_quantity: match order.status { OrderStatus::Filled => order.quantity, OrderStatus::PartiallyFilled => order.quantity / 2, _ => 0, }, }) } else { Err(TestFrameworkError::CrossServiceIntegrationFailed { reason: format!("Order not found: {}", order_id), }) } } pub async fn subscribe_market_data(&self) -> broadcast::Receiver { self.market_data_tx.subscribe() } async fn start_market_data_generator(&self) { let tx = self.market_data_tx.clone(); let running = self.running.clone(); tokio::spawn(async move { let symbols = vec!["EURUSD", "GBPUSD", "USDJPY", "AUDUSD"]; let mut prices: HashMap = symbols .iter() .map(|s| (s.to_string(), 1.2345)) .collect(); while *running.read().await { for symbol in &symbols { // Generate realistic price movement let current_price = prices.get(symbol).unwrap_or(&1.2345); let change = (rand::random::() - 0.5) * 0.001; // ±0.1% let new_price = current_price + change; prices.insert(symbol.clone(), new_price); let market_data = MockMarketData { symbol: symbol.clone(), bid: new_price - 0.0002, ask: new_price + 0.0002, last_price: new_price, volume: 1000 + (rand::random::() % 5000), timestamp: Instant::now(), }; let _ = tx.send(market_data); } tokio::time::sleep(Duration::from_millis(100)).await; } }); } } // ============================================================================ // Mock Backtesting Service // ============================================================================ /// Mock Backtesting Service pub struct MockBacktestingService { backtests: Arc>>, running: Arc>, } #[derive(Debug, Clone)] pub struct MockBacktest { pub id: String, pub strategy_name: String, pub symbols: Vec, pub status: BacktestStatus, pub progress: f64, pub start_time: Instant, pub results: Option, } #[derive(Debug, Clone)] pub enum BacktestStatus { Queued, Running, Completed, Failed, } #[derive(Debug, Clone)] pub struct BacktestResults { pub total_return: f64, pub sharpe_ratio: f64, pub max_drawdown: f64, pub total_trades: u64, } impl MockBacktestingService { pub fn new() -> Self { Self { backtests: Arc::new(RwLock::new(HashMap::new())), running: Arc::new(RwLock::new(false)), } } pub async fn start(&self) -> TestResult<()> { info!("Starting Mock Backtesting Service"); *self.running.write().await = true; Ok(()) } pub async fn stop(&self) -> TestResult<()> { info!("Stopping Mock Backtesting Service"); *self.running.write().await = false; Ok(()) } pub async fn start_backtest(&self, request: StartBacktestRequest) -> TestResult { let backtest_id = Uuid::new_v4().to_string(); let backtest = MockBacktest { id: backtest_id.clone(), strategy_name: request.strategy_name, symbols: request.symbols, status: BacktestStatus::Queued, progress: 0.0, start_time: Instant::now(), results: None, }; self.backtests.write().await.insert(backtest_id.clone(), backtest); // Start backtest execution simulation let backtests = self.backtests.clone(); let id = backtest_id.clone(); tokio::spawn(async move { Self::simulate_backtest_execution(backtests, id).await; }); Ok(StartBacktestResponse { success: true, backtest_id, estimated_duration_seconds: 300, }) } pub async fn get_backtest_status(&self, backtest_id: &str) -> TestResult { let backtests = self.backtests.read().await; if let Some(backtest) = backtests.get(backtest_id) { Ok(BacktestStatusResponse { backtest_id: backtest.id.clone(), status: backtest.status.clone(), progress: backtest.progress, estimated_completion: if backtest.progress > 0.0 { Some(backtest.start_time + Duration::from_secs_f64(300.0 / backtest.progress)) } else { None }, }) } else { Err(TestFrameworkError::CrossServiceIntegrationFailed { reason: format!("Backtest not found: {}", backtest_id), }) } } async fn simulate_backtest_execution( backtests: Arc>>, backtest_id: String, ) { // Simulate backtest progression for progress in (10..=100).step_by(10) { tokio::time::sleep(Duration::from_millis(200)).await; let mut backtests = backtests.write().await; if let Some(backtest) = backtests.get_mut(&backtest_id) { backtest.progress = progress as f64; backtest.status = if progress == 100 { BacktestStatus::Completed } else { BacktestStatus::Running }; if progress == 100 { backtest.results = Some(BacktestResults { total_return: 0.15, // 15% return sharpe_ratio: 1.8, max_drawdown: 0.08, total_trades: 245, }); } } } } } // ============================================================================ // Mock ML Training Service // ============================================================================ /// Mock ML Training Service pub struct MockMLTrainingService { training_jobs: Arc>>, models: Arc>>, running: Arc>, } #[derive(Debug, Clone)] pub struct MockTrainingJob { pub id: String, pub model_name: String, pub model_type: String, pub status: TrainingStatus, pub progress: f64, pub start_time: Instant, } #[derive(Debug, Clone)] pub struct MockModel { pub name: String, pub model_type: String, pub version: String, pub s3_path: String, pub performance_metrics: serde_json::Value, } #[derive(Debug, Clone)] pub enum TrainingStatus { Queued, Training, Completed, Failed, } impl MockMLTrainingService { pub fn new() -> Self { Self { training_jobs: Arc::new(RwLock::new(HashMap::new())), models: Arc::new(RwLock::new(HashMap::new())), running: Arc::new(RwLock::new(false)), } } pub async fn start(&self) -> TestResult<()> { info!("Starting Mock ML Training Service"); *self.running.write().await = true; Ok(()) } pub async fn stop(&self) -> TestResult<()> { info!("Stopping Mock ML Training Service"); *self.running.write().await = false; Ok(()) } pub async fn start_training(&self, request: StartTrainingRequest) -> TestResult { let job_id = Uuid::new_v4().to_string(); let training_job = MockTrainingJob { id: job_id.clone(), model_name: request.model_name, model_type: request.model_type, status: TrainingStatus::Queued, progress: 0.0, start_time: Instant::now(), }; self.training_jobs.write().await.insert(job_id.clone(), training_job); // Start training simulation let training_jobs = self.training_jobs.clone(); let models = self.models.clone(); let id = job_id.clone(); let req = request.clone(); tokio::spawn(async move { Self::simulate_training_execution(training_jobs, models, id, req).await; }); Ok(StartTrainingResponse { success: true, job_id, estimated_duration_minutes: 120, }) } pub async fn get_training_status(&self, job_id: &str) -> TestResult { let training_jobs = self.training_jobs.read().await; if let Some(job) = training_jobs.get(job_id) { Ok(TrainingStatusResponse { job_id: job.id.clone(), status: job.status.clone(), progress: job.progress, current_epoch: (job.progress * 100.0) as u32, loss: 0.001 + (1.0 - job.progress) * 0.1, // Decreasing loss }) } else { Err(TestFrameworkError::CrossServiceIntegrationFailed { reason: format!("Training job not found: {}", job_id), }) } } pub async fn get_model_prediction(&self, request: PredictionRequest) -> TestResult { // Simulate ML inference let inference_start = Instant::now(); tokio::time::sleep(Duration::from_millis(20)).await; // 20ms inference time let inference_latency = inference_start.elapsed(); Ok(PredictionResponse { success: true, predictions: vec![0.75, 0.25], // Buy probability, Sell probability confidence: 0.85, inference_time_ms: inference_latency.as_millis() as u64, }) } async fn simulate_training_execution( training_jobs: Arc>>, models: Arc>>, job_id: String, request: StartTrainingRequest, ) { // Simulate training progression for progress in (5..=100).step_by(5) { tokio::time::sleep(Duration::from_millis(100)).await; let mut jobs = training_jobs.write().await; if let Some(job) = jobs.get_mut(&job_id) { job.progress = progress as f64 / 100.0; job.status = if progress == 100 { TrainingStatus::Completed } else { TrainingStatus::Training }; if progress == 100 { // Create trained model let model = MockModel { name: request.model_name.clone(), model_type: request.model_type.clone(), version: "v1.0".to_string(), s3_path: format!("s3://foxhunt-models/{}/v1.0/model.safetensors", request.model_name), performance_metrics: json!({ "accuracy": 0.94, "precision": 0.91, "recall": 0.89, "f1_score": 0.90 }), }; models.write().await.insert(request.model_name.clone(), model); } } } } } // ============================================================================ // Mock TLI Client // ============================================================================ /// Mock TLI (Terminal Line Interface) Client pub struct MockTLIClient { connected_services: Arc>>, command_history: Arc>>, running: Arc>, } impl MockTLIClient { pub fn new() -> Self { Self { connected_services: Arc::new(RwLock::new(Vec::new())), command_history: Arc::new(RwLock::new(Vec::new())), running: Arc::new(RwLock::new(false)), } } pub async fn start(&self) -> TestResult<()> { info!("Starting Mock TLI Client"); *self.running.write().await = true; Ok(()) } pub async fn stop(&self) -> TestResult<()> { info!("Stopping Mock TLI Client"); *self.running.write().await = false; Ok(()) } pub async fn connect_to_service(&self, service_name: &str, endpoint: &str) -> TestResult<()> { debug!("TLI connecting to service: {} at {}", service_name, endpoint); // Simulate connection tokio::time::sleep(Duration::from_millis(50)).await; self.connected_services.write().await.push(service_name.to_string()); Ok(()) } pub async fn execute_command(&self, command: &str) -> TestResult { debug!("TLI executing command: {}", command); // Store command in history self.command_history.write().await.push(command.to_string()); // Simulate command execution tokio::time::sleep(Duration::from_millis(20)).await; match command { "health" => Ok("All services healthy".to_string()), "status" => Ok("System operational".to_string()), cmd if cmd.starts_with("place_order") => Ok("Order placed successfully".to_string()), cmd if cmd.starts_with("cancel_order") => Ok("Order cancelled".to_string()), _ => Ok(format!("Command executed: {}", command)), } } pub async fn get_connected_services(&self) -> Vec { self.connected_services.read().await.clone() } } // ============================================================================ // Request/Response Types // ============================================================================ #[derive(Debug, Clone)] pub struct PlaceOrderRequest { pub symbol: String, pub side: OrderSide, pub quantity: u64, pub price: f64, } #[derive(Debug, Clone)] pub struct PlaceOrderResponse { pub success: bool, pub order_id: String, pub error_message: String, } #[derive(Debug, Clone)] pub struct OrderStatusResponse { pub order_id: String, pub symbol: String, pub side: OrderSide, pub quantity: u64, pub price: f64, pub status: OrderStatus, pub filled_quantity: u64, } #[derive(Debug, Clone)] pub struct StartBacktestRequest { pub strategy_name: String, pub symbols: Vec, } #[derive(Debug, Clone)] pub struct StartBacktestResponse { pub success: bool, pub backtest_id: String, pub estimated_duration_seconds: u64, } #[derive(Debug, Clone)] pub struct BacktestStatusResponse { pub backtest_id: String, pub status: BacktestStatus, pub progress: f64, pub estimated_completion: Option, } #[derive(Debug, Clone)] pub struct StartTrainingRequest { pub model_name: String, pub model_type: String, } #[derive(Debug, Clone)] pub struct StartTrainingResponse { pub success: bool, pub job_id: String, pub estimated_duration_minutes: u64, } #[derive(Debug, Clone)] pub struct TrainingStatusResponse { pub job_id: String, pub status: TrainingStatus, pub progress: f64, pub current_epoch: u32, pub loss: f64, } #[derive(Debug, Clone)] pub struct PredictionRequest { pub model_name: String, pub features: Vec, } #[derive(Debug, Clone)] pub struct PredictionResponse { pub success: bool, pub predictions: Vec, pub confidence: f64, pub inference_time_ms: u64, }