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}