cadmpeg-ir 0.6.1

CAD data model, validation, diffing, and codec API for cadmpeg.
// SPDX-License-Identifier: Apache-2.0

use super::canonical_json_sha256;
use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy, ResourceDimension};
use cadmpeg_core::CodecError;

#[test]
fn canonical_hash_byte_charge_preserves_digest() {
    let value = std::collections::BTreeMap::from([
        ("escaped", "a\\b\"c\n".repeat(4096)),
        ("unicode", "æΩ🎛".repeat(4096)),
    ]);
    let expected = canonical_json_sha256(
        &cadmpeg_test_support::service_decode_context(),
        &value,
        "canonical hash fixture",
    )
    .unwrap();
    let ctx = cadmpeg_test_support::service_decode_context();
    let actual: Result<String, CodecError> =
        canonical_json_sha256(&ctx, &value, "test canonical hash bytes").map_err(Into::into);
    assert_eq!(actual.unwrap(), expected);
}

#[test]
fn canonical_hash_byte_charge_propagates_resource_refusal() {
    let value = vec!["retained record text"; 1024];
    let arena = DecodeArena::new();
    let mut policy = DecodePolicy::service();
    policy.limits.max_work_units = 0;
    let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
    let result: Result<String, CodecError> =
        canonical_json_sha256(&ctx, &value, "test canonical hash bytes").map_err(Into::into);
    assert!(
        matches!(result, Err(CodecError::ResourceLimit(limit)) if limit.dimension == ResourceDimension::WorkUnits && limit.operation == "test canonical hash bytes")
    );
}

#[test]
fn canonical_hash_admits_buffer_output_work_and_depth_with_the_original_refusal() {
    for dimension in [
        ResourceDimension::MaterializedBytes,
        ResourceDimension::RetainedBytes,
        ResourceDimension::WorkUnits,
        ResourceDimension::RecursionDepth,
    ] {
        let arena = DecodeArena::new();
        let mut policy = DecodePolicy::service();
        match dimension {
            ResourceDimension::MaterializedBytes => policy.limits.max_materialized_bytes = 0,
            ResourceDimension::RetainedBytes => policy.limits.max_retained_bytes = 0,
            ResourceDimension::WorkUnits => policy.limits.max_work_units = 0,
            ResourceDimension::RecursionDepth => policy.limits.max_recursion_depth = 0,
            _ => panic!("canonical hash refusal dimensions"),
        }
        let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
        let error: CodecError =
            canonical_json_sha256(&ctx, &"text", "canonical hash dimension fixture")
                .unwrap_err()
                .into();
        let CodecError::ResourceLimit(limit) = error else {
            panic!("hash must preserve resource refusal");
        };
        assert_eq!(limit.dimension, dimension);
        assert!(
            matches!(ctx.finish_session(), Err(CodecError::ResourceLimit(original)) if original == limit)
        );
    }
}

#[test]
fn canonical_hash_releases_its_buffer_before_the_next_digest() {
    let arena = DecodeArena::new();
    let mut policy = DecodePolicy::service();
    policy.limits.max_materialized_bytes = 8192;
    let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
    let first = canonical_json_sha256(&ctx, &"text", "canonical hash buffer fixture").unwrap();
    let second = canonical_json_sha256(&ctx, &"text", "canonical hash buffer fixture").unwrap();
    assert_eq!(first, second);
    let storage = ctx
        .reserve_scoped(8192, "canonical hash buffer released")
        .unwrap();
    drop(storage);
    ctx.finish_session().unwrap();
}