Files
foxhunt/crates/config/tests/validation_comprehensive_tests.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

933 lines
28 KiB
Rust

//! Comprehensive validation edge cases tests for config package
//!
//! This test suite provides additional edge case coverage for:
//! - ML config validation (SimulationConfig, TrainingConfig)
//! - Risk config validation (StressScenarioConfig, AssetClassMapping)
//! - Broker config validation (routing rules, commission rates)
//! - URL parsing and malformed input handling
//! - Numeric overflow and boundary conditions
//! - String edge cases (Unicode, special chars, empty strings)
//! - Deserialization edge cases (malformed JSON/TOML)
//! - Environment variable parsing edge cases
use config::{
database::DatabaseConfig,
ml_config::{MarketCapTier, MarketState, SimulationConfig, SymbolConfig as MLSymbolConfig},
risk_config::{
AssetClass as RiskAssetClass, AssetClassMapping, RiskConfig as RiskConfigV2,
StressScenarioConfig,
},
runtime::{DatabaseRuntimeConfig, Environment, LimitsConfig, TimeoutConfig},
schemas::{AssetClassificationSchema, S3Config},
structures::{
BrokerConfig, BrokerRoutingRule, CircuitBreakerConfig, CommissionConfig, KellyConfig,
PositionLimitsConfig, VarConfig,
},
};
use std::collections::HashMap;
use std::time::Duration;
// ============================================================================
// ML Config Validation Tests
// ============================================================================
#[test]
fn test_simulation_config_negative_price() {
let mut config = SimulationConfig::default();
config.initial_market_state.default_symbol.initial_price = -100.0;
// Negative prices are structurally allowed but economically invalid
// In production, this should be validated
assert_eq!(
config.initial_market_state.default_symbol.initial_price,
-100.0
);
}
#[test]
fn test_simulation_config_zero_price() {
let mut config = SimulationConfig::default();
config.initial_market_state.default_symbol.initial_price = 0.0;
// Zero prices are structurally allowed
assert_eq!(
config.initial_market_state.default_symbol.initial_price,
0.0
);
}
#[test]
fn test_simulation_config_infinity_price() {
let mut config = SimulationConfig::default();
config.initial_market_state.default_symbol.initial_price = f64::INFINITY;
// Infinity is structurally allowed (serde serializes it)
assert!(config
.initial_market_state
.default_symbol
.initial_price
.is_infinite());
}
#[test]
fn test_simulation_config_nan_price() {
let mut config = SimulationConfig::default();
config.initial_market_state.default_symbol.initial_price = f64::NAN;
// NaN is structurally allowed
assert!(config
.initial_market_state
.default_symbol
.initial_price
.is_nan());
}
#[test]
fn test_simulation_config_negative_volatility() {
let mut config = SimulationConfig::default();
config.initial_market_state.default_symbol.volatility = -0.5;
// Negative volatility is economically invalid but structurally allowed
assert_eq!(config.initial_market_state.default_symbol.volatility, -0.5);
}
#[test]
fn test_simulation_config_extreme_volatility() {
let mut config = SimulationConfig::default();
config.initial_market_state.default_symbol.volatility = 100.0; // 10,000% volatility
// Extreme volatility is structurally allowed
assert_eq!(config.initial_market_state.default_symbol.volatility, 100.0);
}
#[test]
fn test_simulation_config_negative_volume() {
let mut config = SimulationConfig::default();
config.initial_market_state.default_symbol.base_volume = -1000000.0;
// Negative volume is economically invalid but structurally allowed
assert_eq!(
config.initial_market_state.default_symbol.base_volume,
-1000000.0
);
}
#[test]
fn test_simulation_config_spread_min_greater_than_max() {
let mut config = SimulationConfig::default();
config.initial_market_state.default_symbol.min_spread_bps = 50.0;
config.initial_market_state.default_symbol.max_spread_bps = 10.0;
// Min > Max is logically invalid but structurally allowed
assert!(
config.initial_market_state.default_symbol.min_spread_bps
> config.initial_market_state.default_symbol.max_spread_bps
);
}
#[test]
fn test_simulation_parameters_zero_update_rate() {
let mut config = SimulationConfig::default();
config.parameters.update_rate_hz = 0;
// Zero update rate would cause division by zero in interval calculations
assert_eq!(config.parameters.update_rate_hz, 0);
}
#[test]
fn test_simulation_parameters_extreme_update_rate() {
let mut config = SimulationConfig::default();
config.parameters.update_rate_hz = u32::MAX; // ~4 billion Hz
// Extreme update rate is structurally allowed
assert_eq!(config.parameters.update_rate_hz, u32::MAX);
}
#[test]
fn test_simulation_parameters_trend_out_of_range() {
let mut config = SimulationConfig::default();
config.parameters.trend = 5.0; // Should be -1.0 to 1.0
// Out-of-range trend is structurally allowed
assert_eq!(config.parameters.trend, 5.0);
}
#[test]
fn test_test_symbol_config_zero_count() {
let mut config = SimulationConfig::default();
config.test_symbols.count = 0;
// Zero symbols is valid (no test symbols generated)
assert_eq!(config.test_symbols.count, 0);
}
#[test]
fn test_test_symbol_config_extreme_count() {
let mut config = SimulationConfig::default();
config.test_symbols.count = usize::MAX;
// Extreme count would exhaust memory but is structurally allowed
assert_eq!(config.test_symbols.count, usize::MAX);
}
#[test]
fn test_test_symbol_config_inverted_price_range() {
let mut config = SimulationConfig::default();
config.test_symbols.price_range = (500.0, 50.0); // Max < Min
// Inverted range is logically invalid but structurally allowed
assert!(config.test_symbols.price_range.0 > config.test_symbols.price_range.1);
}
#[test]
fn test_test_symbol_config_negative_price_range() {
let mut config = SimulationConfig::default();
config.test_symbols.price_range = (-100.0, -10.0);
// Negative prices are economically invalid but structurally allowed
assert!(config.test_symbols.price_range.0 < 0.0);
}
#[test]
fn test_market_state_empty_symbols() {
let market_state = MarketState {
symbols: HashMap::new(),
default_symbol: MLSymbolConfig {
initial_price: 100.0,
volatility: 0.3,
base_volume: 1_000_000.0,
min_spread_bps: 5.0,
max_spread_bps: 20.0,
market_cap_tier: MarketCapTier::Test,
},
};
// Empty symbols map is valid (all symbols use default)
assert_eq!(market_state.symbols.len(), 0);
}
#[test]
fn test_market_state_duplicate_symbol_keys() {
let mut symbols = HashMap::new();
symbols.insert(
"AAPL".to_string(),
MLSymbolConfig {
initial_price: 150.0,
volatility: 0.25,
base_volume: 50_000_000.0,
min_spread_bps: 1.0,
max_spread_bps: 5.0,
market_cap_tier: MarketCapTier::LargeCap,
},
);
// HashMap automatically handles duplicates (overwrites)
symbols.insert(
"AAPL".to_string(),
MLSymbolConfig {
initial_price: 160.0,
volatility: 0.3,
base_volume: 60_000_000.0,
min_spread_bps: 2.0,
max_spread_bps: 8.0,
market_cap_tier: MarketCapTier::LargeCap,
},
);
// Second insert overwrites first
assert_eq!(symbols.get("AAPL").unwrap().initial_price, 160.0);
}
// ============================================================================
// Risk Config Validation Tests
// ============================================================================
#[test]
fn test_stress_scenario_empty_id() {
let scenario = StressScenarioConfig {
id: String::new(),
name: "Test Scenario".to_string(),
description: "Test".to_string(),
instrument_shocks: HashMap::new(),
asset_class_shocks: HashMap::new(),
volatility_multiplier: 1.5,
volatility_multipliers: HashMap::new(),
correlation_adjustments: HashMap::new(),
liquidity_haircuts: HashMap::new(),
is_active: true,
};
// Empty ID is structurally allowed but should be validated
assert!(scenario.id.is_empty());
}
#[test]
fn test_stress_scenario_negative_volatility_multiplier() {
let scenario = StressScenarioConfig {
id: "test-1".to_string(),
name: "Test Scenario".to_string(),
description: "Test".to_string(),
instrument_shocks: HashMap::new(),
asset_class_shocks: HashMap::new(),
volatility_multiplier: -2.0,
volatility_multipliers: HashMap::new(),
correlation_adjustments: HashMap::new(),
liquidity_haircuts: HashMap::new(),
is_active: true,
};
// Negative multiplier is economically invalid
assert!(scenario.volatility_multiplier < 0.0);
}
#[test]
fn test_stress_scenario_extreme_instrument_shocks() {
let mut shocks = HashMap::new();
shocks.insert("AAPL".to_string(), -100.0); // -10,000% shock (total loss + more)
shocks.insert("MSFT".to_string(), 1000.0); // 100,000% gain
let scenario = StressScenarioConfig {
id: "extreme-1".to_string(),
name: "Extreme Scenario".to_string(),
description: "Extreme shocks".to_string(),
instrument_shocks: shocks,
asset_class_shocks: HashMap::new(),
volatility_multiplier: 1.0,
volatility_multipliers: HashMap::new(),
correlation_adjustments: HashMap::new(),
liquidity_haircuts: HashMap::new(),
is_active: true,
};
// Extreme shocks are structurally allowed
assert_eq!(scenario.instrument_shocks.get("AAPL"), Some(&-100.0));
assert_eq!(scenario.instrument_shocks.get("MSFT"), Some(&1000.0));
}
#[test]
fn test_stress_scenario_liquidity_haircut_greater_than_one() {
let mut haircuts = HashMap::new();
haircuts.insert(RiskAssetClass::SmallCapEquity, 1.5); // 150% haircut
let scenario = StressScenarioConfig {
id: "liquidity-1".to_string(),
name: "Liquidity Test".to_string(),
description: "Test liquidity haircuts".to_string(),
instrument_shocks: HashMap::new(),
asset_class_shocks: HashMap::new(),
volatility_multiplier: 1.0,
volatility_multipliers: HashMap::new(),
correlation_adjustments: HashMap::new(),
liquidity_haircuts: haircuts,
is_active: true,
};
// Haircut > 1.0 means position value goes negative (economically invalid)
assert!(
scenario
.liquidity_haircuts
.get(&RiskAssetClass::SmallCapEquity)
.unwrap()
> &1.0
);
}
#[test]
fn test_asset_class_mapping_empty_mappings() {
let mapping = AssetClassMapping {
mappings: HashMap::new(),
default_class: RiskAssetClass::Alternatives,
};
// Empty mappings is valid (all symbols use default)
assert_eq!(mapping.mappings.len(), 0);
}
#[test]
fn test_asset_class_mapping_get_unmapped_symbol() {
let mapping = AssetClassMapping {
mappings: HashMap::new(),
default_class: RiskAssetClass::Technology,
};
// Unmapped symbol should return None
assert_eq!(mapping.mappings.get("UNKNOWN"), None);
}
#[test]
fn test_risk_config_negative_var_confidence() {
let config = RiskConfigV2 {
stress_scenarios: Vec::new(),
asset_class_mapping: AssetClassMapping {
mappings: HashMap::new(),
default_class: RiskAssetClass::Alternatives,
},
default_volatility_multiplier: 1.0,
max_portfolio_loss_pct: 20.0,
var_confidence_level: -0.5, // Invalid: should be 0.0-1.0
var_time_horizon_days: 1,
};
// Negative confidence is mathematically invalid
assert!(config.var_confidence_level < 0.0);
}
#[test]
fn test_risk_config_var_confidence_greater_than_one() {
let config = RiskConfigV2 {
stress_scenarios: Vec::new(),
asset_class_mapping: AssetClassMapping {
mappings: HashMap::new(),
default_class: RiskAssetClass::Alternatives,
},
default_volatility_multiplier: 1.0,
max_portfolio_loss_pct: 20.0,
var_confidence_level: 1.5, // Invalid: should be 0.0-1.0
var_time_horizon_days: 1,
};
// Confidence > 1.0 is mathematically invalid
assert!(config.var_confidence_level > 1.0);
}
#[test]
fn test_risk_config_zero_time_horizon() {
let config = RiskConfigV2 {
stress_scenarios: Vec::new(),
asset_class_mapping: AssetClassMapping {
mappings: HashMap::new(),
default_class: RiskAssetClass::Alternatives,
},
default_volatility_multiplier: 1.0,
max_portfolio_loss_pct: 20.0,
var_confidence_level: 0.95,
var_time_horizon_days: 0, // Zero horizon is invalid
};
// Zero time horizon makes no economic sense
assert_eq!(config.var_time_horizon_days, 0);
}
// ============================================================================
// Broker Config Validation Tests
// ============================================================================
#[test]
fn test_broker_config_empty_routing_rules() {
let config = BrokerConfig {
routing_rules: Vec::new(),
default_broker: "IBKR".to_string(),
commission_rates: HashMap::new(),
};
// Empty rules is valid (falls back to default broker)
assert_eq!(config.routing_rules.len(), 0);
}
#[test]
fn test_broker_config_empty_default_broker() {
let config = BrokerConfig {
routing_rules: Vec::new(),
default_broker: String::new(),
commission_rates: HashMap::new(),
};
// Empty default broker would cause issues but is structurally allowed
assert!(config.default_broker.is_empty());
}
#[test]
fn test_broker_routing_rule_invalid_regex() {
let rule = BrokerRoutingRule {
priority: 100,
symbol_pattern: "[invalid(regex".to_string(), // Invalid regex
min_quantity: None,
max_quantity: None,
broker_id: "IBKR".to_string(),
description: "Test invalid regex".to_string(),
};
// Invalid regex is structurally allowed (fails at runtime)
assert!(regex::Regex::new(&rule.symbol_pattern).is_err());
}
#[test]
fn test_broker_routing_rule_min_greater_than_max_quantity() {
let rule = BrokerRoutingRule {
priority: 100,
symbol_pattern: ".*".to_string(),
min_quantity: Some(1_000_000.0),
max_quantity: Some(10_000.0), // Min > Max
broker_id: "IBKR".to_string(),
description: "Test invalid quantity range".to_string(),
};
// Inverted range is logically invalid
assert!(rule.min_quantity.unwrap() > rule.max_quantity.unwrap());
}
#[test]
fn test_broker_routing_rule_negative_quantity() {
let rule = BrokerRoutingRule {
priority: 100,
symbol_pattern: ".*".to_string(),
min_quantity: Some(-10_000.0),
max_quantity: Some(-1_000.0),
broker_id: "IBKR".to_string(),
description: "Test negative quantities".to_string(),
};
// Negative quantities are economically invalid
assert!(rule.min_quantity.unwrap() < 0.0);
}
#[test]
fn test_commission_config_negative_rate() {
let config = CommissionConfig {
rate_bps: -0.00005, // Negative commission (broker pays you)
min_commission: 1.0,
};
// Negative rate is economically unusual but structurally allowed
assert!(config.rate_bps < 0.0);
}
#[test]
fn test_commission_config_negative_min_commission() {
let config = CommissionConfig {
rate_bps: 0.00005,
min_commission: -10.0, // Negative minimum
};
// Negative minimum is economically invalid
assert!(config.min_commission < 0.0);
}
#[test]
fn test_commission_config_extreme_rate() {
let config = CommissionConfig {
rate_bps: 100.0, // 10,000 bps = 100% commission
min_commission: 0.0,
};
// 100% commission is economically absurd but structurally allowed
assert_eq!(config.rate_bps, 100.0);
}
// ============================================================================
// Structure Config Validation Tests
// ============================================================================
#[test]
fn test_var_config_confidence_at_boundaries() {
let config_zero = VarConfig {
confidence_level: 0.0,
..VarConfig::default()
};
let config_one = VarConfig {
confidence_level: 1.0,
..VarConfig::default()
};
// Boundaries are mathematically valid
assert_eq!(config_zero.confidence_level, 0.0);
assert_eq!(config_one.confidence_level, 1.0);
}
#[test]
fn test_var_config_negative_lookback_period() {
// u32 can't be negative, but testing zero
let config = VarConfig {
lookback_period_days: 0,
..VarConfig::default()
};
// Zero lookback makes no sense
assert_eq!(config.lookback_period_days, 0);
}
#[test]
fn test_var_config_empty_calculation_method() {
let config = VarConfig {
calculation_method: String::new(),
..VarConfig::default()
};
// Empty method is structurally allowed but should be validated
assert!(config.calculation_method.is_empty());
}
#[test]
fn test_var_config_unknown_calculation_method() {
let config = VarConfig {
calculation_method: "quantum_flux_capacitor".to_string(),
..VarConfig::default()
};
// Unknown method is structurally allowed
assert_eq!(config.calculation_method, "quantum_flux_capacitor");
}
#[test]
fn test_kelly_config_negative_fraction() {
let config = KellyConfig {
kelly_fraction: -0.5,
..KellyConfig::default()
};
// Negative Kelly fraction is mathematically invalid
assert!(config.kelly_fraction < 0.0);
}
#[test]
fn test_kelly_config_fraction_greater_than_one() {
let config = KellyConfig {
kelly_fraction: 2.0, // 200% Kelly = extreme leverage
..KellyConfig::default()
};
// Fraction > 1.0 means over-leveraging
assert!(config.kelly_fraction > 1.0);
}
#[test]
fn test_kelly_config_min_greater_than_max_leverage() {
let config = KellyConfig {
min_kelly_leverage: 5.0,
max_kelly_leverage: 2.0, // Min > Max
..KellyConfig::default()
};
// Inverted range is logically invalid
assert!(config.min_kelly_leverage > config.max_kelly_leverage);
}
#[test]
fn test_kelly_config_zero_lookback_periods() {
let config = KellyConfig {
lookback_periods: 0,
..KellyConfig::default()
};
// Zero lookback makes no sense
assert_eq!(config.lookback_periods, 0);
}
#[test]
fn test_circuit_breaker_negative_threshold() {
let config = CircuitBreakerConfig {
enabled: true,
price_move_threshold: -0.05, // Negative threshold makes no sense
halt_duration_seconds: 300,
};
// Negative threshold is logically invalid
assert!(config.price_move_threshold < 0.0);
}
#[test]
fn test_circuit_breaker_zero_halt_duration() {
let config = CircuitBreakerConfig {
enabled: true,
price_move_threshold: 0.05,
halt_duration_seconds: 0, // Zero duration = instant resume
};
// Zero duration defeats the purpose but is structurally allowed
assert_eq!(config.halt_duration_seconds, 0);
}
#[test]
fn test_circuit_breaker_extreme_halt_duration() {
let config = CircuitBreakerConfig {
enabled: true,
price_move_threshold: 0.05,
halt_duration_seconds: u64::MAX, // ~585 billion years
};
// Extreme duration is structurally allowed
assert_eq!(config.halt_duration_seconds, u64::MAX);
}
#[test]
fn test_position_limits_negative_limits() {
let config = PositionLimitsConfig {
global_limit: -10_000_000.0,
max_leverage: -3.0,
max_var_limit: -100_000.0,
};
// Negative limits are economically invalid
assert!(config.global_limit < 0.0);
assert!(config.max_leverage < 0.0);
assert!(config.max_var_limit < 0.0);
}
#[test]
fn test_position_limits_zero_max_leverage() {
let config = PositionLimitsConfig {
global_limit: 10_000_000.0,
max_leverage: 0.0, // Zero leverage = cash only
max_var_limit: 100_000.0,
};
// Zero leverage is valid (no borrowing)
assert_eq!(config.max_leverage, 0.0);
}
// ============================================================================
// URL Parsing and Malformed Input Tests
// ============================================================================
#[test]
fn test_s3_config_malformed_endpoint_no_scheme() {
let config = S3Config {
endpoint_url: Some("localhost:9000".to_string()), // Missing http://
..S3Config::default()
};
// Missing scheme is structurally allowed (fails at connection time)
assert_eq!(config.endpoint_url, Some("localhost:9000".to_string()));
}
#[test]
fn test_s3_config_endpoint_invalid_characters() {
let config = S3Config {
endpoint_url: Some("http://local host:9000".to_string()), // Space in URL
..S3Config::default()
};
// Invalid URL is structurally allowed
assert!(config.endpoint_url.unwrap().contains(' '));
}
#[test]
fn test_database_config_malformed_url_no_protocol() {
let config = DatabaseConfig {
url: "localhost:5432/foxhunt".to_string(), // Missing postgresql://
..DatabaseConfig::default()
};
// Missing protocol is structurally allowed
assert!(config.validate().is_ok());
assert!(!config.url.starts_with("postgresql://"));
}
#[test]
fn test_database_config_url_special_characters() {
let config = DatabaseConfig {
url: "postgresql://user:p@ss#word!@localhost:5432/db".to_string(),
..DatabaseConfig::default()
};
// Special characters in password are valid if properly encoded
assert!(config.validate().is_ok());
}
#[test]
fn test_database_config_url_unicode_characters() {
let config = DatabaseConfig {
url: "postgresql://用户:密码@localhost:5432/数据库".to_string(),
..DatabaseConfig::default()
};
// Unicode in URL is structurally allowed (may fail at connection)
assert!(config.validate().is_ok());
}
// ============================================================================
// Deserialization Edge Cases
// ============================================================================
#[test]
fn test_s3_config_json_deserialization_with_nulls() {
let json = r#"{
"bucket_name": "test-bucket",
"region": "us-east-1",
"access_key_id": null,
"secret_access_key": null,
"session_token": null,
"endpoint_url": null,
"force_path_style": false,
"timeout": {"secs": 30, "nanos": 0},
"max_retry_attempts": 3,
"use_ssl": true
}"#;
let result: Result<S3Config, _> = serde_json::from_str(json);
assert!(result.is_ok());
let config = result.unwrap();
assert!(config.access_key_id.is_none());
}
#[test]
fn test_s3_config_json_deserialization_missing_optional_fields() {
let json = r#"{
"bucket_name": "test-bucket",
"region": "us-east-1",
"force_path_style": false,
"timeout": {"secs": 30, "nanos": 0},
"max_retry_attempts": 3,
"use_ssl": true
}"#;
let result: Result<S3Config, _> = serde_json::from_str(json);
assert!(result.is_ok());
let config = result.unwrap();
assert!(config.access_key_id.is_none());
}
#[test]
fn test_s3_config_json_deserialization_wrong_types() {
let json = r#"{
"bucket_name": "test-bucket",
"region": "us-east-1",
"force_path_style": false,
"timeout": {"secs": 30, "nanos": 0},
"max_retry_attempts": "three",
"use_ssl": true
}"#;
// String instead of u32 should fail deserialization
let result: Result<S3Config, _> = serde_json::from_str(json);
assert!(result.is_err());
}
#[test]
fn test_database_config_json_with_extra_fields() {
let json = r#"{
"url": "postgresql://localhost/db",
"max_connections": 10,
"min_connections": 1,
"connect_timeout": {"secs": 30, "nanos": 0},
"query_timeout": {"secs": 60, "nanos": 0},
"enable_query_logging": false,
"application_name": "test",
"pool": {
"min_connections": 1,
"max_connections": 10,
"acquire_timeout_secs": 30,
"max_lifetime_secs": 3600,
"idle_timeout_secs": 3600,
"test_before_acquire": true,
"database_url": "postgresql://localhost/db",
"health_check_enabled": true,
"health_check_interval_secs": 60
},
"transaction": {
"isolation_level": "READ_COMMITTED",
"timeout": {"secs": 30, "nanos": 0},
"default_timeout_secs": 30,
"enable_retry": true,
"max_retries": 3,
"retry_delay_ms": 100,
"max_savepoints": 10
}
}"#;
// Should deserialize successfully with correct field names
let result: Result<DatabaseConfig, _> = serde_json::from_str(json);
assert!(result.is_ok(), "Failed to deserialize: {:?}", result.err());
}
#[test]
fn test_asset_classification_schema_json_empty_patterns() {
let json = r#"{
"asset_type_rules": {},
"default_sectors": {},
"currency_patterns": [],
"crypto_patterns": []
}"#;
let result: Result<AssetClassificationSchema, _> = serde_json::from_str(json);
assert!(result.is_ok());
let schema = result.unwrap();
assert_eq!(schema.currency_patterns.len(), 0);
}
// ============================================================================
// Timeout Configuration Edge Cases
// ============================================================================
#[test]
fn test_timeout_config_duration_overflow() {
// Duration::MAX is ~584 years - structurally valid but may not have validation
let config = TimeoutConfig {
grpc_connect_timeout: Duration::MAX,
grpc_request_timeout: Duration::MAX,
keep_alive_interval: Duration::MAX,
keep_alive_timeout: Duration::MAX,
max_concurrent_connections: 100,
};
// Extreme durations are structurally allowed (no upper bound validation currently)
// In production, these would timeout at connection time
assert_eq!(config.grpc_connect_timeout, Duration::MAX);
}
#[test]
fn test_database_runtime_config_very_short_timeouts() {
let config = DatabaseRuntimeConfig {
query_timeout: Duration::from_nanos(1), // 1 nanosecond
connection_timeout: Duration::from_nanos(1),
acquire_timeout: Duration::from_nanos(1),
..DatabaseRuntimeConfig::with_defaults(Environment::Development)
};
// Very short timeouts are structurally allowed
assert_eq!(config.query_timeout.as_nanos(), 1);
}
// ============================================================================
// Limits Configuration Edge Cases
// ============================================================================
#[test]
fn test_limits_config_extreme_batch_size() {
let config = LimitsConfig {
ml_max_batch_size: usize::MAX,
..LimitsConfig::with_defaults(Environment::Development)
};
// Extreme batch size would exhaust memory but passes structural validation
// Validation only checks > 0, not upper bounds
let result = config.validate();
assert!(
result.is_ok(),
"Extreme batch size should be structurally valid"
);
assert_eq!(config.ml_max_batch_size, usize::MAX);
}
#[test]
fn test_limits_config_var_confidence_boundary_conditions() {
// Test exactly at boundaries (0.0 and 1.0)
let config_zero = LimitsConfig {
risk_var_confidence: 0.0,
..LimitsConfig::with_defaults(Environment::Development)
};
let config_one = LimitsConfig {
risk_var_confidence: 1.0,
..LimitsConfig::with_defaults(Environment::Development)
};
// Boundaries should be valid
assert!(config_zero.validate().is_ok());
assert!(config_one.validate().is_ok());
}
#[test]
fn test_limits_config_var_confidence_epsilon_outside_bounds() {
let config_below = LimitsConfig {
risk_var_confidence: -f64::EPSILON,
..LimitsConfig::with_defaults(Environment::Development)
};
let config_above = LimitsConfig {
risk_var_confidence: 1.0 + f64::EPSILON,
..LimitsConfig::with_defaults(Environment::Development)
};
// Even epsilon outside bounds should fail
assert!(config_below.validate().is_err());
assert!(config_above.validate().is_err());
}