subx_cli/config/
test_macros.rs

1//! Test macros for convenient configuration service testing.
2//!
3//! This module provides convenient macros for creating test configurations
4//! and running tests with specific configuration services.
5
6/// Run a test with a custom configuration builder.
7///
8/// This macro takes a configuration builder and a test closure,
9/// creates a configuration service, and runs the test with it.
10///
11/// # Examples
12///
13/// ```rust
14/// use subx_cli::{test_with_config, config::{TestConfigBuilder, ConfigService}};
15///
16/// test_with_config!(
17///     TestConfigBuilder::new().with_ai_provider("openai"),
18///     |config_service: &dyn ConfigService| {
19///         let config = config_service.get_config().unwrap();
20///         assert_eq!(config.ai.provider, "openai");
21///     }
22/// );
23/// ```
24#[macro_export]
25macro_rules! test_with_config {
26    ($config_builder:expr, $test:expr) => {{
27        let config_service = $config_builder.build_service();
28        $test(&config_service)
29    }};
30}
31
32/// Execute ProductionConfigService tests with specified environment variable mapping.
33///
34/// This macro creates a TestEnvironmentProvider, sets the specified environment variables,
35/// then uses that provider to create a ProductionConfigService for testing.
36///
37/// # Arguments
38/// * `$env_vars` - Environment variable mapping expression (HashMap<&str, &str>)
39/// * `$test` - Test closure that receives a ProductionConfigService reference
40///
41/// # Examples
42///
43/// ```rust,ignore
44/// use subx_cli::{test_production_config_with_env, std::collections::HashMap};
45///
46/// let env_vars = [
47///     ("OPENAI_API_KEY", "sk-test-key"),
48///     ("OPENAI_BASE_URL", "https://test.api.com/v1")
49/// ].iter().cloned().collect::<HashMap<_, _>>();
50///
51/// test_production_config_with_env!(env_vars, |service| {
52///     let config = service.get_config().unwrap();
53///     assert_eq!(config.ai.api_key, Some("sk-test-key".to_string()));
54///     assert_eq!(config.ai.base_url, "https://test.api.com/v1");
55/// });
56/// ```
57#[macro_export]
58macro_rules! test_production_config_with_env {
59    ($env_vars:expr, $test:expr) => {{
60        use std::sync::Arc;
61
62        let mut env_provider = $crate::config::TestEnvironmentProvider::new();
63
64        // Set a non-existent config path to avoid interference from existing config files
65        env_provider.set_var(
66            "SUBX_CONFIG_PATH",
67            "/tmp/test_config_macro_does_not_exist.toml",
68        );
69
70        // Convert environment variable mapping to strings and set them in the provider
71        for (key, value) in $env_vars {
72            env_provider.set_var(key, value);
73        }
74
75        let service =
76            $crate::config::ProductionConfigService::with_env_provider(Arc::new(env_provider))
77                .expect("Failed to create ProductionConfigService with environment provider");
78
79        $test(&service)
80    }};
81}
82
83/// Execute ProductionConfigService tests with OPENAI environment variables.
84///
85/// This macro is a convenience version of test_production_config_with_env!,
86/// specifically designed for testing OPENAI_API_KEY and OPENAI_BASE_URL environment variables.
87///
88/// # Arguments
89/// * `$api_key` - OPENAI_API_KEY value (Option<&str>)
90/// * `$base_url` - OPENAI_BASE_URL value (Option<&str>)  
91/// * `$test` - Test closure that receives a ProductionConfigService reference
92///
93/// # Examples
94///
95/// ```rust,ignore
96/// use subx_cli::test_production_config_with_openai_env;
97///
98/// test_production_config_with_openai_env!(
99///     Some("sk-test-key"),
100///     Some("https://test.api.com/v1"),
101///     |service| {
102///         let config = service.get_config().unwrap();
103///         assert_eq!(config.ai.api_key, Some("sk-test-key".to_string()));
104///         assert_eq!(config.ai.base_url, "https://test.api.com/v1");
105///     }
106/// );
107/// ```
108#[macro_export]
109macro_rules! test_production_config_with_openai_env {
110    ($api_key:expr, $base_url:expr, $test:expr) => {{
111        use std::sync::Arc;
112
113        let mut env_provider = $crate::config::TestEnvironmentProvider::new();
114
115        // Set a non-existent config path to avoid interference from existing config files
116        env_provider.set_var(
117            "SUBX_CONFIG_PATH",
118            "/tmp/test_config_openai_macro_does_not_exist.toml",
119        );
120
121        // Set OPENAI_API_KEY (if provided)
122        if let Some(api_key) = $api_key {
123            env_provider.set_var("OPENAI_API_KEY", api_key);
124        }
125
126        // Set OPENAI_BASE_URL (if provided)
127        if let Some(base_url) = $base_url {
128            env_provider.set_var("OPENAI_BASE_URL", base_url);
129        }
130
131        let service =
132            $crate::config::ProductionConfigService::with_env_provider(Arc::new(env_provider))
133                .expect(
134                    "Failed to create ProductionConfigService with OPENAI environment variables",
135                );
136
137        $test(&service)
138    }};
139}
140
141/// Create a temporary ProductionConfigService with environment variable provider for test functions.
142///
143/// This macro creates a ProductionConfigService variable with specified environment variables
144/// that can be used throughout the entire test function.
145///
146/// # Arguments
147/// * `$service_name` - Service variable name
148/// * `$env_vars` - Environment variable mapping expression (HashMap<&str, &str>)
149///
150/// # Examples
151///
152/// ```rust,ignore
153/// use subx_cli::create_production_config_service_with_env;
154///
155/// fn my_test() {
156///     let env_vars = [("OPENAI_API_KEY", "sk-test")].iter().cloned().collect();
157///     create_production_config_service_with_env!(service, env_vars);
158///
159///     let config = service.get_config().unwrap();
160///     assert_eq!(config.ai.api_key, Some("sk-test".to_string()));
161/// }
162/// ```
163#[macro_export]
164macro_rules! create_production_config_service_with_env {
165    ($service_name:ident, $env_vars:expr) => {
166        use std::sync::Arc;
167
168        let mut env_provider = $crate::config::TestEnvironmentProvider::new();
169
170        // Set a non-existent config path to avoid interference from existing config files
171        env_provider.set_var(
172            "SUBX_CONFIG_PATH",
173            "/tmp/test_config_create_macro_does_not_exist.toml",
174        );
175
176        for (key, value) in $env_vars {
177            env_provider.set_var(key, value);
178        }
179
180        let $service_name =
181            $crate::config::ProductionConfigService::with_env_provider(Arc::new(env_provider))
182                .expect("Failed to create ProductionConfigService with environment provider");
183    };
184}
185
186/// Create a ProductionConfigService with empty environment variables for testing.
187///
188/// This macro creates a ProductionConfigService without any environment variables,
189/// used for testing default behavior.
190///
191/// # Arguments
192/// * `$service_name` - Service variable name
193///
194/// # Examples
195///
196/// ```rust,ignore
197/// use subx_cli::create_production_config_service_with_empty_env;
198///
199/// fn my_test() {
200///     create_production_config_service_with_empty_env!(service);
201///
202///     let config = service.get_config().unwrap();
203///     assert_eq!(config.ai.api_key, None); // Expected no API key
204/// }
205/// ```
206#[macro_export]
207macro_rules! create_production_config_service_with_empty_env {
208    ($service_name:ident) => {
209        create_production_config_service_with_env!($service_name, std::collections::HashMap::new())
210    };
211}
212
213#[cfg(test)]
214mod env_macro_tests {
215    use crate::config::service::ConfigService;
216    use std::collections::HashMap;
217
218    #[test]
219    fn test_production_config_with_env_macro() {
220        let env_vars: HashMap<&str, &str> = [
221            ("OPENAI_API_KEY", "sk-macro-test"),
222            ("OPENAI_BASE_URL", "https://macro.test.com/v1"),
223        ]
224        .iter()
225        .cloned()
226        .collect();
227
228        test_production_config_with_env!(
229            env_vars,
230            |service: &crate::config::ProductionConfigService| {
231                let config = service.get_config().unwrap();
232                assert_eq!(config.ai.api_key, Some("sk-macro-test".to_string()));
233                assert_eq!(config.ai.base_url, "https://macro.test.com/v1");
234            }
235        );
236    }
237
238    #[test]
239    fn test_production_config_with_openai_env_macro_both() {
240        test_production_config_with_openai_env!(
241            Some("sk-openai-macro"),
242            Some("https://openai.macro.com/v1"),
243            |service: &crate::config::ProductionConfigService| {
244                let config = service.get_config().unwrap();
245                assert_eq!(config.ai.api_key, Some("sk-openai-macro".to_string()));
246                assert_eq!(config.ai.base_url, "https://openai.macro.com/v1");
247            }
248        );
249    }
250
251    #[test]
252    fn test_production_config_with_openai_env_macro_api_key_only() {
253        test_production_config_with_openai_env!(
254            Some("sk-only-key"),
255            None,
256            |service: &crate::config::ProductionConfigService| {
257                let config = service.get_config().unwrap();
258                assert_eq!(config.ai.api_key, Some("sk-only-key".to_string()));
259                // base_url should use default value
260                assert_eq!(config.ai.base_url, "https://api.openai.com/v1");
261            }
262        );
263    }
264
265    #[test]
266    fn test_production_config_with_openai_env_macro_base_url_only() {
267        test_production_config_with_openai_env!(
268            None,
269            Some("https://only-url.com/v1"),
270            |service: &crate::config::ProductionConfigService| {
271                let config = service.get_config().unwrap();
272                assert_eq!(config.ai.api_key, None);
273                assert_eq!(config.ai.base_url, "https://only-url.com/v1");
274            }
275        );
276    }
277
278    #[test]
279    fn test_production_config_with_openai_env_macro_empty() {
280        test_production_config_with_openai_env!(
281            None,
282            None,
283            |service: &crate::config::ProductionConfigService| {
284                let config = service.get_config().unwrap();
285                assert_eq!(config.ai.api_key, None);
286                assert_eq!(config.ai.base_url, "https://api.openai.com/v1");
287            }
288        );
289    }
290
291    #[test]
292    fn test_create_production_config_service_with_env_macro() {
293        let env_vars: HashMap<&str, &str> = [("OPENAI_API_KEY", "sk-create-macro")]
294            .iter()
295            .cloned()
296            .collect();
297
298        create_production_config_service_with_env!(service, env_vars);
299
300        let config = service.get_config().unwrap();
301        assert_eq!(config.ai.api_key, Some("sk-create-macro".to_string()));
302    }
303
304    #[test]
305    fn test_create_production_config_service_with_empty_env_macro() {
306        create_production_config_service_with_empty_env!(service);
307
308        let config = service.get_config().unwrap();
309        assert_eq!(config.ai.api_key, None);
310        assert_eq!(config.ai.base_url, "https://api.openai.com/v1");
311    }
312}
313
314/// Run a test with the default configuration.
315///
316/// This macro creates a test configuration service with default settings
317/// and runs the provided test closure with it.
318///
319/// # Examples
320///
321/// ```rust,ignore
322/// use subx_cli::{test_with_default_config, config::ConfigService};
323///
324/// test_with_default_config!(|config_service: &dyn ConfigService| {
325///     let config = config_service.get_config().unwrap();
326///     assert_eq!(config.ai.provider, "openai");
327/// });
328/// ```
329#[macro_export]
330macro_rules! test_with_default_config {
331    ($test:expr) => {
332        test_with_config!($crate::config::TestConfigBuilder::new(), $test)
333    };
334}
335
336/// Run a test with specific AI configuration.
337///
338/// This macro creates a test configuration service with the specified
339/// AI provider and model, then runs the test closure.
340///
341/// # Examples
342///
343/// ```rust,ignore
344/// use subx_cli::{test_with_ai_config, config::ConfigService};
345///
346/// test_with_ai_config!("anthropic", "claude-3", |config_service: &dyn ConfigService| {
347///     let config = config_service.get_config().unwrap();
348///     assert_eq!(config.ai.provider, "anthropic");
349///     assert_eq!(config.ai.model, "claude-3");
350/// });
351/// ```
352#[macro_export]
353macro_rules! test_with_ai_config {
354    ($provider:expr, $model:expr, $test:expr) => {
355        test_with_config!(
356            $crate::config::TestConfigBuilder::new()
357                .with_ai_provider($provider)
358                .with_ai_model($model),
359            $test
360        )
361    };
362}
363
364/// Run a test with specific AI configuration including API key.
365///
366/// This macro creates a test configuration service with the specified
367/// AI provider, model, and API key, then runs the test closure.
368///
369/// # Examples
370///
371/// ```rust,ignore
372/// use subx_cli::{test_with_ai_config_and_key, config::ConfigService};
373///
374/// test_with_ai_config_and_key!("openai", "gpt-4.1", "test-key", |config_service: &dyn ConfigService| {
375///     let config = config_service.get_config().unwrap();
376///     assert_eq!(config.ai.provider, "openai");
377///     assert_eq!(config.ai.model, "gpt-4.1");
378///     assert_eq!(config.ai.api_key, Some("test-key".to_string()));
379/// });
380/// ```
381#[macro_export]
382macro_rules! test_with_ai_config_and_key {
383    ($provider:expr, $model:expr, $api_key:expr, $test:expr) => {
384        test_with_config!(
385            $crate::config::TestConfigBuilder::new()
386                .with_ai_provider($provider)
387                .with_ai_model($model)
388                .with_ai_api_key($api_key),
389            $test
390        )
391    };
392}
393
394/// Run a test with specific sync configuration.
395///
396/// This macro creates a test configuration service with the specified
397/// synchronization parameters, then runs the test closure.
398///
399/// # Examples
400///
401/// ```rust,ignore
402/// use subx_cli::{test_with_sync_config, config::ConfigService};
403///
404/// test_with_sync_config!(0.8, 45.0, |config_service: &dyn ConfigService| {
405///     let config = config_service.get_config().unwrap();
406///     assert_eq!(config.sync.correlation_threshold, 0.8);
407///     assert_eq!(config.sync.max_offset_seconds, 45.0);
408/// });
409/// ```
410#[macro_export]
411macro_rules! test_with_sync_config {
412    ($threshold:expr, $max_offset:expr, $test:expr) => {
413        test_with_config!(
414            $crate::config::TestConfigBuilder::new()
415                .with_vad_sensitivity($threshold)
416                .with_sync_method("vad"),
417            $test
418        )
419    };
420}
421
422/// Run a test with specific parallel processing configuration.
423///
424/// This macro creates a test configuration service with the specified
425/// parallel processing parameters, then runs the test closure.
426///
427/// # Examples
428///
429/// ```rust,ignore
430/// use subx_cli::{test_with_parallel_config, config::ConfigService};
431///
432/// test_with_parallel_config!(8, 200, |config_service: &dyn ConfigService| {
433///     let config = config_service.get_config().unwrap();
434///     assert_eq!(config.general.max_concurrent_jobs, 8);
435///     assert_eq!(config.parallel.task_queue_size, 200);
436/// });
437/// ```
438#[macro_export]
439macro_rules! test_with_parallel_config {
440    ($max_jobs:expr, $queue_size:expr, $test:expr) => {
441        test_with_config!(
442            $crate::config::TestConfigBuilder::new()
443                .with_max_concurrent_jobs($max_jobs)
444                .with_task_queue_size($queue_size),
445            $test
446        )
447    };
448}
449
450/// Create a temporary test configuration service for use in test functions.
451///
452/// This macro creates a configuration service variable that can be used
453/// throughout a test function.
454///
455/// # Examples
456///
457/// ```rust,ignore
458/// use subx_cli::create_test_config_service;
459///
460/// fn my_test() {
461///     create_test_config_service!(service, TestConfigBuilder::new().with_ai_provider("openai"));
462///     
463///     let config = service.get_config().unwrap();
464///     assert_eq!(config.ai.provider, "openai");
465/// }
466/// ```
467#[macro_export]
468macro_rules! create_test_config_service {
469    ($service_name:ident, $config_builder:expr) => {
470        let $service_name = $config_builder.build_service();
471    };
472}
473
474/// Create a temporary test configuration service with default settings.
475///
476/// This macro creates a configuration service variable with default settings
477/// that can be used throughout a test function.
478///
479/// # Examples
480///
481/// ```rust,ignore
482/// use subx_cli::create_default_test_config_service;
483///
484/// fn my_test() {
485///     create_default_test_config_service!(service);
486///     
487///     let config = service.get_config().unwrap();
488///     assert_eq!(config.ai.provider, "openai");
489/// }
490/// ```
491#[macro_export]
492macro_rules! create_default_test_config_service {
493    ($service_name:ident) => {
494        create_test_config_service!($service_name, $crate::config::TestConfigBuilder::new());
495    };
496}
497
498#[cfg(test)]
499mod tests {
500    use crate::config::{ConfigService, TestConfigBuilder};
501
502    #[test]
503    fn test_macro_with_config() {
504        test_with_config!(
505            TestConfigBuilder::new().with_ai_provider("test_provider"),
506            |config_service: &crate::config::TestConfigService| {
507                let config = config_service.get_config().unwrap();
508                assert_eq!(config.ai.provider, "test_provider");
509            }
510        );
511    }
512
513    #[test]
514    fn test_macro_with_default_config() {
515        test_with_default_config!(|config_service: &crate::config::TestConfigService| {
516            let config = config_service.get_config().unwrap();
517            assert_eq!(config.ai.provider, "openai");
518        });
519    }
520
521    #[test]
522    fn test_macro_with_ai_config() {
523        test_with_ai_config!(
524            "anthropic",
525            "claude-3",
526            |config_service: &crate::config::TestConfigService| {
527                let config = config_service.get_config().unwrap();
528                assert_eq!(config.ai.provider, "anthropic");
529                assert_eq!(config.ai.model, "claude-3");
530            }
531        );
532    }
533
534    #[test]
535    fn test_macro_with_ai_config_and_key() {
536        test_with_ai_config_and_key!(
537            "openai",
538            "gpt-4.1",
539            "test-key",
540            |config_service: &crate::config::TestConfigService| {
541                let config = config_service.get_config().unwrap();
542                assert_eq!(config.ai.provider, "openai");
543                assert_eq!(config.ai.model, "gpt-4.1");
544                assert_eq!(config.ai.api_key, Some("test-key".to_string()));
545            }
546        );
547    }
548
549    #[test]
550    fn test_macro_with_sync_config() {
551        test_with_sync_config!(
552            0.9,
553            60.0,
554            |config_service: &crate::config::TestConfigService| {
555                let config = config_service.config();
556                // Test new sync configuration structure
557                assert_eq!(config.sync.vad.sensitivity, 0.9);
558                assert_eq!(config.sync.default_method, "vad");
559            }
560        );
561    }
562
563    #[test]
564    fn test_macro_with_parallel_config() {
565        test_with_parallel_config!(
566            16,
567            500,
568            |config_service: &crate::config::TestConfigService| {
569                let config = config_service.get_config().unwrap();
570                assert_eq!(config.general.max_concurrent_jobs, 16);
571                assert_eq!(config.parallel.task_queue_size, 500);
572            }
573        );
574    }
575
576    #[test]
577    fn test_create_test_config_service_macro() {
578        create_test_config_service!(
579            service,
580            TestConfigBuilder::new().with_ai_provider("macro_test")
581        );
582
583        let config = service.get_config().unwrap();
584        assert_eq!(config.ai.provider, "macro_test");
585    }
586
587    #[test]
588    fn test_create_default_test_config_service_macro() {
589        create_default_test_config_service!(service);
590
591        let config = service.get_config().unwrap();
592        assert_eq!(config.ai.provider, "openai");
593    }
594}