use sbom_tools::parse_sbom;
use sbom_tools::quality::{DependencyMetrics, QualityScorer, ScoringProfile};
use std::path::Path;
fn metrics_for(fixture: &str) -> DependencyMetrics {
let sbom = parse_sbom(Path::new(fixture)).unwrap_or_else(|e| panic!("{fixture}: {e}"));
DependencyMetrics::from_sbom(&sbom)
}
fn simplicity_for(fixture: &str) -> f32 {
let m = metrics_for(fixture);
m.software_complexity_index
.unwrap_or_else(|| panic!("{fixture}: complexity was None (graph_analysis_skipped)"))
}
#[test]
fn minimal_cyclonedx_is_simple() {
let s = simplicity_for("tests/fixtures/cyclonedx/minimal.cdx.json");
assert!(
s >= 70.0,
"Minimal CycloneDX simplicity {s} should be >= 70"
);
}
#[test]
fn minimal_spdx_is_simple() {
let s = simplicity_for("tests/fixtures/spdx/minimal.spdx.json");
assert!(s >= 70.0, "Minimal SPDX simplicity {s} should be >= 70");
}
#[test]
fn with_vulnerabilities_is_simple() {
let s = simplicity_for("tests/fixtures/cyclonedx/with-vulnerabilities.cdx.json");
assert!(
s >= 70.0,
"With-vulnerabilities simplicity {s} should be >= 70"
);
}
#[test]
fn demo_sboms_in_range() {
let fixtures = [
"tests/fixtures/demo-old.cdx.json",
"tests/fixtures/demo-new.cdx.json",
"tests/fixtures/demo-eol-old.cdx.json",
"tests/fixtures/demo-eol-new.cdx.json",
];
for fixture in &fixtures {
let s = simplicity_for(fixture);
assert!(
(20.0..=95.0).contains(&s),
"{fixture}: simplicity {s} should be in 20-95 range"
);
}
}
#[test]
fn complexity_fields_populated() {
let m = metrics_for("tests/fixtures/demo-new.cdx.json");
assert!(
!m.graph_analysis_skipped,
"Graph analysis should not be skipped for demo fixture"
);
assert!(m.software_complexity_index.is_some());
assert!(m.complexity_level.is_some());
assert!(m.complexity_factors.is_some());
let factors = m.complexity_factors.unwrap();
for (name, val) in [
("dependency_volume", factors.dependency_volume),
("normalized_depth", factors.normalized_depth),
("fanout_concentration", factors.fanout_concentration),
("cycle_ratio", factors.cycle_ratio),
("fragmentation", factors.fragmentation),
] {
assert!(
(0.0..=1.0).contains(&val),
"Factor {name} = {val} should be in [0, 1]"
);
}
}
#[test]
fn recommendations_include_complexity_when_applicable() {
let sbom = parse_sbom(Path::new("tests/fixtures/demo-new.cdx.json")).unwrap();
let scorer = QualityScorer::new(ScoringProfile::Standard);
let report = scorer.score(&sbom);
assert!(
report
.dependency_metrics
.software_complexity_index
.is_some()
);
if let Some(level) = &report.dependency_metrics.complexity_level {
use sbom_tools::quality::ComplexityLevel;
if matches!(level, ComplexityLevel::High | ComplexityLevel::VeryHigh) {
let has_complexity_rec = report
.recommendations
.iter()
.any(|r| r.message.contains("complex"));
assert!(
has_complexity_rec,
"High/VeryHigh complexity should produce a recommendation"
);
}
}
}
#[test]
fn max_out_degree_for_graph_with_edges() {
let m = metrics_for("tests/fixtures/cyclonedx/minimal.cdx.json");
assert_eq!(
m.max_out_degree, 1,
"max_out_degree should be 1 for minimal fixture with 1 edge"
);
}