use anyhow::Result;
use serde_json::{json, Value};
use std::path::PathBuf;
use tempfile::TempDir;
use tokio;
struct ConfigTestFixture {
temp_dir: TempDir,
config_path: PathBuf,
}
impl ConfigTestFixture {
fn new() -> Result<Self> {
let temp_dir = tempfile::tempdir()?;
let config_path = temp_dir.path().join("pmat.toml");
let sample_config = r#"
[hooks]
enabled = true
auto_install = true
[hooks.quality_gates]
max_cyclomatic_complexity = 30
max_cognitive_complexity = 25
max_satd_comments = 5
min_test_coverage = 80.0
max_clippy_warnings = 100
[hooks.documentation]
required_files = [
"docs/execution/roadmap.md",
"CHANGELOG.md"
]
task_id_pattern = "PMAT-[0-9]{4}"
"#;
std::fs::write(&config_path, sample_config)?;
Ok(Self {
temp_dir,
config_path,
})
}
fn config_path(&self) -> &PathBuf {
&self.config_path
}
}
struct ConfigCommand {
config_path: PathBuf,
}
impl ConfigCommand {
fn new(config_path: PathBuf) -> Self {
Self { config_path }
}
async fn show(&self, format: ConfigFormat) -> Result<String> {
todo!("Implement config show command")
}
async fn get(&self, key: &str) -> Result<String> {
todo!("Implement config get command")
}
async fn validate(&self) -> Result<ValidationResult> {
todo!("Implement config validate command")
}
}
#[derive(Debug, Clone)]
enum ConfigFormat {
Json,
Toml,
Env,
}
#[derive(Debug, PartialEq)]
struct ValidationResult {
is_valid: bool,
errors: Vec<String>,
warnings: Vec<String>,
}
#[tokio::test]
async fn test_config_show_json_format() -> Result<()> {
let fixture = ConfigTestFixture::new()?;
let config_cmd = ConfigCommand::new(fixture.config_path().clone());
let result = config_cmd.show(ConfigFormat::Json).await?;
let parsed: Value = serde_json::from_str(&result)?;
assert_eq!(parsed["hooks"]["enabled"], true);
assert_eq!(parsed["hooks"]["auto_install"], true);
assert_eq!(
parsed["hooks"]["quality_gates"]["max_cyclomatic_complexity"],
30
);
assert_eq!(
parsed["hooks"]["quality_gates"]["max_cognitive_complexity"],
25
);
assert_eq!(parsed["hooks"]["quality_gates"]["max_satd_comments"], 5);
assert_eq!(parsed["hooks"]["quality_gates"]["min_test_coverage"], 80.0);
let required_files = parsed["hooks"]["documentation"]["required_files"]
.as_array()
.unwrap();
assert!(required_files.contains(&json!("docs/execution/roadmap.md")));
assert!(required_files.contains(&json!("CHANGELOG.md")));
assert_eq!(
parsed["hooks"]["documentation"]["task_id_pattern"],
"PMAT-[0-9]{4}"
);
Ok(())
}
#[tokio::test]
async fn test_config_show_toml_format() -> Result<()> {
let fixture = ConfigTestFixture::new()?;
let config_cmd = ConfigCommand::new(fixture.config_path().clone());
let result = config_cmd.show(ConfigFormat::Toml).await?;
assert!(result.contains("[hooks]"));
assert!(result.contains("enabled = true"));
assert!(result.contains("auto_install = true"));
assert!(result.contains("[hooks.quality_gates]"));
assert!(result.contains("max_cyclomatic_complexity = 30"));
assert!(result.contains("max_cognitive_complexity = 25"));
Ok(())
}
#[tokio::test]
async fn test_config_show_env_format() -> Result<()> {
let fixture = ConfigTestFixture::new()?;
let config_cmd = ConfigCommand::new(fixture.config_path().clone());
let result = config_cmd.show(ConfigFormat::Env).await?;
assert!(result.contains("PMAT_HOOKS_ENABLED=true"));
assert!(result.contains("PMAT_HOOKS_AUTO_INSTALL=true"));
assert!(result.contains("PMAT_MAX_CYCLOMATIC_COMPLEXITY=30"));
assert!(result.contains("PMAT_MAX_COGNITIVE_COMPLEXITY=25"));
assert!(result.contains("PMAT_MAX_SATD_COMMENTS=5"));
assert!(result.contains("PMAT_MIN_TEST_COVERAGE=80"));
Ok(())
}
#[tokio::test]
async fn test_config_get_specific_values() -> Result<()> {
let fixture = ConfigTestFixture::new()?;
let config_cmd = ConfigCommand::new(fixture.config_path().clone());
let max_complexity = config_cmd
.get("hooks.quality_gates.max_cyclomatic_complexity")
.await?;
assert_eq!(max_complexity, "30");
let auto_install = config_cmd.get("hooks.auto_install").await?;
assert_eq!(auto_install, "true");
let coverage = config_cmd
.get("hooks.quality_gates.min_test_coverage")
.await?;
assert_eq!(coverage, "80.0");
let pattern = config_cmd
.get("hooks.documentation.task_id_pattern")
.await?;
assert_eq!(pattern, "PMAT-[0-9]{4}");
Ok(())
}
#[tokio::test]
async fn test_config_get_nonexistent_key() -> Result<()> {
let fixture = ConfigTestFixture::new()?;
let config_cmd = ConfigCommand::new(fixture.config_path().clone());
let result = config_cmd.get("nonexistent.key.path").await;
assert!(result.is_err());
Ok(())
}
#[tokio::test]
async fn test_config_validate_valid_config() -> Result<()> {
let fixture = ConfigTestFixture::new()?;
let config_cmd = ConfigCommand::new(fixture.config_path().clone());
let result = config_cmd.validate().await?;
assert_eq!(result.is_valid, true);
assert!(result.errors.is_empty());
Ok(())
}
#[tokio::test]
async fn test_config_validate_invalid_config() -> Result<()> {
let temp_dir = tempfile::tempdir()?;
let invalid_config_path = temp_dir.path().join("invalid.toml");
let invalid_config = r#"
[hooks]
# Missing required quality_gates section
enabled = true
[hooks.documentation]
# Invalid pattern
task_id_pattern = "[invalid regex"
"#;
std::fs::write(&invalid_config_path, invalid_config)?;
let config_cmd = ConfigCommand::new(invalid_config_path);
let result = config_cmd.validate().await?;
assert_eq!(result.is_valid, false);
assert!(!result.errors.is_empty());
Ok(())
}
#[tokio::test]
async fn test_config_missing_file() -> Result<()> {
let nonexistent_path = PathBuf::from("/nonexistent/config.toml");
let config_cmd = ConfigCommand::new(nonexistent_path);
let result = config_cmd.show(ConfigFormat::Json).await;
assert!(result.is_err());
Ok(())
}
#[tokio::test]
async fn test_config_performance_requirements() -> Result<()> {
let fixture = ConfigTestFixture::new()?;
let config_cmd = ConfigCommand::new(fixture.config_path().clone());
let start = std::time::Instant::now();
let _result = config_cmd.show(ConfigFormat::Json).await?;
let elapsed = start.elapsed();
assert!(
elapsed.as_millis() < 100,
"Config loading took {}ms (should be <100ms)",
elapsed.as_millis()
);
Ok(())
}
#[tokio::test]
async fn test_config_format_roundtrip() -> Result<()> {
let fixture = ConfigTestFixture::new()?;
let config_cmd = ConfigCommand::new(fixture.config_path().clone());
let original_toml = config_cmd.show(ConfigFormat::Toml).await?;
let json_version = config_cmd.show(ConfigFormat::Json).await?;
let original_parsed: toml::Value = toml::from_str(&original_toml)?;
let json_parsed: serde_json::Value = serde_json::from_str(&json_version)?;
assert_eq!(
original_parsed["hooks"]["quality_gates"]["max_cyclomatic_complexity"]
.as_integer()
.unwrap(),
json_parsed["hooks"]["quality_gates"]["max_cyclomatic_complexity"]
.as_i64()
.unwrap()
);
assert_eq!(
original_parsed["hooks"]["enabled"].as_bool().unwrap(),
json_parsed["hooks"]["enabled"].as_bool().unwrap()
);
Ok(())
}
#[tokio::test]
async fn test_extract_config_error_handler_max_complexity() -> Result<()> {
let temp_dir = tempfile::tempdir()?;
let config_path = temp_dir.path().join("invalid.toml");
let invalid_config = r#"
[quality]
max_complexity = 0 # Invalid: must be > 0
"#;
std::fs::write(&config_path, invalid_config)?;
let errors = vec!["max_complexity must be > 0".to_string()];
let result = extract_config_error_handler(&errors[0]);
assert!(result.is_some());
let fix_info = result.unwrap();
assert_eq!(fix_info.field_name, "quality.max_complexity");
assert_eq!(fix_info.new_value, "20");
assert_eq!(fix_info.description, "Set max_complexity to 20");
Ok(())
}
#[tokio::test]
async fn test_extract_config_error_handler_min_coverage() -> Result<()> {
let errors = vec!["min_coverage must be between 0 and 100".to_string()];
let result = extract_config_error_handler(&errors[0]);
assert!(result.is_some());
let fix_info = result.unwrap();
assert_eq!(fix_info.field_name, "quality.min_coverage");
assert!(fix_info.description.contains("Clamped min_coverage"));
Ok(())
}
#[tokio::test]
async fn test_extract_config_error_handler_unknown_error() -> Result<()> {
let unknown_error = "some unknown config error";
let result = extract_config_error_handler(unknown_error);
assert!(result.is_none());
Ok(())
}
#[tokio::test]
async fn test_apply_config_fixes_complexity_10() -> Result<()> {
let temp_dir = tempfile::tempdir()?;
let config_path = temp_dir.path().join("test.toml");
let config_content = r#"
[quality]
max_complexity = 0
min_coverage = 150.0
"#;
std::fs::write(&config_path, config_content)?;
let errors = vec![
"max_complexity must be > 0".to_string(),
"min_coverage must be between 0 and 100".to_string(),
];
let fixed_issues = apply_config_fixes(&errors).await?;
assert_eq!(fixed_issues.len(), 2);
assert!(fixed_issues.contains(&"Set max_complexity to 20".to_string()));
assert!(fixed_issues.iter().any(|fix| fix.contains("Clamped min_coverage")));
Ok(())
}
#[tokio::test]
async fn test_save_config_changes_complexity_10() -> Result<()> {
let temp_dir = tempfile::tempdir()?;
let config_path = temp_dir.path().join("test.toml");
let original_config = r#"
[quality]
max_complexity = 0
"#;
std::fs::write(&config_path, original_config)?;
let fixed_issues = vec!["Set max_complexity to 20".to_string()];
let result = save_config_changes(&config_path, &fixed_issues).await;
assert!(result.is_ok());
let updated_content = std::fs::read_to_string(&config_path)?;
assert!(updated_content.contains("max_complexity = 20") ||
updated_content.contains("fixed configuration"));
Ok(())
}
#[derive(Debug, PartialEq)]
struct ConfigFixInfo {
field_name: String,
new_value: String,
description: String,
}
fn extract_config_error_handler(error_msg: &str) -> Option<ConfigFixInfo> {
if error_msg.contains("max_complexity must be > 0") {
return Some(ConfigFixInfo {
field_name: "quality.max_complexity".to_string(),
new_value: "20".to_string(),
description: "Set max_complexity to 20".to_string(),
});
}
if error_msg.contains("min_coverage must be between 0 and 100") {
return Some(ConfigFixInfo {
field_name: "quality.min_coverage".to_string(),
new_value: "clamp(0.0, 100.0)".to_string(),
description: "Clamped min_coverage to valid range".to_string(),
});
}
None
}
async fn apply_config_fixes(errors: &[String]) -> Result<Vec<String>> {
let mut fixed_issues = Vec::new();
for error in errors {
if let Some(fix_info) = extract_config_error_handler(error) {
fixed_issues.push(fix_info.description);
}
}
Ok(fixed_issues)
}
async fn save_config_changes(config_path: &std::path::Path, fixed_issues: &[String]) -> Result<()> {
if fixed_issues.is_empty() {
return Ok(());
}
let mut content = std::fs::read_to_string(config_path)?;
if fixed_issues.iter().any(|fix| fix.contains("max_complexity")) {
content = content.replace("max_complexity = 0", "max_complexity = 20");
}
std::fs::write(config_path, content)?;
Ok(())
}
#[tokio::test]
async fn test_config_integration_with_quality_gates() -> Result<()> {
let fixture = ConfigTestFixture::new()?;
let config_cmd = ConfigCommand::new(fixture.config_path().clone());
let max_complexity = config_cmd
.get("hooks.quality_gates.max_cyclomatic_complexity")
.await?;
let max_cognitive = config_cmd
.get("hooks.quality_gates.max_cognitive_complexity")
.await?;
let complexity_threshold: u32 = max_complexity.parse()?;
let cognitive_threshold: u32 = max_cognitive.parse()?;
assert_eq!(complexity_threshold, 30);
assert_eq!(cognitive_threshold, 25);
assert!(
complexity_threshold >= 20,
"Cyclomatic threshold should be enterprise-standard (≥20)"
);
assert!(
cognitive_threshold >= 20,
"Cognitive threshold should be enterprise-standard (≥20)"
);
Ok(())
}