Files
foxhunt/tests/fixtures/test_config.rs
jgrusewski 6093eac7bf 🔧 Tonic 0.14 Upgrade: Auto-generated and build system changes
Wave 64-65 cleanup: Proto regeneration and build system updates from Tonic 0.12→0.14 upgrade

Files updated:
- Cargo.lock: Dependency resolution for Tonic 0.14.2
- All build.rs: Updated for tonic-prost-build
- Proto files: Regenerated with tonic-prost 0.14
- Examples/tests: Updated for new gRPC API

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-03 07:34:26 +02:00

519 lines
17 KiB
Rust

//! Test Configuration for Foxhunt HFT Trading System
//!
//! This module provides configuration utilities for test environments,
//! including database setup, service configuration, and test parameters.
use std::collections::HashMap;
use std::env;
use serde::{Deserialize, Serialize};
use rust_decimal::Decimal;
/// Test environment configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestConfig {
pub database: DatabaseConfig,
pub services: ServiceConfig,
pub performance: PerformanceConfig,
pub market_data: MarketDataConfig,
pub risk: RiskConfig,
}
/// Database configuration for tests
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseConfig {
pub url: String,
pub max_connections: u32,
pub connection_timeout_ms: u64,
pub query_timeout_ms: u64,
pub enable_logging: bool,
pub auto_migrate: bool,
pub cleanup_on_drop: bool,
}
/// Service configuration for tests
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceConfig {
pub trading_service: ServiceEndpoint,
pub ml_training_service: ServiceEndpoint,
pub backtesting_service: ServiceEndpoint,
pub enable_mocks: bool,
pub mock_latency_ms: u64,
pub mock_failure_rate: f64,
}
/// Service endpoint configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceEndpoint {
pub host: String,
pub port: u16,
pub use_tls: bool,
pub timeout_ms: u64,
pub max_retries: u32,
}
/// Performance testing configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceConfig {
pub max_latency_ns: u64,
pub min_throughput_ops_per_sec: f64,
pub test_duration_secs: u64,
pub warmup_duration_secs: u64,
pub concurrent_operations: usize,
pub stress_test_multiplier: f64,
}
/// Market data configuration for tests
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketDataConfig {
pub default_symbols: Vec<String>,
pub tick_rate_hz: u32,
pub price_precision: u32,
pub volume_range: (u64, u64),
pub volatility_range: (f64, f64),
pub enable_realistic_data: bool,
}
/// Risk management configuration for tests
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RiskConfig {
pub default_var_limit: Decimal,
pub default_position_limit: Decimal,
pub default_concentration_limit: Decimal,
pub stress_test_scenarios: Vec<String>,
pub enable_circuit_breakers: bool,
pub max_drawdown_limit: Decimal,
}
impl Default for TestConfig {
fn default() -> Self {
Self {
database: DatabaseConfig::default(),
services: ServiceConfig::default(),
performance: PerformanceConfig::default(),
market_data: MarketDataConfig::default(),
risk: RiskConfig::default(),
}
}
}
impl Default for DatabaseConfig {
fn default() -> Self {
Self {
url: env::var("TEST_DATABASE_URL")
.unwrap_or_else(|_| "postgresql://foxhunt_test:test_password@localhost:5432/foxhunt_test".to_string()),
max_connections: 10,
connection_timeout_ms: 5000,
query_timeout_ms: 30000,
enable_logging: false, // Disable SQL logging in tests by default
auto_migrate: true,
cleanup_on_drop: true,
}
}
}
impl Default for ServiceConfig {
fn default() -> Self {
Self {
trading_service: ServiceEndpoint {
host: "localhost".to_string(),
port: 8001,
use_tls: false,
timeout_ms: 5000,
max_retries: 3,
},
ml_training_service: ServiceEndpoint {
host: "localhost".to_string(),
port: 8002,
use_tls: false,
timeout_ms: 10000,
max_retries: 3,
},
backtesting_service: ServiceEndpoint {
host: "localhost".to_string(),
port: 8003,
use_tls: false,
timeout_ms: 30000,
max_retries: 3,
},
enable_mocks: true, // Enable mocks by default for unit tests
mock_latency_ms: 1,
mock_failure_rate: 0.0, // No failures by default
}
}
}
impl Default for PerformanceConfig {
fn default() -> Self {
Self {
max_latency_ns: 50_000, // 50µs
min_throughput_ops_per_sec: 1000.0,
test_duration_secs: 10,
warmup_duration_secs: 2,
concurrent_operations: 10,
stress_test_multiplier: 2.0,
}
}
}
impl Default for MarketDataConfig {
fn default() -> Self {
use super::{ALL_TEST_SYMBOLS};
Self {
default_symbols: ALL_TEST_SYMBOLS.iter().map(|&s| s.to_string()).collect(),
tick_rate_hz: 1000, // 1000 ticks per second
price_precision: 2, // 2 decimal places
volume_range: (100, 10000),
volatility_range: (0.01, 0.05), // 1% to 5% daily volatility
enable_realistic_data: true,
}
}
}
impl Default for RiskConfig {
fn default() -> Self {
Self {
default_var_limit: Decimal::from(100000), // $100k
default_position_limit: Decimal::from(1000000), // $1M
default_concentration_limit: Decimal::new(25, 2), // 25%
stress_test_scenarios: vec![
"market_crash".to_string(),
"interest_rate_shock".to_string(),
"liquidity_crisis".to_string(),
],
enable_circuit_breakers: true,
max_drawdown_limit: Decimal::new(20, 2), // 20%
}
}
}
impl TestConfig {
/// Load configuration from environment variables and defaults
pub fn from_env() -> Self {
let mut config = Self::default();
// Override with environment variables if present
if let Ok(db_url) = env::var("TEST_DATABASE_URL") {
config.database.url = db_url;
}
if let Ok(max_connections) = env::var("TEST_DB_MAX_CONNECTIONS") {
if let Ok(connections) = max_connections.parse() {
config.database.max_connections = connections;
}
}
if let Ok(enable_mocks) = env::var("TEST_ENABLE_MOCKS") {
config.services.enable_mocks = enable_mocks.to_lowercase() == "true";
}
if let Ok(max_latency) = env::var("TEST_MAX_LATENCY_NS") {
if let Ok(latency) = max_latency.parse() {
config.performance.max_latency_ns = latency;
}
}
config
}
/// Create configuration for unit tests (fast, mocked)
pub fn for_unit_tests() -> Self {
Self {
database: DatabaseConfig {
max_connections: 5,
connection_timeout_ms: 1000,
query_timeout_ms: 5000,
enable_logging: false,
cleanup_on_drop: true,
..DatabaseConfig::default()
},
services: ServiceConfig {
enable_mocks: true,
mock_latency_ms: 0, // No artificial latency for unit tests
mock_failure_rate: 0.0,
..ServiceConfig::default()
},
performance: PerformanceConfig {
test_duration_secs: 1,
warmup_duration_secs: 0,
concurrent_operations: 1,
..PerformanceConfig::default()
},
market_data: MarketDataConfig {
tick_rate_hz: 10, // Low tick rate for fast tests
..MarketDataConfig::default()
},
..Self::default()
}
}
/// Create configuration for integration tests (realistic)
pub fn for_integration_tests() -> Self {
Self {
services: ServiceConfig {
enable_mocks: false, // Use real services
..ServiceConfig::default()
},
performance: PerformanceConfig {
test_duration_secs: 30,
warmup_duration_secs: 5,
concurrent_operations: 50,
..PerformanceConfig::default()
},
..Self::default()
}
}
/// Create configuration for performance tests (demanding)
pub fn for_performance_tests() -> Self {
Self {
services: ServiceConfig {
enable_mocks: false,
..ServiceConfig::default()
},
performance: PerformanceConfig {
max_latency_ns: 14_000, // 14µs target for HFT
min_throughput_ops_per_sec: 10000.0,
test_duration_secs: 60,
warmup_duration_secs: 10,
concurrent_operations: 100,
stress_test_multiplier: 5.0,
..PerformanceConfig::default()
},
market_data: MarketDataConfig {
tick_rate_hz: 10000, // High frequency data
..MarketDataConfig::default()
},
..Self::default()
}
}
/// Create configuration for stress tests (extreme)
pub fn for_stress_tests() -> Self {
Self {
performance: PerformanceConfig {
test_duration_secs: 300, // 5 minutes
concurrent_operations: 1000,
stress_test_multiplier: 10.0,
..PerformanceConfig::default()
},
market_data: MarketDataConfig {
tick_rate_hz: 50000, // Extreme tick rate
volatility_range: (0.05, 0.20), // Higher volatility
..MarketDataConfig::default()
},
..Self::default()
}
}
/// Validate configuration values
pub fn validate(&self) -> Result<(), String> {
if self.database.max_connections == 0 {
return Err("Database max_connections must be greater than 0".to_string());
}
if self.performance.max_latency_ns == 0 {
return Err("Performance max_latency_ns must be greater than 0".to_string());
}
if self.performance.min_throughput_ops_per_sec <= 0.0 {
return Err("Performance min_throughput_ops_per_sec must be positive".to_string());
}
if self.services.mock_failure_rate < 0.0 || self.services.mock_failure_rate > 1.0 {
return Err("Services mock_failure_rate must be between 0.0 and 1.0".to_string());
}
if self.market_data.tick_rate_hz == 0 {
return Err("Market data tick_rate_hz must be greater than 0".to_string());
}
if self.risk.default_var_limit <= Decimal::ZERO {
return Err("Risk default_var_limit must be positive".to_string());
}
if self.risk.default_concentration_limit <= Decimal::ZERO || self.risk.default_concentration_limit > Decimal::ONE {
return Err("Risk default_concentration_limit must be between 0 and 1".to_string());
}
Ok(())
}
/// Get database URL with test database suffix if not already present
pub fn get_test_database_url(&self) -> String {
let url = &self.database.url;
if url.contains("_test") {
url.clone()
} else {
// Append _test to database name
if let Some(last_slash) = url.rfind('/') {
let (base, db_name) = url.split_at(last_slash + 1);
format!("{}{}_test", base, db_name)
} else {
format!("{}_test", url)
}
}
}
/// Create environment variables map for child processes
pub fn to_env_vars(&self) -> HashMap<String, String> {
let mut env_vars = HashMap::new();
env_vars.insert("TEST_DATABASE_URL".to_string(), self.get_test_database_url());
env_vars.insert("TEST_DB_MAX_CONNECTIONS".to_string(), self.database.max_connections.to_string());
env_vars.insert("TEST_ENABLE_MOCKS".to_string(), self.services.enable_mocks.to_string());
env_vars.insert("TEST_MAX_LATENCY_NS".to_string(), self.performance.max_latency_ns.to_string());
env_vars.insert("TEST_MIN_THROUGHPUT".to_string(), self.performance.min_throughput_ops_per_sec.to_string());
env_vars.insert("RUST_LOG".to_string(), if self.database.enable_logging { "debug" } else { "warn" }.to_string());
env_vars
}
}
/// Test configuration builder for fluent configuration
#[derive(Debug)]
pub struct TestConfigBuilder {
config: TestConfig,
}
impl TestConfigBuilder {
pub fn new() -> Self {
Self {
config: TestConfig::default(),
}
}
pub fn with_database_url(mut self, url: impl Into<String>) -> Self {
self.config.database.url = url.into();
self
}
pub fn with_max_connections(mut self, max_connections: u32) -> Self {
self.config.database.max_connections = max_connections;
self
}
pub fn with_mocks_enabled(mut self, enabled: bool) -> Self {
self.config.services.enable_mocks = enabled;
self
}
pub fn with_max_latency_ns(mut self, latency: u64) -> Self {
self.config.performance.max_latency_ns = latency;
self
}
pub fn with_test_duration(mut self, duration_secs: u64) -> Self {
self.config.performance.test_duration_secs = duration_secs;
self
}
pub fn with_concurrent_operations(mut self, operations: usize) -> Self {
self.config.performance.concurrent_operations = operations;
self
}
pub fn with_var_limit(mut self, limit: Decimal) -> Self {
self.config.risk.default_var_limit = limit;
self
}
pub fn build(self) -> Result<TestConfig, String> {
self.config.validate()?;
Ok(self.config)
}
}
impl Default for TestConfigBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = TestConfig::default();
assert!(config.validate().is_ok());
assert!(config.database.max_connections > 0);
assert!(config.performance.max_latency_ns > 0);
assert!(!config.market_data.default_symbols.is_empty());
}
#[test]
fn test_unit_test_config() {
let config = TestConfig::for_unit_tests();
assert!(config.validate().is_ok());
assert!(config.services.enable_mocks);
assert_eq!(config.services.mock_latency_ms, 0);
assert_eq!(config.performance.test_duration_secs, 1);
}
#[test]
fn test_performance_test_config() {
let config = TestConfig::for_performance_tests();
assert!(config.validate().is_ok());
assert!(!config.services.enable_mocks);
assert_eq!(config.performance.max_latency_ns, 14_000);
assert!(config.performance.concurrent_operations >= 100);
}
#[test]
fn test_config_builder() {
let config = TestConfigBuilder::new()
.with_max_connections(20)
.with_mocks_enabled(false)
.with_max_latency_ns(10_000)
.build()
.unwrap();
assert_eq!(config.database.max_connections, 20);
assert!(!config.services.enable_mocks);
assert_eq!(config.performance.max_latency_ns, 10_000);
}
#[test]
fn test_config_validation() {
let mut config = TestConfig::default();
// Test invalid max_connections
config.database.max_connections = 0;
assert!(config.validate().is_err());
// Test invalid failure rate
config.database.max_connections = 10;
config.services.mock_failure_rate = 1.5;
assert!(config.validate().is_err());
// Test valid config
config.services.mock_failure_rate = 0.1;
assert!(config.validate().is_ok());
}
#[test]
fn test_test_database_url() {
let config = TestConfig::default();
let test_url = config.get_test_database_url();
assert!(test_url.contains("_test"));
// Test with already test database
let mut config_with_test = config.clone();
config_with_test.database.url = "postgresql://user:pass@localhost/db_test".to_string();
let test_url2 = config_with_test.get_test_database_url();
assert_eq!(test_url2, config_with_test.database.url);
}
#[test]
fn test_env_vars_generation() {
let config = TestConfig::default();
let env_vars = config.to_env_vars();
assert!(env_vars.contains_key("TEST_DATABASE_URL"));
assert!(env_vars.contains_key("TEST_DB_MAX_CONNECTIONS"));
assert!(env_vars.contains_key("TEST_ENABLE_MOCKS"));
assert!(env_vars.contains_key("RUST_LOG"));
}
}