icydb-model-macros 0.284.0

Procedural macros for IcyDB application models
Documentation
//! Module: imp::default::tests
//! Responsibility: regression coverage for this module.
//! Does not own: production behavior.
//! Boundary: test-only contracts.

use super::{default_strategy_entity, record_default_strategy, validate_struct_default_request};
use crate::authoring_types::Primitive;
use crate::{
    node::{
        Arg, Def, Entity, Field, FieldGeneration, FieldList, Item, PrimaryKey, PrimaryKeySource,
        Record, Timestamps, Type, Value,
    },
    trait_kind::{TraitBuilder, TraitKind},
};
use quote::format_ident;
use syn::parse_quote;

fn field_with_primitive_default(ident: &str, primitive: Primitive, default: Arg) -> Field {
    Field {
        name: format_ident!("{ident}"),
        value: Value {
            opt: false,
            many: false,
            item: Item {
                primitive: Some(primitive),
                ..Item::default()
            },
        },
        default: Some(default),
        generated: None,
        write_management: None,
    }
}

fn required_field_without_default(ident: &str, primitive: Primitive) -> Field {
    Field {
        name: format_ident!("{ident}"),
        value: Value {
            opt: false,
            many: false,
            item: Item {
                primitive: Some(primitive),
                ..Item::default()
            },
        },
        default: None,
        generated: None,
        write_management: None,
    }
}

fn redundant_default_entity() -> Entity {
    Entity {
        def: Def::new(
            syn::parse_quote!(
                struct RedundantDefaultEntity;
            ),
            crate::crate_path::CratePaths::test(),
        ),
        store: syn::parse_quote!(UiDataStore),
        schema_version: 1,
        primary_key: PrimaryKey {
            fields: vec![format_ident!("id")],
            source: PrimaryKeySource::Internal,
        },
        emit_runtime_adapters: false,
        indexes: vec![],
        relations: vec![],
        constraints: vec![],
        timestamps: Timestamps::default(),
        fields: FieldList {
            fields: vec![
                field_with_primitive_default(
                    "id",
                    Primitive::Nat64,
                    Arg::FuncPath(parse_quote!(u64::default)),
                ),
                field_with_primitive_default(
                    "name",
                    Primitive::Text,
                    Arg::FuncPath(parse_quote!(String::new)),
                ),
            ],
        },
        ty: Type::default(),
        traits: TraitBuilder::default(),
    }
}

#[test]
fn entity_defaults_derive_when_explicit_defaults_match_implicit_defaults() {
    let strategy = default_strategy_entity(
        &redundant_default_entity(),
        &crate::crate_path::CratePaths::test(),
    );

    assert_eq!(strategy.derive, Some(TraitKind::Default));
    assert!(
        strategy.imp.is_none(),
        "redundant defaults should not force a manual Default impl",
    );
}

#[test]
fn entity_default_request_rejects_required_fields_without_construction_values() {
    let mut entity = redundant_default_entity();
    entity
        .fields
        .fields
        .push(required_field_without_default("score", Primitive::Int32));

    validate_struct_default_request(
        "entity",
        &entity.def,
        &entity.fields,
        &crate::crate_path::CratePaths::test(),
    )
    .expect_err("an explicit entity Default request must reject missing construction values");
}

#[test]
fn entity_defaults_keep_manual_impl_for_custom_default_constructors() {
    let mut entity = redundant_default_entity();
    entity.fields.fields[0].default = Some(Arg::FuncPath(parse_quote!(Ulid::generate)));
    entity.fields.fields[0].value.item.primitive = Some(Primitive::Ulid);

    let strategy = default_strategy_entity(&entity, &crate::crate_path::CratePaths::test());

    assert!(
        strategy.derive.is_none(),
        "custom defaults must still bypass derive(Default)",
    );
    assert!(
        strategy.imp.is_some(),
        "custom defaults still require an explicit Default impl",
    );
}

#[test]
fn entity_identity_default_never_calls_the_declarative_generator() {
    let mut entity = redundant_default_entity();
    entity.fields.fields[0].default = None;
    entity.fields.fields[0].generated = Some(FieldGeneration::Insert(Arg::FuncPath(parse_quote!(
        Identity::next
    ))));

    let strategy = default_strategy_entity(&entity, &crate::crate_path::CratePaths::test());
    let tokens = strategy
        .imp
        .expect("identity placeholder requires an explicit Default impl")
        .to_string();

    assert!(tokens.contains("id : :: core :: default :: Default :: default ()"));
    assert!(!tokens.contains("Identity :: next"));
}

#[test]
fn records_follow_the_same_redundant_default_rule() {
    let fields = FieldList {
        fields: vec![
            field_with_primitive_default(
                "enabled",
                Primitive::Bool,
                Arg::FuncPath(parse_quote!(bool::default)),
            ),
            field_with_primitive_default(
                "name",
                Primitive::Text,
                Arg::FuncPath(parse_quote!(String::new)),
            ),
        ],
    };
    let record = Record {
        def: Def::new(
            syn::parse_quote!(
                struct RedundantDefaultRecord;
            ),
            crate::crate_path::CratePaths::test(),
        ),
        emit_runtime_references: false,
        name: None,
        fields,
        traits: TraitBuilder::default(),
        ty: Type::default(),
    };

    let strategy = record_default_strategy(
        &record.def,
        &record.fields,
        &crate::crate_path::CratePaths::test(),
    );

    assert_eq!(strategy.derive, Some(TraitKind::Default));
    assert!(
        strategy.imp.is_none(),
        "records with redundant defaults should derive Default",
    );
}

#[test]
fn record_default_request_rejects_required_fields_without_construction_values() {
    let fields = FieldList {
        fields: vec![required_field_without_default("score", Primitive::Int32)],
    };
    let record = Record {
        def: Def::new(
            syn::parse_quote!(
                struct RequiredRecord;
            ),
            crate::crate_path::CratePaths::test(),
        ),
        emit_runtime_references: false,
        name: None,
        fields,
        traits: TraitBuilder::default(),
        ty: Type::default(),
    };

    validate_struct_default_request(
        "record",
        &record.def,
        &record.fields,
        &crate::crate_path::CratePaths::test(),
    )
    .expect_err("an explicit record Default request must reject missing construction values");
}