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