icydb-model-macros 0.217.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;
        )),
        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());

    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)
        .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);

    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);
    let tokens = strategy
        .imp
        .expect("identity placeholder requires an explicit Default impl")
        .to_string();

    assert!(tokens.contains("id : 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;
        )),
        name: None,
        fields,
        traits: TraitBuilder::default(),
        ty: Type::default(),
    };

    let strategy = record_default_strategy(&record.def, &record.fields);

    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;
        )),
        name: None,
        fields,
        traits: TraitBuilder::default(),
        ty: Type::default(),
    };

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