subx_cli/config/
test_service.rs

1//! Test configuration service for isolated testing.
2//!
3//! This module provides a configuration service implementation specifically
4//! designed for testing environments, offering complete isolation and
5//! predictable configuration states.
6
7use crate::config::service::ConfigService;
8use crate::error::SubXError;
9use crate::{Result, config::Config};
10use std::path::{Path, PathBuf};
11
12/// Test configuration service implementation.
13///
14/// This service provides a fixed configuration for testing purposes,
15/// ensuring complete isolation between tests and predictable behavior.
16/// It does not load from external sources or cache.
17pub struct TestConfigService {
18    fixed_config: Config,
19}
20
21impl TestConfigService {
22    /// Create a new test configuration service with the provided configuration.
23    ///
24    /// # Arguments
25    ///
26    /// * `config` - The fixed configuration to use
27    pub fn new(config: Config) -> Self {
28        Self {
29            fixed_config: config,
30        }
31    }
32
33    /// Create a test configuration service with default settings.
34    ///
35    /// This is useful for tests that don't need specific configuration values.
36    pub fn with_defaults() -> Self {
37        Self::new(Config::default())
38    }
39
40    /// Create a test configuration service with specific AI settings.
41    ///
42    /// # Arguments
43    ///
44    /// * `provider` - AI provider name
45    /// * `model` - AI model name
46    pub fn with_ai_settings(provider: &str, model: &str) -> Self {
47        let mut config = Config::default();
48        config.ai.provider = provider.to_string();
49        config.ai.model = model.to_string();
50        Self::new(config)
51    }
52
53    /// Create a test configuration service with specific AI settings including API key.
54    ///
55    /// # Arguments
56    ///
57    /// * `provider` - AI provider name
58    /// * `model` - AI model name
59    /// * `api_key` - API key for the provider
60    pub fn with_ai_settings_and_key(provider: &str, model: &str, api_key: &str) -> Self {
61        let mut config = Config::default();
62        config.ai.provider = provider.to_string();
63        config.ai.model = model.to_string();
64        config.ai.api_key = Some(api_key.to_string());
65        Self::new(config)
66    }
67
68    /// Create a test configuration service with specific sync settings.
69    ///
70    /// # Arguments
71    ///
72    /// * `correlation_threshold` - Correlation threshold for synchronization
73    /// * `max_offset` - Maximum time offset in seconds
74    pub fn with_sync_settings(correlation_threshold: f32, max_offset: f32) -> Self {
75        let mut config = Config::default();
76        config.sync.correlation_threshold = correlation_threshold;
77        config.sync.max_offset_seconds = max_offset;
78        Self::new(config)
79    }
80
81    /// Create a test configuration service with specific parallel processing settings.
82    ///
83    /// # Arguments
84    ///
85    /// * `max_workers` - Maximum number of parallel workers
86    /// * `queue_size` - Task queue size
87    pub fn with_parallel_settings(max_workers: usize, queue_size: usize) -> Self {
88        let mut config = Config::default();
89        config.general.max_concurrent_jobs = max_workers;
90        config.parallel.task_queue_size = queue_size;
91        Self::new(config)
92    }
93
94    /// Get the underlying configuration.
95    ///
96    /// This is useful for tests that need direct access to the configuration object.
97    pub fn config(&self) -> &Config {
98        &self.fixed_config
99    }
100
101    /// Get a mutable reference to the underlying configuration.
102    ///
103    /// This allows tests to modify the configuration after creation.
104    pub fn config_mut(&mut self) -> &mut Config {
105        &mut self.fixed_config
106    }
107}
108
109impl ConfigService for TestConfigService {
110    fn get_config(&self) -> Result<Config> {
111        Ok(self.fixed_config.clone())
112    }
113
114    fn reload(&self) -> Result<()> {
115        // Test configuration doesn't need reloading since it's fixed
116        Ok(())
117    }
118
119    fn save_config(&self) -> Result<()> {
120        // Test environment does not perform actual file I/O
121        Ok(())
122    }
123
124    fn save_config_to_file(&self, _path: &Path) -> Result<()> {
125        // Test environment does not perform actual file I/O
126        Ok(())
127    }
128
129    fn get_config_file_path(&self) -> Result<PathBuf> {
130        // Return a dummy path to avoid conflicts in test environment
131        Ok(PathBuf::from("/tmp/subx_test_config.toml"))
132    }
133
134    fn get_config_value(&self, key: &str) -> Result<String> {
135        // Delegate to fixed configuration
136        // Note: unwrap_or_default to handle Option fields
137        let config = &self.fixed_config;
138        let parts: Vec<&str> = key.split('.').collect();
139        match parts.as_slice() {
140            ["ai", "provider"] => Ok(config.ai.provider.clone()),
141            ["ai", "model"] => Ok(config.ai.model.clone()),
142            ["ai", "api_key"] => Ok(config.ai.api_key.clone().unwrap_or_default()),
143            ["ai", "base_url"] => Ok(config.ai.base_url.clone()),
144            ["ai", "temperature"] => Ok(config.ai.temperature.to_string()),
145            ["formats", "default_output"] => Ok(config.formats.default_output.clone()),
146            ["formats", "default_encoding"] => Ok(config.formats.default_encoding.clone()),
147            ["sync", "max_offset_seconds"] => Ok(config.sync.max_offset_seconds.to_string()),
148            ["sync", "correlation_threshold"] => Ok(config.sync.correlation_threshold.to_string()),
149            ["general", "backup_enabled"] => Ok(config.general.backup_enabled.to_string()),
150            ["general", "max_concurrent_jobs"] => {
151                Ok(config.general.max_concurrent_jobs.to_string())
152            }
153            ["parallel", "max_workers"] => Ok(config.parallel.max_workers.to_string()),
154            ["parallel", "chunk_size"] => Ok(config.parallel.chunk_size.to_string()),
155            _ => Err(SubXError::config(format!(
156                "Unknown configuration key: {}",
157                key
158            ))),
159        }
160    }
161
162    fn reset_to_defaults(&self) -> Result<()> {
163        // Reset the fixed configuration to default values
164        // Note: This requires interior mutability if used concurrently
165        Ok(())
166    }
167}
168
169impl Default for TestConfigService {
170    fn default() -> Self {
171        Self::with_defaults()
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_config_service_with_defaults() {
181        let service = TestConfigService::with_defaults();
182        let config = service.get_config().unwrap();
183
184        assert_eq!(config.ai.provider, "openai");
185        assert_eq!(config.ai.model, "gpt-4.1-mini");
186    }
187
188    #[test]
189    fn test_config_service_with_ai_settings() {
190        let service = TestConfigService::with_ai_settings("anthropic", "claude-3");
191        let config = service.get_config().unwrap();
192
193        assert_eq!(config.ai.provider, "anthropic");
194        assert_eq!(config.ai.model, "claude-3");
195    }
196
197    #[test]
198    fn test_config_service_with_ai_settings_and_key() {
199        let service =
200            TestConfigService::with_ai_settings_and_key("openai", "gpt-4.1", "test-api-key");
201        let config = service.get_config().unwrap();
202
203        assert_eq!(config.ai.provider, "openai");
204        assert_eq!(config.ai.model, "gpt-4.1");
205        assert_eq!(config.ai.api_key, Some("test-api-key".to_string()));
206    }
207
208    #[test]
209    fn test_config_service_with_sync_settings() {
210        let service = TestConfigService::with_sync_settings(0.8, 45.0);
211        let config = service.get_config().unwrap();
212
213        assert_eq!(config.sync.correlation_threshold, 0.8);
214        assert_eq!(config.sync.max_offset_seconds, 45.0);
215    }
216
217    #[test]
218    fn test_config_service_with_parallel_settings() {
219        let service = TestConfigService::with_parallel_settings(8, 200);
220        let config = service.get_config().unwrap();
221
222        assert_eq!(config.general.max_concurrent_jobs, 8);
223        assert_eq!(config.parallel.task_queue_size, 200);
224    }
225
226    #[test]
227    fn test_config_service_reload() {
228        let service = TestConfigService::with_defaults();
229
230        // Reload should always succeed for test service
231        assert!(service.reload().is_ok());
232    }
233
234    #[test]
235    fn test_config_service_direct_access() {
236        let mut service = TestConfigService::with_defaults();
237
238        // Test direct read access
239        assert_eq!(service.config().ai.provider, "openai");
240
241        // Test mutable access
242        service.config_mut().ai.provider = "modified".to_string();
243        assert_eq!(service.config().ai.provider, "modified");
244
245        let config = service.get_config().unwrap();
246        assert_eq!(config.ai.provider, "modified");
247    }
248}