1#![allow(deprecated)]
2use crate::config::service::ConfigService;
9use crate::error::SubXError;
10use crate::{Result, config::Config};
11use std::path::{Path, PathBuf};
12use std::sync::Mutex;
13
14pub struct TestConfigService {
20 config: Mutex<Config>,
21}
22
23impl TestConfigService {
24 pub fn new(config: Config) -> Self {
30 Self {
31 config: Mutex::new(config),
32 }
33 }
34
35 pub fn with_defaults() -> Self {
39 Self::new(Config::default())
40 }
41
42 pub fn with_ai_settings(provider: &str, model: &str) -> Self {
49 let mut config = Config::default();
50 config.ai.provider = provider.to_string();
51 config.ai.model = model.to_string();
52 Self::new(config)
53 }
54
55 pub fn with_ai_settings_and_key(provider: &str, model: &str, api_key: &str) -> Self {
63 let mut config = Config::default();
64 config.ai.provider = provider.to_string();
65 config.ai.model = model.to_string();
66 config.ai.api_key = Some(api_key.to_string());
67 Self::new(config)
68 }
69
70 pub fn with_sync_settings(correlation_threshold: f32, max_offset: f32) -> Self {
77 let mut config = Config::default();
78 config.sync.correlation_threshold = correlation_threshold;
79 config.sync.max_offset_seconds = max_offset;
80 Self::new(config)
81 }
82
83 pub fn with_parallel_settings(max_workers: usize, queue_size: usize) -> Self {
90 let mut config = Config::default();
91 config.general.max_concurrent_jobs = max_workers;
92 config.parallel.task_queue_size = queue_size;
93 Self::new(config)
94 }
95
96 pub fn config(&self) -> std::sync::MutexGuard<Config> {
100 self.config.lock().unwrap()
101 }
102
103 pub fn config_mut(&self) -> std::sync::MutexGuard<Config> {
107 self.config.lock().unwrap()
108 }
109}
110
111impl ConfigService for TestConfigService {
112 fn get_config(&self) -> Result<Config> {
113 Ok(self.config.lock().unwrap().clone())
114 }
115
116 fn reload(&self) -> Result<()> {
117 Ok(())
119 }
120
121 fn save_config(&self) -> Result<()> {
122 Ok(())
124 }
125
126 fn save_config_to_file(&self, _path: &Path) -> Result<()> {
127 Ok(())
129 }
130
131 fn get_config_file_path(&self) -> Result<PathBuf> {
132 Ok(PathBuf::from("/tmp/subx_test_config.toml"))
134 }
135
136 fn get_config_value(&self, key: &str) -> Result<String> {
137 let config = self.config.lock().unwrap();
140 let parts: Vec<&str> = key.split('.').collect();
141 match parts.as_slice() {
142 ["ai", "provider"] => Ok(config.ai.provider.clone()),
143 ["ai", "model"] => Ok(config.ai.model.clone()),
144 ["ai", "api_key"] => Ok(config.ai.api_key.clone().unwrap_or_default()),
145 ["ai", "base_url"] => Ok(config.ai.base_url.clone()),
146 ["ai", "temperature"] => Ok(config.ai.temperature.to_string()),
147 ["ai", "max_sample_length"] => Ok(config.ai.max_sample_length.to_string()),
148 ["ai", "max_tokens"] => Ok(config.ai.max_tokens.to_string()),
149 ["ai", "retry_attempts"] => Ok(config.ai.retry_attempts.to_string()),
150 ["ai", "retry_delay_ms"] => Ok(config.ai.retry_delay_ms.to_string()),
151 ["ai", "request_timeout_seconds"] => Ok(config.ai.request_timeout_seconds.to_string()),
152 ["formats", "default_output"] => Ok(config.formats.default_output.clone()),
153 ["formats", "default_encoding"] => Ok(config.formats.default_encoding.clone()),
154 ["formats", "preserve_styling"] => Ok(config.formats.preserve_styling.to_string()),
155 ["formats", "encoding_detection_confidence"] => {
156 Ok(config.formats.encoding_detection_confidence.to_string())
157 }
158 ["sync", "max_offset_seconds"] => Ok(config.sync.max_offset_seconds.to_string()),
159 ["sync", "default_method"] => Ok(config.sync.default_method.clone()),
160 ["sync", "vad", "enabled"] => Ok(config.sync.vad.enabled.to_string()),
161 ["sync", "vad", "sensitivity"] => Ok(config.sync.vad.sensitivity.to_string()),
162 ["sync", "vad", "chunk_size"] => Ok(config.sync.vad.chunk_size.to_string()),
163 ["sync", "vad", "sample_rate"] => Ok(config.sync.vad.sample_rate.to_string()),
164 ["sync", "vad", "padding_chunks"] => Ok(config.sync.vad.padding_chunks.to_string()),
165 ["sync", "vad", "min_speech_duration_ms"] => {
166 Ok(config.sync.vad.min_speech_duration_ms.to_string())
167 }
168 ["sync", "vad", "speech_merge_gap_ms"] => {
169 Ok(config.sync.vad.speech_merge_gap_ms.to_string())
170 }
171 ["general", "backup_enabled"] => Ok(config.general.backup_enabled.to_string()),
172 ["general", "task_timeout_seconds"] => {
173 Ok(config.general.task_timeout_seconds.to_string())
174 }
175 ["general", "enable_progress_bar"] => {
176 Ok(config.general.enable_progress_bar.to_string())
177 }
178 ["general", "worker_idle_timeout_seconds"] => {
179 Ok(config.general.worker_idle_timeout_seconds.to_string())
180 }
181 ["general", "max_concurrent_jobs"] => {
182 Ok(config.general.max_concurrent_jobs.to_string())
183 }
184 ["parallel", "max_workers"] => Ok(config.parallel.max_workers.to_string()),
185 ["parallel", "task_queue_size"] => Ok(config.parallel.task_queue_size.to_string()),
186 ["parallel", "enable_task_priorities"] => {
187 Ok(config.parallel.enable_task_priorities.to_string())
188 }
189 ["parallel", "auto_balance_workers"] => {
190 Ok(config.parallel.auto_balance_workers.to_string())
191 }
192 ["parallel", "overflow_strategy"] => {
193 Ok(format!("{:?}", config.parallel.overflow_strategy))
194 }
195 _ => Err(SubXError::config(format!(
196 "Unknown configuration key: {}",
197 key
198 ))),
199 }
200 }
201
202 fn reset_to_defaults(&self) -> Result<()> {
203 *self.config.lock().unwrap() = Config::default();
205 Ok(())
206 }
207
208 fn set_config_value(&self, key: &str, value: &str) -> Result<()> {
209 let mut cfg = self.get_config()?;
211 self.validate_and_set_value(&mut cfg, key, value)?;
213 crate::config::validator::validate_config(&cfg)?;
215 *self.config.lock().unwrap() = cfg;
217 Ok(())
218 }
219}
220
221impl TestConfigService {
222 fn validate_and_set_value(&self, config: &mut Config, key: &str, value: &str) -> Result<()> {
224 use crate::config::OverflowStrategy;
225 use crate::config::validation::*;
226 use crate::error::SubXError;
227
228 let parts: Vec<&str> = key.split('.').collect();
229 match parts.as_slice() {
230 ["ai", "provider"] => {
231 validate_enum(value, &["openai", "anthropic", "local"])?;
232 config.ai.provider = value.to_string();
233 }
234 ["ai", "api_key"] => {
235 if !value.is_empty() {
236 validate_api_key(value)?;
237 config.ai.api_key = Some(value.to_string());
238 } else {
239 config.ai.api_key = None;
240 }
241 }
242 ["ai", "model"] => {
243 config.ai.model = value.to_string();
244 }
245 ["ai", "base_url"] => {
246 validate_url(value)?;
247 config.ai.base_url = value.to_string();
248 }
249 ["ai", "max_sample_length"] => {
250 let v = validate_usize_range(value, 100, 10000)?;
251 config.ai.max_sample_length = v;
252 }
253 ["ai", "temperature"] => {
254 let v = validate_float_range(value, 0.0, 1.0)?;
255 config.ai.temperature = v;
256 }
257 ["ai", "max_tokens"] => {
258 let v = validate_uint_range(value, 1, 100_000)?;
259 config.ai.max_tokens = v;
260 }
261 ["ai", "retry_attempts"] => {
262 let v = validate_uint_range(value, 1, 10)?;
263 config.ai.retry_attempts = v;
264 }
265 ["ai", "retry_delay_ms"] => {
266 let v = validate_u64_range(value, 100, 30000)?;
267 config.ai.retry_delay_ms = v;
268 }
269 ["ai", "request_timeout_seconds"] => {
270 let v = validate_u64_range(value, 10, 600)?;
271 config.ai.request_timeout_seconds = v;
272 }
273 ["formats", "default_output"] => {
274 validate_enum(value, &["srt", "ass", "vtt", "webvtt"])?;
275 config.formats.default_output = value.to_string();
276 }
277 ["formats", "preserve_styling"] => {
278 let v = parse_bool(value)?;
279 config.formats.preserve_styling = v;
280 }
281 ["formats", "default_encoding"] => {
282 validate_enum(value, &["utf-8", "gbk", "big5", "shift_jis"])?;
283 config.formats.default_encoding = value.to_string();
284 }
285 ["formats", "encoding_detection_confidence"] => {
286 let v = validate_float_range(value, 0.0, 1.0)?;
287 config.formats.encoding_detection_confidence = v;
288 }
289 ["sync", "max_offset_seconds"] => {
290 let v = validate_float_range(value, 0.0, 300.0)?;
291 config.sync.max_offset_seconds = v;
292 }
293 ["sync", "default_method"] => {
294 validate_enum(value, &["auto", "vad"])?;
295 config.sync.default_method = value.to_string();
296 }
297 ["sync", "vad", "enabled"] => {
298 let v = parse_bool(value)?;
299 config.sync.vad.enabled = v;
300 }
301 ["sync", "vad", "sensitivity"] => {
302 let v = validate_float_range(value, 0.0, 1.0)?;
303 config.sync.vad.sensitivity = v;
304 }
305 ["sync", "vad", "chunk_size"] => {
306 let v = validate_usize_range(value, 1, usize::MAX)?;
307 config.sync.vad.chunk_size = v;
308 }
309 ["sync", "vad", "sample_rate"] => {
310 validate_enum(value, &["8000", "16000", "22050", "44100", "48000"])?;
311 config.sync.vad.sample_rate = value.parse().unwrap();
312 }
313 ["sync", "vad", "padding_chunks"] => {
314 let v = validate_uint_range(value, 0, u32::MAX)?;
315 config.sync.vad.padding_chunks = v;
316 }
317 ["sync", "vad", "min_speech_duration_ms"] => {
318 let v = validate_uint_range(value, 0, u32::MAX)?;
319 config.sync.vad.min_speech_duration_ms = v;
320 }
321 ["sync", "vad", "speech_merge_gap_ms"] => {
322 let v = validate_uint_range(value, 0, u32::MAX)?;
323 config.sync.vad.speech_merge_gap_ms = v;
324 }
325 ["sync", "correlation_threshold"] => {
326 let v = validate_float_range(value, 0.0, 1.0)?;
327 config.sync.correlation_threshold = v;
328 }
329 ["sync", "dialogue_detection_threshold"] => {
330 let v = validate_float_range(value, 0.0, 1.0)?;
331 config.sync.dialogue_detection_threshold = v;
332 }
333 ["sync", "min_dialogue_duration_ms"] => {
334 let v = validate_uint_range(value, 100, 5000)?;
335 config.sync.min_dialogue_duration_ms = v;
336 }
337 ["sync", "dialogue_merge_gap_ms"] => {
338 let v = validate_uint_range(value, 50, 2000)?;
339 config.sync.dialogue_merge_gap_ms = v;
340 }
341 ["sync", "enable_dialogue_detection"] => {
342 let v = parse_bool(value)?;
343 config.sync.enable_dialogue_detection = v;
344 }
345 ["sync", "audio_sample_rate"] => {
346 let v = validate_uint_range(value, 8000, 192000)?;
347 config.sync.audio_sample_rate = v;
348 }
349 ["sync", "auto_detect_sample_rate"] => {
350 let v = parse_bool(value)?;
351 config.sync.auto_detect_sample_rate = v;
352 }
353 ["general", "backup_enabled"] => {
354 let v = parse_bool(value)?;
355 config.general.backup_enabled = v;
356 }
357 ["general", "max_concurrent_jobs"] => {
358 let v = validate_usize_range(value, 1, 64)?;
359 config.general.max_concurrent_jobs = v;
360 }
361 ["general", "task_timeout_seconds"] => {
362 let v = validate_u64_range(value, 30, 3600)?;
363 config.general.task_timeout_seconds = v;
364 }
365 ["general", "enable_progress_bar"] => {
366 let v = parse_bool(value)?;
367 config.general.enable_progress_bar = v;
368 }
369 ["general", "worker_idle_timeout_seconds"] => {
370 let v = validate_u64_range(value, 10, 3600)?;
371 config.general.worker_idle_timeout_seconds = v;
372 }
373 ["parallel", "max_workers"] => {
374 let v = validate_usize_range(value, 1, 64)?;
375 config.parallel.max_workers = v;
376 }
377 ["parallel", "task_queue_size"] => {
378 let v = validate_usize_range(value, 100, 10000)?;
379 config.parallel.task_queue_size = v;
380 }
381 ["parallel", "enable_task_priorities"] => {
382 let v = parse_bool(value)?;
383 config.parallel.enable_task_priorities = v;
384 }
385 ["parallel", "auto_balance_workers"] => {
386 let v = parse_bool(value)?;
387 config.parallel.auto_balance_workers = v;
388 }
389 ["parallel", "overflow_strategy"] => {
390 validate_enum(value, &["Block", "Drop", "Expand"])?;
391 config.parallel.overflow_strategy = match value {
392 "Block" => OverflowStrategy::Block,
393 "Drop" => OverflowStrategy::Drop,
394 "Expand" => OverflowStrategy::Expand,
395 _ => unreachable!(),
396 };
397 }
398 _ => {
399 return Err(SubXError::config(format!(
400 "Unknown configuration key: {}",
401 key
402 )));
403 }
404 }
405 Ok(())
406 }
407}
408
409impl Default for TestConfigService {
410 fn default() -> Self {
411 Self::with_defaults()
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418
419 #[test]
420 fn test_config_service_with_defaults() {
421 let service = TestConfigService::with_defaults();
422 let config = service.get_config().unwrap();
423
424 assert_eq!(config.ai.provider, "openai");
425 assert_eq!(config.ai.model, "gpt-4.1-mini");
426 }
427
428 #[test]
429 fn test_config_service_with_ai_settings() {
430 let service = TestConfigService::with_ai_settings("anthropic", "claude-3");
431 let config = service.get_config().unwrap();
432
433 assert_eq!(config.ai.provider, "anthropic");
434 assert_eq!(config.ai.model, "claude-3");
435 }
436
437 #[test]
438 fn test_config_service_with_ai_settings_and_key() {
439 let service =
440 TestConfigService::with_ai_settings_and_key("openai", "gpt-4.1", "test-api-key");
441 let config = service.get_config().unwrap();
442
443 assert_eq!(config.ai.provider, "openai");
444 assert_eq!(config.ai.model, "gpt-4.1");
445 assert_eq!(config.ai.api_key, Some("test-api-key".to_string()));
446 }
447
448 #[test]
449 fn test_config_service_with_sync_settings() {
450 let service = TestConfigService::with_sync_settings(0.8, 45.0);
451 let config = service.get_config().unwrap();
452
453 assert_eq!(config.sync.correlation_threshold, 0.8);
454 assert_eq!(config.sync.max_offset_seconds, 45.0);
455 }
456
457 #[test]
458 fn test_config_service_with_parallel_settings() {
459 let service = TestConfigService::with_parallel_settings(8, 200);
460 let config = service.get_config().unwrap();
461
462 assert_eq!(config.general.max_concurrent_jobs, 8);
463 assert_eq!(config.parallel.task_queue_size, 200);
464 }
465
466 #[test]
467 fn test_config_service_reload() {
468 let service = TestConfigService::with_defaults();
469
470 assert!(service.reload().is_ok());
472 }
473
474 #[test]
475 fn test_config_service_direct_access() {
476 let service = TestConfigService::with_defaults();
477
478 assert_eq!(service.config().ai.provider, "openai");
480
481 service.config_mut().ai.provider = "modified".to_string();
483 assert_eq!(service.config().ai.provider, "modified");
484
485 let config = service.get_config().unwrap();
486 assert_eq!(config.ai.provider, "modified");
487 }
488}