//! Integration coverage for the external-scanner supplement primitive
//! (Layer D OSS), driven by on-disk scanner fixtures so the SARIF and
//! generic-JSON shapes stay reviewable.

use std::path::{Path, PathBuf};

use serde_json::Value;

use mcptest_core::security::{
    dedup_against_catalog, normalize_generic_json, normalize_sarif, FindingSource, SecuritySeverity,
};

fn load(name: &str) -> Value {
    let path: PathBuf = Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests")
        .join("fixtures")
        .join("supplement")
        .join(name);
    let body = std::fs::read_to_string(&path)
        .unwrap_or_else(|e| panic!("fixture {} must exist: {e}", path.display()));
    serde_json::from_str(&body)
        .unwrap_or_else(|e| panic!("fixture {} must be valid JSON: {e}", path.display()))
}

#[test]
fn semgrep_sarif_normalizes_with_provenance_and_evidence() {
    let findings = normalize_sarif(&load("semgrep.sarif.json"), "semgrep", false);
    assert_eq!(findings.len(), 2);

    let shell = &findings[0];
    assert_eq!(shell.rule_id, "mcp.tool-shell-exec");
    assert_eq!(shell.severity, SecuritySeverity::High);
    assert_eq!(shell.evidence, "exec(`sh -c ${args.cmd}`)");
    assert_eq!(
        shell.source,
        FindingSource::External {
            tool: "semgrep".to_string()
        }
    );

    // The warning-level result with only an artifact location falls back to the
    // URI for evidence and maps to medium severity.
    let fetch = &findings[1];
    assert_eq!(fetch.severity, SecuritySeverity::Medium);
    assert_eq!(fetch.evidence, "src/tools/fetch_url.ts");
}

#[test]
fn generic_json_dedups_against_the_bundled_catalog() {
    let mut findings = normalize_generic_json(&load("generic-findings.json"), "vendor-cli", false);
    assert_eq!(findings.len(), 2);
    dedup_against_catalog(&mut findings, &["SEC-001", "SEC-003"]);

    // The SEC-001 finding overlaps the bundled catalog, so it is marked a
    // duplicate and does not count toward a grade a second time.
    let injected = &findings[0];
    assert_eq!(injected.rule_id, "SEC-001");
    assert_eq!(injected.duplicate_of, Some("SEC-001".to_string()));
    assert!(!injected.counts_toward_grade());

    // The vendor-specific finding is novel and gradeable.
    let exfil = &findings[1];
    assert_eq!(exfil.rule_id, "vendor.exfil-hint");
    assert_eq!(exfil.severity, SecuritySeverity::Critical);
    assert_eq!(exfil.duplicate_of, None);
    assert!(exfil.counts_toward_grade());
}
