Files
foxhunt/tests/fixtures/mock_services.rs
jgrusewski 6093eac7bf 🔧 Tonic 0.14 Upgrade: Auto-generated and build system changes
Wave 64-65 cleanup: Proto regeneration and build system updates from Tonic 0.12→0.14 upgrade

Files updated:
- Cargo.lock: Dependency resolution for Tonic 0.14.2
- All build.rs: Updated for tonic-prost-build
- Proto files: Regenerated with tonic-prost 0.14
- Examples/tests: Updated for new gRPC API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-03 07:34:26 +02:00

940 lines
29 KiB
Rust

//! Mock Services for Foxhunt HFT Trading System Testing
//!
//! This module provides mock implementations of all external services
//! for isolated testing without dependencies on real services.
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{mpsc, RwLock, Mutex};
use tokio::time::sleep;
use uuid::Uuid;
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use super::test_config::TestConfig;
// =============================================================================
// MOCK TRADING SERVICE
// =============================================================================
/// Mock trading service for testing trading operations
#[derive(Debug, Clone)]
pub struct MockTradingService {
_config: TestConfig,
orders: Arc<RwLock<HashMap<Uuid, MockOrder>>>,
positions: Arc<RwLock<HashMap<String, MockPosition>>>,
order_events: Arc<Mutex<mpsc::UnboundedSender<MockOrderEvent>>>,
latency_ms: u64,
failure_rate: f64,
next_order_id: Arc<RwLock<u64>>,
}
impl MockTradingService {
pub fn new(config: TestConfig) -> Self {
let (sender, _receiver) = mpsc::unbounded_channel();
let latency_ms = config.services.mock_latency_ms;
let failure_rate = config.services.mock_failure_rate;
Self {
latency_ms,
failure_rate,
_config: config,
orders: Arc::new(RwLock::new(HashMap::new())),
positions: Arc::new(RwLock::new(HashMap::new())),
order_events: Arc::new(Mutex::new(sender)),
next_order_id: Arc::new(RwLock::new(1)),
}
}
/// Submit a new order
pub async fn submit_order(&self, request: MockOrderRequest) -> Result<MockOrderResponse, MockServiceError> {
self.simulate_latency().await;
self.simulate_failure()?;
let order_id = {
let mut next_id = self.next_order_id.write().await;
let id = *next_id;
*next_id += 1;
id
};
let order = MockOrder {
id: Uuid::new_v4(),
order_id,
symbol: request.symbol.clone(),
side: request.side,
quantity: request.quantity,
price: request.price,
order_type: request.order_type,
status: MockOrderStatus::Pending,
filled_quantity: Decimal::ZERO,
average_fill_price: Decimal::ZERO,
created_at: Utc::now(),
updated_at: Utc::now(),
};
// Store the order
self.orders.write().await.insert(order.id, order.clone());
// Simulate order processing in background
let service = self.clone();
let order_id = order.id;
tokio::spawn(async move {
service.process_order(order_id).await;
});
Ok(MockOrderResponse {
order_id: order.id,
status: MockOrderStatus::Pending,
message: "Order submitted successfully".to_string(),
})
}
/// Cancel an existing order
pub async fn cancel_order(&self, order_id: Uuid) -> Result<MockOrderResponse, MockServiceError> {
self.simulate_latency().await;
self.simulate_failure()?;
let mut orders = self.orders.write().await;
if let Some(order) = orders.get_mut(&order_id) {
if matches!(order.status, MockOrderStatus::Pending | MockOrderStatus::PartiallyFilled) {
order.status = MockOrderStatus::Cancelled;
order.updated_at = Utc::now();
Ok(MockOrderResponse {
order_id,
status: MockOrderStatus::Cancelled,
message: "Order cancelled successfully".to_string(),
})
} else {
Err(MockServiceError::InvalidOperation(
format!("Cannot cancel order in status: {:?}", order.status)
))
}
} else {
Err(MockServiceError::OrderNotFound(order_id))
}
}
/// Get order status
pub async fn get_order_status(&self, order_id: Uuid) -> Result<MockOrder, MockServiceError> {
self.simulate_latency().await;
self.simulate_failure()?;
let orders = self.orders.read().await;
orders.get(&order_id)
.cloned()
.ok_or(MockServiceError::OrderNotFound(order_id))
}
/// Get all orders for a symbol
pub async fn get_orders_for_symbol(&self, symbol: &str) -> Result<Vec<MockOrder>, MockServiceError> {
self.simulate_latency().await;
self.simulate_failure()?;
let orders = self.orders.read().await;
let filtered_orders: Vec<MockOrder> = orders
.values()
.filter(|order| order.symbol == symbol)
.cloned()
.collect();
Ok(filtered_orders)
}
/// Get current positions
pub async fn get_positions(&self) -> Result<Vec<MockPosition>, MockServiceError> {
self.simulate_latency().await;
self.simulate_failure()?;
let positions = self.positions.read().await;
Ok(positions.values().cloned().collect())
}
/// Process order asynchronously (simulate fill)
async fn process_order(&self, order_id: Uuid) {
// Wait a bit before processing
sleep(Duration::from_millis(100 + self.latency_ms)).await;
let mut orders = self.orders.write().await;
if let Some(order) = orders.get_mut(&order_id) {
// Simulate partial fill first
order.status = MockOrderStatus::PartiallyFilled;
order.filled_quantity = order.quantity / Decimal::from(2); // Fill 50%
order.average_fill_price = order.price;
order.updated_at = Utc::now();
// Send event
self.send_order_event(order.clone()).await;
// Wait a bit more
drop(orders); // Release lock
sleep(Duration::from_millis(200)).await;
// Complete the fill
let mut orders = self.orders.write().await;
if let Some(order) = orders.get_mut(&order_id) {
order.status = MockOrderStatus::Filled;
order.filled_quantity = order.quantity;
order.updated_at = Utc::now();
// Update position
self.update_position(order).await;
// Send final event
self.send_order_event(order.clone()).await;
}
}
}
/// Update position based on filled order
async fn update_position(&self, order: &MockOrder) {
let mut positions = self.positions.write().await;
let position_key = order.symbol.clone();
let position = positions.entry(position_key).or_insert_with(|| MockPosition {
symbol: order.symbol.clone(),
quantity: Decimal::ZERO,
average_price: Decimal::ZERO,
market_value: Decimal::ZERO,
unrealized_pnl: Decimal::ZERO,
updated_at: Utc::now(),
});
// Update position quantity and average price
let old_quantity = position.quantity;
let old_value = old_quantity * position.average_price;
let order_quantity = match order.side {
MockOrderSide::Buy => order.filled_quantity,
MockOrderSide::Sell => -order.filled_quantity,
};
let new_quantity = old_quantity + order_quantity;
let order_value = order.filled_quantity * order.average_fill_price;
if new_quantity != Decimal::ZERO {
let new_total_value = match order.side {
MockOrderSide::Buy => old_value + order_value,
MockOrderSide::Sell => old_value - order_value,
};
position.average_price = new_total_value / new_quantity.abs();
} else {
position.average_price = Decimal::ZERO;
}
position.quantity = new_quantity;
position.market_value = position.quantity * order.average_fill_price; // Assume market price = fill price
position.unrealized_pnl = (order.average_fill_price - position.average_price) * position.quantity;
position.updated_at = Utc::now();
}
/// Send order event
async fn send_order_event(&self, order: MockOrder) {
let event = MockOrderEvent {
order_id: order.id,
symbol: order.symbol,
status: order.status,
filled_quantity: order.filled_quantity,
timestamp: Utc::now(),
};
if let Ok(sender) = self.order_events.try_lock() {
let _ = sender.send(event);
}
}
async fn simulate_latency(&self) {
if self.latency_ms > 0 {
sleep(Duration::from_millis(self.latency_ms)).await;
}
}
fn simulate_failure(&self) -> Result<(), MockServiceError> {
if self.failure_rate > 0.0 {
use rand::Rng;
let mut rng = rand::thread_rng();
if rng.gen::<f64>() < self.failure_rate {
return Err(MockServiceError::SimulatedFailure);
}
}
Ok(())
}
}
// =============================================================================
// MOCK ML TRAINING SERVICE
// =============================================================================
/// Mock ML training service for testing ML operations
#[derive(Debug, Clone)]
pub struct MockMLTrainingService {
_config: TestConfig,
training_jobs: Arc<RwLock<HashMap<Uuid, MockTrainingJob>>>,
models: Arc<RwLock<HashMap<String, MockModel>>>,
latency_ms: u64,
failure_rate: f64,
}
impl MockMLTrainingService {
pub fn new(config: TestConfig) -> Self {
let latency_ms = config.services.mock_latency_ms;
let failure_rate = config.services.mock_failure_rate;
Self {
latency_ms,
failure_rate,
_config: config,
training_jobs: Arc::new(RwLock::new(HashMap::new())),
models: Arc::new(RwLock::new(HashMap::new())),
}
}
/// Start a new training job
pub async fn start_training(&self, request: MockTrainingRequest) -> Result<MockTrainingResponse, MockServiceError> {
self.simulate_latency().await;
self.simulate_failure()?;
let job_id = Uuid::new_v4();
let job = MockTrainingJob {
id: job_id,
model_name: request.model_name.clone(),
model_type: request.model_type,
status: MockTrainingStatus::Running,
progress: 0.0,
start_time: Utc::now(),
end_time: None,
metrics: HashMap::new(),
error_message: None,
};
self.training_jobs.write().await.insert(job_id, job.clone());
// Simulate training in background
let service = self.clone();
tokio::spawn(async move {
service.simulate_training(job_id).await;
});
Ok(MockTrainingResponse {
job_id,
status: MockTrainingStatus::Running,
message: "Training job started".to_string(),
})
}
/// Get training job status
pub async fn get_training_status(&self, job_id: Uuid) -> Result<MockTrainingJob, MockServiceError> {
self.simulate_latency().await;
self.simulate_failure()?;
let jobs = self.training_jobs.read().await;
jobs.get(&job_id)
.cloned()
.ok_or(MockServiceError::JobNotFound(job_id))
}
/// Get available models
pub async fn get_models(&self) -> Result<Vec<MockModel>, MockServiceError> {
self.simulate_latency().await;
self.simulate_failure()?;
let models = self.models.read().await;
Ok(models.values().cloned().collect())
}
/// Run inference with a model
pub async fn run_inference(&self, request: MockInferenceRequest) -> Result<MockInferenceResponse, MockServiceError> {
self.simulate_latency().await;
self.simulate_failure()?;
// Simulate inference calculation
let prediction = match request.model_name.as_str() {
"momentum_model" => 0.75, // Bullish
"mean_reversion_model" => -0.25, // Bearish
_ => 0.0, // Neutral
};
Ok(MockInferenceResponse {
prediction,
confidence: 0.85,
features_used: request.features.len(),
inference_time_ms: self.latency_ms,
})
}
/// Simulate training process
async fn simulate_training(&self, job_id: Uuid) {
let training_duration = Duration::from_secs(5); // Simulate 5 second training
let update_interval = Duration::from_millis(500);
let total_updates = training_duration.as_millis() / update_interval.as_millis();
for i in 0..total_updates {
sleep(update_interval).await;
let progress = (i as f64 + 1.0) / total_updates as f64;
// Update job progress
if let Ok(mut jobs) = self.training_jobs.try_write() {
if let Some(job) = jobs.get_mut(&job_id) {
job.progress = progress;
// Add some metrics
job.metrics.insert("loss".to_string(), 1.0 - (progress * 0.8));
job.metrics.insert("accuracy".to_string(), 0.5 + (progress * 0.4));
if progress >= 1.0 {
job.status = MockTrainingStatus::Completed;
job.end_time = Some(Utc::now());
// Save the trained model
let model = MockModel {
name: job.model_name.clone(),
model_type: job.model_type,
version: "1.0.0".to_string(),
accuracy: job.metrics.get("accuracy").copied().unwrap_or(0.9),
created_at: Utc::now(),
file_path: format!("/models/{}_v1.0.0.bin", job.model_name),
};
if let Ok(mut models) = self.models.try_write() {
models.insert(model.name.clone(), model);
}
}
}
}
}
}
async fn simulate_latency(&self) {
if self.latency_ms > 0 {
sleep(Duration::from_millis(self.latency_ms)).await;
}
}
fn simulate_failure(&self) -> Result<(), MockServiceError> {
if self.failure_rate > 0.0 {
use rand::Rng;
let mut rng = rand::thread_rng();
if rng.gen::<f64>() < self.failure_rate {
return Err(MockServiceError::SimulatedFailure);
}
}
Ok(())
}
}
// =============================================================================
// MOCK BACKTESTING SERVICE
// =============================================================================
/// Mock backtesting service for testing strategy backtests
#[derive(Debug, Clone)]
pub struct MockBacktestingService {
_config: TestConfig,
backtests: Arc<RwLock<HashMap<Uuid, MockBacktest>>>,
latency_ms: u64,
failure_rate: f64,
}
impl MockBacktestingService {
pub fn new(config: TestConfig) -> Self {
let latency_ms = config.services.mock_latency_ms;
let failure_rate = config.services.mock_failure_rate;
Self {
latency_ms,
failure_rate,
_config: config,
backtests: Arc::new(RwLock::new(HashMap::new())),
}
}
/// Start a new backtest
pub async fn start_backtest(&self, request: MockBacktestRequest) -> Result<MockBacktestResponse, MockServiceError> {
self.simulate_latency().await;
self.simulate_failure()?;
let backtest_id = Uuid::new_v4();
let backtest = MockBacktest {
id: backtest_id,
strategy_name: request.strategy_name.clone(),
symbols: request.symbols,
start_date: request.start_date,
end_date: request.end_date,
status: MockBacktestStatus::Running,
progress: 0.0,
results: None,
start_time: Utc::now(),
end_time: None,
};
self.backtests.write().await.insert(backtest_id, backtest);
// Simulate backtest in background
let service = self.clone();
tokio::spawn(async move {
service.simulate_backtest(backtest_id).await;
});
Ok(MockBacktestResponse {
backtest_id,
status: MockBacktestStatus::Running,
message: "Backtest started".to_string(),
})
}
/// Get backtest status
pub async fn get_backtest_status(&self, backtest_id: Uuid) -> Result<MockBacktest, MockServiceError> {
self.simulate_latency().await;
self.simulate_failure()?;
let backtests = self.backtests.read().await;
backtests.get(&backtest_id)
.cloned()
.ok_or(MockServiceError::BacktestNotFound(backtest_id))
}
/// Get all backtests
pub async fn get_backtests(&self) -> Result<Vec<MockBacktest>, MockServiceError> {
self.simulate_latency().await;
self.simulate_failure()?;
let backtests = self.backtests.read().await;
Ok(backtests.values().cloned().collect())
}
/// Simulate backtest process
async fn simulate_backtest(&self, backtest_id: Uuid) {
let backtest_duration = Duration::from_secs(3); // Simulate 3 second backtest
let update_interval = Duration::from_millis(300);
let total_updates = backtest_duration.as_millis() / update_interval.as_millis();
for i in 0..total_updates {
sleep(update_interval).await;
let progress = (i as f64 + 1.0) / total_updates as f64;
if let Ok(mut backtests) = self.backtests.try_write() {
if let Some(backtest) = backtests.get_mut(&backtest_id) {
backtest.progress = progress;
if progress >= 1.0 {
backtest.status = MockBacktestStatus::Completed;
backtest.end_time = Some(Utc::now());
// Generate mock results
backtest.results = Some(MockBacktestResults {
total_return: 0.15, // 15% return
annualized_return: 0.12,
volatility: 0.18,
sharpe_ratio: 0.67,
max_drawdown: 0.08,
win_rate: 0.58,
total_trades: 1250,
profit_factor: 1.35,
});
}
}
}
}
}
async fn simulate_latency(&self) {
if self.latency_ms > 0 {
sleep(Duration::from_millis(self.latency_ms)).await;
}
}
fn simulate_failure(&self) -> Result<(), MockServiceError> {
if self.failure_rate > 0.0 {
use rand::Rng;
let mut rng = rand::thread_rng();
if rng.gen::<f64>() < self.failure_rate {
return Err(MockServiceError::SimulatedFailure);
}
}
Ok(())
}
}
// =============================================================================
// DATA STRUCTURES
// =============================================================================
/// Mock order structure
#[derive(Debug, Clone)]
pub struct MockOrder {
pub id: Uuid,
pub order_id: u64,
pub symbol: String,
pub side: MockOrderSide,
pub quantity: Decimal,
pub price: Decimal,
pub order_type: MockOrderType,
pub status: MockOrderStatus,
pub filled_quantity: Decimal,
pub average_fill_price: Decimal,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MockOrderSide {
Buy,
Sell,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MockOrderType {
Market,
Limit,
Stop,
StopLimit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MockOrderStatus {
Pending,
PartiallyFilled,
Filled,
Cancelled,
Rejected,
}
/// Mock position structure
#[derive(Debug, Clone)]
pub struct MockPosition {
pub symbol: String,
pub quantity: Decimal,
pub average_price: Decimal,
pub market_value: Decimal,
pub unrealized_pnl: Decimal,
pub updated_at: DateTime<Utc>,
}
/// Mock order request
#[derive(Debug, Clone)]
pub struct MockOrderRequest {
pub symbol: String,
pub side: MockOrderSide,
pub quantity: Decimal,
pub price: Decimal,
pub order_type: MockOrderType,
}
/// Mock order response
#[derive(Debug, Clone)]
pub struct MockOrderResponse {
pub order_id: Uuid,
pub status: MockOrderStatus,
pub message: String,
}
/// Mock order event
#[derive(Debug, Clone)]
pub struct MockOrderEvent {
pub order_id: Uuid,
pub symbol: String,
pub status: MockOrderStatus,
pub filled_quantity: Decimal,
pub timestamp: DateTime<Utc>,
}
/// Mock training job
#[derive(Debug, Clone)]
pub struct MockTrainingJob {
pub id: Uuid,
pub model_name: String,
pub model_type: MockModelType,
pub status: MockTrainingStatus,
pub progress: f64,
pub start_time: DateTime<Utc>,
pub end_time: Option<DateTime<Utc>>,
pub metrics: HashMap<String, f64>,
pub error_message: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MockModelType {
Mamba,
Transformer,
DQN,
PPO,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MockTrainingStatus {
Running,
Completed,
Failed,
Cancelled,
}
/// Mock model
#[derive(Debug, Clone)]
pub struct MockModel {
pub name: String,
pub model_type: MockModelType,
pub version: String,
pub accuracy: f64,
pub created_at: DateTime<Utc>,
pub file_path: String,
}
/// Mock training request
#[derive(Debug, Clone)]
pub struct MockTrainingRequest {
pub model_name: String,
pub model_type: MockModelType,
pub data_path: String,
pub hyperparameters: HashMap<String, serde_json::Value>,
}
/// Mock training response
#[derive(Debug, Clone)]
pub struct MockTrainingResponse {
pub job_id: Uuid,
pub status: MockTrainingStatus,
pub message: String,
}
/// Mock inference request
#[derive(Debug, Clone)]
pub struct MockInferenceRequest {
pub model_name: String,
pub features: Vec<f64>,
}
/// Mock inference response
#[derive(Debug, Clone)]
pub struct MockInferenceResponse {
pub prediction: f64,
pub confidence: f64,
pub features_used: usize,
pub inference_time_ms: u64,
}
/// Mock backtest
#[derive(Debug, Clone)]
pub struct MockBacktest {
pub id: Uuid,
pub strategy_name: String,
pub symbols: Vec<String>,
pub start_date: DateTime<Utc>,
pub end_date: DateTime<Utc>,
pub status: MockBacktestStatus,
pub progress: f64,
pub results: Option<MockBacktestResults>,
pub start_time: DateTime<Utc>,
pub end_time: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MockBacktestStatus {
Running,
Completed,
Failed,
Cancelled,
}
/// Mock backtest results
#[derive(Debug, Clone)]
pub struct MockBacktestResults {
pub total_return: f64,
pub annualized_return: f64,
pub volatility: f64,
pub sharpe_ratio: f64,
pub max_drawdown: f64,
pub win_rate: f64,
pub total_trades: u64,
pub profit_factor: f64,
}
/// Mock backtest request
#[derive(Debug, Clone)]
pub struct MockBacktestRequest {
pub strategy_name: String,
pub symbols: Vec<String>,
pub start_date: DateTime<Utc>,
pub end_date: DateTime<Utc>,
pub initial_capital: Decimal,
}
/// Mock backtest response
#[derive(Debug, Clone)]
pub struct MockBacktestResponse {
pub backtest_id: Uuid,
pub status: MockBacktestStatus,
pub message: String,
}
/// Mock service errors
#[derive(Debug, Clone, thiserror::Error)]
pub enum MockServiceError {
#[error("Order not found: {0}")]
OrderNotFound(Uuid),
#[error("Job not found: {0}")]
JobNotFound(Uuid),
#[error("Backtest not found: {0}")]
BacktestNotFound(Uuid),
#[error("Invalid operation: {0}")]
InvalidOperation(String),
#[error("Simulated failure")]
SimulatedFailure,
#[error("Service unavailable")]
ServiceUnavailable,
}
// =============================================================================
// SERVICE FACTORY
// =============================================================================
/// Factory for creating mock services
#[derive(Debug)]
pub struct MockServiceFactory {
config: TestConfig,
}
impl MockServiceFactory {
pub fn new(config: TestConfig) -> Self {
Self { config }
}
pub fn create_trading_service(&self) -> MockTradingService {
MockTradingService::new(self.config.clone())
}
pub fn create_ml_training_service(&self) -> MockMLTrainingService {
MockMLTrainingService::new(self.config.clone())
}
pub fn create_backtesting_service(&self) -> MockBacktestingService {
MockBacktestingService::new(self.config.clone())
}
/// Create all services
pub fn create_all_services(&self) -> (MockTradingService, MockMLTrainingService, MockBacktestingService) {
(
self.create_trading_service(),
self.create_ml_training_service(),
self.create_backtesting_service(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fixtures::TEST_EQUITY_1;
#[tokio::test]
async fn test_mock_trading_service() {
let config = TestConfig::for_unit_tests();
let service = MockTradingService::new(config);
let request = MockOrderRequest {
symbol: TEST_EQUITY_1.to_string(),
side: MockOrderSide::Buy,
quantity: Decimal::from(100),
price: Decimal::from(150),
order_type: MockOrderType::Limit,
};
let response = service.submit_order(request).await.unwrap();
assert_eq!(response.status, MockOrderStatus::Pending);
// Wait for processing
tokio::time::sleep(Duration::from_millis(500)).await;
let order = service.get_order_status(response.order_id).await.unwrap();
assert_eq!(order.status, MockOrderStatus::Filled);
assert_eq!(order.filled_quantity, order.quantity);
}
#[tokio::test]
async fn test_mock_ml_training_service() {
let config = TestConfig::for_unit_tests();
let service = MockMLTrainingService::new(config);
let request = MockTrainingRequest {
model_name: "test_model".to_string(),
model_type: MockModelType::Transformer,
data_path: "/data/test.csv".to_string(),
hyperparameters: HashMap::new(),
};
let response = service.start_training(request).await.unwrap();
assert_eq!(response.status, MockTrainingStatus::Running);
// Wait for training to complete
tokio::time::sleep(Duration::from_secs(6)).await;
let job = service.get_training_status(response.job_id).await.unwrap();
assert_eq!(job.status, MockTrainingStatus::Completed);
assert_eq!(job.progress, 1.0);
}
#[tokio::test]
async fn test_mock_backtesting_service() {
let config = TestConfig::for_unit_tests();
let service = MockBacktestingService::new(config);
let request = MockBacktestRequest {
strategy_name: "test_strategy".to_string(),
symbols: vec![TEST_EQUITY_1.to_string()],
start_date: Utc::now() - chrono::Duration::days(30),
end_date: Utc::now(),
initial_capital: Decimal::from(100000),
};
let response = service.start_backtest(request).await.unwrap();
assert_eq!(response.status, MockBacktestStatus::Running);
// Wait for backtest to complete
tokio::time::sleep(Duration::from_secs(4)).await;
let backtest = service.get_backtest_status(response.backtest_id).await.unwrap();
assert_eq!(backtest.status, MockBacktestStatus::Completed);
assert!(backtest.results.is_some());
}
#[tokio::test]
async fn test_service_factory() {
let config = TestConfig::for_unit_tests();
let factory = MockServiceFactory::new(config);
let (trading, ml, backtesting) = factory.create_all_services();
// Test that all services are created
assert_eq!(trading.latency_ms, 0);
assert_eq!(ml.latency_ms, 0);
assert_eq!(backtesting.latency_ms, 0);
}
#[tokio::test]
async fn test_failure_simulation() {
let mut config = TestConfig::for_unit_tests();
config.services.mock_failure_rate = 1.0; // 100% failure rate
let service = MockTradingService::new(config);
let request = MockOrderRequest {
symbol: TEST_EQUITY_1.to_string(),
side: MockOrderSide::Buy,
quantity: Decimal::from(100),
price: Decimal::from(150),
order_type: MockOrderType::Limit,
};
let result = service.submit_order(request).await;
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), MockServiceError::SimulatedFailure));
}
}