allow-report 0.1.3

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

#[test]
fn diff_json_renderer_includes_occurrence_limit_change() {
    let policy_changes = vec![DiffPolicyChange {
        severity: "fail",
        allow_id: "allow-0001",
        kind: "occurrence_limit_loosened",
        message: "allow-0001 occurrence_limit increased or removed",
        exception_identity: None,
        selector_identity: None,
        selector_precision: None,
        scope: None,
        occurrence_limit: Some(DiffOccurrenceLimitChange {
            before: Some(1),
            after: None,
        }),
        lifecycle: None,
        evidence: None,
        metadata: None,
        requirement: None,
        policy_status: None,
    }];

    let json = render_diff_posture_json(DiffReport {
        net_posture: "worse",
        reviewer_action: "block until fixed",
        summary: DiffPostureSummary {
            current_failures: 0,
            new_findings: 0,
            removed_findings: 0,
            policy_failures: 1,
            policy_review_items: 0,
            policy_improvements: 0,
        },
        finding_changes: &[],
        policy_changes: &policy_changes,
    });

    assert!(json.contains("\"kind\": \"occurrence_limit_loosened\""));
    assert!(json.contains("\"occurrence_limit\": {\"before\": 1, \"after\": null}"));
}

#[test]
fn diff_json_renderer_includes_lifecycle_change() {
    let policy_changes = vec![DiffPolicyChange {
        severity: "review",
        allow_id: "allow-0001",
        kind: "expiry_extended",
        message: "allow-0001 expiry extended or removed",
        exception_identity: None,
        selector_identity: None,
        selector_precision: None,
        scope: None,
        occurrence_limit: None,
        lifecycle: Some(DiffLifecycleChange {
            field: "expires",
            before: Some("2026-09-01"),
            after: Some("2026-12-01"),
        }),
        evidence: None,
        metadata: None,
        requirement: None,
        policy_status: None,
    }];

    let json = render_diff_posture_json(DiffReport {
        net_posture: "review-required",
        reviewer_action: "review policy lifecycle",
        summary: DiffPostureSummary {
            current_failures: 0,
            new_findings: 0,
            removed_findings: 0,
            policy_failures: 0,
            policy_review_items: 1,
            policy_improvements: 0,
        },
        finding_changes: &[],
        policy_changes: &policy_changes,
    });

    assert!(json.contains("\"kind\": \"expiry_extended\""));
    assert!(json.contains(
        "\"lifecycle\": {\"field\": \"expires\", \"before\": \"2026-09-01\", \"after\": \"2026-12-01\"}"
    ));
}

#[test]
fn diff_json_renderer_includes_evidence_change() {
    let removed = vec!["test:old-proof".to_string()];
    let added = vec!["doc:docs/new-proof.md".to_string()];
    let policy_changes = vec![DiffPolicyChange {
        severity: "fail",
        allow_id: "allow-0001",
        kind: "evidence_removed",
        message: "allow-0001 evidence removed",
        exception_identity: None,
        selector_identity: None,
        selector_precision: None,
        scope: None,
        occurrence_limit: None,
        lifecycle: None,
        evidence: Some(DiffEvidenceChange {
            field: "evidence",
            removed: &removed,
            added: &added,
        }),
        metadata: None,
        requirement: None,
        policy_status: None,
    }];

    let json = render_diff_posture_json(DiffReport {
        net_posture: "worse",
        reviewer_action: "block until fixed",
        summary: DiffPostureSummary {
            current_failures: 0,
            new_findings: 0,
            removed_findings: 0,
            policy_failures: 1,
            policy_review_items: 0,
            policy_improvements: 0,
        },
        finding_changes: &[],
        policy_changes: &policy_changes,
    });

    assert!(json.contains("\"kind\": \"evidence_removed\""));
    assert!(json.contains(
        "\"evidence\": {\"field\": \"evidence\", \"removed\": [\"test:old-proof\"], \"added\": [\"doc:docs/new-proof.md\"]}"
    ));
}

#[test]
fn diff_json_renderer_includes_metadata_change() {
    let policy_changes = vec![DiffPolicyChange {
        severity: "fail",
        allow_id: "allow-0001",
        kind: "owner_removed",
        message: "allow-0001 owner removed",
        exception_identity: None,
        selector_identity: None,
        selector_precision: None,
        scope: None,
        occurrence_limit: None,
        lifecycle: None,
        evidence: None,
        metadata: Some(DiffMetadataChange {
            field: "owner",
            before: Some("core"),
            after: None,
        }),
        requirement: None,
        policy_status: None,
    }];

    let json = render_diff_posture_json(DiffReport {
        net_posture: "worse",
        reviewer_action: "block until fixed",
        summary: DiffPostureSummary {
            current_failures: 0,
            new_findings: 0,
            removed_findings: 0,
            policy_failures: 1,
            policy_review_items: 0,
            policy_improvements: 0,
        },
        finding_changes: &[],
        policy_changes: &policy_changes,
    });

    assert!(json.contains("\"kind\": \"owner_removed\""));
    assert!(
        json.contains(
            "\"metadata\": {\"field\": \"owner\", \"before\": \"core\", \"after\": null}"
        )
    );
}

