Files
foxhunt/config/tests/config_loading_tests.rs
jgrusewski a6cb981068 fix(clippy): Eliminate needless operations (borrow/clone/conversion/cast)
Applied clippy auto-fix and manual fixes to eliminate:
- Redundant clones (7 fixes in config tests)
- Useless conversions (1 fix in stress_tests)

Auto-fixed files:
- config/tests/config_loading_tests.rs: 2 redundant clones
- config/tests/hot_reload_integration_tests.rs: 3 redundant clones
- config/tests/schemas_tests.rs: 2 redundant clones
- services/stress_tests/src/metrics.rs: useless u64::try_from conversion

Manual fixes:
- adaptive-strategy/src/regime/mod.rs: Added missing else blocks (2 locations)
- trading_engine/src/timing.rs: Fixed unseparated literal suffixes (3 locations)
- model_loader/src/lib.rs: Changed .to_string() to .to_owned() (2 locations)
- ml/src/tft/quantized_attention.rs: Removed unused DType import

Results:
- 333 auto-fixes across 30 files
- 0 remaining warnings in target categories
- All compilation errors resolved

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 12:11:08 +02:00

496 lines
14 KiB
Rust

//! Comprehensive tests for configuration loading and validation.
//!
//! Tests cover:
//! - Configuration loading from multiple sources (YAML, environment, Vault)
//! - Configuration validation (required fields, ranges)
//! - Default values and precedence (CLI > ENV > DEFAULT)
//! - Schema validation
//! - Edge cases and error handling
use config::manager::{ConfigManager, ConfigManagerBuilder, ServiceConfig};
use config::runtime::{Environment, RuntimeConfig};
use config::vault::VaultConfig;
use serde_json::json;
use std::env;
#[test]
fn test_service_config_required_fields() {
// Valid config with all required fields
let valid_config = ServiceConfig {
name: "test_service".to_owned(),
environment: "production".to_owned(),
version: "1.0.0".to_owned(),
settings: json!({"key": "value"}),
};
assert_eq!(valid_config.name, "test_service");
assert!(!valid_config.name.is_empty());
assert!(!valid_config.environment.is_empty());
}
#[test]
fn test_service_config_empty_name_validation() {
// Config with empty name (should be caught by validation)
let config = ServiceConfig {
name: String::new(),
environment: "production".to_owned(),
version: "1.0.0".to_owned(),
settings: json!({}),
};
assert!(config.name.is_empty());
}
#[test]
fn test_service_config_empty_environment_validation() {
// Config with empty environment
let config = ServiceConfig {
name: "service".to_owned(),
environment: String::new(),
version: "1.0.0".to_owned(),
settings: json!({}),
};
assert!(config.environment.is_empty());
}
#[test]
fn test_vault_config_required_fields_validation() {
// Valid Vault config
let valid_config = VaultConfig::new(
"https://vault.example.com:8200".to_owned(),
"token-12345".to_owned(),
"secret/".to_owned(),
);
assert!(valid_config.validate().is_ok());
}
#[test]
fn test_vault_config_empty_url_validation() {
// Vault config with empty URL
let invalid_config = VaultConfig::new(
String::new(),
"token-12345".to_owned(),
"secret/".to_owned(),
);
let result = invalid_config.validate();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Vault URL cannot be empty");
}
#[test]
fn test_vault_config_empty_token_validation() {
// Vault config with empty token
let invalid_config = VaultConfig::new(
"https://vault.example.com:8200".to_owned(),
String::new(),
"secret/".to_owned(),
);
let result = invalid_config.validate();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Vault token cannot be empty");
}
#[test]
fn test_vault_config_empty_mount_path_validation() {
// Vault config with empty mount path
let invalid_config = VaultConfig::new(
"https://vault.example.com:8200".to_owned(),
"token-12345".to_owned(),
String::new(),
);
let result = invalid_config.validate();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Vault mount path cannot be empty");
}
#[test]
#[serial_test::serial]
fn test_environment_detection_development() {
// Set environment variable
env::set_var("ENVIRONMENT", "development");
let detected = Environment::detect();
assert_eq!(detected, Environment::Development);
assert!(detected.is_development());
assert!(!detected.is_production());
// Clean up
env::remove_var("ENVIRONMENT");
}
#[test]
#[serial_test::serial]
fn test_environment_detection_production() {
// Test both "production" and "prod" variants
env::set_var("ENVIRONMENT", "production");
let detected = Environment::detect();
assert_eq!(detected, Environment::Production);
assert!(detected.is_production());
env::set_var("ENVIRONMENT", "prod");
let detected = Environment::detect();
assert_eq!(detected, Environment::Production);
env::remove_var("ENVIRONMENT");
}
#[test]
#[serial_test::serial]
fn test_environment_detection_staging() {
env::set_var("ENVIRONMENT", "staging");
let detected = Environment::detect();
assert_eq!(detected, Environment::Staging);
env::set_var("ENVIRONMENT", "stage");
let detected = Environment::detect();
assert_eq!(detected, Environment::Staging);
env::remove_var("ENVIRONMENT");
}
#[test]
#[serial_test::serial]
fn test_environment_detection_fallback() {
// Test fallback to development for invalid values
env::set_var("ENVIRONMENT", "invalid");
let detected = Environment::detect();
assert_eq!(detected, Environment::Development);
// Test fallback when environment variable is not set
env::remove_var("ENVIRONMENT");
let detected = Environment::detect();
assert_eq!(detected, Environment::Development);
}
#[test]
fn test_runtime_config_defaults_development() {
let config = RuntimeConfig::with_defaults(Environment::Development);
// Verify development has relaxed timeouts
assert!(config.database.query_timeout.as_millis() >= 1000);
assert!(config.database.pool_size >= 10);
}
#[test]
fn test_runtime_config_defaults_production() {
let config = RuntimeConfig::with_defaults(Environment::Production);
// Verify production has tight timeouts for HFT
assert!(config.database.query_timeout.as_millis() <= 1000);
assert!(config.database.pool_size >= 15);
}
#[test]
fn test_runtime_config_defaults_staging() {
let config = RuntimeConfig::with_defaults(Environment::Staging);
// Verify staging is between development and production
let dev = RuntimeConfig::with_defaults(Environment::Development);
let prod = RuntimeConfig::with_defaults(Environment::Production);
assert!(config.database.query_timeout >= prod.database.query_timeout);
assert!(config.database.query_timeout <= dev.database.query_timeout);
}
#[test]
fn test_config_manager_builder_pattern() {
// Test fluent builder pattern
let config = ServiceConfig {
name: "test_service".to_owned(),
environment: "test".to_owned(),
version: "1.0.0".to_owned(),
settings: json!({"test": "value"}),
};
let manager = ConfigManagerBuilder::new(config)
.with_cache_timeout(std::time::Duration::from_secs(120))
.build();
let retrieved = manager.get_config();
assert_eq!(retrieved.name, "test_service");
}
#[test]
fn test_config_manager_cache_timeout_configuration() {
let config = ServiceConfig {
name: "test".to_owned(),
environment: "test".to_owned(),
version: "1.0.0".to_owned(),
settings: json!({}),
};
let custom_timeout = std::time::Duration::from_secs(600);
let manager = ConfigManagerBuilder::new(config)
.with_cache_timeout(custom_timeout)
.build();
// Verify manager was created (cache_timeout is private, can't test directly)
let retrieved = manager.get_config();
assert_eq!(retrieved.name, "test");
}
#[test]
fn test_service_config_settings_json_validation() {
// Test valid JSON settings
let config = ServiceConfig {
name: "test".to_owned(),
environment: "test".to_owned(),
version: "1.0.0".to_owned(),
settings: json!({
"database": {
"host": "localhost",
"port": 5432
},
"features": ["trading", "ml"]
}),
};
// Verify JSON structure
assert!(config.settings.is_object());
assert!(config.settings.get("database").is_some());
assert!(config.settings.get("features").is_some());
}
#[test]
fn test_service_config_version_format() {
// Test various version formats
let versions = vec!["1.0.0", "2.1.3", "0.0.1-alpha", "1.2.3-beta.1"];
for version in versions {
let config = ServiceConfig {
name: "test".to_owned(),
environment: "test".to_owned(),
version: version.to_owned(),
settings: json!({}),
};
assert_eq!(config.version, version);
}
}
#[test]
fn test_config_manager_multiple_instances() {
// Test that multiple ConfigManager instances can coexist
let config1 = ServiceConfig {
name: "service1".to_owned(),
environment: "prod".to_owned(),
version: "1.0.0".to_owned(),
settings: json!({}),
};
let config2 = ServiceConfig {
name: "service2".to_owned(),
environment: "dev".to_owned(),
version: "2.0.0".to_owned(),
settings: json!({}),
};
let manager1 = ConfigManager::new(config1);
let manager2 = ConfigManager::new(config2);
assert_eq!(manager1.get_config().name, "service1");
assert_eq!(manager2.get_config().name, "service2");
}
#[test]
fn test_vault_config_namespace_optional() {
// Test Vault config without namespace
let config_without = VaultConfig::new(
"https://vault.example.com".to_owned(),
"token".to_owned(),
"secret/".to_owned(),
);
assert!(config_without.namespace.is_none());
// Test Vault config with namespace
let config_with = VaultConfig::new(
"https://vault.example.com".to_owned(),
"token".to_owned(),
"secret/".to_owned(),
)
.with_namespace("production".to_owned());
assert!(config_with.namespace.is_some());
assert_eq!(config_with.namespace.as_ref().unwrap(), "production");
}
#[test]
#[serial_test::serial]
fn test_runtime_config_precedence() {
// Test that environment variables override defaults
env::set_var("DATABASE_POOL_SIZE", "25");
let config = RuntimeConfig::from_env().unwrap();
assert_eq!(config.database.pool_size, 25);
env::remove_var("DATABASE_POOL_SIZE");
}
#[test]
#[serial_test::serial]
fn test_runtime_config_invalid_env_var_fallback() {
// Test that invalid environment variable falls back to default
env::set_var("DATABASE_POOL_SIZE", "invalid");
// Should fall back to default instead of panicking
let _result = RuntimeConfig::from_env();
// Invalid value should cause error or use default
env::remove_var("DATABASE_POOL_SIZE");
}
#[test]
fn test_service_config_serialization_roundtrip() {
let original = ServiceConfig {
name: "test_service".to_owned(),
environment: "production".to_owned(),
version: "1.2.3".to_owned(),
settings: json!({
"key1": "value1",
"key2": 42,
"nested": {"inner": "data"}
}),
};
// Serialize to JSON
let serialized = serde_json::to_string(&original).unwrap();
// Deserialize back
let deserialized: ServiceConfig = serde_json::from_str(&serialized).unwrap();
// Verify round-trip
assert_eq!(original.name, deserialized.name);
assert_eq!(original.environment, deserialized.environment);
assert_eq!(original.version, deserialized.version);
assert_eq!(original.settings, deserialized.settings);
}
#[test]
fn test_config_manager_concurrent_cache_access() {
use std::sync::Arc;
use std::thread;
let config = ServiceConfig {
name: "concurrent_test".to_owned(),
environment: "test".to_owned(),
version: "1.0.0".to_owned(),
settings: json!({}),
};
let manager = Arc::new(ConfigManager::new(config));
let mut handles = vec![];
// Spawn 10 threads that write and read cache concurrently
for i in 0..10 {
let manager_clone = Arc::clone(&manager);
let handle = thread::spawn(move || {
let key = format!("thread_key_{}", i);
let value = json!({"thread_id": i, "data": format!("data_{}", i)});
manager_clone.set_cached_config(key.clone(), value);
let retrieved = manager_clone.get_cached_config(&key);
assert!(retrieved.is_some());
retrieved.unwrap()
});
handles.push(handle);
}
// Verify all threads completed successfully
for handle in handles {
let result = handle.join();
assert!(result.is_ok());
}
}
#[test]
#[serial_test::serial]
fn test_environment_case_insensitivity() {
// Test case-insensitive environment detection
let test_cases = vec![
("PRODUCTION", Environment::Production),
("Production", Environment::Production),
("production", Environment::Production),
("PROD", Environment::Production),
("Prod", Environment::Production),
("STAGING", Environment::Staging),
("staging", Environment::Staging),
("STAGE", Environment::Staging),
("DEVELOPMENT", Environment::Development),
("development", Environment::Development),
];
for (input, expected) in test_cases {
env::set_var("ENVIRONMENT", input);
let detected = Environment::detect();
assert_eq!(detected, expected, "Failed for input: {}", input);
}
env::remove_var("ENVIRONMENT");
}
#[test]
fn test_config_manager_cache_expiration() {
use std::thread;
use std::time::Duration;
let config = ServiceConfig {
name: "cache_test".to_owned(),
environment: "test".to_owned(),
version: "1.0.0".to_owned(),
settings: json!({}),
};
// Create manager with very short cache timeout (100ms)
let manager = ConfigManagerBuilder::new(config)
.with_cache_timeout(Duration::from_millis(100))
.build();
// Set cache value
let value = json!({"data": "test"});
manager.set_cached_config("test_key".to_owned(), value);
// Should be available immediately
assert!(manager.get_cached_config("test_key").is_some());
// Wait for cache to expire
thread::sleep(Duration::from_millis(150));
// Should be expired now (might be None or Some depending on cleanup timing)
let _retrieved = manager.get_cached_config("test_key");
// Note: The cache might still exist but be expired, depends on implementation
}
#[test]
fn test_vault_config_debug_redaction() {
let config = VaultConfig::new(
"https://vault.example.com:8200".to_owned(),
"super-secret-token".to_owned(),
"secret/".to_owned(),
);
// Debug output should redact the token
let debug_output = format!("{:?}", config);
assert!(debug_output.contains("***REDACTED***"));
assert!(!debug_output.contains("super-secret-token"));
}
#[test]
fn test_vault_config_serialization_redaction() {
let config = VaultConfig::new(
"https://vault.example.com:8200".to_owned(),
"super-secret-token".to_owned(),
"secret/".to_owned(),
);
// Serialized output should redact the token
let serialized = serde_json::to_string(&config).unwrap();
assert!(serialized.contains("***REDACTED***"));
assert!(!serialized.contains("super-secret-token"));
}