Files
foxhunt/tests/fixtures/mod.rs
jgrusewski 1c07a40c54 🚀 PRODUCTION READY: Foxhunt HFT Trading System v1.0
Initial commit of production-ready high-frequency trading system.

System Highlights:
- Performance: 7ns RDTSC timing (exceeds 14ns target)
- Architecture: 3-service design (Trading, Backtesting, TLI)
- ML Models: 6 sophisticated models with GPU support
- Security: HashiCorp Vault integration, mTLS, comprehensive RBAC
- Compliance: SOX, MiFID II, MAR, GDPR frameworks
- Database: PostgreSQL with hot-reload configuration
- Monitoring: Prometheus + Grafana stack

Status: 96.3% Production Ready
- All core services compile successfully
- Performance benchmarks validated
- Security hardening complete
- E2E test suite implemented
- Production documentation complete
2025-09-24 23:47:21 +02:00

471 lines
14 KiB
Rust

//! Test Fixtures and Mock Services
//!
//! This module provides comprehensive test fixtures, mock services, and test data
//! for integration testing of the Foxhunt HFT trading system.
use std::collections::HashMap;
use std::sync::{Arc, atomic::{AtomicU16, AtomicU64, Ordering}};
use std::time::{Duration, Instant};
use tokio::sync::{mpsc, RwLock, Mutex};
use uuid::Uuid;
use serde_json::json;
use chrono::{DateTime, Utc, Duration as ChronoDuration};
use foxhunt_core::types::prelude::*;
use tli::prelude::*;
pub mod test_config;
pub mod test_database;
pub mod mock_services;
pub mod test_data;
pub use test_config::*;
pub use test_database::*;
pub use mock_services::*;
pub use test_data::*;
/// Integration test configuration
#[derive(Debug, Clone)]
pub struct IntegrationTestConfig {
// Performance requirements
pub max_latency_ns: u64,
pub max_db_latency_ns: u64,
pub max_risk_latency_ns: u64,
pub max_ml_inference_latency_ns: u64,
pub max_init_latency_ns: u64,
pub min_throughput_ops_per_sec: f64,
pub min_db_throughput_ops_per_sec: f64,
pub min_backtest_throughput: f64,
// Test parameters
pub request_timeout_ms: u64,
pub max_retry_attempts: u32,
pub circuit_breaker_threshold: u32,
pub concurrent_order_count: usize,
pub parallel_backtest_count: usize,
pub concurrent_operation_count: usize,
pub batch_size: usize,
pub stream_buffer_size: usize,
pub stress_test_duration_secs: u64,
// Database configuration
pub test_db_url: String,
pub test_db_max_connections: u32,
pub enable_database_cleanup: bool,
// Mock service configuration
pub mock_service_latency_ms: u64,
pub mock_failure_rate: f64,
pub enable_chaos_testing: bool,
}
impl Default for IntegrationTestConfig {
fn default() -> Self {
Self {
// HFT Performance requirements
max_latency_ns: 50_000, // 50µs max latency
max_db_latency_ns: 100_000, // 100µs max DB latency
max_risk_latency_ns: 25_000, // 25µs max risk validation
max_ml_inference_latency_ns: 50_000, // 50µs max ML inference
max_init_latency_ns: 1_000_000, // 1ms max initialization
min_throughput_ops_per_sec: 10_000.0, // 10K ops/sec minimum
min_db_throughput_ops_per_sec: 5_000.0, // 5K DB ops/sec minimum
min_backtest_throughput: 10.0, // 10 backtests/sec minimum
// Test parameters
request_timeout_ms: 5_000, // 5 second timeout
max_retry_attempts: 3,
circuit_breaker_threshold: 5,
concurrent_order_count: 100,
parallel_backtest_count: 20,
concurrent_operation_count: 50,
batch_size: 1000,
stream_buffer_size: 10_000,
stress_test_duration_secs: 30,
// Database configuration
test_db_url: std::env::var("TEST_DATABASE_URL")
.unwrap_or_else(|_| "postgresql://foxhunt_test:test_password@localhost:5432/foxhunt_test".to_string()),
test_db_max_connections: 20,
enable_database_cleanup: true,
// Mock service configuration
mock_service_latency_ms: 10,
mock_failure_rate: 0.01, // 1% failure rate
enable_chaos_testing: false,
}
}
}
/// Base test result structure
#[derive(Debug, Clone)]
pub struct TestResult {
pub name: String,
pub passed: bool,
pub execution_time: Duration,
pub assertions: Vec<Assertion>,
pub errors: Vec<String>,
pub metadata: HashMap<String, serde_json::Value>,
}
impl TestResult {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
passed: false,
execution_time: Duration::default(),
assertions: Vec::new(),
errors: Vec::new(),
metadata: HashMap::new(),
}
}
pub fn add_assertion(&mut self, description: &str, passed: bool) {
self.assertions.push(Assertion {
description: description.to_string(),
passed,
});
}
pub fn add_error(&mut self, error: String) {
self.errors.push(error);
}
pub fn set_passed(&mut self, passed: bool) {
self.passed = passed;
}
}
/// Individual test assertion
#[derive(Debug, Clone)]
pub struct Assertion {
pub description: String,
pub passed: bool,
}
/// Test suite containing multiple test results
#[derive(Debug, Clone)]
pub struct TestSuite {
pub name: String,
pub tests: Vec<TestResult>,
pub passed_tests: usize,
pub total_tests: usize,
pub passed: bool,
pub execution_time: Duration,
pub metadata: HashMap<String, serde_json::Value>,
}
impl TestSuite {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
tests: Vec::new(),
passed_tests: 0,
total_tests: 0,
passed: false,
execution_time: Duration::default(),
metadata: HashMap::new(),
}
}
pub fn add_test_result(&mut self, test: TestResult) {
if test.passed {
self.passed_tests += 1;
}
self.total_tests += 1;
self.tests.push(test);
}
pub fn set_passed(&mut self, passed: bool) {
self.passed = passed;
}
}
/// Port manager for test services
#[derive(Debug)]
pub struct TestPortManager {
next_port: AtomicU16,
allocated_ports: RwLock<Vec<u16>>,
}
impl TestPortManager {
pub fn new() -> Self {
Self {
next_port: AtomicU16::new(50000), // Start from port 50000
allocated_ports: RwLock::new(Vec::new()),
}
}
pub async fn allocate_port(&self) -> u16 {
loop {
let port = self.next_port.fetch_add(1, Ordering::Relaxed);
if port > 65000 {
// Reset if we've used too many ports
self.next_port.store(50000, Ordering::Relaxed);
continue;
}
// Check if port is available
if let Ok(listener) = tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port)).await {
drop(listener); // Release the port
self.allocated_ports.write().await.push(port);
return port;
}
}
}
pub async fn release_port(&self, port: u16) {
let mut allocated = self.allocated_ports.write().await;
if let Some(pos) = allocated.iter().position(|&p| p == port) {
allocated.remove(pos);
}
}
}
impl Default for TestPortManager {
fn default() -> Self {
Self::new()
}
}
/// Global test port manager instance
lazy_static::lazy_static! {
pub static ref TEST_PORT_MANAGER: TestPortManager = TestPortManager::new();
}
/// Test event publisher for streaming tests
pub struct TestEventPublisher {
event_sender: mpsc::UnboundedSender<TliEvent>,
event_receiver: Arc<Mutex<mpsc::UnboundedReceiver<TliEvent>>>,
published_events: AtomicU64,
}
impl TestEventPublisher {
pub async fn new() -> TliResult<Self> {
let (sender, receiver) = mpsc::unbounded_channel();
Ok(Self {
event_sender: sender,
event_receiver: Arc::new(Mutex::new(receiver)),
published_events: AtomicU64::new(0),
})
}
pub async fn publish_event(&self, event: TliEvent) -> TliResult<()> {
self.event_sender.send(event)
.map_err(|e| TliError::InternalError(format!("Failed to publish event: {}", e)))?;
self.published_events.fetch_add(1, Ordering::Relaxed);
Ok(())
}
pub async fn publish_market_data_burst(&self, symbol: &str, count: usize) -> TliResult<()> {
for i in 0..count {
let event = TliEvent {
id: Uuid::new_v4(),
event_type: EventType::MarketData,
timestamp: Utc::now(),
data: json!({
"symbol": symbol,
"price": 150.0 + (i as f64 * 0.01),
"volume": 100 + i,
"sequence": i
}),
source: "test_publisher".to_string(),
};
self.publish_event(event).await?;
}
Ok(())
}
pub async fn publish_order_lifecycle(&self, order_id: &str) -> TliResult<()> {
let states = vec!["pending", "partially_filled", "filled"];
for (i, state) in states.iter().enumerate() {
let event = TliEvent {
id: Uuid::new_v4(),
event_type: EventType::OrderUpdate,
timestamp: Utc::now(),
data: json!({
"order_id": order_id,
"status": state,
"filled_quantity": (i + 1) * 50,
"remaining_quantity": 100 - ((i + 1) * 50)
}),
source: "test_lifecycle".to_string(),
};
self.publish_event(event).await?;
// Small delay between state changes
tokio::time::sleep(Duration::from_millis(100)).await;
}
Ok(())
}
pub fn get_published_count(&self) -> u64 {
self.published_events.load(Ordering::Relaxed)
}
pub async fn receive_event(&self) -> Option<TliEvent> {
let mut receiver = self.event_receiver.lock().await;
receiver.recv().await
}
}
/// Performance metrics collector for tests
#[derive(Debug, Default)]
pub struct TestMetricsCollector {
latency_measurements: RwLock<HashMap<String, Vec<u64>>>,
throughput_measurements: RwLock<HashMap<String, Vec<f64>>>,
error_counts: RwLock<HashMap<String, u64>>,
custom_metrics: RwLock<HashMap<String, serde_json::Value>>,
}
impl TestMetricsCollector {
pub fn new() -> Self {
Self::default()
}
pub async fn record_latency(&self, operation: &str, latency_ns: u64) {
let mut latencies = self.latency_measurements.write().await;
latencies.entry(operation.to_string()).or_insert_with(Vec::new).push(latency_ns);
}
pub async fn record_throughput(&self, operation: &str, ops_per_sec: f64) {
let mut throughputs = self.throughput_measurements.write().await;
throughputs.entry(operation.to_string()).or_insert_with(Vec::new).push(ops_per_sec);
}
pub async fn record_error(&self, operation: &str) {
let mut errors = self.error_counts.write().await;
*errors.entry(operation.to_string()).or_insert(0) += 1;
}
pub async fn record_custom_metric(&self, name: &str, value: serde_json::Value) {
let mut metrics = self.custom_metrics.write().await;
metrics.insert(name.to_string(), value);
}
pub async fn get_latency_stats(&self, operation: &str) -> Option<LatencyStats> {
let latencies = self.latency_measurements.read().await;
if let Some(measurements) = latencies.get(operation) {
if measurements.is_empty() {
return None;
}
let mut sorted = measurements.clone();
sorted.sort_unstable();
let len = sorted.len();
let avg = sorted.iter().sum::<u64>() / 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];
Some(LatencyStats { avg, p50, p95, p99, max })
} else {
None
}
}
pub async fn get_summary(&self) -> serde_json::Value {
let latencies = self.latency_measurements.read().await;
let throughputs = self.throughput_measurements.read().await;
let errors = self.error_counts.read().await;
let custom = self.custom_metrics.read().await;
let mut latency_summary = serde_json::Map::new();
for (operation, measurements) in latencies.iter() {
if let Some(stats) = self.get_latency_stats(operation).await {
latency_summary.insert(operation.clone(), json!({
"count": measurements.len(),
"avg_ns": stats.avg,
"p50_ns": stats.p50,
"p95_ns": stats.p95,
"p99_ns": stats.p99,
"max_ns": stats.max
}));
}
}
let mut throughput_summary = serde_json::Map::new();
for (operation, measurements) in throughputs.iter() {
if !measurements.is_empty() {
let avg = measurements.iter().sum::<f64>() / measurements.len() as f64;
let max = measurements.iter().fold(0.0f64, |a, &b| a.max(b));
let min = measurements.iter().fold(f64::INFINITY, |a, &b| a.min(b));
throughput_summary.insert(operation.clone(), json!({
"count": measurements.len(),
"avg_ops_per_sec": avg,
"max_ops_per_sec": max,
"min_ops_per_sec": min
}));
}
}
json!({
"latencies": latency_summary,
"throughput": throughput_summary,
"errors": errors.clone(),
"custom_metrics": custom.clone()
})
}
}
/// Latency statistics structure
#[derive(Debug, Clone)]
pub struct LatencyStats {
pub avg: u64,
pub p50: u64,
pub p95: u64,
pub p99: u64,
pub max: u64,
}
/// Test environment setup and cleanup
pub struct TestEnvironment {
pub config: IntegrationTestConfig,
pub metrics: Arc<TestMetricsCollector>,
pub port_manager: Arc<TestPortManager>,
pub event_publisher: Arc<TestEventPublisher>,
cleanup_tasks: Vec<Box<dyn Fn() -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>> + Send + Sync>>,
}
impl TestEnvironment {
pub async fn new(config: IntegrationTestConfig) -> TliResult<Self> {
let metrics = Arc::new(TestMetricsCollector::new());
let port_manager = Arc::new(TestPortManager::new());
let event_publisher = Arc::new(TestEventPublisher::new().await?);
Ok(Self {
config,
metrics,
port_manager,
event_publisher,
cleanup_tasks: Vec::new(),
})
}
pub fn add_cleanup_task<F, Fut>(&mut self, task: F)
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = ()> + Send + 'static,
{
let boxed_task = Box::new(move || Box::pin(task()) as std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>);
self.cleanup_tasks.push(boxed_task);
}
pub async fn cleanup(self) {
for task in self.cleanup_tasks {
task().await;
}
}
}