/home/jgrusewski/Work/foxhunt/config/src/manager.rs
Line | Count | Source |
1 | | /// Builder for ConfigManager with advanced configuration options. |
2 | | /// |
3 | | /// Provides a fluent interface for constructing ConfigManager instances |
4 | | /// with optional asset classification, caching, and database integration. |
5 | | pub struct ConfigManagerBuilder { |
6 | | config: ServiceConfig, |
7 | | asset_manager: Option<crate::asset_classification::AssetClassificationManager>, |
8 | | cache_timeout: std::time::Duration, |
9 | | } |
10 | | |
11 | | impl ConfigManagerBuilder { |
12 | | /// Creates a new ConfigManagerBuilder with the specified service configuration. |
13 | 7 | pub fn new(config: ServiceConfig) -> Self { |
14 | 7 | Self { |
15 | 7 | config, |
16 | 7 | asset_manager: None, |
17 | 7 | cache_timeout: std::time::Duration::from_secs(300), |
18 | 7 | } |
19 | 7 | } |
20 | | |
21 | | /// Sets the asset classification manager. |
22 | 2 | pub fn with_asset_classification( |
23 | 2 | mut self, |
24 | 2 | manager: crate::asset_classification::AssetClassificationManager, |
25 | 2 | ) -> Self { |
26 | 2 | self.asset_manager = Some(manager); |
27 | 2 | self |
28 | 2 | } |
29 | | |
30 | | /// Sets the cache timeout duration. |
31 | 4 | pub fn with_cache_timeout(mut self, timeout: std::time::Duration) -> Self { |
32 | 4 | self.cache_timeout = timeout; |
33 | 4 | self |
34 | 4 | } |
35 | | |
36 | | /// Builds the ConfigManager with the specified configuration. |
37 | 7 | pub fn build(self) -> ConfigManager { |
38 | 7 | ConfigManager { |
39 | 7 | config: Arc::new(self.config), |
40 | 7 | asset_classification: Arc::new(RwLock::new(self.asset_manager)), |
41 | 7 | cache: Arc::new(RwLock::new(HashMap::new())), |
42 | 7 | cache_timeout: self.cache_timeout, |
43 | 7 | } |
44 | 7 | } |
45 | | |
46 | | /// Builds the ConfigManager with database integration. |
47 | | #[cfg(feature = "postgres")] |
48 | | pub async fn build_with_database( |
49 | | self, |
50 | | database_pool: sqlx::PgPool, |
51 | | ) -> Result<ConfigManager, Box<dyn std::error::Error + Send + Sync>> { |
52 | | let manager = self.build(); |
53 | | manager |
54 | | .initialize_asset_classification(database_pool) |
55 | | .await?; |
56 | | Ok(manager) |
57 | | } |
58 | | } |
59 | | |
60 | | // Configuration management and service configuration structures. |
61 | | // |
62 | | // This module provides the core configuration management infrastructure for |
63 | | // the Foxhunt trading system. It handles service-specific configuration, |
64 | | // environment management, and provides thread-safe access to configuration |
65 | | // data across the application. |
66 | | |
67 | | use chrono::{DateTime, Utc}; |
68 | | use serde::{Deserialize, Serialize}; |
69 | | use std::collections::HashMap; |
70 | | use std::sync::{Arc, RwLock}; |
71 | | |
72 | | /// Service-specific configuration structure. |
73 | | /// |
74 | | /// Contains metadata and settings for a specific service in the Foxhunt |
75 | | /// trading system. Supports environment-specific configuration and |
76 | | /// versioning for configuration management and deployment tracking. |
77 | | #[derive(Debug, Clone, Serialize, Deserialize)] |
78 | | pub struct ServiceConfig { |
79 | | /// Service name (e.g., "trading_service", "ml_training_service") |
80 | | pub name: String, |
81 | | /// Deployment environment (e.g., "development", "staging", "production") |
82 | | pub environment: String, |
83 | | /// Service version for deployment tracking |
84 | | pub version: String, |
85 | | /// Service-specific configuration settings as JSON |
86 | | pub settings: serde_json::Value, |
87 | | } |
88 | | |
89 | | /// Thread-safe configuration manager for comprehensive service configuration. |
90 | | /// |
91 | | /// Provides centralized access to service configuration with support for: |
92 | | /// - Asset classification management |
93 | | /// - Hot-reload capabilities |
94 | | /// - Environment-specific settings |
95 | | /// - Thread-safe access patterns |
96 | | /// |
97 | | /// Ensures configuration consistency across all components of a service. |
98 | | pub struct ConfigManager { |
99 | | config: Arc<ServiceConfig>, |
100 | | /// Asset classification manager for symbol-based configuration |
101 | | asset_classification: |
102 | | Arc<RwLock<Option<crate::asset_classification::AssetClassificationManager>>>, |
103 | | /// Configuration cache for performance |
104 | | cache: Arc<RwLock<HashMap<String, (serde_json::Value, DateTime<Utc>)>>>, |
105 | | /// Cache timeout duration |
106 | | cache_timeout: std::time::Duration, |
107 | | } |
108 | | |
109 | | impl ConfigManager { |
110 | | /// Creates a new ConfigManager with the provided service configuration. |
111 | | /// |
112 | | /// The configuration is wrapped in an Arc for efficient sharing across |
113 | | /// multiple threads and components within the service. |
114 | | /// |
115 | | /// # Arguments |
116 | | /// |
117 | | /// * `config` - The service configuration to manage |
118 | 18 | pub fn new(config: ServiceConfig) -> Self { |
119 | 18 | Self { |
120 | 18 | config: Arc::new(config), |
121 | 18 | asset_classification: Arc::new(RwLock::new(None)), |
122 | 18 | cache: Arc::new(RwLock::new(HashMap::new())), |
123 | 18 | cache_timeout: std::time::Duration::from_secs(300), // 5 minutes |
124 | 18 | } |
125 | 18 | } |
126 | | |
127 | | /// Creates a new ConfigManager with asset classification support. |
128 | | /// |
129 | | /// Initializes the manager with both service configuration and |
130 | | /// asset classification capabilities for comprehensive trading |
131 | | /// parameter management. |
132 | | /// |
133 | | /// # Arguments |
134 | | /// |
135 | | /// * `config` - The service configuration to manage |
136 | | /// * `asset_manager` - Pre-configured asset classification manager |
137 | 1 | pub fn with_asset_classification( |
138 | 1 | config: ServiceConfig, |
139 | 1 | asset_manager: crate::asset_classification::AssetClassificationManager, |
140 | 1 | ) -> Self { |
141 | 1 | Self { |
142 | 1 | config: Arc::new(config), |
143 | 1 | asset_classification: Arc::new(RwLock::new(Some(asset_manager))), |
144 | 1 | cache: Arc::new(RwLock::new(HashMap::new())), |
145 | 1 | cache_timeout: std::time::Duration::from_secs(300), |
146 | 1 | } |
147 | 1 | } |
148 | | |
149 | | /// Returns a shared reference to the service configuration. |
150 | | /// |
151 | | /// Provides thread-safe access to the configuration data through Arc cloning. |
152 | | /// The returned Arc can be shared across threads without additional locking. |
153 | | /// |
154 | | /// # Returns |
155 | | /// |
156 | | /// An Arc containing the service configuration |
157 | 8 | pub fn get_config(&self) -> Arc<ServiceConfig> { |
158 | 8 | Arc::clone(&self.config) |
159 | 8 | } |
160 | | |
161 | | /// Initializes asset classification with database-backed configurations. |
162 | | /// |
163 | | /// Loads asset classification configurations from the database and |
164 | | /// initializes the asset classification manager for dynamic symbol |
165 | | /// classification and trading parameter retrieval. |
166 | | #[cfg(feature = "postgres")] |
167 | | pub async fn initialize_asset_classification( |
168 | | &self, |
169 | | database_pool: sqlx::PgPool, |
170 | | ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { |
171 | | let loader = crate::database::PostgresAssetClassificationLoader::with_pool(database_pool); |
172 | | let configs = loader.load_asset_configurations().await?; |
173 | | |
174 | | let mut manager = crate::asset_classification::AssetClassificationManager::new(); |
175 | | manager.load_configurations(configs).await?; |
176 | | |
177 | | if let Ok(mut asset_classification) = self.asset_classification.write() { |
178 | | *asset_classification = Some(manager); |
179 | | } |
180 | | |
181 | | Ok(()) |
182 | | } |
183 | | |
184 | | /// Classifies a symbol using the asset classification manager. |
185 | | /// |
186 | | /// Returns the asset class for the given symbol based on configured |
187 | | /// pattern matching rules and explicit mappings. |
188 | | /// |
189 | | /// # Arguments |
190 | | /// |
191 | | /// * `symbol` - The trading symbol to classify |
192 | | /// |
193 | | /// # Returns |
194 | | /// |
195 | | /// The asset class or Unknown if classification fails |
196 | 2 | pub fn classify_symbol(&self, symbol: &str) -> crate::asset_classification::AssetClass { |
197 | 2 | if let Ok(asset_classification) = self.asset_classification.read() { |
198 | 2 | if let Some(ref manager1 ) = *asset_classification { |
199 | 1 | return manager.classify_symbol(symbol); |
200 | 1 | } |
201 | 0 | } |
202 | 1 | crate::asset_classification::AssetClass::Unknown |
203 | 2 | } |
204 | | |
205 | | /// Gets trading parameters for a symbol. |
206 | | /// |
207 | | /// Retrieves comprehensive trading parameters including position limits, |
208 | | /// risk thresholds, and execution configuration for the specified symbol. |
209 | | /// |
210 | | /// # Arguments |
211 | | /// |
212 | | /// * `symbol` - The trading symbol |
213 | | /// |
214 | | /// # Returns |
215 | | /// |
216 | | /// Trading parameters if available, None otherwise |
217 | 2 | pub fn get_trading_parameters( |
218 | 2 | &self, |
219 | 2 | symbol: &str, |
220 | 2 | ) -> Option<crate::asset_classification::TradingParameters> { |
221 | 2 | if let Ok(asset_classification) = self.asset_classification.read() { |
222 | 2 | if let Some(ref manager1 ) = *asset_classification { |
223 | 1 | return manager.get_trading_parameters(symbol).cloned(); |
224 | 1 | } |
225 | 0 | } |
226 | 1 | None |
227 | 2 | } |
228 | | |
229 | | /// Gets volatility profile for a symbol. |
230 | | /// |
231 | | /// Retrieves the volatility profile including base volatility, |
232 | | /// stress multipliers, and jump risk characteristics. |
233 | | /// |
234 | | /// # Arguments |
235 | | /// |
236 | | /// * `symbol` - The trading symbol |
237 | | /// |
238 | | /// # Returns |
239 | | /// |
240 | | /// Volatility profile if available, None otherwise |
241 | 1 | pub fn get_volatility_profile( |
242 | 1 | &self, |
243 | 1 | symbol: &str, |
244 | 1 | ) -> Option<crate::asset_classification::VolatilityProfile> { |
245 | 1 | if let Ok(asset_classification) = self.asset_classification.read() { |
246 | 1 | if let Some(ref manager0 ) = *asset_classification { |
247 | 0 | return manager.get_volatility_profile(symbol).cloned(); |
248 | 1 | } |
249 | 0 | } |
250 | 1 | None |
251 | 1 | } |
252 | | |
253 | | /// Gets daily volatility estimate for a symbol. |
254 | | /// |
255 | | /// Calculates the daily volatility from the annual volatility |
256 | | /// using standard financial mathematics (annual / sqrt(252)). |
257 | | /// |
258 | | /// # Arguments |
259 | | /// |
260 | | /// * `symbol` - The trading symbol |
261 | | /// |
262 | | /// # Returns |
263 | | /// |
264 | | /// Daily volatility estimate as a decimal |
265 | 3 | pub fn get_daily_volatility(&self, symbol: &str) -> f64 { |
266 | 3 | if let Ok(asset_classification) = self.asset_classification.read() { |
267 | 3 | if let Some(ref manager1 ) = *asset_classification { |
268 | 1 | return manager.get_daily_volatility(symbol); |
269 | 2 | } |
270 | 0 | } |
271 | 2 | 0.05 // Default 5% daily volatility for unknown symbols |
272 | 3 | } |
273 | | |
274 | | /// Gets position size recommendation for a symbol. |
275 | | /// |
276 | | /// Calculates recommended position size based on portfolio NAV |
277 | | /// and the symbol's configured position limits. |
278 | | /// |
279 | | /// # Arguments |
280 | | /// |
281 | | /// * `symbol` - The trading symbol |
282 | | /// * `portfolio_nav` - Current portfolio net asset value |
283 | | /// |
284 | | /// # Returns |
285 | | /// |
286 | | /// Recommended position size if available |
287 | 3 | pub fn get_position_size_recommendation( |
288 | 3 | &self, |
289 | 3 | symbol: &str, |
290 | 3 | portfolio_nav: rust_decimal::Decimal, |
291 | 3 | ) -> Option<rust_decimal::Decimal> { |
292 | 3 | if let Ok(asset_classification) = self.asset_classification.read() { |
293 | 3 | if let Some(ref manager1 ) = *asset_classification { |
294 | 1 | return manager.get_position_size_recommendation(symbol, portfolio_nav); |
295 | 2 | } |
296 | 0 | } |
297 | 2 | None |
298 | 3 | } |
299 | | |
300 | | /// Checks if trading is active for a symbol at the given time. |
301 | | /// |
302 | | /// Validates trading hours and market schedule for the symbol. |
303 | | /// |
304 | | /// # Arguments |
305 | | /// |
306 | | /// * `symbol` - The trading symbol |
307 | | /// * `timestamp` - The timestamp to check |
308 | | /// |
309 | | /// # Returns |
310 | | /// |
311 | | /// True if trading is active, false otherwise |
312 | 1 | pub fn is_trading_active(&self, symbol: &str, timestamp: DateTime<Utc>) -> bool { |
313 | 1 | if let Ok(asset_classification) = self.asset_classification.read() { |
314 | 1 | if let Some(ref manager0 ) = *asset_classification { |
315 | 0 | return manager.is_trading_active(symbol, timestamp); |
316 | 1 | } |
317 | 0 | } |
318 | 1 | true // Default to always active if no classification available |
319 | 1 | } |
320 | | |
321 | | /// Reloads asset classification configurations. |
322 | | /// |
323 | | /// Triggers a reload of asset classification configurations |
324 | | /// for hot-reload functionality in production environments. |
325 | | #[cfg(feature = "postgres")] |
326 | | pub async fn reload_asset_classification( |
327 | | &self, |
328 | | database_pool: sqlx::PgPool, |
329 | | ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { |
330 | | let needs_reload = { |
331 | | let asset_classification = self.asset_classification.read().ok(); |
332 | | asset_classification |
333 | | .as_ref() |
334 | | .and_then(|ac| ac.as_ref()) |
335 | | .map(|manager| manager.needs_reload()) |
336 | | .unwrap_or(false) |
337 | | }; // Lock released here |
338 | | |
339 | | if needs_reload { |
340 | | self.initialize_asset_classification(database_pool).await?; |
341 | | } |
342 | | Ok(()) |
343 | | } |
344 | | |
345 | | /// Gets cached configuration value. |
346 | | /// |
347 | | /// Retrieves a cached configuration value with automatic expiration. |
348 | | /// |
349 | | /// # Arguments |
350 | | /// |
351 | | /// * `key` - Cache key |
352 | | /// |
353 | | /// # Returns |
354 | | /// |
355 | | /// Cached value if available and not expired |
356 | 31 | pub fn get_cached_config(&self, key: &str) -> Option<serde_json::Value> { |
357 | 31 | if let Ok(cache) = self.cache.read() { |
358 | 31 | if let Some((value28 , timestamp28 )) = cache.get(key) { |
359 | 28 | let elapsed = Utc::now().signed_duration_since(*timestamp); |
360 | 28 | if elapsed.to_std().unwrap_or_default() < self.cache_timeout { |
361 | 27 | return Some(value.clone()); |
362 | 1 | } |
363 | 3 | } |
364 | 0 | } |
365 | 4 | None |
366 | 31 | } |
367 | | |
368 | | /// Sets cached configuration value. |
369 | | /// |
370 | | /// Stores a configuration value in the cache with timestamp. |
371 | | /// |
372 | | /// # Arguments |
373 | | /// |
374 | | /// * `key` - Cache key |
375 | | /// * `value` - Value to cache |
376 | 28 | pub fn set_cached_config(&self, key: String, value: serde_json::Value) { |
377 | 28 | if let Ok(mut cache) = self.cache.write() { |
378 | 28 | cache.insert(key, (value, Utc::now())); |
379 | 28 | }0 |
380 | 28 | } |
381 | | |
382 | | /// Clears expired cache entries. |
383 | | /// |
384 | | /// Removes cache entries that have exceeded the timeout duration. |
385 | 1 | pub fn cleanup_cache(&self) { |
386 | 1 | if let Ok(mut cache) = self.cache.write() { |
387 | 1 | let now = Utc::now(); |
388 | 1 | cache.retain(|_, (_, timestamp)| { |
389 | 1 | let elapsed = now.signed_duration_since(*timestamp); |
390 | 1 | elapsed.to_std().unwrap_or_default() < self.cache_timeout |
391 | 1 | }); |
392 | 0 | } |
393 | 1 | } |
394 | | } |
395 | | |
396 | | #[cfg(test)] |
397 | | mod tests { |
398 | | use super::*; |
399 | | use serde_json::json; |
400 | | |
401 | 28 | fn create_test_config() -> ServiceConfig { |
402 | 28 | ServiceConfig { |
403 | 28 | name: "test_service".to_string(), |
404 | 28 | environment: "test".to_string(), |
405 | 28 | version: "1.0.0".to_string(), |
406 | 28 | settings: json!({"test_key": "test_value"}), |
407 | 28 | } |
408 | 28 | } |
409 | | |
410 | | #[test] |
411 | 1 | fn test_service_config_creation() { |
412 | 1 | let config = create_test_config(); |
413 | 1 | assert_eq!(config.name, "test_service"); |
414 | 1 | assert_eq!(config.environment, "test"); |
415 | 1 | assert_eq!(config.version, "1.0.0"); |
416 | 1 | } |
417 | | |
418 | | #[test] |
419 | 1 | fn test_config_manager_new() { |
420 | 1 | let config = create_test_config(); |
421 | 1 | let manager = ConfigManager::new(config); |
422 | 1 | let retrieved_config = manager.get_config(); |
423 | 1 | assert_eq!(retrieved_config.name, "test_service"); |
424 | 1 | } |
425 | | |
426 | | #[test] |
427 | 1 | fn test_config_manager_builder() { |
428 | 1 | let config = create_test_config(); |
429 | 1 | let manager = ConfigManagerBuilder::new(config) |
430 | 1 | .with_cache_timeout(std::time::Duration::from_secs(60)) |
431 | 1 | .build(); |
432 | | |
433 | 1 | let retrieved_config = manager.get_config(); |
434 | 1 | assert_eq!(retrieved_config.name, "test_service"); |
435 | 1 | } |
436 | | |
437 | | #[test] |
438 | 1 | fn test_config_manager_cache_set_and_get() { |
439 | 1 | let config = create_test_config(); |
440 | 1 | let manager = ConfigManager::new(config); |
441 | | |
442 | 1 | let test_value = json!({"cached": "data"}); |
443 | 1 | manager.set_cached_config("test_key".to_string(), test_value.clone()); |
444 | | |
445 | 1 | let retrieved = manager.get_cached_config("test_key"); |
446 | 1 | assert!(retrieved.is_some()); |
447 | 1 | assert_eq!(retrieved.unwrap(), test_value); |
448 | 1 | } |
449 | | |
450 | | #[test] |
451 | 1 | fn test_config_manager_cache_miss() { |
452 | 1 | let config = create_test_config(); |
453 | 1 | let manager = ConfigManager::new(config); |
454 | | |
455 | 1 | let retrieved = manager.get_cached_config("nonexistent_key"); |
456 | 1 | assert!(retrieved.is_none()); |
457 | 1 | } |
458 | | |
459 | | #[test] |
460 | 1 | fn test_config_manager_cleanup_cache() { |
461 | 1 | let config = create_test_config(); |
462 | 1 | let manager = ConfigManager::new(config); |
463 | | |
464 | 1 | let test_value = json!({"cached": "data"}); |
465 | 1 | manager.set_cached_config("test_key".to_string(), test_value); |
466 | | |
467 | 1 | manager.cleanup_cache(); |
468 | | |
469 | | // Cache entry should still exist since it was just created |
470 | 1 | let retrieved = manager.get_cached_config("test_key"); |
471 | 1 | assert!(retrieved.is_some()); |
472 | 1 | } |
473 | | |
474 | | #[test] |
475 | 1 | fn test_config_manager_classify_symbol_without_asset_manager() { |
476 | 1 | let config = create_test_config(); |
477 | 1 | let manager = ConfigManager::new(config); |
478 | | |
479 | 1 | let asset_class = manager.classify_symbol("AAPL"); |
480 | 1 | assert_eq!( |
481 | | asset_class, |
482 | | crate::asset_classification::AssetClass::Unknown |
483 | | ); |
484 | 1 | } |
485 | | |
486 | | #[test] |
487 | 1 | fn test_config_manager_get_daily_volatility_default() { |
488 | 1 | let config = create_test_config(); |
489 | 1 | let manager = ConfigManager::new(config); |
490 | | |
491 | 1 | let volatility = manager.get_daily_volatility("AAPL"); |
492 | 1 | assert_eq!(volatility, 0.05); // Default value |
493 | 1 | } |
494 | | |
495 | | #[test] |
496 | 1 | fn test_config_manager_is_trading_active_default() { |
497 | 1 | let config = create_test_config(); |
498 | 1 | let manager = ConfigManager::new(config); |
499 | | |
500 | 1 | let now = chrono::Utc::now(); |
501 | 1 | let is_active = manager.is_trading_active("AAPL", now); |
502 | 1 | assert!(is_active); // Default to always active |
503 | 1 | } |
504 | | |
505 | | #[test] |
506 | 1 | fn test_config_manager_get_trading_parameters_none() { |
507 | 1 | let config = create_test_config(); |
508 | 1 | let manager = ConfigManager::new(config); |
509 | | |
510 | 1 | let params = manager.get_trading_parameters("AAPL"); |
511 | 1 | assert!(params.is_none()); |
512 | 1 | } |
513 | | |
514 | | #[test] |
515 | 1 | fn test_config_manager_get_volatility_profile_none() { |
516 | 1 | let config = create_test_config(); |
517 | 1 | let manager = ConfigManager::new(config); |
518 | | |
519 | 1 | let profile = manager.get_volatility_profile("AAPL"); |
520 | 1 | assert!(profile.is_none()); |
521 | 1 | } |
522 | | |
523 | | #[test] |
524 | 1 | fn test_config_manager_get_position_size_recommendation_none() { |
525 | 1 | let config = create_test_config(); |
526 | 1 | let manager = ConfigManager::new(config); |
527 | | |
528 | 1 | let recommendation = |
529 | 1 | manager.get_position_size_recommendation("AAPL", rust_decimal::Decimal::new(100000, 0)); |
530 | 1 | assert!(recommendation.is_none()); |
531 | 1 | } |
532 | | |
533 | | #[test] |
534 | 1 | fn test_config_manager_with_asset_classification() { |
535 | 1 | let config = create_test_config(); |
536 | 1 | let asset_manager = crate::asset_classification::AssetClassificationManager::new(); |
537 | 1 | let manager = ConfigManager::with_asset_classification(config, asset_manager); |
538 | | |
539 | 1 | let retrieved_config = manager.get_config(); |
540 | 1 | assert_eq!(retrieved_config.name, "test_service"); |
541 | 1 | } |
542 | | |
543 | | #[test] |
544 | 1 | fn test_builder_with_asset_classification() { |
545 | 1 | let config = create_test_config(); |
546 | 1 | let asset_manager = crate::asset_classification::AssetClassificationManager::new(); |
547 | | |
548 | 1 | let manager = ConfigManagerBuilder::new(config) |
549 | 1 | .with_asset_classification(asset_manager) |
550 | 1 | .build(); |
551 | | |
552 | 1 | let retrieved_config = manager.get_config(); |
553 | 1 | assert_eq!(retrieved_config.name, "test_service"); |
554 | 1 | } |
555 | | |
556 | | #[test] |
557 | 1 | fn test_service_config_serialization() { |
558 | 1 | let config = create_test_config(); |
559 | 1 | let serialized = serde_json::to_string(&config).unwrap(); |
560 | 1 | let deserialized: ServiceConfig = serde_json::from_str(&serialized).unwrap(); |
561 | | |
562 | 1 | assert_eq!(config.name, deserialized.name); |
563 | 1 | assert_eq!(config.environment, deserialized.environment); |
564 | 1 | assert_eq!(config.version, deserialized.version); |
565 | 1 | } |
566 | | |
567 | | #[test] |
568 | 1 | fn test_config_manager_multiple_cache_entries() { |
569 | 1 | let config = create_test_config(); |
570 | 1 | let manager = ConfigManager::new(config); |
571 | | |
572 | 11 | for i10 in 0..10 { |
573 | 10 | manager.set_cached_config(format!("key_{}", i), json!({"value": i})); |
574 | 10 | } |
575 | | |
576 | 11 | for i10 in 0..10 { |
577 | 10 | let retrieved = manager.get_cached_config(&format!("key_{}", i)); |
578 | 10 | assert!(retrieved.is_some()); |
579 | | } |
580 | 1 | } |
581 | | |
582 | | #[test] |
583 | 1 | fn test_config_manager_cache_overwrite() { |
584 | 1 | let config = create_test_config(); |
585 | 1 | let manager = ConfigManager::new(config); |
586 | | |
587 | 1 | manager.set_cached_config("key".to_string(), json!({"value": 1})); |
588 | 1 | manager.set_cached_config("key".to_string(), json!({"value": 2})); |
589 | | |
590 | 1 | let retrieved = manager.get_cached_config("key"); |
591 | 1 | assert_eq!(retrieved.unwrap(), json!({"value": 2})); |
592 | 1 | } |
593 | | |
594 | | #[test] |
595 | 1 | fn test_builder_custom_cache_timeout() { |
596 | 1 | let config = create_test_config(); |
597 | 1 | let custom_timeout = std::time::Duration::from_secs(120); |
598 | | |
599 | 1 | let manager = ConfigManagerBuilder::new(config) |
600 | 1 | .with_cache_timeout(custom_timeout) |
601 | 1 | .build(); |
602 | | |
603 | | // Cache timeout is set internally |
604 | 1 | let retrieved_config = manager.get_config(); |
605 | 1 | assert_eq!(retrieved_config.name, "test_service"); |
606 | 1 | } |
607 | | |
608 | | #[test] |
609 | 1 | fn test_config_manager_shared_config() { |
610 | 1 | let config = create_test_config(); |
611 | 1 | let manager = ConfigManager::new(config); |
612 | | |
613 | 1 | let config1 = manager.get_config(); |
614 | 1 | let config2 = manager.get_config(); |
615 | | |
616 | | // Both should point to the same Arc |
617 | 1 | assert_eq!(config1.name, config2.name); |
618 | 1 | } |
619 | | |
620 | | #[test] |
621 | 1 | fn test_service_config_clone() { |
622 | 1 | let config1 = create_test_config(); |
623 | 1 | let config2 = config1.clone(); |
624 | | |
625 | 1 | assert_eq!(config1.name, config2.name); |
626 | 1 | assert_eq!(config1.environment, config2.environment); |
627 | 1 | assert_eq!(config1.version, config2.version); |
628 | 1 | } |
629 | | |
630 | | #[test] |
631 | 1 | fn test_config_manager_cache_timeout_configuration() { |
632 | 1 | let config = create_test_config(); |
633 | 1 | let custom_timeout = std::time::Duration::from_millis(10); |
634 | 1 | let manager = ConfigManagerBuilder::new(config) |
635 | 1 | .with_cache_timeout(custom_timeout) |
636 | 1 | .build(); |
637 | | |
638 | | // Cache timeout is configured internally |
639 | 1 | assert_eq!(manager.cache_timeout, custom_timeout); |
640 | | |
641 | | // Test that cache still works normally |
642 | 1 | manager.set_cached_config("test_key".to_string(), json!({"value": 42})); |
643 | 1 | assert!(manager.get_cached_config("test_key").is_some()); |
644 | 1 | } |
645 | | |
646 | | #[test] |
647 | 1 | fn test_config_manager_concurrent_access() { |
648 | | use std::sync::Arc; |
649 | | use std::thread; |
650 | | |
651 | 1 | let config = create_test_config(); |
652 | 1 | let manager = Arc::new(ConfigManager::new(config)); |
653 | | |
654 | 1 | let mut handles = vec![]; |
655 | | |
656 | 11 | for i10 in 0..10 { |
657 | 10 | let manager_clone = Arc::clone(&manager); |
658 | 10 | let handle = thread::spawn(move || { |
659 | 10 | manager_clone |
660 | 10 | .set_cached_config(format!("concurrent_key_{}", i), json!({"thread_id": i})); |
661 | 10 | manager_clone.get_cached_config(&format!("concurrent_key_{}", i)) |
662 | 10 | }); |
663 | 10 | handles.push(handle); |
664 | | } |
665 | | |
666 | 11 | for handle10 in handles { |
667 | 10 | assert!(handle.join().unwrap().is_some()); |
668 | | } |
669 | 1 | } |
670 | | |
671 | | #[test] |
672 | 1 | fn test_config_manager_daily_volatility_fallback() { |
673 | 1 | let config = create_test_config(); |
674 | 1 | let manager = ConfigManager::new(config); |
675 | | |
676 | | // Should return default 5% for unknown symbols |
677 | 1 | let vol = manager.get_daily_volatility("UNKNOWN_SYMBOL"); |
678 | 1 | assert_eq!(vol, 0.05); |
679 | 1 | } |
680 | | |
681 | | #[test] |
682 | 1 | fn test_config_manager_position_size_none() { |
683 | 1 | let config = create_test_config(); |
684 | 1 | let manager = ConfigManager::new(config); |
685 | | |
686 | | // Should return None without asset classification |
687 | 1 | let size = |
688 | 1 | manager.get_position_size_recommendation("AAPL", rust_decimal::Decimal::new(100000, 0)); |
689 | 1 | assert!(size.is_none()); |
690 | 1 | } |
691 | | |
692 | | #[test] |
693 | 1 | fn test_service_config_validation() { |
694 | 1 | let mut config = create_test_config(); |
695 | | |
696 | | // Valid config |
697 | 1 | assert!(!config.name.is_empty()); |
698 | 1 | assert!(!config.environment.is_empty()); |
699 | | |
700 | | // Test with empty name |
701 | 1 | config.name = String::new(); |
702 | 1 | assert!(config.name.is_empty()); |
703 | 1 | } |
704 | | |
705 | | #[test] |
706 | 1 | fn test_config_manager_cache_clear() { |
707 | 1 | let config = create_test_config(); |
708 | 1 | let manager = ConfigManager::new(config); |
709 | | |
710 | | // Add some cache entries |
711 | 1 | manager.set_cached_config("key1".to_string(), json!({"value": 1})); |
712 | 1 | manager.set_cached_config("key2".to_string(), json!({"value": 2})); |
713 | | |
714 | 1 | assert!(manager.get_cached_config("key1").is_some()); |
715 | 1 | assert!(manager.get_cached_config("key2").is_some()); |
716 | | |
717 | | // Manual clear |
718 | 1 | if let Ok(mut cache) = manager.cache.write() { |
719 | 1 | cache.clear(); |
720 | 1 | }0 |
721 | | |
722 | 1 | assert!(manager.get_cached_config("key1").is_none()); |
723 | 1 | assert!(manager.get_cached_config("key2").is_none()); |
724 | 1 | } |
725 | | |
726 | | #[test] |
727 | 1 | fn test_builder_default_values() { |
728 | 1 | let config = create_test_config(); |
729 | 1 | let manager = ConfigManagerBuilder::new(config.clone()).build(); |
730 | | |
731 | 1 | let retrieved = manager.get_config(); |
732 | 1 | assert_eq!(retrieved.name, config.name); |
733 | 1 | assert_eq!(retrieved.environment, config.environment); |
734 | 1 | } |
735 | | |
736 | | #[test] |
737 | 1 | fn test_config_manager_arc_cloning() { |
738 | 1 | let config = create_test_config(); |
739 | 1 | let manager = ConfigManager::new(config); |
740 | | |
741 | 1 | let config1 = Arc::clone(&manager.config); |
742 | 1 | let config2 = Arc::clone(&manager.config); |
743 | | |
744 | 1 | assert_eq!(config1.name, config2.name); |
745 | 1 | assert_eq!(Arc::strong_count(&manager.config), 3); // Original + 2 clones |
746 | 1 | } |
747 | | } |