#[test]
fn test_cb081_detects_excessive_direct_deps() {
let temp = TempDir::new().unwrap();
let mut deps =
String::from("[package]\nname = \"test\"\nversion = \"0.1.0\"\n\n[dependencies]\n");
for i in 0..60 {
deps.push_str(&format!("dep{} = \"1.0\"\n", i));
}
fs::write(temp.path().join("Cargo.toml"), &deps).unwrap();
fs::write(
temp.path().join("Cargo.lock"),
"[[package]]\nname = \"test\"",
)
.unwrap();
let report = detect_cb081_dependency_count(temp.path());
assert_eq!(report.direct_count, 60);
assert_eq!(report.score, 0); assert!(!report.violations.is_empty());
assert_eq!(report.violations[0].pattern_id, "CB-081-A");
}
#[test]
fn test_cb081_moderate_deps() {
let temp = TempDir::new().unwrap();
let mut deps =
String::from("[package]\nname = \"test\"\nversion = \"0.1.0\"\n\n[dependencies]\n");
for i in 0..35 {
deps.push_str(&format!("dep{} = \"1.0\"\n", i));
}
fs::write(temp.path().join("Cargo.toml"), &deps).unwrap();
let mut lock = String::new();
for _ in 0..180 {
lock.push_str("[[package]]\nname = \"pkg\"\n");
}
fs::write(temp.path().join("Cargo.lock"), &lock).unwrap();
let report = detect_cb081_dependency_count(temp.path());
assert_eq!(report.direct_count, 35);
assert_eq!(report.transitive_count, 180);
assert_eq!(report.score, 3); }
#[test]
fn test_cb081_low_deps_excellent() {
let temp = TempDir::new().unwrap();
let mut deps =
String::from("[package]\nname = \"test\"\nversion = \"0.1.0\"\n\n[dependencies]\n");
for i in 0..15 {
deps.push_str(&format!("dep{} = \"1.0\"\n", i));
}
fs::write(temp.path().join("Cargo.toml"), &deps).unwrap();
let mut lock = String::new();
for _ in 0..80 {
lock.push_str("[[package]]\nname = \"pkg\"\n");
}
fs::write(temp.path().join("Cargo.lock"), &lock).unwrap();
let report = detect_cb081_dependency_count(temp.path());
assert_eq!(report.direct_count, 15);
assert_eq!(report.transitive_count, 80);
assert_eq!(report.score, 5); assert!(report.violations.is_empty());
}
#[test]
fn test_cb081_excludes_dev_dependencies() {
let temp = TempDir::new().unwrap();
let deps = r#"
[package]
name = "test"
version = "0.1.0"
[dependencies]
serde = "1.0"
anyhow = "1.0"
[dev-dependencies]
criterion = "0.5"
tempfile = "3.0"
proptest = "1.0"
quickcheck = "1.0"
tokio-test = "0.4"
"#;
fs::write(temp.path().join("Cargo.toml"), deps).unwrap();
fs::write(
temp.path().join("Cargo.lock"),
"[[package]]\nname = \"test\"",
)
.unwrap();
let report = detect_cb081_dependency_count(temp.path());
assert_eq!(report.direct_count, 2);
}
#[test]
fn test_cb081_no_cargo_toml() {
let temp = TempDir::new().unwrap();
let report = detect_cb081_dependency_count(temp.path());
assert_eq!(report.direct_count, 0);
assert_eq!(report.transitive_count, 0);
}
fn tree_fingerprint(root: &std::path::Path) -> Vec<String> {
let mut out = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
let Ok(entries) = fs::read_dir(&dir) else {
continue;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path.clone());
}
if let Ok(rel) = path.strip_prefix(root) {
out.push(rel.display().to_string());
}
}
}
out.sort();
out
}
#[test]
fn cb081_does_not_write_into_the_audited_project() {
let temp = TempDir::new().unwrap();
fs::write(
temp.path().join("Cargo.toml"),
"[package]\nname = \"test\"\nversion = \"0.1.0\"\n\n[dependencies]\nserde = \"1.0\"\n",
)
.unwrap();
fs::write(
temp.path().join("Cargo.lock"),
"[[package]]\nname = \"serde\"\nversion = \"1.0.0\"\n",
)
.unwrap();
let before = tree_fingerprint(temp.path());
let lock_before = fs::read(temp.path().join("Cargo.lock")).unwrap();
let first = detect_cb081_dependency_count(temp.path());
let after = tree_fingerprint(temp.path());
assert_eq!(
before, after,
"scoring a project must leave it byte-for-byte alone"
);
assert_eq!(
fs::read(temp.path().join("Cargo.lock")).unwrap(),
lock_before,
"CB-081 rewrote the audited project's Cargo.lock"
);
assert!(
!temp.path().join(".pmat").exists(),
"CB-081 created a .pmat/ directory in the audited project"
);
let second = detect_cb081_dependency_count(temp.path());
assert_eq!(first.direct_count, second.direct_count);
assert_eq!(first.transitive_count, second.transitive_count);
assert_eq!(tree_fingerprint(temp.path()), before);
}
#[test]
fn test_cb400_no_git_hooks_dir() {
let temp = TempDir::new().unwrap();
let violations = detect_cb400_git_hooks_quality(temp.path());
assert!(violations.is_empty(), "No hooks dir should return empty");
}
#[test]
fn test_cb400_empty_git_hooks_dir() {
let temp = TempDir::new().unwrap();
fs::create_dir_all(temp.path().join(".git/hooks")).unwrap();
let violations = detect_cb400_git_hooks_quality(temp.path());
assert!(violations.is_empty(), "Empty hooks dir should return empty");
}
#[test]
fn test_cb400_sample_hooks_ignored() {
let temp = TempDir::new().unwrap();
let hooks_dir = temp.path().join(".git/hooks");
fs::create_dir_all(&hooks_dir).unwrap();
fs::write(
hooks_dir.join("pre-commit.sample"),
"#!/bin/bash\necho test",
)
.unwrap();
let violations = detect_cb400_git_hooks_quality(temp.path());
assert!(violations.is_empty(), "Sample hooks should be ignored");
}
#[test]
fn test_cb401_no_makefile() {
let temp = TempDir::new().unwrap();
let violations = detect_cb401_makefile_quality(temp.path());
assert!(violations.is_empty(), "No Makefile should return empty");
}
#[test]
fn test_cb402_no_shell_scripts() {
let temp = TempDir::new().unwrap();
let violations = detect_cb402_shell_script_quality(temp.path());
assert!(
violations.is_empty(),
"No shell scripts should return empty"
);
}
#[test]
fn test_cb402_target_dir_excluded() {
let temp = TempDir::new().unwrap();
let target_dir = temp.path().join("target");
fs::create_dir_all(&target_dir).unwrap();
fs::write(target_dir.join("test.sh"), "#!/bin/bash\necho test").unwrap();
let violations = detect_cb402_shell_script_quality(temp.path());
assert!(
violations.is_empty(),
"Scripts in target/ should be ignored"
);
}
#[test]
fn test_parse_bashrs_json_array() {
let json = r#"[{"code":"SC2086","message":"Double quote","line":5,"severity":"warning"}]"#;
let result = parse_bashrs_json_output(json).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].code, "SC2086");
assert_eq!(result[0].line, 5);
}
#[test]
fn test_parse_bashrs_json_object() {
let json = r#"{"diagnostics":[{"code":"SC2046","message":"Quote this","line":3,"severity":"error"}]}"#;
let result = parse_bashrs_json_output(json).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].code, "SC2046");
assert_eq!(result[0].severity, "error");
}
#[test]
fn test_parse_bashrs_json_invalid() {
let json = "not valid json";
let result = parse_bashrs_json_output(json).unwrap();
assert!(result.is_empty(), "Invalid JSON should return empty");
}
#[test]
fn test_parse_bashrs_json_empty_array() {
let json = "[]";
let result = parse_bashrs_json_output(json).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_parse_bashrs_json_multiple_issues() {
let json = r#"[
{"code":"SC2086","message":"Double quote","line":5,"severity":"warning"},
{"code":"SC2046","message":"Quote this","line":10,"severity":"error"},
{"code":"SC2116","message":"Useless echo","line":15,"severity":"info"}
]"#;
let result = parse_bashrs_json_output(json).unwrap();
assert_eq!(result.len(), 3);
assert_eq!(result[0].code, "SC2086");
assert_eq!(result[1].code, "SC2046");
assert_eq!(result[2].code, "SC2116");
}