//! Test utilities and helper functions //! //! Common testing utilities shared across all test modules. use core::types::prelude::*; // CANONICAL TYPE IMPORTS - Use types::prelude::Decimal use std::collections::HashMap; /// Test data generators for consistent test scenarios pub struct TestDataGenerator { order_counter: std::sync::atomic::AtomicU64, trade_counter: std::sync::atomic::AtomicU64, } impl TestDataGenerator { pub fn new() -> Self { Self { order_counter: std::sync::atomic::AtomicU64::new(1), trade_counter: std::sync::atomic::AtomicU64::new(1), } } pub fn generate_order(&self, symbol: &str, side: Side, quantity: u32, price: Option) -> Order { let order_id = self.order_counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst); Order { id: OrderId::new(format!("TEST-ORDER-{:06}", order_id)), symbol: Symbol::new(symbol.to_string()).map_err(|e| format!("Failed to create symbol: {}", e)).unwrap(), side, order_type: if price.is_some() { OrderType::Limit } else { OrderType::Market }, quantity: Quantity::new(Decimal::from(quantity)).map_err(|e| format!("Failed to create quantity: {}", e)).unwrap(), price: price.map(|p| Price::new(Decimal::from_f64_retain(p).unwrap()).map_err(|e| format!("Failed to create price: {}", e)).unwrap()), time_in_force: TimeInForce::Day, timestamp: chrono::Utc::now(), status: OrderStatus::New, client_id: ClientId::new("TEST-CLIENT".to_string()), } } pub fn generate_fill(&self, order: &Order, fill_quantity: Option, fill_price: Option) -> Fill { let trade_id = self.trade_counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst); let fill_id = format!("FILL-{:06}", trade_id); let quantity = fill_quantity .map(|q| Quantity::new(Decimal::from(q)).map_err(|e| format!("Failed to create fill quantity: {}", e)).unwrap()) .unwrap_or(order.quantity.clone()); let price = fill_price .map(|p| Price::new(Decimal::from_f64_retain(p).unwrap()).map_err(|e| format!("Failed to create fill price: {}", e)).unwrap()) .or_else(|| order.price.clone()) .unwrap_or_else(|| Price::new(Decimal::from(100)).map_err(|e| format!("Failed to create default price: {}", e)).unwrap()); Fill { id: FillId::new(fill_id), order_id: order.id.clone(), trade_id: TradeId::new(format!("TRADE-{:06}", trade_id)), symbol: order.symbol.clone(), side: order.side, quantity, price, timestamp: chrono::Utc::now(), commission: Some(Money::new(Decimal::from_str("1.00").map_err(|e| format!("Failed to create commission decimal: {}", e)).unwrap(), Currency::USD)), } } pub fn generate_position(&self, symbol: &str, quantity: i32, avg_price: f64) -> Position { Position { symbol: Symbol::new(symbol.to_string()).map_err(|e| format!("Failed to create symbol: {}", e)).unwrap(), quantity: Quantity::new(Decimal::from(quantity.abs())).map_err(|e| format!("Failed to create quantity: {}", e)).unwrap(), average_price: Price::new(Decimal::from_f64_retain(avg_price).unwrap()).map_err(|e| format!("Failed to create average price: {}", e)).unwrap(), realized_pnl: PnL::new(Decimal::ZERO).map_err(|e| format!("Failed to create realized PnL: {}", e)).unwrap(), unrealized_pnl: PnL::new(Decimal::ZERO).map_err(|e| format!("Failed to create unrealized PnL: {}", e)).unwrap(), last_updated: chrono::Utc::now(), } } pub fn generate_market_data_event(&self, symbol: &str, price: f64, volume: u64) -> MarketDataEvent { MarketDataEvent { symbol: Symbol::new(symbol.to_string()).map_err(|e| format!("Failed to create symbol: {}", e)).unwrap(), price: Price::new(Decimal::from_f64_retain(price).unwrap()).map_err(|e| format!("Failed to create price: {}", e)).unwrap(), timestamp: chrono::Utc::now(), volume: Volume::new(Decimal::from(volume)).map_err(|e| format!("Failed to create volume: {}", e)).unwrap(), } } } impl Default for TestDataGenerator { fn default() -> Self { Self::new() } } /// Mock market data provider for testing pub struct MockMarketDataProvider { prices: HashMap, volumes: HashMap, subscribers: Vec>, } impl MockMarketDataProvider { pub fn new() -> Self { Self { prices: HashMap::new(), volumes: HashMap::new(), subscribers: Vec::new(), } } pub fn set_price(&mut self, symbol: Symbol, price: Price) { self.prices.insert(symbol.clone(), price.clone()); let event = MarketDataEvent { symbol, price, timestamp: chrono::Utc::now(), volume: Volume::new(Decimal::from(1000)).map_err(|e| format!("Failed to create volume: {}", e)).unwrap(), }; // Notify subscribers self.subscribers.retain(|sender| { sender.send(event.clone()).is_ok() }); } pub fn get_price(&self, symbol: &Symbol) -> Option<&Price> { self.prices.get(symbol) } pub fn subscribe(&mut self) -> tokio::sync::mpsc::UnboundedReceiver { let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); self.subscribers.push(sender); receiver } } /// Test assertions for financial calculations pub struct FinancialAssertions; impl FinancialAssertions { pub fn assert_price_equal(actual: &Price, expected: &Price, tolerance: Option) { let tolerance = tolerance.unwrap_or_else(|| Decimal::from_str("0.01").map_err(|e| format!("Failed to create tolerance: {}", e)).unwrap()); let diff = (actual.value() - expected.value()).abs(); assert!( diff <= tolerance, "Price assertion failed: actual={}, expected={}, tolerance={}, diff={}", actual.value(), expected.value(), tolerance, diff ); } pub fn assert_quantity_equal(actual: &Quantity, expected: &Quantity, tolerance: Option) { let tolerance = tolerance.unwrap_or_else(|| Decimal::from_str("0.000001").map_err(|e| format!("Failed to create tolerance: {}", e)).unwrap()); let diff = (actual.value() - expected.value()).abs(); assert!( diff <= tolerance, "Quantity assertion failed: actual={}, expected={}, tolerance={}, diff={}", actual.value(), expected.value(), tolerance, diff ); } pub fn assert_pnl_equal(actual: &PnL, expected: &PnL, tolerance: Option) { let tolerance = tolerance.unwrap_or_else(|| Decimal::from_str("0.01").map_err(|e| format!("Failed to create tolerance: {}", e)).unwrap()); let diff = (actual.value() - expected.value()).abs(); assert!( diff <= tolerance, "PnL assertion failed: actual={}, expected={}, tolerance={}, diff={}", actual.value(), expected.value(), tolerance, diff ); } pub fn assert_money_equal(actual: &Money, expected: &Money, tolerance: Option) { assert_eq!(actual.currency, expected.currency, "Currency mismatch"); let tolerance = tolerance.unwrap_or_else(|| Decimal::from_str("0.01").map_err(|e| format!("Failed to create tolerance: {}", e)).unwrap()); let diff = (actual.amount - expected.amount).abs(); assert!( diff <= tolerance, "Money assertion failed: actual={}, expected={}, tolerance={}, diff={}", actual.amount, expected.amount, tolerance, diff ); } pub fn assert_percentage_equal(actual: Decimal, expected: Decimal, tolerance: Option) { let tolerance = tolerance.unwrap_or_else(|| Decimal::from_str("0.01").map_err(|e| format!("Failed to create tolerance: {}", e)).unwrap()); // 1% let diff = (actual - expected).abs(); assert!( diff <= tolerance, "Percentage assertion failed: actual={}%, expected={}%, tolerance={}%, diff={}%", actual * Decimal::from(100), expected * Decimal::from(100), tolerance * Decimal::from(100), diff * Decimal::from(100) ); } } /// Performance measurement utilities for tests pub struct PerformanceMeasurement { measurements: HashMap, } impl PerformanceMeasurement { pub fn new() -> Self { Self { measurements: HashMap::new(), } } pub fn measure(&mut self, name: &str, f: F) -> R where F: FnOnce() -> R, { let start = std::time::Instant::now(); let result = f(); let duration = start.elapsed(); self.measurements.insert(name.to_string(), duration); result } pub async fn measure_async(&mut self, name: &str, f: F) -> R where F: FnOnce() -> Fut, Fut: std::future::Future, { let start = std::time::Instant::now(); let result = f().await; let duration = start.elapsed(); self.measurements.insert(name.to_string(), duration); result } pub fn get_measurement(&self, name: &str) -> Option { self.measurements.get(name).copied() } pub fn assert_performance(&self, name: &str, max_duration: std::time::Duration) { if let Some(actual) = self.get_measurement(name) { assert!( actual <= max_duration, "Performance assertion failed for '{}': actual={:?}, max={:?}", name, actual, max_duration ); } else { panic!("No measurement found for '{}'", name); } } pub fn print_summary(&self) { println!("\n=== Performance Summary ==="); for (name, duration) in &self.measurements { println!("{}: {:?}", name, duration); } println!("==========================\n"); } } impl Default for PerformanceMeasurement { fn default() -> Self { Self::new() } } /// Test scenario builders for complex integration tests pub struct TestScenarioBuilder { orders: Vec, market_events: Vec, expected_fills: Vec, generator: TestDataGenerator, } impl TestScenarioBuilder { pub fn new() -> Self { Self { orders: Vec::new(), market_events: Vec::new(), expected_fills: Vec::new(), generator: TestDataGenerator::new(), } } pub fn with_buy_order(mut self, symbol: &str, quantity: u32, price: f64) -> Self { let order = self.generator.generate_order(symbol, Side::Buy, quantity, Some(price)); self.orders.push(order); self } pub fn with_sell_order(mut self, symbol: &str, quantity: u32, price: f64) -> Self { let order = self.generator.generate_order(symbol, Side::Sell, quantity, Some(price)); self.orders.push(order); self } pub fn with_market_data(mut self, symbol: &str, price: f64, volume: u64) -> Self { let event = self.generator.generate_market_data_event(symbol, price, volume); self.market_events.push(event); self } pub fn expect_fill(mut self, order_index: usize, quantity: Option, price: Option) -> Self { if let Some(order) = self.orders.get(order_index) { let fill = self.generator.generate_fill(order, quantity, price); self.expected_fills.push(fill); } self } pub fn build(self) -> TestScenario { TestScenario { orders: self.orders, market_events: self.market_events, expected_fills: self.expected_fills, } } } impl Default for TestScenarioBuilder { fn default() -> Self { Self::new() } } pub struct TestScenario { pub orders: Vec, pub market_events: Vec, pub expected_fills: Vec, } /// Database test utilities pub struct DatabaseTestUtils; impl DatabaseTestUtils { pub async fn setup_test_database() -> Result> { // Return a test database connection string // In a real implementation, this would set up a temporary database Ok(std::env::var("TEST_DATABASE_URL").unwrap_or_else(|_| "postgresql://localhost/foxhunt_test".to_string())) } pub async fn cleanup_test_database(_connection_string: &str) -> Result<(), Box> { // Clean up test database Ok(()) } pub async fn insert_test_data(_connection: &str, _data: &[T]) -> Result<(), Box> where T: serde::Serialize, { // Insert test data into database Ok(()) } } /// Concurrency test utilities pub struct ConcurrencyTestUtils; impl ConcurrencyTestUtils { pub async fn run_concurrent_operations( operations: Vec, max_concurrent: usize, ) -> Vec>> where F: FnOnce() -> Fut + Send + 'static, Fut: std::future::Future>> + Send + 'static, R: Send + 'static, { use tokio::sync::Semaphore; use std::sync::Arc; let semaphore = Arc::new(Semaphore::new(max_concurrent)); let mut handles = Vec::new(); for operation in operations { let permit = semaphore.clone().acquire_owned().await.unwrap(); let handle = tokio::spawn(async move { let _permit = permit; operation().await }); handles.push(handle); } let mut results = Vec::new(); for handle in handles { match handle.await { Ok(result) => results.push(result), Err(e) => results.push(Err(Box::new(e) as Box)), } } results } pub async fn stress_test_operation( operation: F, num_iterations: usize, max_concurrent: usize, ) -> StressTestResults where F: Fn() -> Fut + Send + Sync + 'static, Fut: std::future::Future>> + Send + 'static, R: Send + 'static, F: Clone, { let start_time = std::time::Instant::now(); let operations = (0..num_iterations).map(|_| operation.clone()).collect(); let results = Self::run_concurrent_operations(operations, max_concurrent).await; let total_duration = start_time.elapsed(); let successful = results.iter().filter(|r| r.is_ok()).count(); let failed = results.len() - successful; StressTestResults { total_operations: num_iterations, successful_operations: successful, failed_operations: failed, total_duration, operations_per_second: num_iterations as f64 / total_duration.as_secs_f64(), results, } } } pub struct StressTestResults { pub total_operations: usize, pub successful_operations: usize, pub failed_operations: usize, pub total_duration: std::time::Duration, pub operations_per_second: f64, pub results: Vec>>, } impl StressTestResults { pub fn success_rate(&self) -> f64 { self.successful_operations as f64 / self.total_operations as f64 } pub fn assert_success_rate(&self, minimum_rate: f64) { let actual_rate = self.success_rate(); assert!( actual_rate >= minimum_rate, "Success rate too low: actual={:.2}%, minimum={:.2}%", actual_rate * 100.0, minimum_rate * 100.0 ); } pub fn assert_throughput(&self, minimum_ops_per_sec: f64) { assert!( self.operations_per_second >= minimum_ops_per_sec, "Throughput too low: actual={:.2} ops/sec, minimum={:.2} ops/sec", self.operations_per_second, minimum_ops_per_sec ); } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_data_generator() { let generator = TestDataGenerator::new(); let order1 = generator.generate_order("AAPL", Side::Buy, 100, Some(150.0)); let order2 = generator.generate_order("MSFT", Side::Sell, 200, Some(300.0)); // Orders should have unique IDs assert_ne!(order1.id.value(), order2.id.value()); // Order properties should match inputs assert_eq!(order1.symbol.as_str(), "AAPL"); assert_eq!(order1.side, Side::Buy); assert_eq!(order1.quantity.value(), Decimal::from(100)); assert_eq!(order1.price.as_ref().unwrap().value(), Decimal::from(150)); } #[tokio::test] async fn test_financial_assertions() { let price1 = Price::new(Decimal::from_str("150.00").unwrap()).map_err(|e| format!("Failed to create test price1: {}", e)).unwrap(); let price2 = Price::new(Decimal::from_str("150.01").unwrap()).map_err(|e| format!("Failed to create test price2: {}", e)).unwrap(); // Should pass with default tolerance FinancialAssertions::assert_price_equal(&price1, &price2, None); // Should pass with custom tolerance FinancialAssertions::assert_price_equal( &price1, &price2, Some(Decimal::from_str("0.02").map_err(|e| format!("Failed to create tolerance decimal: {}", e)).unwrap()) ); } #[test] fn test_performance_measurement() { let mut perf = PerformanceMeasurement::new(); let result = perf.measure("test_operation", || { std::thread::sleep(std::time::Duration::from_millis(10)); 42 }); assert_eq!(result, 42); let measurement = perf.get_measurement("test_operation"); assert!(measurement.is_some()); assert!(measurement.unwrap() >= std::time::Duration::from_millis(10)); // Should assert performance within reasonable bounds perf.assert_performance("test_operation", std::time::Duration::from_millis(50)); } #[tokio::test] async fn test_scenario_builder() { let scenario = TestScenarioBuilder::new() .with_buy_order("AAPL", 100, 150.0) .with_sell_order("AAPL", 50, 151.0) .with_market_data("AAPL", 150.5, 10000) .expect_fill(0, Some(50), Some(150.5)) .build(); assert_eq!(scenario.orders.len(), 2); assert_eq!(scenario.market_events.len(), 1); assert_eq!(scenario.expected_fills.len(), 1); // Verify scenario structure assert_eq!(scenario.orders[0].side, Side::Buy); assert_eq!(scenario.orders[1].side, Side::Sell); assert_eq!(scenario.market_events[0].symbol.as_str(), "AAPL"); assert_eq!(scenario.expected_fills[0].quantity.value(), Decimal::from(50)); } }