cargo-allow 0.1.3

Source-tree exception ledger and policy scanner for Rust repositories.
use crate::artifact_schema_support::{
    assert_enum_equals, assert_required_fields, enum_strings, parse_schema, required_schema_pointer,
};
use allow_diff::{
    EvidenceChangeField, LifecycleChangeField, MetadataChangeField, RequirementChangeField,
    ScopeChangeField,
};
use serde_json::Value;

#[test]
fn report_schema_locks_diff_policy_detail_contracts() {
    let schema = parse_schema(
        "report",
        include_str!("../../../docs/schemas/report.schema.json"),
    );

    let policy_status_change =
        required_schema_pointer("report", &schema, "/$defs/policy_status_change");
    assert_eq!(
        policy_status_change
            .get("additionalProperties")
            .and_then(Value::as_bool),
        Some(false),
        "report policy status changes should reject unknown fields"
    );
    assert_required_fields(
        "report policy status change",
        policy_status_change,
        &["before", "after"],
    );
    for field in ["before", "after"] {
        assert_eq!(
            schema
                .pointer(&format!(
                    "/$defs/policy_status_change/properties/{field}/type/0"
                ))
                .and_then(Value::as_str),
            Some("string"),
            "report policy status {field} first type"
        );
        assert_eq!(
            schema
                .pointer(&format!(
                    "/$defs/policy_status_change/properties/{field}/type/1"
                ))
                .and_then(Value::as_str),
            Some("null"),
            "report policy status {field} second type"
        );
    }

    let requirement_change =
        required_schema_pointer("report", &schema, "/$defs/requirement_change");
    assert_eq!(
        requirement_change
            .get("additionalProperties")
            .and_then(Value::as_bool),
        Some(false),
        "report requirement changes should reject unknown fields"
    );
    assert_required_fields(
        "report requirement change",
        requirement_change,
        &["field", "before", "after"],
    );
    assert_eq!(
        schema
            .pointer("/$defs/requirement_change/properties/field/$ref")
            .and_then(Value::as_str),
        Some("#/$defs/requirement_change_field"),
        "report requirement changes should use the requirement field vocabulary"
    );
    for field in ["before", "after"] {
        assert_eq!(
            schema
                .pointer(&format!(
                    "/$defs/requirement_change/properties/{field}/type"
                ))
                .and_then(Value::as_str),
            Some("boolean"),
            "report requirement {field} type"
        );
    }
    assert_enum_equals(
        "report requirement fields",
        &schema,
        "/$defs/requirement_change_field/enum",
        &enum_strings(RequirementChangeField::ALL, RequirementChangeField::as_str),
    );

    let metadata_change = required_schema_pointer("report", &schema, "/$defs/metadata_change");
    assert_eq!(
        metadata_change
            .get("additionalProperties")
            .and_then(Value::as_bool),
        Some(false),
        "report metadata changes should reject unknown fields"
    );
    assert_required_fields(
        "report metadata change",
        metadata_change,
        &["field", "before", "after"],
    );
    assert_eq!(
        schema
            .pointer("/$defs/metadata_change/properties/field/$ref")
            .and_then(Value::as_str),
        Some("#/$defs/metadata_change_field"),
        "report metadata changes should use the metadata field vocabulary"
    );
    for field in ["before", "after"] {
        assert_eq!(
            schema
                .pointer(&format!("/$defs/metadata_change/properties/{field}/type/0"))
                .and_then(Value::as_str),
            Some("string"),
            "report metadata {field} first type"
        );
        assert_eq!(
            schema
                .pointer(&format!("/$defs/metadata_change/properties/{field}/type/1"))
                .and_then(Value::as_str),
            Some("null"),
            "report metadata {field} second type"
        );
    }
    assert_enum_equals(
        "report metadata fields",
        &schema,
        "/$defs/metadata_change_field/enum",
        &enum_strings(MetadataChangeField::ALL, MetadataChangeField::as_str),
    );

    let evidence_change = required_schema_pointer("report", &schema, "/$defs/evidence_change");
    assert_eq!(
        evidence_change
            .get("additionalProperties")
            .and_then(Value::as_bool),
        Some(false),
        "report evidence changes should reject unknown fields"
    );
    assert_required_fields(
        "report evidence change",
        evidence_change,
        &["field", "removed", "added"],
    );
    assert_eq!(
        schema
            .pointer("/$defs/evidence_change/properties/field/$ref")
            .and_then(Value::as_str),
        Some("#/$defs/evidence_change_field"),
        "report evidence changes should use the evidence field vocabulary"
    );
    for field in ["removed", "added"] {
        assert_eq!(
            schema
                .pointer(&format!("/$defs/evidence_change/properties/{field}/type"))
                .and_then(Value::as_str),
            Some("array"),
            "report evidence {field} type"
        );
        assert_eq!(
            schema
                .pointer(&format!(
                    "/$defs/evidence_change/properties/{field}/items/type"
                ))
                .and_then(Value::as_str),
            Some("string"),
            "report evidence {field} item type"
        );
        assert_eq!(
            schema
                .pointer(&format!(
                    "/$defs/evidence_change/properties/{field}/items/minLength"
                ))
                .and_then(Value::as_u64),
            Some(1),
            "report evidence {field} item minLength"
        );
    }
    assert_enum_equals(
        "report evidence fields",
        &schema,
        "/$defs/evidence_change_field/enum",
        &enum_strings(EvidenceChangeField::ALL, EvidenceChangeField::as_str),
    );

    let lifecycle_change = required_schema_pointer("report", &schema, "/$defs/lifecycle_change");
    assert_eq!(
        lifecycle_change
            .get("additionalProperties")
            .and_then(Value::as_bool),
        Some(false),
        "report lifecycle changes should reject unknown fields"
    );
    assert_required_fields(
        "report lifecycle change",
        lifecycle_change,
        &["field", "before", "after"],
    );
    assert_eq!(
        schema
            .pointer("/$defs/lifecycle_change/properties/field/$ref")
            .and_then(Value::as_str),
        Some("#/$defs/lifecycle_change_field"),
        "report lifecycle changes should use the lifecycle field vocabulary"
    );
    for field in ["before", "after"] {
        assert_eq!(
            schema
                .pointer(&format!(
                    "/$defs/lifecycle_change/properties/{field}/type/0"
                ))
                .and_then(Value::as_str),
            Some("string"),
            "report lifecycle {field} first type"
        );
        assert_eq!(
            schema
                .pointer(&format!(
                    "/$defs/lifecycle_change/properties/{field}/type/1"
                ))
                .and_then(Value::as_str),
            Some("null"),
            "report lifecycle {field} second type"
        );
    }
    assert_enum_equals(
        "report lifecycle fields",
        &schema,
        "/$defs/lifecycle_change_field/enum",
        &enum_strings(LifecycleChangeField::ALL, LifecycleChangeField::as_str),
    );

    let occurrence_limit =
        required_schema_pointer("report", &schema, "/$defs/occurrence_limit_change");
    assert_eq!(
        occurrence_limit
            .get("additionalProperties")
            .and_then(Value::as_bool),
        Some(false),
        "report occurrence limit changes should reject unknown fields"
    );
    assert_required_fields(
        "report occurrence limit change",
        occurrence_limit,
        &["before", "after"],
    );
    for field in ["before", "after"] {
        assert_eq!(
            schema
                .pointer(&format!(
                    "/$defs/occurrence_limit_change/properties/{field}/type/0"
                ))
                .and_then(Value::as_str),
            Some("integer"),
            "report occurrence limit {field} first type"
        );
        assert_eq!(
            schema
                .pointer(&format!(
                    "/$defs/occurrence_limit_change/properties/{field}/type/1"
                ))
                .and_then(Value::as_str),
            Some("null"),
            "report occurrence limit {field} second type"
        );
        assert_eq!(
            schema
                .pointer(&format!(
                    "/$defs/occurrence_limit_change/properties/{field}/minimum"
                ))
                .and_then(Value::as_u64),
            Some(0),
            "report occurrence limit {field} minimum"
        );
    }

    let scope_change = required_schema_pointer("report", &schema, "/$defs/scope_change");
    assert_eq!(
        scope_change
            .get("additionalProperties")
            .and_then(Value::as_bool),
        Some(false),
        "report scope changes should reject unknown fields"
    );
    assert_required_fields(
        "report scope change",
        scope_change,
        &["field", "before", "after"],
    );
    assert_eq!(
        schema
            .pointer("/$defs/scope_change/properties/field/$ref")
            .and_then(Value::as_str),
        Some("#/$defs/scope_change_field"),
        "report scope changes should use the scope field vocabulary"
    );
    for field in ["before", "after"] {
        assert_eq!(
            schema
                .pointer(&format!("/$defs/scope_change/properties/{field}/type/0"))
                .and_then(Value::as_str),
            Some("string"),
            "report scope {field} first type"
        );
        assert_eq!(
            schema
                .pointer(&format!("/$defs/scope_change/properties/{field}/type/1"))
                .and_then(Value::as_str),
            Some("null"),
            "report scope {field} second type"
        );
    }
    assert_enum_equals(
        "report scope fields",
        &schema,
        "/$defs/scope_change_field/enum",
        &enum_strings(ScopeChangeField::ALL, ScopeChangeField::as_str),
    );
}