allow-report 0.1.4

Report and receipt rendering for cargo-allow source exception scans.
Documentation
use super::*;

#[test]
fn migrate_json_renderer_records_io_summary_and_notes() {
    let report = MigrateReport {
        inventory: InventoryContext::new(
            "source_tree",
            "policy_migration",
            "git_tracked",
            Some("H:/Code/Rust/cargo-allow"),
            Some(76),
        ),
        input_kind: "repo_policy",
        input_path: "policy",
        output_path: "policy/allow.toml",
        force: true,
        allow_entries: 12,
        baseline_debt: 5,
        unsafe_entries: 2,
        lint_exception_entries: 4,
        entries_with_evidence: 3,
        weak_evidence_references: Some(2),
        unsafe_weak_evidence_references: Some(1),
        notes: "migration notes",
    };

    let json = render_migrate_json(report);

    assert!(json.contains("\"schema_id\": \"cargo-allow.migrate.v1\""));
    assert!(json.contains("\"command\": \"migrate\""));
    assert!(json.contains("\"scanner\": \"policy_migration\""));
    assert!(json.contains("\"source\": \"git_tracked\""));
    assert!(json.contains("\"files_scanned\": 76"));
    assert!(json.contains("\"kind\": \"repo_policy\""));
    assert!(json.contains("\"path\": \"policy\""));
    assert!(json.contains("\"path\": \"policy/allow.toml\""));
    assert!(json.contains("\"force\": true"));
    assert!(json.contains("\"allow_entries\": 12"));
    assert!(json.contains("\"baseline_debt\": 5"));
    assert!(json.contains("\"unsafe_entries\": 2"));
    assert!(json.contains("\"lint_exception_entries\": 4"));
    assert!(json.contains("\"entries_with_evidence\": 3"));
    assert!(json.contains("\"weak_evidence_references\": 2"));
    assert!(json.contains("\"unsafe_weak_evidence_references\": 1"));
    assert!(json.contains("\"notes\": \"migration notes\""));
    let expected = format!(
        r#"{{
  "schema_version": 1,
  "schema_id": "cargo-allow.migrate.v1",
  "tool": "cargo-allow",
  "command": "migrate",
  "claim_boundary": {},
  "scanner_limitations": {},
  "inventory": {{
    "scope": "source_tree",
    "scanner": "policy_migration",
    "source": "git_tracked",
    "root": "H:/Code/Rust/cargo-allow",
    "files_scanned": 76
  }},
  "input": {{
    "kind": "repo_policy",
    "path": "policy"
  }},
  "output": {{
    "path": "policy/allow.toml",
    "force": true
  }},
  "summary": {{
    "allow_entries": 12,
    "baseline_debt": 5,
    "unsafe_entries": 2,
    "lint_exception_entries": 4,
    "entries_with_evidence": 3,
    "weak_evidence_references": 2,
    "unsafe_weak_evidence_references": 1
  }},
  "notes": "migration notes"
}}
"#,
        render_claim_boundary_json(),
        render_scanner_limitations_json()
    );
    assert_eq!(json, expected);

    let text = render_migrate_human(report);

    assert!(text.contains("cargo-allow migrate summary"));
    assert!(text.contains("input_kind: repo_policy"));
    assert!(text.contains("input: policy"));
    assert!(text.contains("output: policy/allow.toml"));
    assert!(text.contains("force: true"));
    assert!(text.contains("allow_entries: 12"));
    assert!(text.contains("baseline_debt: 5"));
    assert!(text.contains("unsafe_entries: 2"));
    assert!(text.contains("lint_exception_entries: 4"));
    assert!(text.contains("entries_with_evidence: 3"));
    assert!(text.contains("weak_evidence_references: 2"));
    assert!(text.contains("unsafe_weak_evidence_references: 1"));
    assert!(
        text.contains("inventory: source_tree/policy_migration via git_tracked; files scanned: 76")
    );
    assert!(text.contains("source_tree_root: H:/Code/Rust/cargo-allow"));
    assert!(text.contains("migration notes"));
    assert!(text.contains(CLAIM_BOUNDARY_TEXT));
}

#[test]
fn migrate_report_from_config_counts_summary_fields() {
    let mut cfg = allow_core::AllowConfig::empty();
    cfg.allow = vec![
        allow_entry(
            "allow-baseline",
            allow_core::FindingKind::Panic,
            "baseline_debt",
            &[],
        ),
        allow_entry(
            "allow-unsafe",
            allow_core::FindingKind::Unsafe,
            "ffi_boundary",
            &["doc:docs/safety.md"],
        ),
        allow_entry(
            "allow-non-rust",
            allow_core::FindingKind::NonRustFile,
            "release_script",
            &["issue:123"],
        ),
        allow_entry(
            "allow-lint",
            allow_core::FindingKind::LintException,
            "lint_exception",
            &[],
        ),
    ];

    let report = MigrateReport::from_config(
        InventoryContext::new(
            "source_tree",
            "policy_migration",
            "filesystem_fallback",
            Some("snapshot"),
            Some(3),
        ),
        &cfg,
        "repo_policy",
        "policy",
        "policy/allow.toml",
        false,
        "migration notes",
    );

    assert_eq!(report.allow_entries, 4);
    assert_eq!(report.baseline_debt, 1);
    assert_eq!(report.unsafe_entries, 1);
    assert_eq!(report.lint_exception_entries, 1);
    assert_eq!(report.entries_with_evidence, 2);
    assert_eq!(report.weak_evidence_references, None);
    assert_eq!(report.unsafe_weak_evidence_references, None);
    assert_eq!(report.inventory.scanner, "policy_migration");
}

fn allow_entry(
    id: &str,
    kind: allow_core::FindingKind,
    classification: &str,
    evidence: &[&str],
) -> allow_core::AllowEntry {
    allow_core::AllowEntry {
        id: id.to_string(),
        kind,
        family: None,
        path: None,
        glob: None,
        owner: "owner".to_string(),
        classification: classification.to_string(),
        reason: "reason".to_string(),
        evidence: evidence.iter().map(|item| (*item).to_string()).collect(),
        links: Vec::new(),
        occurrence_limit: None,
        lifecycle: allow_core::Lifecycle::empty(),
        selector: allow_core::Selector::default(),
        last_seen: None,
    }
}