Coverage Report

Created: 2025-10-06 12:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}