use super::*;
#[test]
pub(crate) fn dependency_rules_flag_local_manifest_and_lockfile_posture() {
let _guard = analysis_lock();
let dir = tempdir().expect("tempdir");
fs::write(dir.path().join("README.md"), "# Fixture\n").expect("readme write");
fs::write(
dir.path().join("Cargo.toml"),
r#"[package]
name = "dependency-positive-fixture"
version = "0.1.0"
edition = "2021"
[dependencies]
wildcard = "*"
gitdep = { git = "https://example.invalid/repo.git", rev = "1111111111111111111111111111111111111111" }
gitunpinned = { git = "https://example.invalid/unpinned.git" }
pathdep = { path = "../local-path" }
"#,
)
.expect("manifest write");
fs::write(
dir.path().join("Cargo.lock"),
r#"version = 3
[[package]]
name = "duplicate"
version = "1.0.0"
[[package]]
name = "duplicate"
version = "2.0.0"
[[package]]
name = "duplicate"
version = "3.0.0"
"#,
)
.expect("lockfile write");
let report = run_project_analysis(
dir.path(),
AnalysisOptions {
paths: vec![PathBuf::from(".")],
no_config: true,
no_baseline: true,
..default_test_options()
},
)
.expect("analysis succeeds");
assert!(report.diagnostics.is_empty(), "{:?}", report.diagnostics);
assert_has_rule(&report, "dependency.git-source");
assert_has_rule(&report, "dependency.git-unpinned-revision");
assert_has_rule(&report, "dependency.path-source");
assert_has_rule(&report, "dependency.wildcard-version");
assert_has_rule(&report, "dependency.duplicate-locked-version");
assert_has_rule(&report, "dependency.missing-package-metadata");
let git = report
.findings
.iter()
.find(|finding| finding.rule_id == "dependency.git-source")
.expect("git source finding");
assert_eq!(git.file_path, "Cargo.toml");
assert_eq!(git.line, Some(8));
assert_eq!(git.symbol.as_deref(), Some("gitdep"));
assert_eq!(git.pillar, Pillar::Security);
let unpinned = report
.findings
.iter()
.find(|finding| finding.rule_id == "dependency.git-unpinned-revision")
.expect("unpinned git source finding");
assert_eq!(unpinned.file_path, "Cargo.toml");
assert_eq!(unpinned.line, Some(9));
assert_eq!(unpinned.symbol.as_deref(), Some("gitunpinned"));
assert_eq!(unpinned.pillar, Pillar::Security);
let metadata = report
.findings
.iter()
.find(|finding| finding.rule_id == "dependency.missing-package-metadata")
.expect("metadata finding");
assert_eq!(metadata.file_path, "Cargo.toml");
assert_eq!(metadata.line, Some(1));
assert_eq!(metadata.pillar, Pillar::Documentation);
let duplicate = report
.findings
.iter()
.find(|finding| finding.rule_id == "dependency.duplicate-locked-version")
.expect("duplicate lockfile finding");
assert_eq!(duplicate.file_path, "Cargo.lock");
assert_eq!(duplicate.line, Some(4));
assert_eq!(duplicate.symbol.as_deref(), Some("duplicate"));
let security = report
.score
.pillars
.iter()
.find(|pillar| pillar.pillar == Pillar::Security)
.expect("security score");
assert!(
security.findings >= 5,
"expected dependency findings to affect security: {security:?}"
);
}
#[test]
pub(crate) fn dependency_rules_scan_target_specific_manifest_tables() {
let _guard = analysis_lock();
let dir = tempdir().expect("tempdir");
fs::write(dir.path().join("README.md"), "# Fixture\n").expect("readme write");
fs::write(
dir.path().join("Cargo.toml"),
r#"[package]
name = "target-dependency-fixture"
version = "0.1.0"
edition = "2021"
[target.'cfg(unix)'.dependencies]
targetwild = "*"
targetgit = { git = "https://example.invalid/target.git" }
"#,
)
.expect("manifest write");
let report = run_project_analysis(
dir.path(),
AnalysisOptions {
paths: vec![PathBuf::from(".")],
no_config: true,
no_baseline: true,
..default_test_options()
},
)
.expect("analysis succeeds");
let wildcard = report
.findings
.iter()
.find(|finding| finding.rule_id == "dependency.wildcard-version")
.expect("target wildcard finding");
assert_eq!(wildcard.symbol.as_deref(), Some("targetwild"));
assert_eq!(wildcard.line, Some(7));
let unpinned = report
.findings
.iter()
.find(|finding| finding.rule_id == "dependency.git-unpinned-revision")
.expect("target git finding");
assert_eq!(unpinned.symbol.as_deref(), Some("targetgit"));
assert_eq!(unpinned.line, Some(8));
}
#[test]
pub(crate) fn dependency_rules_accept_clean_manifest_and_config_threshold() {
let _guard = analysis_lock();
let clean_dir = tempdir().expect("tempdir");
fs::write(clean_dir.path().join("README.md"), "# Fixture\n").expect("readme write");
fs::write(
clean_dir.path().join("Cargo.toml"),
r#"[package]
name = "dependency-negative-fixture"
version = "0.1.0"
edition = "2021"
description = "Synthetic fixture for dependency rule tests."
license = "MIT"
[dependencies]
serde = "1"
"#,
)
.expect("manifest write");
fs::write(
clean_dir.path().join("Cargo.lock"),
r#"version = 3
[[package]]
name = "serde"
version = "1.0.0"
"#,
)
.expect("lockfile write");
let clean = run_project_analysis(
clean_dir.path(),
AnalysisOptions {
paths: vec![PathBuf::from(".")],
no_config: true,
no_baseline: true,
..default_test_options()
},
)
.expect("clean analysis succeeds");
assert_missing_rule(&clean, "dependency.git-source");
assert_missing_rule(&clean, "dependency.git-unpinned-revision");
assert_missing_rule(&clean, "dependency.path-source");
assert_missing_rule(&clean, "dependency.wildcard-version");
assert_missing_rule(&clean, "dependency.duplicate-locked-version");
assert_missing_rule(&clean, "dependency.missing-package-metadata");
let threshold_dir = tempdir().expect("tempdir");
fs::write(threshold_dir.path().join("README.md"), "# Fixture\n").expect("readme write");
fs::write(
threshold_dir.path().join("Cargo.toml"),
r#"[package]
name = "dependency-threshold-fixture"
version = "0.1.0"
edition = "2021"
description = "Synthetic fixture for dependency threshold tests."
license = "MIT"
"#,
)
.expect("manifest write");
fs::write(
threshold_dir.path().join("Cargo.lock"),
r#"version = 3
[[package]]
name = "duplicate"
version = "1.0.0"
[[package]]
name = "duplicate"
version = "2.0.0"
[[package]]
name = "duplicate"
version = "3.0.0"
"#,
)
.expect("lockfile write");
write_config(
threshold_dir.path(),
r#"
rules:
dependency.duplicate-locked-version:
threshold: 3
severity: advisory
"#,
);
let thresholded = run_project_analysis(
threshold_dir.path(),
AnalysisOptions {
paths: vec![PathBuf::from(".")],
no_config: false,
no_baseline: true,
..default_test_options()
},
)
.expect("thresholded analysis succeeds");
assert_missing_rule(&thresholded, "dependency.duplicate-locked-version");
write_config(
threshold_dir.path(),
r#"
rules:
dependency.duplicate-locked-version:
threshold: 2
severity: severe
"#,
);
let error =
load_config(threshold_dir.path(), &default_test_options()).expect_err("bad threshold");
assert!(
error.contains(
"config key `rules.dependency.duplicate-locked-version.severity` must be advisory, warning, or error"
),
"{error}"
);
}
#[test]
pub(crate) fn architecture_rules_flag_module_shape_and_public_surface() {
let _guard = analysis_lock();
let dir = tempdir().expect("tempdir");
fs::create_dir_all(dir.path().join("src")).expect("src dir");
fs::write(dir.path().join("README.md"), "# Fixture\n").expect("readme write");
fs::write(
dir.path().join("Cargo.toml"),
r#"[package]
name = "architecture-positive-fixture"
version = "0.1.0"
edition = "2021"
description = "Synthetic fixture for architecture rule tests."
license = "MIT"
"#,
)
.expect("manifest write");
fs::write(
dir.path().join("src/lib.rs"),
r#"pub mod api {
pub struct One;
pub struct Two;
pub enum Three {
Ready,
}
pub trait Four {}
}
mod alpha;
mod beta;
mod gamma;
"#,
)
.expect("lib write");
write_config(
dir.path(),
r#"
rules:
architecture.module-fan-out:
threshold: 2
severity: advisory
architecture.public-api-surface:
threshold: 2
severity: advisory
architecture.large-module:
threshold: 3
severity: advisory
"#,
);
let report = run_project_analysis(
dir.path(),
AnalysisOptions {
paths: vec![PathBuf::from(".")],
no_config: false,
no_baseline: true,
..default_test_options()
},
)
.expect("architecture analysis succeeds");
assert!(report.diagnostics.is_empty(), "{:?}", report.diagnostics);
assert_has_rule(&report, "architecture.module-fan-out");
assert_has_rule(&report, "architecture.public-api-surface");
assert_has_rule(&report, "architecture.large-module");
let fan_out = report
.findings
.iter()
.find(|finding| finding.rule_id == "architecture.module-fan-out")
.expect("module fan-out finding");
assert_eq!(fan_out.file_path, "src/lib.rs");
assert_eq!(fan_out.line, Some(1));
assert_eq!(fan_out.symbol.as_deref(), Some("src/lib.rs"));
assert_eq!(fan_out.metadata["modules"], json!(4));
assert!(fan_out.message.contains("4 child modules"));
let public_surface = report
.findings
.iter()
.find(|finding| finding.rule_id == "architecture.public-api-surface")
.expect("public API finding");
assert_eq!(public_surface.symbol.as_deref(), Some("api"));
assert_eq!(public_surface.metadata["publicItems"], json!(4));
let large_module = report
.findings
.iter()
.find(|finding| finding.rule_id == "architecture.large-module")
.expect("large module finding");
assert_eq!(large_module.symbol.as_deref(), Some("api"));
assert_eq!(large_module.metadata["items"], json!(4));
}
#[test]
pub(crate) fn architecture_rules_accept_small_modules_and_validate_threshold() {
let _guard = analysis_lock();
let dir = tempdir().expect("tempdir");
fs::create_dir_all(dir.path().join("src")).expect("src dir");
fs::write(dir.path().join("README.md"), "# Fixture\n").expect("readme write");
fs::write(
dir.path().join("Cargo.toml"),
r#"[package]
name = "architecture-negative-fixture"
version = "0.1.0"
edition = "2021"
description = "Synthetic fixture for architecture rule tests."
license = "MIT"
"#,
)
.expect("manifest write");
fs::write(
dir.path().join("src/lib.rs"),
r#"pub mod api {
pub struct One;
}
mod alpha;
"#,
)
.expect("lib write");
let report = run_project_analysis(
dir.path(),
AnalysisOptions {
paths: vec![PathBuf::from(".")],
no_config: true,
no_baseline: true,
..default_test_options()
},
)
.expect("small architecture analysis succeeds");
assert_missing_rule(&report, "architecture.module-fan-out");
assert_missing_rule(&report, "architecture.public-api-surface");
assert_missing_rule(&report, "architecture.large-module");
write_config(
dir.path(),
r#"
rules:
architecture.large-module:
threshold: 2
severity: severe
"#,
);
let error =
load_config(dir.path(), &default_test_options()).expect_err("bad threshold rejected");
assert!(
error.contains(
"config key `rules.architecture.large-module.severity` must be advisory, warning, or error"
),
"{error}"
);
}
#[test]
pub(crate) fn missing_readme_accepts_common_case_variants() {
let _guard = analysis_lock();
let dir = tempdir().expect("tempdir");
baseline_with_lib(dir.path(), "pub fn entry() {}\n");
fs::remove_file(dir.path().join("README.md")).expect("remove readme");
fs::write(dir.path().join("readme.md"), "# Fixture\n").expect("readme write");
let report = run_project_analysis(
dir.path(),
AnalysisOptions {
paths: vec![PathBuf::from(".")],
no_config: true,
no_baseline: true,
..default_test_options()
},
)
.expect("analysis succeeds");
assert_missing_rule(&report, "docs.missing-readme");
}