Files
foxhunt/tests/integration/event_storage.rs
jgrusewski c0be3ca530 🔧 Major compilation fixes across entire workspace - Significant progress achieved
## Summary of Compilation Fixes

### Core Infrastructure Improvements
- **Fixed import system**: Established canonical type imports from common::types
- **Resolved syntax errors**: Fixed malformed use statements with embedded comments
- **Import consolidation**: Eliminated duplicate and conflicting type imports
- **Type visibility**: Improved public/private type access patterns

### Major Areas Fixed

#### Trading Engine (trading_engine/)
-  Fixed syntax errors in types/basic.rs with clean re-exports
-  Resolved OrderSide/Side naming conflicts
-  Fixed type_registry.rs malformed imports
-  Consolidated canonical type imports from common::types
-  Fixed broker_client.rs duplicate OrderStatus imports
- 🔄 Remaining: 41 type visibility errors (down from 286+ errors)

#### Common Types (common/)
-  Established as single source of truth for all types
-  Clean type definitions with proper visibility
-  Consistent error handling patterns

#### Data Pipeline (data/)
-  Updated imports to use canonical common::types
-  Fixed provider trait implementations
-  Resolved database integration issues

#### ML Components (ml/)
-  Fixed model interface imports
-  Updated feature extraction systems
-  Resolved training pipeline dependencies

#### Risk Management (risk/)
-  Fixed safety module imports
-  Updated VaR calculator dependencies
-  Consolidated compliance types

#### Services
-  Trading Service: Fixed repository implementations
-  Backtesting Service: Updated strategy engines
-  TLI: Fixed dashboard and UI components

#### Test Infrastructure
-  Updated integration test imports
-  Fixed performance benchmark dependencies
-  Resolved mock implementations

### Technical Achievements

#### Import System Overhaul
- Established common::types as canonical source
- Eliminated circular dependencies
- Fixed visibility modifiers (pub use vs use)
- Resolved naming conflicts (Side → OrderSide)

#### Type System Cleanup
- Consolidated duplicate type definitions
- Fixed malformed syntax (comments in use statements)
- Standardized error handling patterns
- Improved module structure

#### Configuration Management
- Enhanced config crate integration
- Fixed database configuration patterns
- Improved hot-reload mechanisms

### Error Reduction Progress
- **Before**: 371+ compilation errors across workspace
- **After**: ~202 errors remaining (46% reduction achieved)
- **Major**: Fixed critical syntax errors preventing any compilation
- **Infrastructure**: Resolved fundamental import and type system issues

### Files Modified: 347
- Core types and infrastructure
- Service implementations
- Test suites and benchmarks
- Configuration systems
- Database integrations

### Next Steps
- Complete remaining type visibility fixes in trading_engine
- Finalize import resolution in remaining modules
- Validate cross-crate dependencies
- Run comprehensive test suite

This represents a major milestone in achieving zero compilation errors across
the entire Foxhunt HFT trading system workspace. The foundational type system
and import structure has been successfully established and standardized.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-27 20:56:22 +02:00

1067 lines
39 KiB
Rust

//! 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.
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<TestEventPublisher>,
/// Performance metrics collector
metrics: Arc<StorageMetrics>,
/// Test configuration
config: IntegrationTestConfig,
}
/// Performance metrics for storage operations
#[derive(Debug, Default)]
pub struct StorageMetrics {
/// Insert latency measurements (nanoseconds)
pub insert_latencies: RwLock<Vec<u64>>,
/// Query latency measurements (nanoseconds)
pub query_latencies: RwLock<Vec<u64>>,
/// Batch insert latencies (nanoseconds)
pub batch_insert_latencies: RwLock<Vec<u64>>,
/// Connection acquisition latencies (nanoseconds)
pub connection_latencies: RwLock<Vec<u64>>,
/// 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<Self> {
// 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<TestResult> {
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<TestResult> {
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<TestResult> {
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<TestResult> {
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<TestResult> {
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<String> = row.get("strategy");
let venue: Option<String> = 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<TestResult> {
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<TestSuite> {
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<u64, sqlx::Error> {
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::<u64>() / 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");
}
}