//! Trading Flow Integration Tests //! //! Comprehensive integration tests for TLI Client ↔ Trading Service communication. //! Tests end-to-end trading workflows including order submission, risk validation, //! execution, and monitoring with performance benchmarks. #![allow(unused_crate_dependencies)] use std::collections::HashMap; use std::sync::{Arc, atomic::{AtomicU64, Ordering}}; use std::time::{Duration, Instant}; use tokio::sync::{mpsc, RwLock, Mutex}; use tokio::time::timeout; use uuid::Uuid; use serde_json::json; use tli::prelude::*; // use risk::prelude::*; // REMOVED - prelude does not exist use crate::fixtures::*; use crate::mocks::*; /// Trading flow integration test suite pub struct TradingFlowTests { /// TLI client suite for testing client_suite: TliClientSuite, /// Mock trading service mock_trading_service: MockTradingService, /// PostgreSQL test database test_db: TestDatabase, /// Performance metrics collector metrics: Arc, /// Test configuration config: IntegrationTestConfig, } /// Performance metrics for trading operations #[derive(Debug, Default)] pub struct PerformanceMetrics { /// Order submission latency measurements (nanoseconds) pub order_submission_latencies: RwLock>, /// Risk validation latency measurements (nanoseconds) pub risk_validation_latencies: RwLock>, /// Database persistence latency measurements (nanoseconds) pub database_latencies: RwLock>, /// Total throughput counter pub total_operations: AtomicU64, /// Error counter pub error_count: AtomicU64, /// Memory usage tracking pub memory_usage_mb: AtomicU64, } impl TradingFlowTests { /// Create new trading flow test suite pub async fn new(config: IntegrationTestConfig) -> TliResult { // Initialize test database let test_db = TestDatabase::new().await?; // Initialize mock trading service let mock_trading_service = MockTradingService::new().await?; // Create TLI client suite with test endpoints let client_suite = TliClientBuilder::new() .with_service_endpoint( "trading_service".to_string(), format!("http://localhost:{}", mock_trading_service.port()) ) .with_trading_config(TradingClientConfig { timeout_ms: config.request_timeout_ms, max_retry_attempts: config.max_retry_attempts, circuit_breaker_threshold: config.circuit_breaker_threshold, ..Default::default() }) .build() .await?; Ok(Self { client_suite, mock_trading_service, test_db, metrics: Arc::new(PerformanceMetrics::default()), config, }) } /// Test basic order submission flow pub async fn test_basic_order_submission(&self) -> TliResult { let mut test_result = TestResult::new("basic_order_submission"); let start_time = Instant::now(); // Create test order let order_request = SubmitOrderRequest { symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 100.0, client_order_id: format!("test_order_{}", Uuid::new_v4()), metadata: HashMap::new(), }; // Configure mock response self.mock_trading_service.configure_order_response( &order_request.client_order_id, SubmitOrderResponse { success: true, order_id: format!("order_{}", Uuid::new_v4()), message: "Order submitted successfully".to_string(), execution_time_ns: 15_000, // 15µs simulated execution time } ).await; // Measure order submission latency let submission_start = Instant::now(); let response = match self.client_suite.trading_client { Some(ref client) => { timeout( Duration::from_millis(self.config.request_timeout_ms), client.submit_order(order_request.clone()) ).await } None => { test_result.add_error("Trading client not available".to_string()); return Ok(test_result); } }; let submission_latency = submission_start.elapsed().as_nanos() as u64; // Record performance metrics self.metrics.order_submission_latencies.write().await.push(submission_latency); self.metrics.total_operations.fetch_add(1, Ordering::Relaxed); // Validate response match response { Ok(Ok(response)) => { let resp = response.into_inner(); test_result.add_assertion("Order submission successful", resp.success); test_result.add_assertion("Order ID provided", !resp.order_id.is_empty()); test_result.add_assertion( "Latency within HFT requirements", submission_latency < self.config.max_latency_ns ); // Verify database persistence let db_start = Instant::now(); let order_persisted = self.test_db.verify_order_persisted( &resp.order_id ).await.unwrap_or(false); let db_latency = db_start.elapsed().as_nanos() as u64; self.metrics.database_latencies.write().await.push(db_latency); test_result.add_assertion("Order persisted to database", order_persisted); test_result.add_assertion( "Database latency acceptable", db_latency < self.config.max_db_latency_ns ); } Ok(Err(e)) => { test_result.add_error(format!("Order submission failed: {:?}", e)); self.metrics.error_count.fetch_add(1, Ordering::Relaxed); } Err(_) => { test_result.add_error("Order submission timeout".to_string()); self.metrics.error_count.fetch_add(1, Ordering::Relaxed); } } test_result.execution_time = start_time.elapsed(); test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Test risk-integrated order submission with validation pub async fn test_risk_integrated_order_submission(&self) -> TliResult { let mut test_result = TestResult::new("risk_integrated_order_submission"); let start_time = Instant::now(); // Create high-risk order that should trigger risk checks let risky_order = SubmitOrderRequest { symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 100_000.0, // Large quantity to trigger risk limits price: Some(200.0), // High price client_order_id: format!("risky_order_{}", Uuid::new_v4()), metadata: HashMap::new(), }; // Configure mock risk service to reject this order self.mock_trading_service.configure_risk_rejection( &risky_order.client_order_id, "Position limit exceeded: would result in 150% of max allowed position".to_string() ).await; // Submit order and measure risk validation latency let risk_start = Instant::now(); let response = match self.client_suite.trading_client { Some(ref client) => { timeout( Duration::from_millis(self.config.request_timeout_ms), client.submit_order(risky_order.clone()) ).await } None => { test_result.add_error("Trading client not available".to_string()); return Ok(test_result); } }; let risk_latency = risk_start.elapsed().as_nanos() as u64; self.metrics.risk_validation_latencies.write().await.push(risk_latency); // Validate risk rejection match response { Ok(Ok(response)) => { let resp = response.into_inner(); test_result.add_assertion("Order correctly rejected", !resp.success); test_result.add_assertion("Risk reason provided", resp.message.contains("Position limit")); test_result.add_assertion( "Risk validation latency acceptable", risk_latency < self.config.max_risk_latency_ns ); } Ok(Err(e)) => { test_result.add_error(format!("Unexpected error: {:?}", e)); } Err(_) => { test_result.add_error("Risk validation timeout".to_string()); } } test_result.execution_time = start_time.elapsed(); test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Test order lifecycle with position tracking pub async fn test_order_lifecycle_with_position_tracking(&self) -> TliResult { let mut test_result = TestResult::new("order_lifecycle_position_tracking"); let start_time = Instant::now(); let order_id = format!("lifecycle_order_{}", Uuid::new_v4()); // Create order let order_request = SubmitOrderRequest { symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Limit as i32, quantity: 500.0, price: Some(150.0), client_order_id: order_id.clone(), metadata: HashMap::new(), }; // Configure mock to simulate full order lifecycle self.mock_trading_service.configure_lifecycle_simulation( &order_id, vec![ OrderStatus::Pending, OrderStatus::PartiallyFilled, OrderStatus::Filled ] ).await; // Submit order let submit_response = match self.client_suite.trading_client { Some(ref client) => { let response = client.submit_order(order_request).await?; response.into_inner() }, None => { test_result.add_error("Trading client not available".to_string()); return Ok(test_result); } }; test_result.add_assertion("Order submission successful", submit_response.success); // Monitor order status changes let mut status_updates = Vec::new(); let mut position_updates = Vec::new(); // Start monitoring streams if let Some(ref client) = self.client_suite.trading_client { let order_stream = client.subscribe_to_order_updates().await?; let position_stream = client.subscribe_to_position_updates().await?; // Collect updates for 5 seconds let monitor_duration = Duration::from_secs(5); let monitor_start = Instant::now(); while monitor_start.elapsed() < monitor_duration { tokio::select! { order_update = order_stream.recv() => { if let Some(update) = order_update { if update.order_id == submit_response.order_id { status_updates.push(update); } } } position_update = position_stream.recv() => { if let Some(update) = position_update { if update.symbol == "AAPL" { position_updates.push(update); } } } _ = tokio::time::sleep(Duration::from_millis(100)) => { // Continue monitoring } } } } // Validate lifecycle test_result.add_assertion("Received status updates", !status_updates.is_empty()); test_result.add_assertion("Received position updates", !position_updates.is_empty()); // Verify final state if let Some(final_status) = status_updates.last() { test_result.add_assertion("Order reached filled state", final_status.status == OrderStatus::Filled); } // Verify position tracking if let Some(final_position) = position_updates.last() { test_result.add_assertion("Position updated correctly", final_position.quantity == 500.0); } test_result.execution_time = start_time.elapsed(); test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Test concurrent order submissions for throughput validation pub async fn test_concurrent_order_throughput(&self) -> TliResult { let mut test_result = TestResult::new("concurrent_order_throughput"); let start_time = Instant::now(); let num_concurrent_orders = self.config.concurrent_order_count; let mut handles = Vec::new(); let metrics = Arc::clone(&self.metrics); // Submit multiple orders concurrently for i in 0..num_concurrent_orders { let client = match self.client_suite.trading_client { Some(ref c) => c.clone(), None => { test_result.add_error("Trading client not available".to_string()); return Ok(test_result); } }; let order_request = SubmitOrderRequest { symbol: "AAPL".to_string(), side: if i % 2 == 0 { OrderSide::Buy } else { OrderSide::Sell } as i32, order_type: OrderType::Market as i32, quantity: 100.0, client_order_id: format!("concurrent_order_{}_{}", i, Uuid::new_v4()), metadata: HashMap::new(), }; let metrics_clone = Arc::clone(&metrics); let handle = tokio::spawn(async move { let start = Instant::now(); let result = client.submit_order(order_request).await; let latency = start.elapsed().as_nanos() as u64; metrics_clone.order_submission_latencies.write().await.push(latency); metrics_clone.total_operations.fetch_add(1, Ordering::Relaxed); match result { Ok(response) => { let resp = response.into_inner(); resp.success }, Err(_) => { metrics_clone.error_count.fetch_add(1, Ordering::Relaxed); false } } }); handles.push(handle); } // Wait for all orders to complete let mut successful_orders = 0; for handle in handles { if let Ok(success) = handle.await { if success { successful_orders += 1; } } } let total_time = start_time.elapsed(); let throughput = successful_orders as f64 / total_time.as_secs_f64(); // Validate throughput requirements test_result.add_assertion( "Minimum successful orders", successful_orders >= (num_concurrent_orders * 9 / 10) // 90% success rate ); test_result.add_assertion( "Throughput meets HFT requirements", throughput >= self.config.min_throughput_ops_per_sec ); test_result.metadata.insert("throughput_ops_per_sec".to_string(), json!(throughput)); test_result.metadata.insert("successful_orders".to_string(), json!(successful_orders)); test_result.metadata.insert("total_orders".to_string(), json!(num_concurrent_orders)); test_result.execution_time = total_time; test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Test circuit breaker functionality pub async fn test_circuit_breaker_activation(&self) -> TliResult { let mut test_result = TestResult::new("circuit_breaker_activation"); let start_time = Instant::now(); // Configure mock to fail multiple consecutive requests self.mock_trading_service.configure_failure_sequence(10).await; let mut consecutive_failures = 0; let max_attempts = 15; // Submit orders until circuit breaker activates for i in 0..max_attempts { let order_request = SubmitOrderRequest { symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 100.0, client_order_id: format!("cb_test_order_{}", i), metadata: HashMap::new(), }; let response = match self.client_suite.trading_client { Some(ref client) => client.submit_order(order_request).await, None => { test_result.add_error("Trading client not available".to_string()); return Ok(test_result); } }; match response { Ok(response) => { let resp = response.into_inner(); if !resp.success { consecutive_failures += 1; if consecutive_failures >= self.config.circuit_breaker_threshold { test_result.add_assertion("Circuit breaker activated", true); break; } } else { consecutive_failures = 0; // Reset on success } } Err(TliError::CircuitBreakerOpen) => { test_result.add_assertion("Circuit breaker properly triggered", true); break; } Err(_) => { // Handle other errors consecutive_failures += 1; } } } // Verify circuit breaker status if let Some(ref client) = self.client_suite.trading_client { let response = client.get_circuit_breaker_status().await?; let status = response.into_inner(); test_result.add_assertion("Circuit breaker status available", status.is_open); } test_result.execution_time = start_time.elapsed(); test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Test emergency stop functionality pub async fn test_emergency_stop(&self) -> TliResult { let mut test_result = TestResult::new("emergency_stop"); let start_time = Instant::now(); // Submit initial order to ensure system is active let order_request = SubmitOrderRequest { symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 100.0, client_order_id: format!("pre_stop_order_{}", Uuid::new_v4()), metadata: HashMap::new(), }; if let Some(ref client) = self.client_suite.trading_client { let response = client.submit_order(order_request).await?; let initial_response = response.into_inner(); test_result.add_assertion("Initial order successful", initial_response.success); // Trigger emergency stop let response = client.trigger_emergency_stop("Integration test emergency stop").await?; let stop_result = response.into_inner(); test_result.add_assertion("Emergency stop triggered successfully", stop_result.success); // Wait for stop to propagate tokio::time::sleep(Duration::from_millis(100)).await; // Attempt to submit order after emergency stop let post_stop_order = SubmitOrderRequest { symbol: "AAPL".to_string(), side: OrderSide::Buy as i32, order_type: OrderType::Market as i32, quantity: 100.0, client_order_id: format!("post_stop_order_{}", Uuid::new_v4()), metadata: HashMap::new(), }; let post_stop_response = client.submit_order(post_stop_order).await; match post_stop_response { Ok(response) => { let resp = response.into_inner(); test_result.add_assertion("Order rejected after emergency stop", !resp.success); test_result.add_assertion("Emergency stop message provided", resp.message.contains("emergency") || resp.message.contains("stopped")); } Err(TliError::TradingHalted) => { test_result.add_assertion("Trading halted error received", true); } Err(e) => { test_result.add_error(format!("Unexpected error after emergency stop: {:?}", e)); } } // Verify emergency stop status let response = client.get_trading_status().await?; let status = response.into_inner(); test_result.add_assertion("Trading status shows emergency stop", status.emergency_stop_active); } test_result.execution_time = start_time.elapsed(); test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Run complete trading flow test suite pub async fn run_complete_suite(&self) -> TliResult { let mut suite = TestSuite::new("trading_flow_integration"); let suite_start = Instant::now(); // Run all test cases let tests = vec![ self.test_basic_order_submission().await?, self.test_risk_integrated_order_submission().await?, self.test_order_lifecycle_with_position_tracking().await?, self.test_concurrent_order_throughput().await?, self.test_circuit_breaker_activation().await?, self.test_emergency_stop().await?, ]; for test in tests { suite.add_test_result(test); } // Generate performance summary let metrics = self.generate_performance_summary().await; suite.metadata.insert("performance_metrics".to_string(), json!(metrics)); suite.execution_time = suite_start.elapsed(); suite.set_passed(suite.passed_tests >= suite.total_tests * 80 / 100); // 80% pass rate Ok(suite) } /// Generate comprehensive performance summary async fn generate_performance_summary(&self) -> serde_json::Value { let order_latencies = self.metrics.order_submission_latencies.read().await; let risk_latencies = self.metrics.risk_validation_latencies.read().await; let db_latencies = self.metrics.database_latencies.read().await; let order_stats = calculate_latency_stats(&order_latencies); let risk_stats = calculate_latency_stats(&risk_latencies); let db_stats = calculate_latency_stats(&db_latencies); json!({ "order_submission": { "count": order_latencies.len(), "avg_ns": order_stats.avg, "p50_ns": order_stats.p50, "p95_ns": order_stats.p95, "p99_ns": order_stats.p99, "max_ns": order_stats.max }, "risk_validation": { "count": risk_latencies.len(), "avg_ns": risk_stats.avg, "p50_ns": risk_stats.p50, "p95_ns": risk_stats.p95, "p99_ns": risk_stats.p99, "max_ns": risk_stats.max }, "database_operations": { "count": db_latencies.len(), "avg_ns": db_stats.avg, "p50_ns": db_stats.p50, "p95_ns": db_stats.p95, "p99_ns": db_stats.p99, "max_ns": db_stats.max }, "total_operations": self.metrics.total_operations.load(Ordering::Relaxed), "error_count": self.metrics.error_count.load(Ordering::Relaxed), "memory_usage_mb": self.metrics.memory_usage_mb.load(Ordering::Relaxed) }) } } /// Calculate latency statistics from measurements fn calculate_latency_stats(latencies: &[u64]) -> LatencyStats { if latencies.is_empty() { return LatencyStats::default(); } let mut sorted = latencies.to_vec(); sorted.sort_unstable(); let len = sorted.len(); let avg = sorted.iter().sum::() / len as u64; let p50 = sorted[len * 50 / 100]; let p95 = sorted[len * 95 / 100]; let p99 = sorted[len * 99 / 100]; let max = sorted[len - 1]; LatencyStats { avg, p50, p95, p99, max } } /// Latency statistics structure #[derive(Debug, Default)] struct LatencyStats { avg: u64, p50: u64, p95: u64, p99: u64, max: u64, } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_trading_flow_integration() { let config = IntegrationTestConfig::default(); let tests = TradingFlowTests::new(config).await.unwrap(); let results = tests.run_complete_suite().await.unwrap(); println!("Trading Flow Integration Test Results:"); println!("Passed: {}/{}", results.passed_tests, results.total_tests); println!("Execution time: {:?}", results.execution_time); // Print performance metrics if let Some(metrics) = results.metadata.get("performance_metrics") { println!("Performance Metrics: {}", serde_json::to_string_pretty(metrics).unwrap()); } assert!(results.passed, "Trading flow integration tests should pass"); } }