//! Event Storage Integration Tests //! //! Comprehensive integration tests for PostgreSQL event storage functionality. //! Tests database persistence, event streaming, data integrity, and performance //! requirements for the HFT trading system. #![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 chrono::{DateTime, Utc, Duration as ChronoDuration}; use sqlx::{PgPool, Row}; use tli::prelude::*; use crate::fixtures::*; use crate::mocks::*; /// Event storage integration test suite pub struct EventStorageTests { /// PostgreSQL connection pool db_pool: PgPool, /// Test database manager test_db_manager: TestDatabaseManager, /// Event publisher for testing event_publisher: Arc, /// Performance metrics collector metrics: Arc, /// Test configuration config: IntegrationTestConfig, } /// Performance metrics for storage operations #[derive(Debug, Default)] pub struct StorageMetrics { /// Insert latency measurements (nanoseconds) pub insert_latencies: RwLock>, /// Query latency measurements (nanoseconds) pub query_latencies: RwLock>, /// Batch insert latencies (nanoseconds) pub batch_insert_latencies: RwLock>, /// Connection acquisition latencies (nanoseconds) pub connection_latencies: RwLock>, /// Total events inserted pub total_events_inserted: AtomicU64, /// Total queries executed pub total_queries_executed: AtomicU64, /// Error counter pub error_count: AtomicU64, /// Database size tracking (bytes) pub database_size_bytes: AtomicU64, } impl EventStorageTests { /// Create new event storage test suite pub async fn new(config: IntegrationTestConfig) -> TliResult { // Initialize test database manager let test_db_manager = TestDatabaseManager::new().await?; // Get connection pool let db_pool = test_db_manager.get_pool().clone(); // Initialize event publisher let event_publisher = Arc::new(TestEventPublisher::new().await?); // Create database schema Self::initialize_test_schema(&db_pool).await?; Ok(Self { db_pool, test_db_manager, event_publisher, metrics: Arc::new(StorageMetrics::default()), config, }) } /// Initialize test database schema async fn initialize_test_schema(pool: &PgPool) -> TliResult<()> { let schema_sql = r#" -- Events table for storing all trading events CREATE TABLE IF NOT EXISTS trading_events ( id BIGSERIAL PRIMARY KEY, event_id UUID NOT NULL UNIQUE, event_type VARCHAR(50) NOT NULL, timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), symbol VARCHAR(20), data JSONB NOT NULL, source VARCHAR(50), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), INDEX (timestamp), INDEX (event_type), INDEX (symbol), INDEX USING GIN (data) ); -- Orders table for order lifecycle tracking CREATE TABLE IF NOT EXISTS orders ( id BIGSERIAL PRIMARY KEY, order_id UUID NOT NULL UNIQUE, client_order_id VARCHAR(100) NOT NULL, symbol VARCHAR(20) NOT NULL, side VARCHAR(10) NOT NULL, order_type VARCHAR(20) NOT NULL, quantity DECIMAL(20,8) NOT NULL, price DECIMAL(20,8), status VARCHAR(20) NOT NULL DEFAULT 'pending', filled_quantity DECIMAL(20,8) DEFAULT 0, average_price DECIMAL(20,8), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), INDEX (order_id), INDEX (client_order_id), INDEX (symbol), INDEX (status), INDEX (created_at) ); -- Positions table for position tracking CREATE TABLE IF NOT EXISTS positions ( id BIGSERIAL PRIMARY KEY, account_id VARCHAR(50) NOT NULL, symbol VARCHAR(20) NOT NULL, quantity DECIMAL(20,8) NOT NULL DEFAULT 0, average_price DECIMAL(20,8) DEFAULT 0, market_value DECIMAL(20,2) DEFAULT 0, unrealized_pnl DECIMAL(20,2) DEFAULT 0, realized_pnl DECIMAL(20,2) DEFAULT 0, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE(account_id, symbol), INDEX (account_id), INDEX (symbol), INDEX (updated_at) ); -- Risk events table for risk management tracking CREATE TABLE IF NOT EXISTS risk_events ( id BIGSERIAL PRIMARY KEY, event_id UUID NOT NULL UNIQUE, event_type VARCHAR(50) NOT NULL, severity VARCHAR(20) NOT NULL, timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), account_id VARCHAR(50), symbol VARCHAR(20), message TEXT NOT NULL, data JSONB, resolved BOOLEAN DEFAULT FALSE, resolved_at TIMESTAMPTZ, INDEX (timestamp), INDEX (event_type), INDEX (severity), INDEX (account_id), INDEX (resolved) ); -- Market data table for market events CREATE TABLE IF NOT EXISTS market_data ( id BIGSERIAL PRIMARY KEY, symbol VARCHAR(20) NOT NULL, timestamp TIMESTAMPTZ NOT NULL, price DECIMAL(20,8) NOT NULL, volume DECIMAL(20,8), bid_price DECIMAL(20,8), ask_price DECIMAL(20,8), bid_size DECIMAL(20,8), ask_size DECIMAL(20,8), data_type VARCHAR(20) NOT NULL, INDEX (symbol, timestamp), INDEX (timestamp), INDEX (data_type) ); -- Performance metrics table CREATE TABLE IF NOT EXISTS performance_metrics ( id BIGSERIAL PRIMARY KEY, metric_name VARCHAR(100) NOT NULL, metric_value DECIMAL(20,8) NOT NULL, timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), tags JSONB, INDEX (metric_name, timestamp), INDEX (timestamp) ); "#; sqlx::query(schema_sql) .execute(pool) .await .map_err(|e| TliError::DatabaseError(format!("Schema creation failed: {}", e)))?; Ok(()) } /// Test basic event insertion and retrieval pub async fn test_basic_event_storage(&self) -> TliResult { let mut test_result = TestResult::new("basic_event_storage"); let start_time = Instant::now(); // Create test trading event let event_id = Uuid::new_v4(); let test_event = json!({ "event_id": event_id, "event_type": "order_submitted", "symbol": "AAPL", "data": { "order_id": Uuid::new_v4(), "side": "buy", "quantity": 100.0, "price": 150.0, "timestamp": Utc::now() }, "source": "trading_service" }); // Measure insertion latency let insert_start = Instant::now(); let insert_result = sqlx::query( r#" INSERT INTO trading_events (event_id, event_type, symbol, data, source) VALUES ($1, $2, $3, $4, $5) "# ) .bind(event_id) .bind("order_submitted") .bind("AAPL") .bind(&test_event["data"]) .bind("trading_service") .execute(&self.db_pool) .await; let insert_latency = insert_start.elapsed().as_nanos() as u64; self.metrics.insert_latencies.write().await.push(insert_latency); self.metrics.total_events_inserted.fetch_add(1, Ordering::Relaxed); match insert_result { Ok(result) => { test_result.add_assertion("Event inserted successfully", result.rows_affected() == 1); test_result.add_assertion( "Insert latency acceptable", insert_latency < self.config.max_db_latency_ns ); } Err(e) => { test_result.add_error(format!("Event insertion failed: {}", e)); self.metrics.error_count.fetch_add(1, Ordering::Relaxed); return Ok(test_result); } } // Test event retrieval let query_start = Instant::now(); let retrieved_event = sqlx::query( r#" SELECT event_id, event_type, symbol, data, source, timestamp FROM trading_events WHERE event_id = $1 "# ) .bind(event_id) .fetch_one(&self.db_pool) .await; let query_latency = query_start.elapsed().as_nanos() as u64; self.metrics.query_latencies.write().await.push(query_latency); self.metrics.total_queries_executed.fetch_add(1, Ordering::Relaxed); match retrieved_event { Ok(row) => { let retrieved_event_id: Uuid = row.get("event_id"); let retrieved_event_type: String = row.get("event_type"); let retrieved_symbol: String = row.get("symbol"); test_result.add_assertion("Event retrieved successfully", retrieved_event_id == event_id); test_result.add_assertion("Event type matches", retrieved_event_type == "order_submitted"); test_result.add_assertion("Symbol matches", retrieved_symbol == "AAPL"); test_result.add_assertion( "Query latency acceptable", query_latency < self.config.max_db_latency_ns ); } Err(e) => { test_result.add_error(format!("Event retrieval failed: {}", e)); 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 high-throughput batch insertion pub async fn test_batch_event_insertion(&self) -> TliResult { let mut test_result = TestResult::new("batch_event_insertion"); let start_time = Instant::now(); let batch_size = self.config.batch_size; let mut event_data = Vec::new(); // Generate batch of events for i in 0..batch_size { let event_id = Uuid::new_v4(); let event_type = match i % 4 { 0 => "order_submitted", 1 => "order_filled", 2 => "position_updated", _ => "market_data", }; let symbol = match i % 3 { 0 => "AAPL", 1 => "MSFT", _ => "GOOGL", }; event_data.push(( event_id, event_type, symbol, json!({ "sequence": i, "timestamp": Utc::now(), "value": i as f64 * 1.5, "metadata": { "batch_test": true } }) )); } // Measure batch insertion latency let batch_start = Instant::now(); let mut transaction = self.db_pool.begin().await.map_err(|e| { TliError::DatabaseError(format!("Failed to begin transaction: {}", e)) })?; for (event_id, event_type, symbol, data) in &event_data { sqlx::query( r#" INSERT INTO trading_events (event_id, event_type, symbol, data, source) VALUES ($1, $2, $3, $4, $5) "# ) .bind(event_id) .bind(event_type) .bind(symbol) .bind(data) .bind("batch_test") .execute(&mut *transaction) .await .map_err(|e| TliError::DatabaseError(format!("Batch insert failed: {}", e)))?; } transaction.commit().await.map_err(|e| { TliError::DatabaseError(format!("Transaction commit failed: {}", e)) })?; let batch_latency = batch_start.elapsed().as_nanos() as u64; self.metrics.batch_insert_latencies.write().await.push(batch_latency); self.metrics.total_events_inserted.fetch_add(batch_size as u64, Ordering::Relaxed); // Calculate throughput let throughput = batch_size as f64 / batch_start.elapsed().as_secs_f64(); test_result.add_assertion( "Batch insertion completed", true // We made it here without errors ); test_result.add_assertion( "Batch latency acceptable", batch_latency < (self.config.max_db_latency_ns * batch_size as u64) ); test_result.add_assertion( "Throughput meets HFT requirements", throughput >= self.config.min_db_throughput_ops_per_sec ); // Verify all events were inserted let count_result = sqlx::query_scalar::<_, i64>( "SELECT COUNT(*) FROM trading_events WHERE source = 'batch_test'" ) .fetch_one(&self.db_pool) .await .map_err(|e| TliError::DatabaseError(format!("Count query failed: {}", e)))?; test_result.add_assertion( "All batch events persisted", count_result == batch_size as i64 ); test_result.metadata.insert("throughput_events_per_sec".to_string(), json!(throughput)); test_result.metadata.insert("batch_size".to_string(), json!(batch_size)); test_result.metadata.insert("batch_latency_ns".to_string(), json!(batch_latency)); test_result.execution_time = start_time.elapsed(); test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Test order lifecycle tracking with database consistency pub async fn test_order_lifecycle_tracking(&self) -> TliResult { let mut test_result = TestResult::new("order_lifecycle_tracking"); let start_time = Instant::now(); let order_id = Uuid::new_v4(); let client_order_id = format!("test_order_{}", Uuid::new_v4()); // Insert initial order let insert_start = Instant::now(); let insert_result = sqlx::query( r#" INSERT INTO orders (order_id, client_order_id, symbol, side, order_type, quantity, price, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "# ) .bind(order_id) .bind(&client_order_id) .bind("AAPL") .bind("buy") .bind("limit") .bind(rust_decimal::Decimal::new(1000, 0)) // 100.0 .bind(rust_decimal::Decimal::new(15000, 2)) // 150.00 .bind("pending") .execute(&self.db_pool) .await; let insert_latency = insert_start.elapsed().as_nanos() as u64; self.metrics.insert_latencies.write().await.push(insert_latency); match insert_result { Ok(result) => { test_result.add_assertion("Order inserted", result.rows_affected() == 1); } Err(e) => { test_result.add_error(format!("Order insertion failed: {}", e)); return Ok(test_result); } } // Simulate order lifecycle updates let lifecycle_states = vec![ ("partially_filled", rust_decimal::Decimal::new(500, 0), Some(rust_decimal::Decimal::new(14950, 2))), ("filled", rust_decimal::Decimal::new(1000, 0), Some(rust_decimal::Decimal::new(14975, 2))), ]; for (status, filled_qty, avg_price) in lifecycle_states { let update_start = Instant::now(); let update_result = sqlx::query( r#" UPDATE orders SET status = $1, filled_quantity = $2, average_price = $3, updated_at = NOW() WHERE order_id = $4 "# ) .bind(status) .bind(filled_qty) .bind(avg_price) .bind(order_id) .execute(&self.db_pool) .await; let update_latency = update_start.elapsed().as_nanos() as u64; self.metrics.query_latencies.write().await.push(update_latency); match update_result { Ok(result) => { test_result.add_assertion( &format!("Order updated to {}", status), result.rows_affected() == 1 ); test_result.add_assertion( &format!("Update latency acceptable for {}", status), update_latency < self.config.max_db_latency_ns ); } Err(e) => { test_result.add_error(format!("Order update to {} failed: {}", status, e)); } } // Create corresponding event let event_id = Uuid::new_v4(); sqlx::query( r#" INSERT INTO trading_events (event_id, event_type, symbol, data, source) VALUES ($1, $2, $3, $4, $5) "# ) .bind(event_id) .bind(format!("order_{}", status)) .bind("AAPL") .bind(json!({ "order_id": order_id, "client_order_id": client_order_id, "status": status, "filled_quantity": filled_qty, "average_price": avg_price })) .bind("order_tracker") .execute(&self.db_pool) .await .map_err(|e| TliError::DatabaseError(format!("Event insertion failed: {}", e)))?; self.metrics.total_events_inserted.fetch_add(1, Ordering::Relaxed); } // Verify final order state let final_order = sqlx::query( r#" SELECT order_id, status, filled_quantity, average_price FROM orders WHERE order_id = $1 "# ) .bind(order_id) .fetch_one(&self.db_pool) .await .map_err(|e| TliError::DatabaseError(format!("Final order query failed: {}", e)))?; let final_status: String = final_order.get("status"); let final_filled: rust_decimal::Decimal = final_order.get("filled_quantity"); test_result.add_assertion("Final status is filled", final_status == "filled"); test_result.add_assertion( "Final filled quantity correct", final_filled == rust_decimal::Decimal::new(1000, 0) ); // Verify event consistency let event_count: i64 = sqlx::query_scalar( "SELECT COUNT(*) FROM trading_events WHERE data->>'order_id' = $1" ) .bind(order_id.to_string()) .fetch_one(&self.db_pool) .await .map_err(|e| TliError::DatabaseError(format!("Event count query failed: {}", e)))?; test_result.add_assertion("All lifecycle events recorded", event_count >= 2); test_result.execution_time = start_time.elapsed(); test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Test concurrent database access and transaction isolation pub async fn test_concurrent_database_access(&self) -> TliResult { let mut test_result = TestResult::new("concurrent_database_access"); let start_time = Instant::now(); let num_concurrent_operations = self.config.concurrent_operation_count; let mut handles = Vec::new(); // Launch concurrent operations for i in 0..num_concurrent_operations { let pool = self.db_pool.clone(); let metrics = Arc::clone(&self.metrics); let handle = tokio::spawn(async move { let operation_start = Instant::now(); // Simulate concurrent order insertion let order_id = Uuid::new_v4(); let client_order_id = format!("concurrent_order_{}_{}", i, Uuid::new_v4()); let result = sqlx::query( r#" INSERT INTO orders (order_id, client_order_id, symbol, side, order_type, quantity, price, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "# ) .bind(order_id) .bind(client_order_id) .bind("AAPL") .bind(if i % 2 == 0 { "buy" } else { "sell" }) .bind("market") .bind(rust_decimal::Decimal::new(100 + (i as i64 * 10), 0)) .bind(rust_decimal::Decimal::new(15000 + (i as i64 * 100), 2)) .bind("pending") .execute(&pool) .await; let operation_latency = operation_start.elapsed().as_nanos() as u64; metrics.insert_latencies.write().await.push(operation_latency); match result { Ok(_) => { metrics.total_events_inserted.fetch_add(1, Ordering::Relaxed); true } Err(_) => { metrics.error_count.fetch_add(1, Ordering::Relaxed); false } } }); handles.push(handle); } // Wait for all operations to complete let mut successful_operations = 0; for handle in handles { if let Ok(success) = handle.await { if success { successful_operations += 1; } } } let total_time = start_time.elapsed(); let throughput = successful_operations as f64 / total_time.as_secs_f64(); test_result.add_assertion( "High success rate for concurrent operations", successful_operations >= (num_concurrent_operations * 95 / 100) // 95% success rate ); test_result.add_assertion( "Concurrent throughput acceptable", throughput >= self.config.min_db_throughput_ops_per_sec * 0.8 // 80% of single-threaded ); // Verify no data corruption occurred let total_orders: i64 = sqlx::query_scalar( "SELECT COUNT(*) FROM orders WHERE client_order_id LIKE 'concurrent_order_%'" ) .fetch_one(&self.db_pool) .await .map_err(|e| TliError::DatabaseError(format!("Order count query failed: {}", e)))?; test_result.add_assertion( "No data corruption in concurrent access", total_orders == successful_operations as i64 ); test_result.metadata.insert("concurrent_throughput_ops_per_sec".to_string(), json!(throughput)); test_result.metadata.insert("successful_operations".to_string(), json!(successful_operations)); test_result.metadata.insert("total_operations".to_string(), json!(num_concurrent_operations)); test_result.execution_time = total_time; test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Test data integrity and constraint validation pub async fn test_data_integrity_constraints(&self) -> TliResult { let mut test_result = TestResult::new("data_integrity_constraints"); let start_time = Instant::now(); // Test unique constraint on order_id let duplicate_order_id = Uuid::new_v4(); // Insert first order let first_insert = sqlx::query( r#" INSERT INTO orders (order_id, client_order_id, symbol, side, order_type, quantity, price, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "# ) .bind(duplicate_order_id) .bind("first_order") .bind("AAPL") .bind("buy") .bind("market") .bind(rust_decimal::Decimal::new(100, 0)) .bind(rust_decimal::Decimal::new(15000, 2)) .bind("pending") .execute(&self.db_pool) .await; test_result.add_assertion("First order inserted", first_insert.is_ok()); // Attempt to insert duplicate order_id let duplicate_insert = sqlx::query( r#" INSERT INTO orders (order_id, client_order_id, symbol, side, order_type, quantity, price, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "# ) .bind(duplicate_order_id) .bind("duplicate_order") .bind("AAPL") .bind("sell") .bind("limit") .bind(rust_decimal::Decimal::new(200, 0)) .bind(rust_decimal::Decimal::new(16000, 2)) .bind("pending") .execute(&self.db_pool) .await; test_result.add_assertion("Duplicate order_id rejected", duplicate_insert.is_err()); // Test foreign key constraints and data validation let position_test_account = "test_account_123"; // Insert position let position_insert = sqlx::query( r#" INSERT INTO positions (account_id, symbol, quantity, average_price, market_value) VALUES ($1, $2, $3, $4, $5) "# ) .bind(position_test_account) .bind("AAPL") .bind(rust_decimal::Decimal::new(50000, 2)) // 500.00 shares .bind(rust_decimal::Decimal::new(15000, 2)) // $150.00 avg price .bind(rust_decimal::Decimal::new(7500000, 2)) // $75,000 market value .execute(&self.db_pool) .await; test_result.add_assertion("Position inserted", position_insert.is_ok()); // Test position update let position_update = sqlx::query( r#" UPDATE positions SET quantity = $1, market_value = $2, updated_at = NOW() WHERE account_id = $3 AND symbol = $4 "# ) .bind(rust_decimal::Decimal::new(60000, 2)) // 600.00 shares .bind(rust_decimal::Decimal::new(9000000, 2)) // $90,000 market value .bind(position_test_account) .bind("AAPL") .execute(&self.db_pool) .await; test_result.add_assertion("Position updated", position_update.is_ok()); // Test JSON data validation in events let event_id = Uuid::new_v4(); let complex_event_data = json!({ "order_details": { "order_id": Uuid::new_v4(), "symbol": "AAPL", "quantity": 100.0, "price": 150.0, "metadata": { "strategy": "momentum", "confidence": 0.85, "risk_score": 0.23 } }, "execution_details": { "venue": "NASDAQ", "route": "SMART", "timestamp": Utc::now(), "latency_ns": 15000 } }); let json_insert = sqlx::query( r#" INSERT INTO trading_events (event_id, event_type, symbol, data, source) VALUES ($1, $2, $3, $4, $5) "# ) .bind(event_id) .bind("complex_order_event") .bind("AAPL") .bind(complex_event_data) .bind("integrity_test") .execute(&self.db_pool) .await; test_result.add_assertion("Complex JSON event inserted", json_insert.is_ok()); // Test JSON query capabilities let json_query_result = sqlx::query( r#" SELECT data->'order_details'->>'strategy' as strategy, data->'execution_details'->>'venue' as venue FROM trading_events WHERE event_id = $1 "# ) .bind(event_id) .fetch_one(&self.db_pool) .await; match json_query_result { Ok(row) => { let strategy: Option = row.get("strategy"); let venue: Option = row.get("venue"); test_result.add_assertion("JSON strategy extracted", strategy == Some("momentum".to_string())); test_result.add_assertion("JSON venue extracted", venue == Some("NASDAQ".to_string())); } Err(e) => { test_result.add_error(format!("JSON query failed: {}", e)); } } test_result.execution_time = start_time.elapsed(); test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Test database performance under stress pub async fn test_database_stress_performance(&self) -> TliResult { let mut test_result = TestResult::new("database_stress_performance"); let start_time = Instant::now(); let stress_duration = Duration::from_secs(self.config.stress_test_duration_secs); let stress_start = Instant::now(); let mut operation_count = 0; let mut error_count = 0; // Run continuous operations for stress duration while stress_start.elapsed() < stress_duration { let batch_start = Instant::now(); // Perform a batch of mixed operations let batch_futures = (0..10).map(|i| { let pool = self.db_pool.clone(); async move { match i % 4 { 0 => { // Insert order let order_id = Uuid::new_v4(); sqlx::query( r#" INSERT INTO orders (order_id, client_order_id, symbol, side, order_type, quantity, price, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "# ) .bind(order_id) .bind(format!("stress_order_{}", Uuid::new_v4())) .bind("AAPL") .bind("buy") .bind("market") .bind(rust_decimal::Decimal::new(100, 0)) .bind(rust_decimal::Decimal::new(15000, 2)) .bind("pending") .execute(&pool) .await .map(|_| ()) } 1 => { // Insert event let event_id = Uuid::new_v4(); sqlx::query( r#" INSERT INTO trading_events (event_id, event_type, symbol, data, source) VALUES ($1, $2, $3, $4, $5) "# ) .bind(event_id) .bind("stress_test_event") .bind("AAPL") .bind(json!({"stress_test": true, "timestamp": Utc::now()})) .bind("stress_test") .execute(&pool) .await .map(|_| ()) } 2 => { // Query orders sqlx::query( "SELECT COUNT(*) FROM orders WHERE symbol = $1 AND status = $2" ) .bind("AAPL") .bind("pending") .fetch_one(&pool) .await .map(|_| ()) } _ => { // Query events sqlx::query( "SELECT COUNT(*) FROM trading_events WHERE event_type = $1" ) .bind("stress_test_event") .fetch_one(&pool) .await .map(|_| ()) } } } }); let results = futures::future::join_all(batch_futures).await; for result in results { operation_count += 1; if result.is_err() { error_count += 1; } } let batch_latency = batch_start.elapsed().as_nanos() as u64; self.metrics.batch_insert_latencies.write().await.push(batch_latency); // Small delay to prevent overwhelming the database tokio::time::sleep(Duration::from_millis(10)).await; } let total_time = start_time.elapsed(); let overall_throughput = operation_count as f64 / total_time.as_secs_f64(); let error_rate = error_count as f64 / operation_count as f64; test_result.add_assertion( "Stress test completed", total_time >= stress_duration ); test_result.add_assertion( "Low error rate under stress", error_rate < 0.05 // Less than 5% error rate ); test_result.add_assertion( "Maintained throughput under stress", overall_throughput >= self.config.min_db_throughput_ops_per_sec * 0.6 // 60% of normal ); test_result.metadata.insert("stress_throughput_ops_per_sec".to_string(), json!(overall_throughput)); test_result.metadata.insert("total_operations".to_string(), json!(operation_count)); test_result.metadata.insert("error_count".to_string(), json!(error_count)); test_result.metadata.insert("error_rate".to_string(), json!(error_rate)); test_result.execution_time = total_time; test_result.set_passed(test_result.errors.is_empty()); Ok(test_result) } /// Run complete event storage test suite pub async fn run_complete_suite(&self) -> TliResult { let mut suite = TestSuite::new("event_storage_integration"); let suite_start = Instant::now(); // Run all test cases let tests = vec![ self.test_basic_event_storage().await?, self.test_batch_event_insertion().await?, self.test_order_lifecycle_tracking().await?, self.test_concurrent_database_access().await?, self.test_data_integrity_constraints().await?, self.test_database_stress_performance().await?, ]; for test in tests { suite.add_test_result(test); } // Generate performance summary let metrics = self.generate_performance_summary().await; suite.metadata.insert("storage_metrics".to_string(), json!(metrics)); // Update database size metric if let Ok(size) = self.get_database_size().await { self.metrics.database_size_bytes.store(size, Ordering::Relaxed); suite.metadata.insert("database_size_bytes".to_string(), json!(size)); } suite.execution_time = suite_start.elapsed(); suite.set_passed(suite.passed_tests >= suite.total_tests * 85 / 100); // 85% pass rate Ok(suite) } /// Generate comprehensive performance summary async fn generate_performance_summary(&self) -> serde_json::Value { let insert_latencies = self.metrics.insert_latencies.read().await; let query_latencies = self.metrics.query_latencies.read().await; let batch_latencies = self.metrics.batch_insert_latencies.read().await; let insert_stats = calculate_latency_stats(&insert_latencies); let query_stats = calculate_latency_stats(&query_latencies); let batch_stats = calculate_latency_stats(&batch_latencies); json!({ "insert_operations": { "count": insert_latencies.len(), "avg_ns": insert_stats.avg, "p95_ns": insert_stats.p95, "p99_ns": insert_stats.p99, "max_ns": insert_stats.max }, "query_operations": { "count": query_latencies.len(), "avg_ns": query_stats.avg, "p95_ns": query_stats.p95, "p99_ns": query_stats.p99, "max_ns": query_stats.max }, "batch_operations": { "count": batch_latencies.len(), "avg_ns": batch_stats.avg, "p95_ns": batch_stats.p95, "p99_ns": batch_stats.p99, "max_ns": batch_stats.max }, "total_events_inserted": self.metrics.total_events_inserted.load(Ordering::Relaxed), "total_queries_executed": self.metrics.total_queries_executed.load(Ordering::Relaxed), "error_count": self.metrics.error_count.load(Ordering::Relaxed), "database_size_bytes": self.metrics.database_size_bytes.load(Ordering::Relaxed) }) } /// Get current database size async fn get_database_size(&self) -> Result { let size: i64 = sqlx::query_scalar("SELECT pg_database_size(current_database())") .fetch_one(&self.db_pool) .await?; Ok(size as u64) } } /// 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 p95 = sorted[len * 95 / 100]; let p99 = sorted[len * 99 / 100]; let max = sorted[len - 1]; LatencyStats { avg, p95, p99, max } } /// Latency statistics structure #[derive(Debug, Default)] struct LatencyStats { avg: u64, p95: u64, p99: u64, max: u64, } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_event_storage_integration() { let config = IntegrationTestConfig::default(); let tests = EventStorageTests::new(config).await.unwrap(); let results = tests.run_complete_suite().await.unwrap(); println!("Event Storage 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("storage_metrics") { println!("Storage Metrics: {}", serde_json::to_string_pretty(metrics).unwrap()); } assert!(results.passed, "Event storage integration tests should pass"); } }