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}