#[test]
fn diff_json_renderer_includes_requirement_change() {
    let policy_changes = vec![DiffPolicyChange {
        severity: "fail",
        allow_id: "requirements.owner_required",
        kind: "requirement_loosened",
        message: "requirements.owner_required loosened: true -> false",
        exception_identity: None,
        selector_identity: None,
        selector_precision: None,
        scope: None,
        occurrence_limit: None,
        lifecycle: None,
        evidence: None,
        metadata: None,
        requirement: Some(DiffRequirementChange {
            field: "owner_required",
            before: true,
            after: false,
        }),
        policy_status: None,
    }];

    let json = render_diff_posture_json(DiffReport {
        net_posture: "worse",
        reviewer_action: "block until fixed",
        summary: DiffPostureSummary {
            current_failures: 0,
            new_findings: 0,
            removed_findings: 0,
            policy_failures: 1,
            policy_review_items: 0,
            policy_improvements: 0,
        },
        finding_changes: &[],
        policy_changes: &policy_changes,
    });

    assert!(json.contains("\"kind\": \"requirement_loosened\""));
    assert!(json.contains(
        "\"requirement\": {\"field\": \"owner_required\", \"before\": true, \"after\": false}"
    ));
}

#[test]
fn diff_json_renderer_includes_policy_status_change() {
    let policy_changes = vec![DiffPolicyChange {
        severity: "fail",
        allow_id: "policy.status",
        kind: "policy_status_weakened",
        message: "policy.status weakened: active -> advisory",
        exception_identity: None,
        selector_identity: None,
        selector_precision: None,
        scope: None,
        occurrence_limit: None,
        lifecycle: None,
        evidence: None,
        metadata: None,
        requirement: None,
        policy_status: Some(DiffPolicyStatusChange {
            before: Some("active"),
            after: Some("advisory"),
        }),
    }];

    let json = render_diff_posture_json(DiffReport {
        net_posture: "worse",
        reviewer_action: "block until fixed",
        summary: DiffPostureSummary {
            current_failures: 0,
            new_findings: 0,
            removed_findings: 0,
            policy_failures: 1,
            policy_review_items: 0,
            policy_improvements: 0,
        },
        finding_changes: &[],
        policy_changes: &policy_changes,
    });

    assert!(json.contains("\"kind\": \"policy_status_weakened\""));
    assert!(json.contains("\"policy_status\": {\"before\": \"active\", \"after\": \"advisory\"}"));
}

#[test]
fn diff_json_renderer_includes_exception_identity_change() {
    let policy_changes = vec![DiffPolicyChange {
        severity: "fail",
        allow_id: "allow-0001",
        kind: "kind_changed",
        message: "allow-0001 changed governed exception kind: panic -> unsafe",
        exception_identity: Some(DiffExceptionIdentityChange {
            field: "kind",
            before: Some("panic"),
            after: Some("unsafe"),
        }),
        selector_identity: None,
        selector_precision: None,
        scope: None,
        occurrence_limit: None,
        lifecycle: None,
        evidence: None,
        metadata: None,
        requirement: None,
        policy_status: None,
    }];

    let json = render_diff_posture_json(DiffReport {
        net_posture: "worse",
        reviewer_action: "block until fixed",
        summary: DiffPostureSummary {
            current_failures: 0,
            new_findings: 0,
            removed_findings: 0,
            policy_failures: 1,
            policy_review_items: 0,
            policy_improvements: 0,
        },
        finding_changes: &[],
        policy_changes: &policy_changes,
    });

    assert!(json.contains("\"kind\": \"kind_changed\""));
    assert!(json.contains(
        "\"exception_identity\": {\"field\": \"kind\", \"before\": \"panic\", \"after\": \"unsafe\"}"
    ));
}

#[test]
fn diff_json_renderer_includes_selector_identity_change() {
    let changed_fields = ["container", "normalized_snippet_hash"];
    let policy_changes = vec![DiffPolicyChange {
        severity: "review",
        allow_id: "allow-0001",
        kind: "selector_changed",
        message: "allow-0001 selector identity changed",
        exception_identity: None,
        selector_identity: Some(DiffSelectorIdentityChange {
            changed_fields: &changed_fields,
        }),
        selector_precision: None,
        scope: None,
        occurrence_limit: None,
        lifecycle: None,
        evidence: None,
        metadata: None,
        requirement: None,
        policy_status: None,
    }];

    let json = render_diff_posture_json(DiffReport {
        net_posture: "review-required",
        reviewer_action: "review policy changes",
        summary: DiffPostureSummary {
            current_failures: 0,
            new_findings: 0,
            removed_findings: 0,
            policy_failures: 0,
            policy_review_items: 1,
            policy_improvements: 0,
        },
        finding_changes: &[],
        policy_changes: &policy_changes,
    });

    assert!(json.contains("\"kind\": \"selector_changed\""));
    assert!(json.contains(
        "\"selector_identity\": {\"changed_fields\": [\"container\", \"normalized_snippet_hash\"]}"
    ));
}