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>
760 lines
25 KiB
Rust
760 lines
25 KiB
Rust
/// Builder for ConfigManager with advanced configuration options.
|
|
///
|
|
/// Provides a fluent interface for constructing ConfigManager instances
|
|
/// with optional asset classification, caching, and database integration.
|
|
pub struct ConfigManagerBuilder {
|
|
config: ServiceConfig,
|
|
asset_manager: Option<crate::asset_classification::AssetClassificationManager>,
|
|
cache_timeout: std::time::Duration,
|
|
}
|
|
|
|
impl ConfigManagerBuilder {
|
|
/// Creates a new ConfigManagerBuilder with the specified service configuration.
|
|
pub const fn new(config: ServiceConfig) -> Self {
|
|
Self {
|
|
config,
|
|
asset_manager: None,
|
|
cache_timeout: std::time::Duration::from_secs(300),
|
|
}
|
|
}
|
|
|
|
/// Sets the asset classification manager.
|
|
pub fn with_asset_classification(
|
|
mut self,
|
|
manager: crate::asset_classification::AssetClassificationManager,
|
|
) -> Self {
|
|
self.asset_manager = Some(manager);
|
|
self
|
|
}
|
|
|
|
/// Sets the cache timeout duration.
|
|
pub const fn with_cache_timeout(mut self, timeout: std::time::Duration) -> Self {
|
|
self.cache_timeout = timeout;
|
|
self
|
|
}
|
|
|
|
/// Builds the ConfigManager with the specified configuration.
|
|
pub fn build(self) -> ConfigManager {
|
|
ConfigManager {
|
|
config: Arc::new(self.config),
|
|
asset_classification: Arc::new(RwLock::new(self.asset_manager)),
|
|
cache: Arc::new(RwLock::new(HashMap::new())),
|
|
cache_timeout: self.cache_timeout,
|
|
}
|
|
}
|
|
|
|
/// Builds the ConfigManager with database integration.
|
|
///
|
|
/// # Errors
|
|
/// Returns error if the operation fails
|
|
#[cfg(feature = "postgres")]
|
|
pub async fn build_with_database(
|
|
self,
|
|
database_pool: sqlx::PgPool,
|
|
) -> Result<ConfigManager, Box<dyn std::error::Error + Send + Sync>> {
|
|
let manager = self.build();
|
|
manager
|
|
.initialize_asset_classification(database_pool)
|
|
.await?;
|
|
Ok(manager)
|
|
}
|
|
}
|
|
|
|
// Configuration management and service configuration structures.
|
|
//
|
|
// This module provides the core configuration management infrastructure for
|
|
// the Foxhunt trading system. It handles service-specific configuration,
|
|
// environment management, and provides thread-safe access to configuration
|
|
// data across the application.
|
|
|
|
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::sync::{Arc, RwLock};
|
|
|
|
/// Service-specific configuration structure.
|
|
///
|
|
/// Contains metadata and settings for a specific service in the Foxhunt
|
|
/// trading system. Supports environment-specific configuration and
|
|
/// versioning for configuration management and deployment tracking.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ServiceConfig {
|
|
/// Service name (e.g., "trading_service", "ml_training_service")
|
|
pub name: String,
|
|
/// Deployment environment (e.g., "development", "staging", "production")
|
|
pub environment: String,
|
|
/// Service version for deployment tracking
|
|
pub version: String,
|
|
/// Service-specific configuration settings as JSON
|
|
pub settings: serde_json::Value,
|
|
}
|
|
|
|
/// Thread-safe configuration manager for comprehensive service configuration.
|
|
///
|
|
/// Provides centralized access to service configuration with support for:
|
|
/// - Asset classification management
|
|
///
|
|
/// - Hot-reload capabilities
|
|
/// - Environment-specific settings
|
|
///
|
|
/// - Thread-safe access patterns
|
|
///
|
|
/// Ensures configuration consistency across all components of a service.
|
|
pub struct ConfigManager {
|
|
config: Arc<ServiceConfig>,
|
|
/// Asset classification manager for symbol-based configuration
|
|
asset_classification:
|
|
Arc<RwLock<Option<crate::asset_classification::AssetClassificationManager>>>,
|
|
/// Configuration cache for performance
|
|
cache: Arc<RwLock<HashMap<String, (serde_json::Value, DateTime<Utc>)>>>,
|
|
/// Cache timeout duration
|
|
cache_timeout: std::time::Duration,
|
|
}
|
|
|
|
impl ConfigManager {
|
|
/// Creates a new ConfigManager with the provided service configuration.
|
|
///
|
|
/// The configuration is wrapped in an Arc for efficient sharing across
|
|
/// multiple threads and components within the service.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `config` - The service configuration to manage
|
|
pub fn new(config: ServiceConfig) -> Self {
|
|
Self {
|
|
config: Arc::new(config),
|
|
asset_classification: Arc::new(RwLock::new(None)),
|
|
cache: Arc::new(RwLock::new(HashMap::new())),
|
|
cache_timeout: std::time::Duration::from_secs(300), // 5 minutes
|
|
}
|
|
}
|
|
|
|
/// Creates a new ConfigManager with asset classification support.
|
|
///
|
|
/// Initializes the manager with both service configuration and
|
|
/// asset classification capabilities for comprehensive trading
|
|
/// parameter management.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `config` - The service configuration to manage
|
|
/// * `asset_manager` - Pre-configured asset classification manager
|
|
pub fn with_asset_classification(
|
|
config: ServiceConfig,
|
|
asset_manager: crate::asset_classification::AssetClassificationManager,
|
|
) -> Self {
|
|
Self {
|
|
config: Arc::new(config),
|
|
asset_classification: Arc::new(RwLock::new(Some(asset_manager))),
|
|
cache: Arc::new(RwLock::new(HashMap::new())),
|
|
cache_timeout: std::time::Duration::from_secs(300),
|
|
}
|
|
}
|
|
|
|
/// Returns a shared reference to the service configuration.
|
|
///
|
|
/// Provides thread-safe access to the configuration data through Arc cloning.
|
|
///
|
|
/// The returned Arc can be shared across threads without additional locking.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// An Arc containing the service configuration
|
|
pub fn get_config(&self) -> Arc<ServiceConfig> {
|
|
Arc::clone(&self.config)
|
|
}
|
|
|
|
/// Initializes asset classification with database-backed configurations.
|
|
///
|
|
/// Loads asset classification configurations from the database and
|
|
/// initializes the asset classification manager for dynamic symbol
|
|
/// classification and trading parameter retrieval.
|
|
///
|
|
/// # Errors
|
|
/// Returns error if the operation fails
|
|
#[cfg(feature = "postgres")]
|
|
pub async fn initialize_asset_classification(
|
|
&self,
|
|
database_pool: sqlx::PgPool,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let mut loader = crate::database::PostgresAssetClassificationLoader::with_pool(database_pool);
|
|
let configs = loader.load_asset_configurations().await?;
|
|
|
|
let mut manager = crate::asset_classification::AssetClassificationManager::new();
|
|
manager.load_configurations(configs).await?;
|
|
|
|
if let Ok(mut asset_classification) = self.asset_classification.write() {
|
|
*asset_classification = Some(manager);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Classifies a symbol using the asset classification manager.
|
|
///
|
|
/// Returns the asset class for the given symbol based on configured
|
|
/// pattern matching rules and explicit mappings.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `symbol` - The trading symbol to classify
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The asset class or Unknown if classification fails
|
|
pub fn classify_symbol(&self, symbol: &str) -> crate::asset_classification::AssetClass {
|
|
if let Ok(asset_classification) = self.asset_classification.read() {
|
|
if let Some(ref manager) = *asset_classification {
|
|
return manager.classify_symbol(symbol);
|
|
}
|
|
}
|
|
crate::asset_classification::AssetClass::Unknown
|
|
}
|
|
|
|
/// Gets trading parameters for a symbol.
|
|
///
|
|
/// Retrieves comprehensive trading parameters including position limits,
|
|
/// risk thresholds, and execution configuration for the specified symbol.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `symbol` - The trading symbol
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Trading parameters if available, None otherwise
|
|
pub fn get_trading_parameters(
|
|
&self,
|
|
symbol: &str,
|
|
) -> Option<crate::asset_classification::TradingParameters> {
|
|
if let Ok(asset_classification) = self.asset_classification.read() {
|
|
if let Some(ref manager) = *asset_classification {
|
|
return manager.get_trading_parameters(symbol).cloned();
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Gets volatility profile for a symbol.
|
|
///
|
|
/// Retrieves the volatility profile including base volatility,
|
|
/// stress multipliers, and jump risk characteristics.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `symbol` - The trading symbol
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Volatility profile if available, None otherwise
|
|
pub fn get_volatility_profile(
|
|
&self,
|
|
symbol: &str,
|
|
) -> Option<crate::asset_classification::VolatilityProfile> {
|
|
if let Ok(asset_classification) = self.asset_classification.read() {
|
|
if let Some(ref manager) = *asset_classification {
|
|
return manager.get_volatility_profile(symbol).cloned();
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Gets daily volatility estimate for a symbol.
|
|
///
|
|
/// Calculates the daily volatility from the annual volatility
|
|
/// using standard financial mathematics (annual / sqrt(252)).
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `symbol` - The trading symbol
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Daily volatility estimate as a decimal
|
|
pub fn get_daily_volatility(&self, symbol: &str) -> f64 {
|
|
if let Ok(asset_classification) = self.asset_classification.read() {
|
|
if let Some(ref manager) = *asset_classification {
|
|
return manager.get_daily_volatility(symbol);
|
|
}
|
|
}
|
|
0.05 // Default 5% daily volatility for unknown symbols
|
|
}
|
|
|
|
/// Gets position size recommendation for a symbol.
|
|
///
|
|
/// Calculates recommended position size based on portfolio NAV
|
|
/// and the symbol's configured position limits.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `symbol` - The trading symbol
|
|
/// * `portfolio_nav` - Current portfolio net asset value
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Recommended position size if available
|
|
pub fn get_position_size_recommendation(
|
|
&self,
|
|
symbol: &str,
|
|
portfolio_nav: rust_decimal::Decimal,
|
|
) -> Option<rust_decimal::Decimal> {
|
|
if let Ok(asset_classification) = self.asset_classification.read() {
|
|
if let Some(ref manager) = *asset_classification {
|
|
return manager.get_position_size_recommendation(symbol, portfolio_nav);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Checks if trading is active for a symbol at the given time.
|
|
///
|
|
/// Validates trading hours and market schedule for the symbol.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `symbol` - The trading symbol
|
|
/// * `timestamp` - The timestamp to check
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// True if trading is active, false otherwise
|
|
pub fn is_trading_active(&self, symbol: &str, timestamp: DateTime<Utc>) -> bool {
|
|
if let Ok(asset_classification) = self.asset_classification.read() {
|
|
if let Some(ref manager) = *asset_classification {
|
|
return manager.is_trading_active(symbol, timestamp);
|
|
}
|
|
}
|
|
true // Default to always active if no classification available
|
|
}
|
|
|
|
/// Reloads asset classification configurations.
|
|
///
|
|
/// Triggers a reload of asset classification configurations
|
|
/// for hot-reload functionality in production environments.
|
|
///
|
|
/// # Errors
|
|
/// Returns error if the operation fails
|
|
#[cfg(feature = "postgres")]
|
|
pub async fn reload_asset_classification(
|
|
&self,
|
|
database_pool: sqlx::PgPool,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let needs_reload = {
|
|
let asset_classification = self.asset_classification.read().ok();
|
|
asset_classification
|
|
.as_ref()
|
|
.and_then(|ac| ac.as_ref())
|
|
.map(|manager| manager.needs_reload())
|
|
.unwrap_or(false)
|
|
}; // Lock released here
|
|
|
|
if needs_reload {
|
|
self.initialize_asset_classification(database_pool).await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Gets cached configuration value.
|
|
///
|
|
/// Retrieves a cached configuration value with automatic expiration.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `key` - Cache key
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Cached value if available and not expired
|
|
pub fn get_cached_config(&self, key: &str) -> Option<serde_json::Value> {
|
|
if let Ok(cache) = self.cache.read() {
|
|
if let Some((value, timestamp)) = cache.get(key) {
|
|
let elapsed = Utc::now().signed_duration_since(*timestamp);
|
|
if elapsed.to_std().unwrap_or_default() < self.cache_timeout {
|
|
return Some(value.clone());
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Sets cached configuration value.
|
|
///
|
|
/// Stores a configuration value in the cache with timestamp.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `key` - Cache key
|
|
/// * `value` - Value to cache
|
|
pub fn set_cached_config(&self, key: String, value: serde_json::Value) {
|
|
if let Ok(mut cache) = self.cache.write() {
|
|
cache.insert(key, (value, Utc::now()));
|
|
}
|
|
}
|
|
|
|
/// Clears expired cache entries.
|
|
///
|
|
/// Removes cache entries that have exceeded the timeout duration.
|
|
pub fn cleanup_cache(&self) {
|
|
if let Ok(mut cache) = self.cache.write() {
|
|
let now = Utc::now();
|
|
cache.retain(|_, (_, timestamp)| {
|
|
let elapsed = now.signed_duration_since(*timestamp);
|
|
elapsed.to_std().unwrap_or_default() < self.cache_timeout
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use serde_json::json;
|
|
|
|
fn create_test_config() -> ServiceConfig {
|
|
ServiceConfig {
|
|
name: "test_service".to_owned(),
|
|
environment: "test".to_owned(),
|
|
version: "1.0.0".to_owned(),
|
|
settings: json!({"test_key": "test_value"}),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_service_config_creation() {
|
|
let config = create_test_config();
|
|
assert_eq!(config.name, "test_service");
|
|
assert_eq!(config.environment, "test");
|
|
assert_eq!(config.version, "1.0.0");
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_new() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
let retrieved_config = manager.get_config();
|
|
assert_eq!(retrieved_config.name, "test_service");
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_builder() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManagerBuilder::new(config)
|
|
.with_cache_timeout(std::time::Duration::from_secs(60))
|
|
.build();
|
|
|
|
let retrieved_config = manager.get_config();
|
|
assert_eq!(retrieved_config.name, "test_service");
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_cache_set_and_get() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
let test_value = json!({"cached": "data"});
|
|
manager.set_cached_config("test_key".to_owned(), test_value.clone());
|
|
|
|
let retrieved = manager.get_cached_config("test_key");
|
|
assert!(retrieved.is_some());
|
|
assert_eq!(retrieved.unwrap(), test_value);
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_cache_miss() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
let retrieved = manager.get_cached_config("nonexistent_key");
|
|
assert!(retrieved.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_cleanup_cache() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
let test_value = json!({"cached": "data"});
|
|
manager.set_cached_config("test_key".to_owned(), test_value);
|
|
|
|
manager.cleanup_cache();
|
|
|
|
// Cache entry should still exist since it was just created
|
|
let retrieved = manager.get_cached_config("test_key");
|
|
assert!(retrieved.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_classify_symbol_without_asset_manager() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
let asset_class = manager.classify_symbol("AAPL");
|
|
assert_eq!(
|
|
asset_class,
|
|
crate::asset_classification::AssetClass::Unknown
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_get_daily_volatility_default() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
let volatility = manager.get_daily_volatility("AAPL");
|
|
assert_eq!(volatility, 0.05); // Default value
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_is_trading_active_default() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
let now = chrono::Utc::now();
|
|
let is_active = manager.is_trading_active("AAPL", now);
|
|
assert!(is_active); // Default to always active
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_get_trading_parameters_none() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
let params = manager.get_trading_parameters("AAPL");
|
|
assert!(params.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_get_volatility_profile_none() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
let profile = manager.get_volatility_profile("AAPL");
|
|
assert!(profile.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_get_position_size_recommendation_none() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
let recommendation =
|
|
manager.get_position_size_recommendation("AAPL", rust_decimal::Decimal::new(100000, 0));
|
|
assert!(recommendation.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_with_asset_classification() {
|
|
let config = create_test_config();
|
|
let asset_manager = crate::asset_classification::AssetClassificationManager::new();
|
|
let manager = ConfigManager::with_asset_classification(config, asset_manager);
|
|
|
|
let retrieved_config = manager.get_config();
|
|
assert_eq!(retrieved_config.name, "test_service");
|
|
}
|
|
|
|
#[test]
|
|
fn test_builder_with_asset_classification() {
|
|
let config = create_test_config();
|
|
let asset_manager = crate::asset_classification::AssetClassificationManager::new();
|
|
|
|
let manager = ConfigManagerBuilder::new(config)
|
|
.with_asset_classification(asset_manager)
|
|
.build();
|
|
|
|
let retrieved_config = manager.get_config();
|
|
assert_eq!(retrieved_config.name, "test_service");
|
|
}
|
|
|
|
#[test]
|
|
fn test_service_config_serialization() {
|
|
let config = create_test_config();
|
|
let serialized = serde_json::to_string(&config).unwrap();
|
|
let deserialized: ServiceConfig = serde_json::from_str(&serialized).unwrap();
|
|
|
|
assert_eq!(config.name, deserialized.name);
|
|
assert_eq!(config.environment, deserialized.environment);
|
|
assert_eq!(config.version, deserialized.version);
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_multiple_cache_entries() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
for i in 0..10 {
|
|
manager.set_cached_config(format!("key_{}", i), json!({"value": i}));
|
|
}
|
|
|
|
for i in 0..10 {
|
|
let retrieved = manager.get_cached_config(&format!("key_{}", i));
|
|
assert!(retrieved.is_some());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_cache_overwrite() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
manager.set_cached_config("key".to_owned(), json!({"value": 1}));
|
|
manager.set_cached_config("key".to_owned(), json!({"value": 2}));
|
|
|
|
let retrieved = manager.get_cached_config("key");
|
|
assert_eq!(retrieved.unwrap(), json!({"value": 2}));
|
|
}
|
|
|
|
#[test]
|
|
fn test_builder_custom_cache_timeout() {
|
|
let config = create_test_config();
|
|
let custom_timeout = std::time::Duration::from_secs(120);
|
|
|
|
let manager = ConfigManagerBuilder::new(config)
|
|
.with_cache_timeout(custom_timeout)
|
|
.build();
|
|
|
|
// Cache timeout is set internally
|
|
let retrieved_config = manager.get_config();
|
|
assert_eq!(retrieved_config.name, "test_service");
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_shared_config() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
let config1 = manager.get_config();
|
|
let config2 = manager.get_config();
|
|
|
|
// Both should point to the same Arc
|
|
assert_eq!(config1.name, config2.name);
|
|
}
|
|
|
|
#[test]
|
|
fn test_service_config_clone() {
|
|
let config1 = create_test_config();
|
|
let config2 = config1.clone();
|
|
|
|
assert_eq!(config1.name, config2.name);
|
|
assert_eq!(config1.environment, config2.environment);
|
|
assert_eq!(config1.version, config2.version);
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_cache_timeout_configuration() {
|
|
let config = create_test_config();
|
|
let custom_timeout = std::time::Duration::from_millis(10);
|
|
let manager = ConfigManagerBuilder::new(config)
|
|
.with_cache_timeout(custom_timeout)
|
|
.build();
|
|
|
|
// Cache timeout is configured internally
|
|
assert_eq!(manager.cache_timeout, custom_timeout);
|
|
|
|
// Test that cache still works normally
|
|
manager.set_cached_config("test_key".to_owned(), json!({"value": 42}));
|
|
assert!(manager.get_cached_config("test_key").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_concurrent_access() {
|
|
use std::sync::Arc;
|
|
use std::thread;
|
|
|
|
let config = create_test_config();
|
|
let manager = Arc::new(ConfigManager::new(config));
|
|
|
|
let mut handles = vec![];
|
|
|
|
for i in 0..10 {
|
|
let manager_clone = Arc::clone(&manager);
|
|
let handle = thread::spawn(move || {
|
|
manager_clone
|
|
.set_cached_config(format!("concurrent_key_{}", i), json!({"thread_id": i}));
|
|
manager_clone.get_cached_config(&format!("concurrent_key_{}", i))
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
for handle in handles {
|
|
assert!(handle.join().unwrap().is_some());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_daily_volatility_fallback() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
// Should return default 5% for unknown symbols
|
|
let vol = manager.get_daily_volatility("UNKNOWN_SYMBOL");
|
|
assert_eq!(vol, 0.05);
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_position_size_none() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
// Should return None without asset classification
|
|
let size =
|
|
manager.get_position_size_recommendation("AAPL", rust_decimal::Decimal::new(100000, 0));
|
|
assert!(size.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_service_config_validation() {
|
|
let mut config = create_test_config();
|
|
|
|
// Valid config
|
|
assert!(!config.name.is_empty());
|
|
assert!(!config.environment.is_empty());
|
|
|
|
// Test with empty name
|
|
config.name = String::new();
|
|
assert!(config.name.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_cache_clear() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
// Add some cache entries
|
|
manager.set_cached_config("key1".to_owned(), json!({"value": 1}));
|
|
manager.set_cached_config("key2".to_owned(), json!({"value": 2}));
|
|
|
|
assert!(manager.get_cached_config("key1").is_some());
|
|
assert!(manager.get_cached_config("key2").is_some());
|
|
|
|
// Manual clear
|
|
if let Ok(mut cache) = manager.cache.write() {
|
|
cache.clear();
|
|
}
|
|
|
|
assert!(manager.get_cached_config("key1").is_none());
|
|
assert!(manager.get_cached_config("key2").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_builder_default_values() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManagerBuilder::new(config.clone()).build();
|
|
|
|
let retrieved = manager.get_config();
|
|
assert_eq!(retrieved.name, config.name);
|
|
assert_eq!(retrieved.environment, config.environment);
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_manager_arc_cloning() {
|
|
let config = create_test_config();
|
|
let manager = ConfigManager::new(config);
|
|
|
|
let config1 = Arc::clone(&manager.config);
|
|
let config2 = Arc::clone(&manager.config);
|
|
|
|
assert_eq!(config1.name, config2.name);
|
|
assert_eq!(Arc::strong_count(&manager.config), 3); // Original + 2 clones
|
|
}
|
|
}
|