icydb-core 0.180.1

IcyDB — A schema-first typed query engine and persistence runtime for Internet Computer canisters
Documentation
use super::*;
use crate::{
    model::{EntityModel, FieldKind, FieldModel, IndexModel, PrimaryKeyModel},
    testing::entity_model_from_static,
};

static SCALAR_FIELDS: [FieldModel; 2] = [
    FieldModel::generated("id", FieldKind::Nat64),
    FieldModel::generated("name", FieldKind::Text { max_len: None }),
];
static EMPTY_INDEXES: [&IndexModel; 0] = [];
static SCALAR_MODEL: EntityModel = entity_model_from_static(
    "access::validate::tests::ScalarEntity",
    "ScalarEntity",
    &SCALAR_FIELDS[0],
    0,
    &SCALAR_FIELDS,
    &EMPTY_INDEXES,
);

static COMPOSITE_FIELDS: [FieldModel; 3] = [
    FieldModel::generated("tenant_id", FieldKind::Nat64),
    FieldModel::generated("local_id", FieldKind::Nat64),
    FieldModel::generated("label", FieldKind::Text { max_len: None }),
];
static COMPOSITE_PK_FIELDS: [&FieldModel; 2] = [&COMPOSITE_FIELDS[0], &COMPOSITE_FIELDS[1]];
static COMPOSITE_MODEL: EntityModel = EntityModel::generated_with_primary_key_model(
    "access::validate::tests::CompositeEntity",
    "CompositeEntity",
    1,
    PrimaryKeyModel::ordered(&COMPOSITE_PK_FIELDS),
    0,
    &COMPOSITE_FIELDS,
    &EMPTY_INDEXES,
);

#[test]
fn validate_pk_literal_keeps_scalar_key_validation() {
    let schema = SchemaInfo::cached_for_generated_entity_model(&SCALAR_MODEL);

    validate_access_structure_model(schema, &SCALAR_MODEL, &AccessPlan::by_key(Value::Nat64(7)))
        .expect("scalar primary-key literal should validate");

    let err = validate_access_structure_model(
        schema,
        &SCALAR_MODEL,
        &AccessPlan::by_key(Value::List(vec![Value::Nat64(7)])),
    )
    .expect_err("scalar primary-key validation should reject list literals");

    std::assert_matches!(err, AccessPlanError::PrimaryKeyMismatch { .. });
}

#[test]
fn validate_pk_literal_accepts_ordered_composite_key_value_list() {
    let schema = SchemaInfo::cached_for_generated_entity_model(&COMPOSITE_MODEL);
    let key = Value::List(vec![Value::Nat64(7), Value::Nat64(11)]);

    validate_access_structure_model(schema, &COMPOSITE_MODEL, &AccessPlan::by_key(key))
        .expect("ordered composite primary-key literal should validate");
}

#[test]
fn validate_pk_literal_accepts_composite_by_keys() {
    let schema = SchemaInfo::cached_for_generated_entity_model(&COMPOSITE_MODEL);
    let keys = vec![
        Value::List(vec![Value::Nat64(7), Value::Nat64(11)]),
        Value::List(vec![Value::Nat64(7), Value::Nat64(12)]),
    ];

    validate_access_structure_model(schema, &COMPOSITE_MODEL, &AccessPlan::by_keys(keys))
        .expect("ordered composite primary-key list should validate");
}

#[test]
fn runtime_invariants_validate_composite_by_key_shape() {
    let schema = SchemaInfo::cached_for_generated_entity_model(&COMPOSITE_MODEL);

    validate_access_runtime_invariants_with_schema(
        schema,
        &AccessPlan::by_key(Value::List(vec![Value::Nat64(7), Value::Nat64(11)])),
    )
    .expect("runtime access validation should accept ordered composite keys");

    let err = validate_access_runtime_invariants_with_schema(
        schema,
        &AccessPlan::by_key(Value::Nat64(7)),
    )
    .expect_err("runtime access validation should reject scalar value for composite key");

    std::assert_matches!(err, AccessPlanError::PrimaryKeyMismatch { .. });
}

#[test]
fn runtime_invariants_validate_composite_by_keys_shape() {
    let schema = SchemaInfo::cached_for_generated_entity_model(&COMPOSITE_MODEL);
    let valid_keys = vec![
        Value::List(vec![Value::Nat64(7), Value::Nat64(11)]),
        Value::List(vec![Value::Nat64(7), Value::Nat64(12)]),
    ];
    validate_access_runtime_invariants_with_schema(schema, &AccessPlan::by_keys(valid_keys))
        .expect("runtime access validation should accept ordered composite key lists");

    let invalid_keys = vec![
        Value::List(vec![Value::Nat64(7), Value::Nat64(11)]),
        Value::List(vec![Value::Nat64(7)]),
    ];
    let err =
        validate_access_runtime_invariants_with_schema(schema, &AccessPlan::by_keys(invalid_keys))
            .expect_err("runtime access validation should reject malformed composite key lists");

    std::assert_matches!(err, AccessPlanError::PrimaryKeyMismatch { .. });
}

#[test]
fn validate_pk_literal_rejects_composite_key_with_wrong_arity() {
    let schema = SchemaInfo::cached_for_generated_entity_model(&COMPOSITE_MODEL);
    let key = Value::List(vec![Value::Nat64(7)]);

    let err = validate_access_structure_model(schema, &COMPOSITE_MODEL, &AccessPlan::by_key(key))
        .expect_err("composite primary-key validation should reject wrong arity");

    std::assert_matches!(err, AccessPlanError::PrimaryKeyMismatch { .. });
}

#[test]
fn validate_pk_literal_rejects_composite_key_component_type_mismatch() {
    let schema = SchemaInfo::cached_for_generated_entity_model(&COMPOSITE_MODEL);
    let key = Value::List(vec![Value::Nat64(7), Value::Text("wrong".to_string())]);

    let err = validate_access_structure_model(schema, &COMPOSITE_MODEL, &AccessPlan::by_key(key))
        .expect_err("composite primary-key validation should reject component mismatch");

    std::assert_matches!(err, AccessPlanError::PrimaryKeyMismatch { .. });
}

#[test]
fn validate_pk_range_rejects_composite_primary_key_ranges() {
    let schema = SchemaInfo::cached_for_generated_entity_model(&COMPOSITE_MODEL);
    let range = AccessPlan::key_range(
        Value::List(vec![Value::Nat64(7), Value::Nat64(11)]),
        Value::List(vec![Value::Nat64(7), Value::Nat64(12)]),
    );

    let err = validate_access_structure_model(schema, &COMPOSITE_MODEL, &range)
        .expect_err("composite primary-key range access is deferred and should reject");

    std::assert_matches!(
        err,
        AccessPlanError::CompositePrimaryKeyRangeUnsupported { .. }
    );
}

#[test]
fn runtime_invariants_reject_composite_primary_key_ranges() {
    let schema = SchemaInfo::cached_for_generated_entity_model(&COMPOSITE_MODEL);
    let range = AccessPlan::key_range(
        Value::List(vec![Value::Nat64(7), Value::Nat64(11)]),
        Value::List(vec![Value::Nat64(7), Value::Nat64(12)]),
    );

    let err = validate_access_runtime_invariants_with_schema(schema, &range)
        .expect_err("runtime access validation should also reject composite key ranges");

    std::assert_matches!(
        err,
        AccessPlanError::CompositePrimaryKeyRangeUnsupported { .. }
    );
}