use std::path::{Path, PathBuf};
use killer::analyzer::{Analyzer, Category, Finding, Rule, Severity};
use killer::config::Config;
use killer::report::Report;
use killer::results::Verdict;
use killer::scanner::{self, FileData, Language};
fn fixture(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join(name)
}
fn analyze(dir: &Path) -> (killer::scanner::ScanResult, Vec<Finding>) {
let config = Config::load(dir).expect("config loads");
let scan = scanner::scan(dir, &config);
let findings = Analyzer::with_default_rules(&config).analyze(&scan);
(scan, findings)
}
#[test]
fn scanner_collects_files_and_languages() {
let (scan, _) = analyze(&fixture("vulnerable_project"));
assert!(scan.stats.files >= 2, "should find the fixture files");
assert!(scan.stats.lines_of_code > 0);
assert!(scan.stats.languages.contains(&"JavaScript".to_string()));
assert!(scan.stats.languages.contains(&"Python".to_string()));
}
#[test]
fn vulnerable_project_triggers_security_rules() {
let (_, findings) = analyze(&fixture("vulnerable_project"));
assert!(
findings.iter().any(|f| f.rule == "hardcoded-secret"),
"expected a hardcoded-secret finding"
);
assert!(
findings.iter().any(|f| f.rule == "dangerous-command"),
"expected a dangerous-command finding"
);
assert!(findings
.iter()
.any(|f| f.category == Category::Security && f.severity <= Severity::High));
}
#[test]
fn vulnerable_project_tracks_todo_and_fixme() {
let (_, findings) = analyze(&fixture("vulnerable_project"));
assert!(
findings.iter().any(|f| f.rule == "todo-tracker"),
"expected TODO/FIXME markers to be tracked"
);
}
#[test]
fn clean_project_has_no_blocking_issues() {
let dir = fixture("clean_project");
let (scan, findings) = analyze(&dir);
let report = Report::new("clean".into(), scan.stats, findings);
assert!(
!report.has_blocking_issues(),
"clean project should have no critical/high issues, got: {:#?}",
report.findings
);
assert_eq!(report.score(), 100, "clean project should score 100");
}
#[test]
fn report_renders_and_scores() {
let (scan, findings) = analyze(&fixture("vulnerable_project"));
let report = Report::new("vulnerable".into(), scan.stats, findings);
assert!(report.score() <= 100);
assert!(report.has_blocking_issues());
let text = report.render_terminal();
assert!(text.contains("KILLER REPORT"));
assert!(text.contains("Score:"));
}
#[test]
fn respects_config_rule_toggle() {
let dir = fixture("vulnerable_project");
let mut config = Config::default();
config.rules.secret_detection = false;
let scan = scanner::scan(&dir, &config);
let findings = Analyzer::with_default_rules(&config).analyze(&scan);
assert!(
!findings.iter().any(|f| f.rule == "hardcoded-secret"),
"disabling secret_detection should remove those findings"
);
assert!(findings.iter().any(|f| f.rule == "dangerous-command"));
}
struct OutOfCrateRule;
impl Rule for OutOfCrateRule {
fn id(&self) -> &str {
"out-of-crate"
}
fn check(&self, file: &FileData) -> Vec<Finding> {
if file.language != Language::Python {
return Vec::new();
}
vec![Finding::new(
self.id(),
"Python file",
Category::Quality,
Severity::Info,
&file.path,
0,
"seen by a third-party rule",
)
.with_suggestion("nothing to do")]
}
}
#[test]
fn third_party_rule_needs_only_id_and_check() {
let dir = fixture("vulnerable_project");
let config = Config::load(&dir).expect("config loads");
let scan = scanner::scan(&dir, &config);
let findings = Analyzer::new(vec![Box::new(OutOfCrateRule)]).analyze(&scan);
assert!(
!findings.is_empty(),
"the rule should have fired on the Python fixture"
);
assert_eq!(findings[0].rule, "out-of-crate");
assert_eq!(findings[0].suggestion.as_deref(), Some("nothing to do"));
assert_eq!(OutOfCrateRule.name(), "out-of-crate");
assert_eq!(OutOfCrateRule.description(), "");
assert_eq!(OutOfCrateRule.category(), Category::Quality);
}
fn treat_as_secure(verdict: Verdict) -> bool {
matches!(verdict, Verdict::Secure)
}
#[test]
fn unknown_verdicts_are_not_secure() {
assert!(treat_as_secure(Verdict::Secure));
assert!(!treat_as_secure(Verdict::Vulnerable));
assert!(!treat_as_secure(Verdict::Inconclusive));
assert!(!treat_as_secure(Verdict::Errored));
}
#[test]
fn open_enums_accept_a_wildcard_arm() {
let blocking = |s: Severity| matches!(s, Severity::Critical | Severity::High);
assert!(blocking(Severity::Critical));
assert!(!blocking(Severity::Info));
let category = match Category::Security {
Category::Security => "security",
_ => "other",
};
assert_eq!(category, "security");
let label = match Language::from_path(Path::new("a.rs")) {
Language::Rust => "rust",
_ => "other",
};
assert_eq!(label, "rust");
}