use std::fs;
use std::path::PathBuf;
fn cleanlib_bin() -> PathBuf {
let p = std::env!("CARGO_BIN_EXE_cleanlib");
PathBuf::from(p)
}
fn fixture_path(name: &str) -> PathBuf {
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
p.push("tests");
p.push("fixtures");
p.push("sarif");
p.push(name);
p
}
fn assert_output_flag_accepts_sarif(args: &[&str], verb: &str) {
let out = std::process::Command::new(cleanlib_bin())
.args(args)
.output()
.unwrap_or_else(|e| panic!("[{verb}] failed to invoke cleanlib: {e}"));
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!stderr.to_lowercase().contains("invalid value"),
"[{verb}] --output sarif must not emit clap 'invalid value' error; got:\n{stderr}"
);
}
#[test]
fn verdict_output_flag_accepts_sarif() {
assert_output_flag_accepts_sarif(
&[
"verdict",
"--ecosystem", "npm",
"--package", "cors",
"--version", "2.8.4",
"--output", "sarif",
],
"verdict sarif",
);
}
#[test]
fn scan_output_flag_accepts_sarif() {
assert_output_flag_accepts_sarif(
&[
"scan",
"--ecosystem", "npm",
"--packages", "/dev/null",
"--output", "sarif",
],
"scan sarif",
);
}
#[test]
fn verdict_help_lists_sarif_as_possible_value() {
let out = std::process::Command::new(cleanlib_bin())
.args(["verdict", "--help"])
.output()
.expect("failed to invoke cleanlib verdict --help");
assert!(out.status.success(), "verdict --help must exit zero");
let stdout = String::from_utf8_lossy(&out.stdout);
let stdout_lc = stdout.to_lowercase();
assert!(
stdout_lc.contains("sarif"),
"expected `sarif` to appear in `verdict --help` possible-values; got:\n{stdout}"
);
}
fn read_or_write_golden(fixture: &str, actual: &str) {
let path = fixture_path(fixture);
if std::env::var("UPDATE_SARIF_GOLDENS").is_ok() {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("create fixture directory");
}
fs::write(&path, actual).expect("write regenerated golden fixture");
return;
}
let expected = fs::read_to_string(&path).unwrap_or_else(|e| {
panic!(
"missing SARIF golden fixture at {}: {}\n\
Run `UPDATE_SARIF_GOLDENS=1 cargo test -p cleanlib-cli --test integration \
sarif_golden` to (re)generate.",
path.display(),
e
)
});
assert_eq!(
actual.trim(),
expected.trim(),
"SARIF golden fixture drift at {}. \
If the change is intentional, regenerate with \
`UPDATE_SARIF_GOLDENS=1 cargo test -p cleanlib-cli --test integration sarif_golden`.",
path.display(),
);
}
#[test]
fn verdict_sarif_golden_shape() {
let expected_shape = serde_json::json!({
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [{
"tool": {
"driver": {
"name": "cleanlib",
"version": env!("CARGO_PKG_VERSION"),
"informationUri": "https://cleanlibrary.clnstrt.dev"
}
},
"results": [{
"ruleId": "DM_THRESHOLD_BLOCK",
"level": "error",
"message": {
"text": "DENY: npm/malicious-pkg@1.0.0 — DM_THRESHOLD_BLOCK. Blocked by dependency-manager threshold policy."
},
"locations": [{
"logicalLocations": [{
"name": "malicious-pkg",
"fullyQualifiedName": "pkg:npm/malicious-pkg@1.0.0",
"kind": "package"
}]
}],
"properties": {
"verdict": "DM_THRESHOLD_BLOCK",
"severity": "HIGH",
"composite_score": 90,
"verdict_id": "vrd-golden-001"
}
}]
}]
});
let rendered = serde_json::to_string_pretty(&expected_shape).unwrap();
read_or_write_golden("verdict_deny.sarif.json", &rendered);
}
#[test]
fn sarif_schema_required_keys_present() {
let path = fixture_path("verdict_deny.sarif.json");
if !path.exists() {
return;
}
let raw = fs::read_to_string(&path).expect("read golden fixture");
let value: serde_json::Value =
serde_json::from_str(&raw).expect("golden fixture must be valid JSON");
for key in ["$schema", "version", "runs"] {
assert!(
value.get(key).is_some(),
"SARIF v2.1.0 log envelope MUST carry `{key}`"
);
}
assert_eq!(value["version"], "2.1.0", "version must be pinned to 2.1.0");
let run = &value["runs"][0];
for key in ["tool", "results"] {
assert!(run.get(key).is_some(), "SARIF run MUST carry `{key}`");
}
let driver = &run["tool"]["driver"];
for key in ["name"] {
assert!(
driver.get(key).is_some(),
"SARIF toolComponent (driver) MUST carry `{key}`"
);
}
let result = &run["results"][0];
for key in ["message"] {
assert!(
result.get(key).is_some(),
"SARIF result MUST carry `{key}`"
);
}
assert!(
result["message"].get("text").is_some()
|| result["message"].get("markdown").is_some(),
"SARIF message MUST carry `text` or `markdown`"
);
if let Some(level) = result.get("level").and_then(|v| v.as_str()) {
assert!(
matches!(level, "none" | "note" | "warning" | "error"),
"SARIF result.level MUST be one of the closed vocab; got `{level}`"
);
}
}