mcp_langbase_reasoning/config/
mod.rs

1//! Configuration management for the MCP server.
2//!
3//! This module provides configuration structures loaded from environment variables.
4//! See [`Config::from_env`] for the main entry point.
5
6use std::env;
7use std::path::PathBuf;
8
9use tracing::{debug, warn};
10
11use crate::error::AppError;
12
13/// Application configuration loaded from environment variables.
14#[derive(Debug, Clone)]
15pub struct Config {
16    /// Langbase API configuration.
17    pub langbase: LangbaseConfig,
18    /// Database configuration.
19    pub database: DatabaseConfig,
20    /// Logging configuration.
21    pub logging: LoggingConfig,
22    /// HTTP request configuration.
23    pub request: RequestConfig,
24    /// Langbase pipe name configuration.
25    pub pipes: PipeConfig,
26    /// Error handling behavior configuration.
27    pub error_handling: ErrorHandlingConfig,
28}
29
30/// Error handling behavior configuration.
31///
32/// Note: This struct is now empty as all fallback patterns have been removed.
33/// The system now always uses strict error handling - all parse failures and
34/// API failures propagate as errors. This struct is kept for backward compatibility
35/// and potential future configuration options.
36#[derive(Debug, Clone, Default)]
37pub struct ErrorHandlingConfig {
38    // All fields removed - strict mode is now the only mode.
39    // Kept as empty struct for backward compatibility.
40}
41
42/// Langbase API configuration.
43#[derive(Debug, Clone)]
44pub struct LangbaseConfig {
45    /// API key for authentication.
46    pub api_key: String,
47    /// Base URL for the Langbase API.
48    pub base_url: String,
49}
50
51/// Database configuration.
52#[derive(Debug, Clone)]
53pub struct DatabaseConfig {
54    /// Path to the SQLite database file.
55    pub path: PathBuf,
56    /// Maximum number of database connections.
57    pub max_connections: u32,
58}
59
60/// Logging configuration.
61#[derive(Debug, Clone)]
62pub struct LoggingConfig {
63    /// Log level (e.g., "info", "debug", "warn").
64    pub level: String,
65    /// Log output format.
66    pub format: LogFormat,
67}
68
69/// Log output format.
70#[derive(Debug, Clone, PartialEq)]
71pub enum LogFormat {
72    /// Human-readable pretty format.
73    Pretty,
74    /// Machine-readable JSON format.
75    Json,
76}
77
78/// HTTP request configuration.
79#[derive(Debug, Clone)]
80pub struct RequestConfig {
81    /// Request timeout in milliseconds.
82    pub timeout_ms: u64,
83    /// Maximum number of retry attempts.
84    pub max_retries: u32,
85    /// Delay between retries in milliseconds.
86    pub retry_delay_ms: u64,
87}
88
89/// Langbase pipe name configuration.
90#[derive(Debug, Clone)]
91pub struct PipeConfig {
92    /// Pipe name for linear reasoning mode.
93    pub linear: String,
94    /// Pipe name for tree reasoning mode.
95    pub tree: String,
96    /// Pipe name for divergent reasoning mode.
97    pub divergent: String,
98    /// Pipe name for reflection mode.
99    pub reflection: String,
100    /// Pipe name for auto mode routing.
101    pub auto_router: String,
102    /// Optional pipe name for auto mode.
103    pub auto: Option<String>,
104    /// Optional pipe name for backtracking mode.
105    pub backtracking: Option<String>,
106    /// Optional Graph-of-Thoughts pipe configuration.
107    pub got: Option<GotPipeConfig>,
108    /// Optional detection pipe configuration.
109    pub detection: Option<DetectionPipeConfig>,
110    /// Optional decision framework pipe configuration.
111    pub decision: Option<DecisionPipeConfig>,
112    /// Optional evidence assessment pipe configuration.
113    pub evidence: Option<EvidencePipeConfig>,
114}
115
116/// Detection pipe configuration for bias and fallacy analysis.
117#[derive(Debug, Clone)]
118pub struct DetectionPipeConfig {
119    /// Consolidated pipe name for all detection operations (prompts passed dynamically).
120    pub pipe: Option<String>,
121}
122
123/// Graph-of-Thoughts pipe configuration.
124#[derive(Debug, Clone)]
125pub struct GotPipeConfig {
126    /// Consolidated pipe name for all GoT operations (prompts passed dynamically).
127    pub pipe: Option<String>,
128    /// Maximum number of nodes in the graph.
129    pub max_nodes: Option<usize>,
130    /// Maximum depth of the graph.
131    pub max_depth: Option<usize>,
132    /// Default number of continuations (k).
133    pub default_k: Option<usize>,
134    /// Score threshold for pruning nodes.
135    pub prune_threshold: Option<f64>,
136}
137
138/// Decision framework pipe configuration (consolidated - prompts passed dynamically).
139#[derive(Debug, Clone)]
140pub struct DecisionPipeConfig {
141    /// Consolidated pipe name for decision analysis operations.
142    pub pipe: Option<String>,
143}
144
145/// Evidence assessment pipe configuration (consolidated - prompts passed dynamically).
146#[derive(Debug, Clone)]
147pub struct EvidencePipeConfig {
148    /// Consolidated pipe name for evidence assessment operations.
149    pub pipe: Option<String>,
150}
151
152impl Config {
153    /// Load configuration from environment variables
154    pub fn from_env() -> Result<Self, AppError> {
155        // Load .env file if present, with discriminated error handling
156        match dotenvy::dotenv() {
157            Ok(path) => {
158                debug!(path = %path.display(), "Loaded .env file");
159            }
160            Err(dotenvy::Error::Io(ref e)) if e.kind() == std::io::ErrorKind::NotFound => {
161                // .env file not found - this is normal, use environment variables
162                debug!("No .env file found, using environment variables");
163            }
164            Err(e) => {
165                warn!(
166                    error = %e,
167                    "Failed to load .env file - check file permissions and syntax"
168                );
169            }
170        }
171
172        let langbase = LangbaseConfig {
173            api_key: env::var("LANGBASE_API_KEY").map_err(|_| AppError::Config {
174                message: "LANGBASE_API_KEY is required".to_string(),
175            })?,
176            base_url: env::var("LANGBASE_BASE_URL")
177                .unwrap_or_else(|_| "https://api.langbase.com".to_string()),
178        };
179
180        let database = DatabaseConfig {
181            path: PathBuf::from(
182                env::var("DATABASE_PATH").unwrap_or_else(|_| "./data/reasoning.db".to_string()),
183            ),
184            max_connections: env::var("DATABASE_MAX_CONNECTIONS")
185                .ok()
186                .and_then(|s| s.parse().ok())
187                .unwrap_or(5),
188        };
189
190        let logging = LoggingConfig {
191            level: env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string()),
192            format: match env::var("LOG_FORMAT")
193                .unwrap_or_else(|_| "pretty".to_string())
194                .to_lowercase()
195                .as_str()
196            {
197                "json" => LogFormat::Json,
198                _ => LogFormat::Pretty,
199            },
200        };
201
202        let request = RequestConfig {
203            timeout_ms: env::var("REQUEST_TIMEOUT_MS")
204                .ok()
205                .and_then(|s| s.parse().ok())
206                .unwrap_or(30000),
207            max_retries: env::var("MAX_RETRIES")
208                .ok()
209                .and_then(|s| s.parse().ok())
210                .unwrap_or(3),
211            retry_delay_ms: env::var("RETRY_DELAY_MS")
212                .ok()
213                .and_then(|s| s.parse().ok())
214                .unwrap_or(1000),
215        };
216
217        // Build GoT pipe config if any GoT env vars are set
218        let got_config = {
219            let pipe = env::var("PIPE_GOT").ok();
220            let max_nodes = env::var("GOT_MAX_NODES").ok().and_then(|s| s.parse().ok());
221            let max_depth = env::var("GOT_MAX_DEPTH").ok().and_then(|s| s.parse().ok());
222            let default_k = env::var("GOT_DEFAULT_K").ok().and_then(|s| s.parse().ok());
223            let prune_threshold = env::var("GOT_PRUNE_THRESHOLD")
224                .ok()
225                .and_then(|s| s.parse().ok());
226
227            // Only create config if any value is set
228            if pipe.is_some()
229                || max_nodes.is_some()
230                || max_depth.is_some()
231                || default_k.is_some()
232                || prune_threshold.is_some()
233            {
234                Some(GotPipeConfig {
235                    pipe,
236                    max_nodes,
237                    max_depth,
238                    default_k,
239                    prune_threshold,
240                })
241            } else {
242                None
243            }
244        };
245
246        // Detection pipe config - read from env var (filter empty strings)
247        let detection_pipe_env = env::var("PIPE_DETECTION").ok().filter(|s| !s.is_empty());
248        debug!(
249            pipe_detection_env = ?detection_pipe_env,
250            "Loading PIPE_DETECTION from environment"
251        );
252        let detection_config = Some(DetectionPipeConfig {
253            pipe: detection_pipe_env,
254        });
255
256        // Decision pipe config - read from env var (filter empty strings)
257        let decision_pipe_env = env::var("PIPE_DECISION_FRAMEWORK")
258            .ok()
259            .filter(|s| !s.is_empty());
260        debug!(
261            pipe_decision_env = ?decision_pipe_env,
262            "Loading PIPE_DECISION_FRAMEWORK from environment"
263        );
264        let decision_config = Some(DecisionPipeConfig {
265            pipe: decision_pipe_env.clone(),
266        });
267
268        // Evidence pipe config - uses same env var as decision
269        let evidence_config = Some(EvidencePipeConfig {
270            pipe: decision_pipe_env,
271        });
272
273        let pipes = PipeConfig {
274            linear: env::var("PIPE_LINEAR").unwrap_or_else(|_| "linear-reasoning-v1".to_string()),
275            tree: env::var("PIPE_TREE").unwrap_or_else(|_| "tree-reasoning-v1".to_string()),
276            divergent: env::var("PIPE_DIVERGENT")
277                .unwrap_or_else(|_| "divergent-reasoning-v1".to_string()),
278            reflection: env::var("PIPE_REFLECTION").unwrap_or_else(|_| "reflection-v1".to_string()),
279            auto_router: env::var("PIPE_AUTO").unwrap_or_else(|_| "mode-router-v1".to_string()),
280            auto: env::var("PIPE_AUTO").ok(),
281            backtracking: env::var("PIPE_BACKTRACKING").ok(),
282            got: got_config,
283            detection: detection_config,
284            decision: decision_config,
285            evidence: evidence_config,
286        };
287
288        // Error handling configuration (now empty - strict mode is always on)
289        let error_handling = ErrorHandlingConfig::default();
290        debug!("Strict error handling enabled - all parse/API failures propagate as errors");
291
292        Ok(Config {
293            langbase,
294            database,
295            logging,
296            request,
297            pipes,
298            error_handling,
299        })
300    }
301}
302
303impl Default for RequestConfig {
304    fn default() -> Self {
305        Self {
306            timeout_ms: 30000,
307            max_retries: 3,
308            retry_delay_ms: 1000,
309        }
310    }
311}
312
313impl Default for PipeConfig {
314    fn default() -> Self {
315        Self {
316            linear: "linear-reasoning-v1".to_string(),
317            tree: "tree-reasoning-v1".to_string(),
318            divergent: "divergent-reasoning-v1".to_string(),
319            reflection: "reflection-v1".to_string(),
320            auto_router: "mode-router-v1".to_string(),
321            auto: None,
322            backtracking: None,
323            got: None,
324            detection: None,
325            decision: None,
326            evidence: None,
327        }
328    }
329}
330
331impl Default for DetectionPipeConfig {
332    fn default() -> Self {
333        Self {
334            pipe: Some("detection-v1".to_string()),
335        }
336    }
337}
338
339impl Default for GotPipeConfig {
340    fn default() -> Self {
341        Self {
342            pipe: Some("got-reasoning-v1".to_string()),
343            max_nodes: Some(100),
344            max_depth: Some(10),
345            default_k: Some(3),
346            prune_threshold: Some(0.3),
347        }
348    }
349}
350
351impl Default for DecisionPipeConfig {
352    fn default() -> Self {
353        Self {
354            pipe: Some("decision-framework-v1".to_string()),
355        }
356    }
357}
358
359impl Default for EvidencePipeConfig {
360    fn default() -> Self {
361        Self {
362            pipe: Some("decision-framework-v1".to_string()),
363        }
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370
371    #[test]
372    fn test_request_config_default() {
373        let config = RequestConfig::default();
374        assert_eq!(config.timeout_ms, 30000);
375        assert_eq!(config.max_retries, 3);
376        assert_eq!(config.retry_delay_ms, 1000);
377    }
378
379    #[test]
380    fn test_pipe_config_default() {
381        let config = PipeConfig::default();
382        assert_eq!(config.linear, "linear-reasoning-v1");
383        assert_eq!(config.tree, "tree-reasoning-v1");
384        assert_eq!(config.divergent, "divergent-reasoning-v1");
385        assert_eq!(config.reflection, "reflection-v1");
386        assert_eq!(config.auto_router, "mode-router-v1");
387        assert!(config.auto.is_none());
388        assert!(config.backtracking.is_none());
389        assert!(config.got.is_none());
390        assert!(config.detection.is_none());
391        assert!(config.decision.is_none());
392        assert!(config.evidence.is_none());
393    }
394
395    #[test]
396    fn test_detection_pipe_config_default() {
397        let config = DetectionPipeConfig::default();
398        assert_eq!(config.pipe, Some("detection-v1".to_string()));
399    }
400
401    #[test]
402    fn test_got_pipe_config_default() {
403        let config = GotPipeConfig::default();
404        assert_eq!(config.pipe, Some("got-reasoning-v1".to_string()));
405        assert_eq!(config.max_nodes, Some(100));
406        assert_eq!(config.max_depth, Some(10));
407        assert_eq!(config.default_k, Some(3));
408        assert_eq!(config.prune_threshold, Some(0.3));
409    }
410
411    #[test]
412    fn test_log_format_variants() {
413        assert_eq!(LogFormat::Pretty, LogFormat::Pretty);
414        assert_eq!(LogFormat::Json, LogFormat::Json);
415        assert_ne!(LogFormat::Pretty, LogFormat::Json);
416    }
417
418    #[test]
419    fn test_decision_pipe_config_default() {
420        let config = DecisionPipeConfig::default();
421        assert_eq!(config.pipe, Some("decision-framework-v1".to_string()));
422    }
423
424    #[test]
425    fn test_evidence_pipe_config_default() {
426        let config = EvidencePipeConfig::default();
427        assert_eq!(config.pipe, Some("decision-framework-v1".to_string()));
428    }
429
430    // Note: Config::from_env() tests are in tests/config_env_test.rs
431    // because they require serial execution and full env var control.
432    // Unit tests here focus on Default impls and type behavior.
433
434    #[test]
435    fn test_database_config_struct() {
436        let config = DatabaseConfig {
437            path: PathBuf::from("/test/path.db"),
438            max_connections: 10,
439        };
440        assert_eq!(config.path, PathBuf::from("/test/path.db"));
441        assert_eq!(config.max_connections, 10);
442    }
443
444    #[test]
445    fn test_langbase_config_struct() {
446        let config = LangbaseConfig {
447            api_key: "test-key".to_string(),
448            base_url: "https://test.api.com".to_string(),
449        };
450        assert_eq!(config.api_key, "test-key");
451        assert_eq!(config.base_url, "https://test.api.com");
452    }
453
454    #[test]
455    fn test_logging_config_struct() {
456        let config_pretty = LoggingConfig {
457            level: "debug".to_string(),
458            format: LogFormat::Pretty,
459        };
460        assert_eq!(config_pretty.level, "debug");
461        assert_eq!(config_pretty.format, LogFormat::Pretty);
462
463        let config_json = LoggingConfig {
464            level: "info".to_string(),
465            format: LogFormat::Json,
466        };
467        assert_eq!(config_json.level, "info");
468        assert_eq!(config_json.format, LogFormat::Json);
469    }
470
471    #[test]
472    fn test_request_config_struct() {
473        let config = RequestConfig {
474            timeout_ms: 60000,
475            max_retries: 5,
476            retry_delay_ms: 2000,
477        };
478        assert_eq!(config.timeout_ms, 60000);
479        assert_eq!(config.max_retries, 5);
480        assert_eq!(config.retry_delay_ms, 2000);
481    }
482
483    #[test]
484    fn test_pipe_config_struct_all_fields() {
485        let config = PipeConfig {
486            linear: "linear-v1".to_string(),
487            tree: "tree-v1".to_string(),
488            divergent: "divergent-v1".to_string(),
489            reflection: "reflection-v1".to_string(),
490            auto_router: "router-v1".to_string(),
491            auto: Some("auto-v1".to_string()),
492            backtracking: Some("backtrack-v1".to_string()),
493            got: Some(GotPipeConfig::default()),
494            detection: Some(DetectionPipeConfig::default()),
495            decision: Some(DecisionPipeConfig::default()),
496            evidence: Some(EvidencePipeConfig::default()),
497        };
498
499        assert_eq!(config.linear, "linear-v1");
500        assert_eq!(config.tree, "tree-v1");
501        assert_eq!(config.divergent, "divergent-v1");
502        assert_eq!(config.reflection, "reflection-v1");
503        assert_eq!(config.auto_router, "router-v1");
504        assert_eq!(config.auto, Some("auto-v1".to_string()));
505        assert_eq!(config.backtracking, Some("backtrack-v1".to_string()));
506        assert!(config.got.is_some());
507        assert!(config.detection.is_some());
508        assert!(config.decision.is_some());
509        assert!(config.evidence.is_some());
510    }
511
512    #[test]
513    fn test_detection_pipe_config_struct() {
514        let config = DetectionPipeConfig {
515            pipe: Some("detection-v1".to_string()),
516        };
517        assert_eq!(config.pipe, Some("detection-v1".to_string()));
518    }
519
520    #[test]
521    fn test_detection_pipe_config_none_values() {
522        let config = DetectionPipeConfig { pipe: None };
523        assert!(config.pipe.is_none());
524    }
525
526    #[test]
527    fn test_got_pipe_config_struct_all_fields() {
528        let config = GotPipeConfig {
529            pipe: Some("got-reasoning-v1".to_string()),
530            max_nodes: Some(50),
531            max_depth: Some(5),
532            default_k: Some(2),
533            prune_threshold: Some(0.5),
534        };
535
536        assert_eq!(config.pipe, Some("got-reasoning-v1".to_string()));
537        assert_eq!(config.max_nodes, Some(50));
538        assert_eq!(config.max_depth, Some(5));
539        assert_eq!(config.default_k, Some(2));
540        assert_eq!(config.prune_threshold, Some(0.5));
541    }
542
543    #[test]
544    fn test_got_pipe_config_none_values() {
545        let config = GotPipeConfig {
546            pipe: None,
547            max_nodes: None,
548            max_depth: None,
549            default_k: None,
550            prune_threshold: None,
551        };
552
553        assert!(config.pipe.is_none());
554        assert!(config.max_nodes.is_none());
555        assert!(config.max_depth.is_none());
556        assert!(config.default_k.is_none());
557        assert!(config.prune_threshold.is_none());
558    }
559
560    #[test]
561    fn test_decision_pipe_config_struct() {
562        let config = DecisionPipeConfig {
563            pipe: Some("decision-framework-v1".to_string()),
564        };
565        assert_eq!(config.pipe, Some("decision-framework-v1".to_string()));
566    }
567
568    #[test]
569    fn test_decision_pipe_config_none_values() {
570        let config = DecisionPipeConfig { pipe: None };
571        assert!(config.pipe.is_none());
572    }
573
574    #[test]
575    fn test_evidence_pipe_config_struct() {
576        let config = EvidencePipeConfig {
577            pipe: Some("decision-framework-v1".to_string()),
578        };
579        assert_eq!(config.pipe, Some("decision-framework-v1".to_string()));
580    }
581
582    #[test]
583    fn test_evidence_pipe_config_none_values() {
584        let config = EvidencePipeConfig { pipe: None };
585        assert!(config.pipe.is_none());
586    }
587
588    #[test]
589    fn test_config_struct_clone() {
590        let config = RequestConfig::default();
591        let cloned = config.clone();
592        assert_eq!(config.timeout_ms, cloned.timeout_ms);
593        assert_eq!(config.max_retries, cloned.max_retries);
594        assert_eq!(config.retry_delay_ms, cloned.retry_delay_ms);
595    }
596
597    #[test]
598    fn test_pipe_config_clone() {
599        let config = PipeConfig::default();
600        let cloned = config.clone();
601        assert_eq!(config.linear, cloned.linear);
602        assert_eq!(config.tree, cloned.tree);
603        assert_eq!(config.divergent, cloned.divergent);
604    }
605
606    #[test]
607    fn test_log_format_debug() {
608        let pretty = LogFormat::Pretty;
609        let json = LogFormat::Json;
610        assert!(format!("{:?}", pretty).contains("Pretty"));
611        assert!(format!("{:?}", json).contains("Json"));
612    }
613
614    #[test]
615    fn test_database_config_debug() {
616        let config = DatabaseConfig {
617            path: PathBuf::from("/test.db"),
618            max_connections: 5,
619        };
620        let debug_str = format!("{:?}", config);
621        assert!(debug_str.contains("DatabaseConfig"));
622        assert!(debug_str.contains("test.db"));
623    }
624
625    #[test]
626    fn test_langbase_config_debug() {
627        let config = LangbaseConfig {
628            api_key: "key123".to_string(),
629            base_url: "https://api.test.com".to_string(),
630        };
631        let debug_str = format!("{:?}", config);
632        assert!(debug_str.contains("LangbaseConfig"));
633        assert!(debug_str.contains("key123"));
634    }
635
636    #[test]
637    fn test_got_pipe_config_default_values() {
638        let config = GotPipeConfig::default();
639
640        // Verify consolidated pipe has expected default value
641        assert_eq!(config.pipe.as_deref(), Some("got-reasoning-v1"));
642
643        // Verify all numeric fields have expected default values
644        assert_eq!(config.max_nodes, Some(100));
645        assert_eq!(config.max_depth, Some(10));
646        assert_eq!(config.default_k, Some(3));
647        assert_eq!(config.prune_threshold, Some(0.3));
648    }
649
650    #[test]
651    fn test_detection_pipe_config_default_values() {
652        let config = DetectionPipeConfig::default();
653        assert_eq!(config.pipe.as_deref(), Some("detection-v1"));
654    }
655
656    #[test]
657    fn test_decision_pipe_config_default_values() {
658        let config = DecisionPipeConfig::default();
659        assert_eq!(config.pipe.as_deref(), Some("decision-framework-v1"));
660    }
661
662    #[test]
663    fn test_evidence_pipe_config_default_values() {
664        let config = EvidencePipeConfig::default();
665        assert_eq!(config.pipe.as_deref(), Some("decision-framework-v1"));
666    }
667
668    #[test]
669    fn test_request_config_default_values() {
670        let config = RequestConfig::default();
671
672        // Verify all fields have expected defaults
673        assert_eq!(config.timeout_ms, 30000);
674        assert_eq!(config.max_retries, 3);
675        assert_eq!(config.retry_delay_ms, 1000);
676    }
677
678    #[test]
679    fn test_pipe_config_default_values() {
680        let config = PipeConfig::default();
681
682        // Verify all required string fields
683        assert_eq!(config.linear, "linear-reasoning-v1");
684        assert_eq!(config.tree, "tree-reasoning-v1");
685        assert_eq!(config.divergent, "divergent-reasoning-v1");
686        assert_eq!(config.reflection, "reflection-v1");
687        assert_eq!(config.auto_router, "mode-router-v1");
688
689        // Verify all optional fields are None by default
690        assert!(config.auto.is_none());
691        assert!(config.backtracking.is_none());
692        assert!(config.got.is_none());
693        assert!(config.detection.is_none());
694        assert!(config.decision.is_none());
695        assert!(config.evidence.is_none());
696    }
697
698    #[test]
699    fn test_log_format_clone() {
700        let original = LogFormat::Pretty;
701        let cloned = original.clone();
702        assert_eq!(original, cloned);
703
704        let original_json = LogFormat::Json;
705        let cloned_json = original_json.clone();
706        assert_eq!(original_json, cloned_json);
707    }
708
709    #[test]
710    fn test_detection_pipe_config_clone() {
711        let config = DetectionPipeConfig::default();
712        let cloned = config.clone();
713        assert_eq!(config.pipe, cloned.pipe);
714    }
715
716    #[test]
717    fn test_got_pipe_config_clone() {
718        let config = GotPipeConfig::default();
719        let cloned = config.clone();
720        assert_eq!(config.pipe, cloned.pipe);
721        assert_eq!(config.max_nodes, cloned.max_nodes);
722        assert_eq!(config.prune_threshold, cloned.prune_threshold);
723    }
724
725    #[test]
726    fn test_decision_pipe_config_clone() {
727        let config = DecisionPipeConfig::default();
728        let cloned = config.clone();
729        assert_eq!(config.pipe, cloned.pipe);
730    }
731
732    #[test]
733    fn test_evidence_pipe_config_clone() {
734        let config = EvidencePipeConfig::default();
735        let cloned = config.clone();
736        assert_eq!(config.pipe, cloned.pipe);
737    }
738
739    #[test]
740    fn test_database_config_clone() {
741        let config = DatabaseConfig {
742            path: PathBuf::from("/test.db"),
743            max_connections: 10,
744        };
745        let cloned = config.clone();
746        assert_eq!(config.path, cloned.path);
747        assert_eq!(config.max_connections, cloned.max_connections);
748    }
749
750    #[test]
751    fn test_langbase_config_clone() {
752        let config = LangbaseConfig {
753            api_key: "test-key".to_string(),
754            base_url: "https://test.com".to_string(),
755        };
756        let cloned = config.clone();
757        assert_eq!(config.api_key, cloned.api_key);
758        assert_eq!(config.base_url, cloned.base_url);
759    }
760
761    #[test]
762    fn test_logging_config_clone() {
763        let config = LoggingConfig {
764            level: "debug".to_string(),
765            format: LogFormat::Pretty,
766        };
767        let cloned = config.clone();
768        assert_eq!(config.level, cloned.level);
769        assert_eq!(config.format, cloned.format);
770    }
771
772    #[test]
773    fn test_request_config_clone() {
774        let config = RequestConfig {
775            timeout_ms: 5000,
776            max_retries: 2,
777            retry_delay_ms: 500,
778        };
779        let cloned = config.clone();
780        assert_eq!(config.timeout_ms, cloned.timeout_ms);
781        assert_eq!(config.max_retries, cloned.max_retries);
782        assert_eq!(config.retry_delay_ms, cloned.retry_delay_ms);
783    }
784
785    // Tests for ErrorHandlingConfig (now empty - strict mode is always on)
786
787    #[test]
788    fn test_error_handling_config_default() {
789        let _config = ErrorHandlingConfig::default();
790        // Config is now empty - strict mode is the only mode
791    }
792
793    #[test]
794    fn test_error_handling_config_clone() {
795        let config = ErrorHandlingConfig::default();
796        let _cloned = config.clone();
797        // Empty struct can be cloned
798    }
799
800    #[test]
801    fn test_error_handling_config_debug() {
802        let config = ErrorHandlingConfig::default();
803        let debug_str = format!("{:?}", config);
804        assert!(debug_str.contains("ErrorHandlingConfig"));
805    }
806}