#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::path::PathBuf;
use fleetreach_cli::config::Config;
use fleetreach_cli::{build_report, reach, retain_reachable, ScanData};
use fleetreach_core::semver::Version;
use fleetreach_core::{
DependencyKind, Ecosystem, Exploitability, Occurrence, Provenance, RepoId, RepoOutcome,
ScanStatus, Severity, VulnFinding,
};
fn base() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
}
fn provenance() -> Provenance {
Provenance {
tool_version: "0.1.0".into(),
rustsec_crate_version: "0.33.0".into(),
db_commit: None,
db_timestamp: None,
host_os: "linux".into(),
host_arch: "x86_64".into(),
generated_at: "2026-06-24T00:00:00Z".into(),
}
}
fn finding(id: &str, repo: &str) -> VulnFinding {
VulnFinding {
advisory_id: id.into(),
aliases: vec![],
ecosystem: Default::default(),
affected_functions: vec!["fixturevuln::boom".into()],
reachable: None,
reachability: None,
exploit: Exploitability::default(),
title: id.into(),
severity: Severity::High,
cvss_score: None,
url: None,
occurrences: vec![Occurrence::InRepo {
repo: RepoId(repo.into()),
package: "fixturevuln".into(),
installed: Version::new(1, 0, 0),
patched: vec![],
dependency_kind: DependencyKind::Transitive,
dependency_path: vec![],
active: None,
source: Default::default(),
}],
}
}
fn config() -> Config {
Config::from_str(
r#"
[[repo]]
id = "uses"
path = "reach-uses"
[[repo]]
id = "clean"
path = "reach-clean"
"#,
&base(),
"reach.toml",
)
.expect("valid config")
}
#[test]
fn assess_flags_source_presence_per_repo() {
let scan = ScanData {
skipped_unparseable: 0,
vulnerabilities: vec![finding("R-USES", "uses"), finding("R-CLEAN", "clean")],
warnings: vec![],
outcomes: vec![
RepoOutcome {
repo: RepoId("uses".into()),
status: ScanStatus::Scanned {
vulns: 1,
warnings: 0,
},
},
RepoOutcome {
repo: RepoId("clean".into()),
status: ScanStatus::Scanned {
vulns: 1,
warnings: 0,
},
},
],
};
let mut report = build_report(scan, &[], None, provenance());
reach::assess(&mut report, &config());
let by_id = |id: &str| {
report
.vulnerabilities
.iter()
.find(|v| v.advisory_id == id)
.unwrap()
.reachable
};
assert_eq!(
by_id("R-USES"),
Some(true),
"boom() appears in reach-uses source"
);
assert_eq!(
by_id("R-CLEAN"),
Some(false),
"boom is not in reach-clean source"
);
let removed = retain_reachable(&mut report);
assert_eq!(removed, 1);
assert_eq!(report.vulnerabilities.len(), 1);
assert_eq!(report.vulnerabilities[0].advisory_id, "R-USES");
}
fn tier_c(
id: &str,
eco: Ecosystem,
repo: &str,
package: &str,
kind: DependencyKind,
) -> VulnFinding {
VulnFinding {
advisory_id: id.into(),
aliases: vec![],
ecosystem: eco,
affected_functions: vec![], reachable: None,
reachability: None,
exploit: Exploitability::default(),
title: id.into(),
severity: Severity::High,
cvss_score: None,
url: None,
occurrences: vec![Occurrence::InRepo {
repo: RepoId(repo.into()),
package: package.into(),
installed: Version::new(1, 0, 0),
patched: vec![],
dependency_kind: kind,
dependency_path: vec![],
active: None,
source: Default::default(),
}],
}
}
fn tier_c_config() -> Config {
Config::from_str(
r#"
[[repo]]
id = "npm"
path = "reach-npm"
ecosystem = "npm"
[[repo]]
id = "julia"
path = "reach-julia"
ecosystem = "julia"
[[repo]]
id = "ruby"
path = "reach-rubygems"
ecosystem = "rubygems"
[[repo]]
id = "pypi"
path = "reach-pypi"
ecosystem = "pypi"
[[repo]]
id = "nuget"
path = "reach-nuget"
ecosystem = "nuget"
[[repo]]
id = "maven"
path = "reach-maven"
ecosystem = "maven"
[[repo]]
id = "hex"
path = "reach-hex"
ecosystem = "hex"
[[repo]]
id = "gha"
path = "reach-ghactions"
ecosystem = "githubactions"
"#,
&base(),
"reach.toml",
)
.expect("valid config")
}
#[test]
fn hex_and_github_actions_reachability() {
use DependencyKind::Direct;
let scan = ScanData {
skipped_unparseable: 0,
vulnerabilities: vec![
tier_c("HEX-USED", Ecosystem::Hex, "hex", "plug", Direct),
tier_c("HEX-UNUSED", Ecosystem::Hex, "hex", "ecto", Direct),
tier_c(
"GHA-USED",
Ecosystem::GitHubActions,
"gha",
"actions/checkout",
Direct,
),
tier_c(
"GHA-UNUSED",
Ecosystem::GitHubActions,
"gha",
"actions/setup-go",
Direct,
),
],
warnings: vec![],
outcomes: vec![],
};
let mut report = build_report(scan, &[], None, provenance());
reach::assess(&mut report, &tier_c_config());
let by_id = |id: &str| {
report
.vulnerabilities
.iter()
.find(|v| v.advisory_id == id)
.unwrap()
.reachable
};
assert_eq!(by_id("HEX-USED"), Some(true), "Plug module used in app.ex");
assert_eq!(by_id("HEX-UNUSED"), None, "unused hex dep stays unknown");
assert_eq!(by_id("GHA-USED"), Some(true), "actions/checkout is used:");
assert_eq!(
by_id("GHA-UNUSED"),
None,
"unreferenced action stays unknown"
);
assert_eq!(retain_reachable(&mut report), 0, "never auto-suppress");
}
#[test]
fn name_mapping_ecosystems_detect_imports_securely() {
use DependencyKind::{Direct, Transitive};
let scan = ScanData {
skipped_unparseable: 0,
vulnerabilities: vec![
tier_c("PY-USED", Ecosystem::Pypi, "pypi", "requests", Direct),
tier_c(
"NG-USED",
Ecosystem::NuGet,
"nuget",
"Newtonsoft.Json",
Direct,
),
tier_c(
"MV-USED",
Ecosystem::Maven,
"maven",
"org.apache.logging.log4j:log4j-core",
Direct,
),
tier_c("PY-UNUSED", Ecosystem::Pypi, "pypi", "django", Direct),
tier_c(
"NG-TRANS",
Ecosystem::NuGet,
"nuget",
"Newtonsoft.Json",
Transitive,
),
],
warnings: vec![],
outcomes: vec![],
};
let mut report = build_report(scan, &[], None, provenance());
reach::assess(&mut report, &tier_c_config());
let by_id = |id: &str| {
report
.vulnerabilities
.iter()
.find(|v| v.advisory_id == id)
.unwrap()
.reachable
};
assert_eq!(by_id("PY-USED"), Some(true), "requests imported in app.py");
assert_eq!(
by_id("NG-USED"),
Some(true),
"Newtonsoft.Json used in Program.cs"
);
assert_eq!(
by_id("MV-USED"),
Some(true),
"log4j group imported in App.java"
);
assert_eq!(
by_id("PY-UNUSED"),
None,
"unimported direct dep stays unknown"
);
assert_eq!(by_id("NG-TRANS"), None, "transitive stays unknown");
let removed = retain_reachable(&mut report);
assert_eq!(
removed, 0,
"a heuristic miss must never auto-suppress a finding"
);
}
#[test]
fn tier_c_import_presence_is_secure() {
use DependencyKind::{Direct, Transitive};
let scan = ScanData {
skipped_unparseable: 0,
vulnerabilities: vec![
tier_c("NPM-USED", Ecosystem::Npm, "npm", "lodash", Direct),
tier_c("JL-USED", Ecosystem::Julia, "julia", "HTTP", Direct),
tier_c("RB-USED", Ecosystem::RubyGems, "ruby", "rack", Direct),
tier_c("NPM-UNUSED", Ecosystem::Npm, "npm", "express", Direct),
tier_c("NPM-TRANS", Ecosystem::Npm, "npm", "lodash", Transitive),
],
warnings: vec![],
outcomes: vec![],
};
let mut report = build_report(scan, &[], None, provenance());
reach::assess(&mut report, &tier_c_config());
let by_id = |id: &str| {
report
.vulnerabilities
.iter()
.find(|v| v.advisory_id == id)
.unwrap()
.reachable
};
assert_eq!(
by_id("NPM-USED"),
Some(true),
"lodash is required in app.js"
);
assert_eq!(by_id("JL-USED"), Some(true), "HTTP is `using`d in main.jl");
assert_eq!(by_id("RB-USED"), Some(true), "rack is required in app.rb");
assert_eq!(
by_id("NPM-UNUSED"),
None,
"unimported direct dep stays unknown"
);
assert_eq!(by_id("NPM-TRANS"), None, "transitive dep stays unknown");
let before = report.vulnerabilities.len();
let removed = retain_reachable(&mut report);
assert_eq!(
removed, 0,
"a grep miss must never auto-suppress a Tier-C finding"
);
assert_eq!(report.vulnerabilities.len(), before);
}