#![allow(clippy::mixed_attributes_style)] // Generated protobuf code uses mixed attributes #![allow(clippy::empty_line_after_doc_comments)] // Doc comment formatting acceptable #![allow(clippy::redundant_pattern_matching)] // Explicit pattern matching preferred in tests #![allow(clippy::manual_clamp)] // Explicit min/max preferred in test code #![allow(clippy::unnecessary_to_owned)] // Explicit to_string in test HTML generation //! Foxhunt E2E Testing Framework //! //! Comprehensive End-to-End testing framework for the Foxhunt High-Frequency Trading system. //! Tests complete integration between TLI client, Trading Service, Backtesting Service, //! ML Training Service, database interactions, and complete trading workflows. pub mod clients; pub mod database; pub mod framework; pub mod ml_pipeline; pub mod performance; pub mod proto; pub mod services; pub mod utils; pub mod workflows; // Re-export commonly used types from workflows pub use workflows::WorkflowTestResult; // Re-export E2ETestFramework for public access in tests pub use crate::framework::E2ETestFramework; use anyhow::Result; use std::future::Future; use std::pin::Pin; /// E2E Test Result type pub type E2ETestResult = Result; /// Async test function trait pub type E2ETestFn = fn(E2ETestFramework) -> Pin + Send>>; /// E2E test macro for creating standardized tests /// /// This macro provides: /// - Automatic service orchestration /// /// - Test framework initialization /// - Proper cleanup on test completion or failure /// /// - Performance monitoring /// - Comprehensive logging /// /// # Example /// /// ```rust /// use foxhunt_e2e::{e2e_test, E2ETestFramework, E2ETestResult}; /// use std::sync::Arc; /// /// // Basic usage with owned framework /// e2e_test!(test_basic_trading_flow, |framework: E2ETestFramework| async { /// let trading_client = framework.get_trading_client().await?; /// /// let response = trading_client.get_account_info().await?; /// assert!(response.total_value > 0.0); /// /// Ok(()) /// }); /// /// // Usage with Arc and async move /// e2e_test!(test_with_arc, |framework: Arc| async move { /// let workflows = SomeWorkflow::new(framework); /// workflows.test_something().await?; /// /// Ok(()) /// }); /// ``` #[macro_export] macro_rules! e2e_test { // Pattern 1: async move closure with mut (captures framework by value) ($test_name:ident, |mut $framework:ident: $framework_type:ty| async move $test_body:block) => { #[tokio::test] async fn $test_name() -> $crate::E2ETestResult { use tracing::{info, error, warn}; use std::time::Instant; info!("🚀 Starting E2E test: {}", stringify!($test_name)); let start_time = Instant::now(); // Initialize the test framework let mut framework_instance = match $crate::framework::E2ETestFramework::new().await { Ok(framework) => { info!("✅ E2E test framework initialized successfully"); framework } Err(e) => { error!("❌ Failed to initialize E2E test framework: {}", e); return Err(e); } }; // Start services if needed if let Err(e) = framework_instance.start_services().await { error!("❌ Failed to start services: {}", e); return Err(e); } // Execute the test body (async move closure) let test_result: $crate::E2ETestResult = { let mut $framework: $framework_type = framework_instance; (async move $test_body).await }; // Cleanup and report results match &test_result { Ok(_) => { let duration = start_time.elapsed(); info!("✅ E2E test {} completed successfully in {:?}", stringify!($test_name), duration); } Err(e) => { let duration = start_time.elapsed(); error!("❌ E2E test {} failed after {:?}: {}", stringify!($test_name), duration, e); } } test_result } }; // Pattern 2: async move closure (captures framework by value) ($test_name:ident, |$framework:ident: $framework_type:ty| async move $test_body:block) => { #[tokio::test] async fn $test_name() -> $crate::E2ETestResult { use tracing::{info, error, warn}; use std::time::Instant; info!("🚀 Starting E2E test: {}", stringify!($test_name)); let start_time = Instant::now(); // Initialize the test framework let mut framework_instance = match $crate::framework::E2ETestFramework::new().await { Ok(framework) => { info!("✅ E2E test framework initialized successfully"); framework } Err(e) => { error!("❌ Failed to initialize E2E test framework: {}", e); return Err(e); } }; // Start services if needed if let Err(e) = framework_instance.start_services().await { error!("❌ Failed to start services: {}", e); return Err(e); } // Execute the test body (async move closure) let test_result: $crate::E2ETestResult = { let $framework: $framework_type = std::sync::Arc::new(framework_instance); (async move $test_body).await }; // Cleanup and report results match &test_result { Ok(_) => { let duration = start_time.elapsed(); info!("✅ E2E test {} completed successfully in {:?}", stringify!($test_name), duration); } Err(e) => { let duration = start_time.elapsed(); error!("❌ E2E test {} failed after {:?}: {}", stringify!($test_name), duration, e); } } test_result } }; // Pattern 3: async closure with mut (borrows framework) ($test_name:ident, |mut $framework:ident: $framework_type:ty| async $test_body:block) => { #[tokio::test] async fn $test_name() -> $crate::E2ETestResult { use tracing::{info, error, warn}; use std::time::Instant; info!("🚀 Starting E2E test: {}", stringify!($test_name)); let start_time = Instant::now(); // Initialize the test framework let mut $framework = match $crate::framework::E2ETestFramework::new().await { Ok(framework) => { info!("✅ E2E test framework initialized successfully"); framework } Err(e) => { error!("❌ Failed to initialize E2E test framework: {}", e); return Err(e); } }; // Start services if needed if let Err(e) = $framework.start_services().await { error!("❌ Failed to start services: {}", e); return Err(e); } // Execute the test body (async closure) let test_result: $crate::E2ETestResult = (async $test_body).await; // Cleanup and report results match &test_result { Ok(_) => { let duration = start_time.elapsed(); info!("✅ E2E test {} completed successfully in {:?}", stringify!($test_name), duration); } Err(e) => { let duration = start_time.elapsed(); error!("❌ E2E test {} failed after {:?}: {}", stringify!($test_name), duration, e); } } // Stop services and cleanup if let Err(e) = $framework.stop_services().await { warn!("⚠️ Failed to stop services cleanly: {}", e); } test_result } }; // Pattern 4: async closure (borrows framework) ($test_name:ident, |$framework:ident: $framework_type:ty| async $test_body:block) => { #[tokio::test] async fn $test_name() -> $crate::E2ETestResult { use tracing::{info, error, warn}; use std::time::Instant; info!("🚀 Starting E2E test: {}", stringify!($test_name)); let start_time = Instant::now(); // Initialize the test framework let mut $framework = match $crate::framework::E2ETestFramework::new().await { Ok(framework) => { info!("✅ E2E test framework initialized successfully"); framework } Err(e) => { error!("❌ Failed to initialize E2E test framework: {}", e); return Err(e); } }; // Start services if needed if let Err(e) = $framework.start_services().await { error!("❌ Failed to start services: {}", e); return Err(e); } // Execute the test body (async closure) let test_result: $crate::E2ETestResult = (async $test_body).await; // Cleanup and report results match &test_result { Ok(_) => { let duration = start_time.elapsed(); info!("✅ E2E test {} completed successfully in {:?}", stringify!($test_name), duration); } Err(e) => { let duration = start_time.elapsed(); error!("❌ E2E test {} failed after {:?}: {}", stringify!($test_name), duration, e); } } // Stop services and cleanup if let Err(e) = $framework.stop_services().await { warn!("⚠️ Failed to stop services cleanly: {}", e); } test_result } }; } /// Utilities for test data generation and validation pub mod test_utils { use crate::TestOrder; use anyhow::Result; use common::types::{Exchange, HftTimestamp, MarketTick, Price, Quantity, Symbol, TickType}; use rand::Rng; use std::time::{SystemTime, UNIX_EPOCH}; /// Generate realistic market data for testing pub fn generate_market_data(symbol: &str, count: usize) -> Vec { let mut rng = rand::thread_rng(); let mut price: f64 = 150.0; // Base price let mut ticks = Vec::with_capacity(count); for i in 0..count { price += rng.gen_range(-0.5..0.5); price = price.max(100.0).min(200.0); // Keep price in reasonable range let tick = MarketTick::new( Symbol::new(symbol.to_string()), Price::from_f64(price).unwrap_or(Price::ZERO), Quantity::from_f64(rng.gen_range(100..1000) as f64).unwrap(), TickType::Trade, Exchange::NASDAQ, i as u64, ); match tick { Ok(t) => ticks.push(t), Err(_) => { // Fallback to with_timestamp if new() fails ticks.push(MarketTick::with_timestamp( Symbol::new(symbol.to_string()), Price::from_f64(price).unwrap_or(Price::ZERO), Quantity::from_f64(rng.gen_range(100..1000) as f64).unwrap(), HftTimestamp::from_nanos( SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos() as u64, ), TickType::Trade, Exchange::NASDAQ, i as u64, )); }, } } ticks } /// Generate realistic order for testing pub fn generate_test_order(symbol: &str) -> TestOrder { let mut rng = rand::thread_rng(); TestOrder { symbol: symbol.to_string(), side: if rng.gen_bool(0.5) { "buy" } else { "sell" }.to_string(), order_type: "market".to_string(), quantity: rng.gen_range(100.0..1000.0), price: None, // Market order time_in_force: "day".to_string(), } } /// Wait for condition with timeout pub async fn wait_for_condition( condition: F, timeout_secs: u64, check_interval_ms: u64, ) -> Result<()> where F: Fn() -> Fut, Fut: std::future::Future, { use tokio::time::{sleep, Duration, Instant}; let start = Instant::now(); let timeout = Duration::from_secs(timeout_secs); let interval = Duration::from_millis(check_interval_ms); while start.elapsed() < timeout { if condition().await { return Ok(()); } sleep(interval).await; } Err(anyhow::anyhow!( "Condition not met within {} seconds", timeout_secs )) } } /// Test data structures #[derive(Debug, Clone)] pub struct TestOrder { pub symbol: String, pub side: String, pub order_type: String, pub quantity: f64, pub price: Option, pub time_in_force: String, } #[derive(Debug, Clone)] pub struct TestPosition { pub symbol: String, pub quantity: f64, pub average_price: f64, pub market_value: f64, pub unrealized_pnl: f64, } // test_utils module already defined above, no need to re-export #[cfg(test)] mod tests { use super::*; #[tokio::test] #[ignore] // Requires running services (database, JWT, ML pipeline) async fn test_framework_initialization() { let framework = framework::E2ETestFramework::new().await; assert!( framework.is_ok(), "Framework should initialize successfully" ); } #[test] fn test_data_generation() { let ticks = test_utils::generate_market_data("AAPL", 10); assert_eq!(ticks.len(), 10); assert!(ticks.iter().all(|t| t.symbol == "AAPL")); assert!(ticks.iter().all(|t| t.price > 100.0 && t.price < 200.0)); } #[test] fn test_order_generation() { let order = test_utils::generate_test_order("MSFT"); assert_eq!(order.symbol, "MSFT"); assert!(order.side == "buy" || order.side == "sell"); assert!(order.quantity >= 100.0 && order.quantity <= 1000.0); } }