use std::process::Command;
fn cha_binary() -> String {
let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.pop(); path.push("target/release/cha");
path.to_string_lossy().to_string()
}
fn fixture(name: &str) -> String {
let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("tests/fixtures");
path.push(name);
path.to_string_lossy().to_string()
}
fn run_analyze(file: &str, format: &str) -> (i32, String) {
let output = Command::new(cha_binary())
.args(["analyze", file, "--format", format])
.output()
.expect("failed to run cha");
let code = output.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
(code, stdout)
}
#[test]
fn smelly_file_detects_naming_convention() {
let (_, out) = run_analyze(&fixture("smelly.ts"), "terminal");
assert!(
out.contains("[naming_convention]"),
"expected naming_convention finding"
);
assert!(out.contains("badClass"), "expected badClass in output");
}
#[test]
fn smelly_file_detects_long_parameter_list() {
let (_, out) = run_analyze(&fixture("smelly.ts"), "terminal");
assert!(
out.contains("[long_parameter_list]"),
"expected long_parameter_list finding"
);
}
#[test]
fn smelly_file_detects_high_complexity() {
let (_, out) = run_analyze(&fixture("smelly.ts"), "terminal");
assert!(
out.contains("[high_complexity]"),
"expected high_complexity finding"
);
}
#[test]
fn clean_file_has_no_warnings() {
let (code, out) = run_analyze(&fixture("clean.ts"), "terminal");
assert!(!out.contains("⚠"), "expected no warnings");
assert!(!out.contains("✖"), "expected no errors");
assert_eq!(code, 0);
}
#[test]
fn json_output_is_valid() {
let (_, out) = run_analyze(&fixture("smelly.ts"), "json");
let parsed: serde_json::Value = serde_json::from_str(&out).expect("invalid JSON output");
assert!(parsed.is_object(), "JSON output should be an object");
let findings = parsed.get("findings").expect("missing findings key");
assert!(findings.is_array());
let arr = findings.as_array().unwrap();
assert!(!arr.is_empty(), "expected at least one finding");
let first = &arr[0];
assert!(first.get("smell_name").is_some());
assert!(first.get("severity").is_some());
assert!(first.get("message").is_some());
let scores = parsed
.get("health_scores")
.expect("missing health_scores key");
assert!(scores.is_array());
}
#[test]
fn sarif_output_is_valid() {
let (_, out) = run_analyze(&fixture("smelly.ts"), "sarif");
let parsed: serde_json::Value = serde_json::from_str(&out).expect("invalid SARIF output");
assert_eq!(parsed["version"], "2.1.0");
assert!(parsed["runs"].is_array());
}
#[test]
fn fail_on_warning_exits_nonzero() {
let output = Command::new(cha_binary())
.args(["analyze", &fixture("smelly.ts"), "--fail-on", "warning"])
.output()
.expect("failed to run cha");
assert_ne!(
output.status.code().unwrap_or(0),
0,
"expected nonzero exit"
);
}
#[test]
fn fail_on_error_exits_zero_for_warnings_only() {
let output = Command::new(cha_binary())
.args(["analyze", &fixture("clean.ts"), "--fail-on", "error"])
.output()
.expect("failed to run cha");
assert_eq!(output.status.code().unwrap_or(-1), 0);
}
#[test]
fn plugin_filter_limits_output() {
let output = Command::new(cha_binary())
.args([
"analyze",
&fixture("smelly.ts"),
"--plugin",
"naming",
"--format",
"json",
])
.output()
.expect("failed to run cha");
let out = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value = serde_json::from_str(&out).expect("invalid JSON");
let findings = parsed["findings"].as_array().expect("missing findings");
for f in findings {
let name = f["smell_name"].as_str().unwrap_or("");
assert!(
name.starts_with("naming"),
"unexpected finding from non-naming plugin: {name}"
);
}
}
#[test]
fn plugin_filter_unknown_plugin_produces_no_findings() {
let output = Command::new(cha_binary())
.args([
"analyze",
&fixture("smelly.ts"),
"--plugin",
"nonexistent_plugin_xyz",
"--format",
"json",
])
.output()
.expect("failed to run cha");
let out = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value = serde_json::from_str(&out).expect("invalid JSON");
let findings = parsed["findings"].as_array().expect("missing findings");
assert!(
findings.is_empty(),
"expected no findings for unknown plugin"
);
}
#[test]
fn llm_output_contains_findings_section() {
let (_, out) = run_analyze(&fixture("smelly.ts"), "llm");
assert!(
out.contains("finding") || out.contains("smell") || out.contains("issue"),
"LLM output missing expected content"
);
}
#[test]
fn clean_file_exits_zero_with_fail_on_warning() {
let output = Command::new(cha_binary())
.args(["analyze", &fixture("clean.ts"), "--fail-on", "warning"])
.output()
.expect("failed to run cha");
assert_eq!(output.status.code().unwrap_or(-1), 0);
}