xtask/tests/audit_follows_deny.rs
//! ★ **cargo-audit and cargo-deny judge advisories by ONE list** (lane HOL-NCSC, NCSC P3).
//!
//! The `[advisories] ignore` of `deny.toml` carries each exception with its reason; cargo-audit
//! does not read it, so `.cargo/audit.toml` repeats the ids. The two drifted (2026-10-30:
//! the security workflow ignored one id, deny.toml six). This guard holds them equal, keeps
//! the workflow free of a third list (`--ignore` on its command line), and holds that
//! `cargo deny check` runs in CI: a step of the full set of `cargo xtask ci` and a job of the
//! security workflow.
use std::collections::BTreeSet;
use std::path::PathBuf;
fn root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("xtask/..")
.to_path_buf()
}
fn read(rel: &str) -> String {
std::fs::read_to_string(root().join(rel)).unwrap_or_else(|e| panic!("{rel}: {e}"))
}
/// The ids of a toml's `[advisories] ignore`: plain strings or `{ id = .. }` tables.
fn ignored(rel: &str) -> BTreeSet<String> {
let v: toml::Value = toml::from_str(&read(rel)).unwrap_or_else(|e| panic!("{rel}: {e}"));
let list = v
.get("advisories")
.and_then(|a| a.get("ignore"))
.and_then(|i| i.as_array())
.cloned()
.unwrap_or_default();
list.iter()
.map(|e| match e {
toml::Value::String(s) => s.clone(),
other => other
.get("id")
.and_then(|i| i.as_str())
.unwrap_or_else(|| panic!("{rel}: an ignore entry without an id: {other}"))
.to_owned(),
})
.collect()
}
/// The workflow without its comment lines.
fn workflow() -> String {
read(".forgejo/workflows/security.yml")
.lines()
.filter(|l| !l.trim_start().starts_with('#'))
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn cargo_audit_ignores_exactly_what_deny_toml_ignores() {
let deny = ignored("deny.toml");
assert!(
!deny.is_empty(),
"deny.toml ignores nothing? the guard would be vacuous"
);
let audit = ignored(".cargo/audit.toml");
assert_eq!(
audit, deny,
".cargo/audit.toml and deny.toml ignore different advisories"
);
assert!(
!workflow().contains("--ignore"),
"the security workflow keeps a list of its own; cargo audit reads .cargo/audit.toml"
);
}
#[test]
fn cargo_deny_check_runs_in_ci() {
let wf = workflow();
assert!(
wf.contains("cargo deny --locked check"),
"the security workflow does not run cargo deny --locked check"
);
let ci = read("xtask/src/ci.rs");
assert!(
ci.contains(r#"Step::host("deny", "cargo", &["deny", "--locked", "check"])"#),
"cargo xtask ci has no deny step"
);
}