cadmpeg-ir 0.6.2

CAD data model, validation, diffing, and codec API for cadmpeg.
// SPDX-License-Identifier: Apache-2.0
use super::super::NativeRecord;
use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy};
use serde::Serialize;

#[test]
fn borrowed_comparison_checks_large_payloads_without_cloning_them() {
    #[derive(Serialize)]
    struct Record<'a> {
        id: &'a str,
        data: &'a [u8],
        text: &'a str,
    }
    let mut data = vec![123; 100_000];
    let expected = Record {
        id: "test:native:record#payload",
        data: &data,
        text: "escape \" ☃",
    };
    let stored = NativeRecord::from_typed(&expected).unwrap();
    let arena = DecodeArena::new();
    let policy = DecodePolicy::service();
    let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &policy).unwrap();
    assert!(stored.matches_typed_for_decode(&ctx, &expected).unwrap());
    assert!(ctx.finish_session().is_ok());
    data[99_999] = 124;
    let changed = Record {
        id: "test:native:record#payload",
        data: &data,
        text: "escape \" ☃",
    };
    assert!(!stored
        .matches_typed_for_decode(&super::super::test_ctx(), &changed)
        .unwrap());
}

#[test]
fn borrowed_comparison_preserves_every_json_shape_and_rejects_drift() {
    #[derive(Serialize)]
    enum Variant {
        Unit,
        New(u64),
        Tuple(u8, bool),
        Struct { text: &'static str },
    }
    #[derive(Serialize)]
    struct Record<'a> {
        id: &'a str,
        variants: [Variant; 4],
        maybe: Option<u32>,
        raw: &'a serde_json::value::RawValue,
        map: std::collections::BTreeMap<i32, bool>,
    }
    let raw =
        serde_json::value::RawValue::from_string("{\"payload\":[1,null,\"x\"]}".into()).unwrap();
    let expected = Record {
        id: "test:native:record#shapes",
        variants: [
            Variant::Unit,
            Variant::New(7),
            Variant::Tuple(3, true),
            Variant::Struct { text: "word" },
        ],
        maybe: None,
        raw: &raw,
        map: [(-3, true), (7, false)].into(),
    };
    let stored = NativeRecord::from_typed(&expected).unwrap();
    assert!(stored
        .matches_typed_for_decode(&super::super::test_ctx(), &expected)
        .unwrap());
    let json = serde_json::to_value(&stored).unwrap();
    for changed in [
        {
            let mut value = json.clone();
            value.as_object_mut().unwrap().remove("maybe");
            value
        },
        {
            let mut value = json.clone();
            value["extra"] = serde_json::json!(1);
            value
        },
        {
            let mut value = json.clone();
            value["variants"][2]["Tuple"][1] = serde_json::json!(false);
            value
        },
        {
            let mut value = json.clone();
            value["map"]["7"] = serde_json::json!(true);
            value
        },
    ] {
        let changed: NativeRecord = serde_json::from_value(changed).unwrap();
        assert!(!changed
            .matches_typed_for_decode(&super::super::test_ctx(), &expected)
            .unwrap());
    }
}

#[test]
fn borrowed_comparison_handles_raw_record_roots_and_rejects_duplicate_keys() {
    struct Duplicate;
    impl Serialize for Duplicate {
        fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
            use serde::ser::SerializeMap;
            let mut map = serializer.serialize_map(Some(2))?;
            map.serialize_entry("id", "test:native:record#raw")?;
            map.serialize_entry("id", "test:native:record#raw")?;
            map.end()
        }
    }

    let raw = serde_json::value::RawValue::from_string(
        r#"{"id":"test:native:record#raw","data":[1,2,3]}"#.into(),
    )
    .unwrap();
    let stored = NativeRecord::from_typed(&raw).unwrap();
    assert!(stored
        .matches_typed_for_decode(&super::super::test_ctx(), &raw)
        .unwrap());
    assert!(stored
        .matches_typed_for_decode(&super::super::test_ctx(), &Duplicate)
        .is_err());
}

#[test]
fn borrowed_comparison_enforces_native_depth_even_when_the_actual_record_differs() {
    let stored = NativeRecord::from_typed(&serde_json::json!({
        "id": "test:native:record#depth", "data": null,
    }))
    .unwrap();
    let mut data = serde_json::Value::Null;
    for _ in 0..=super::super::MAX_NATIVE_NESTING_DEPTH {
        data = serde_json::Value::Array(vec![data]);
    }
    let expected = serde_json::json!({ "id": stored.id(), "data": data });
    assert!(stored
        .matches_typed_for_decode(&super::super::test_ctx(), &expected)
        .is_err());
}

#[test]
fn borrowed_comparison_refuses_at_the_depth_limit_and_rejects_a_nonfinite_expected_number() {
    #[derive(Serialize)]
    struct Nonfinite<'a> {
        id: &'a str,
        value: f64,
    }

    let expected = serde_json::json!({ "id": "test:native:record#limits", "data": [1,2,3] });
    let stored: NativeRecord = serde_json::from_value(expected.clone()).unwrap();
    let arena = DecodeArena::new();
    let mut policy = DecodePolicy::service();
    policy.limits.max_recursion_depth = 0;
    let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &policy).unwrap();
    let error = stored
        .matches_typed_for_decode(&ctx, &expected)
        .unwrap_err();
    let cadmpeg_core::CodecError::ResourceLimit(first) = cadmpeg_core::CodecError::from(error)
    else {
        panic!("resource refusal")
    };
    assert_eq!(
        first.dimension,
        cadmpeg_core::decode::ResourceDimension::RecursionDepth
    );
    assert_eq!(ctx.resource_refusal(), Some(first));
    assert!(stored.matches_typed_for_decode(&ctx, &expected).is_err());
    assert!(stored
        .matches_typed_for_decode(
            &super::super::test_ctx(),
            &Nonfinite {
                id: "test:native:record#limits",
                value: f64::NAN
            }
        )
        .is_err());
}