entity-derive-impl 0.3.0

Internal proc-macro implementation for entity-derive. Use entity-derive instead.
Documentation
// SPDX-FileCopyrightText: 2025-2026 RAprogramm <andrey.rozanov.vl@gmail.com>
// SPDX-License-Identifier: MIT

//! Tests for command attribute parsing.
//!
//! This module contains comprehensive tests for the `#[command(...)]` attribute
//! parser. Tests cover all syntax variations, edge cases, and error handling.
//!
//! # Test Categories
//!
//! | Category | Tests | Coverage |
//! |----------|-------|----------|
//! | Basic | `parse_simple_command` | Name-only syntax |
//! | Fields | `parse_command_with_fields`, `*_multiple_fields` | Colon syntax |
//! | Options | `parse_requires_id_*`, `parse_source_*` | Option parsing |
//! | Payload | `parse_custom_payload_*`, `parse_command_with_result` | Custom types |
//! | Kind | `parse_kind_*` | Kind hint validation |
//! | Security | `parse_security_*` | Security override |
//! | Naming | `struct_name_*`, `handler_method_name_*` | Name generation |
//! | Errors | `parse_invalid_*`, `parse_unknown_*` | Error handling |
//!
//! # Test Methodology
//!
//! Tests use `syn::parse_quote!` to create struct definitions with attributes,
//! then verify the parsed `CommandDef` fields match expectations:
//!
//! ```rust,ignore
//! let input: syn::DeriveInput = syn::parse_quote! {
//!     #[command(Register)]
//!     struct User {}
//! };
//! let cmds = parse_command_attrs(&input.attrs);
//! assert_eq!(cmds[0].name.to_string(), "Register");
//! ```
//!
//! # Field Source Tests
//!
//! Tests verify correct source selection:
//!
//! | Input | Expected Source |
//! |-------|-----------------|
//! | `Register` | `Create` (default) |
//! | `source = "update"` | `Update` |
//! | `UpdateEmail: email` | `Fields(["email"])` |
//! | `payload = "T"` | `Custom(T)` |
//! | `requires_id` | `None` |

use proc_macro2::Span;
use syn::Ident;

use super::*;

#[test]
fn parse_simple_command() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Register)]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert_eq!(cmds[0].name.to_string(), "Register");
    assert_eq!(cmds[0].source, CommandSource::Create);
    assert!(!cmds[0].requires_id);
}

#[test]
fn parse_command_with_fields() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(UpdateEmail: email)]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert_eq!(cmds[0].name.to_string(), "UpdateEmail");
    if let CommandSource::Fields(ref fields) = cmds[0].source {
        assert_eq!(fields.len(), 1);
        assert_eq!(fields[0].to_string(), "email");
    } else {
        panic!("Expected Fields source");
    }
    assert!(cmds[0].requires_id);
}

#[test]
fn parse_command_with_multiple_fields() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(UpdateProfile: name, avatar, bio)]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    if let CommandSource::Fields(ref fields) = cmds[0].source {
        assert_eq!(fields.len(), 3);
        assert_eq!(fields[0].to_string(), "name");
        assert_eq!(fields[1].to_string(), "avatar");
        assert_eq!(fields[2].to_string(), "bio");
    } else {
        panic!("Expected Fields source");
    }
}

#[test]
fn parse_requires_id_command() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Deactivate, requires_id)]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert!(cmds[0].requires_id);
    assert_eq!(cmds[0].source, CommandSource::None);
}

#[test]
fn parse_custom_payload_command() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Transfer, payload = "TransferPayload")]
        struct Account {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert!(matches!(cmds[0].source, CommandSource::Custom(_)));
}

#[test]
fn parse_command_with_result() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Transfer, payload = "TransferPayload", result = "TransferResult")]
        struct Account {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert!(cmds[0].result_type.is_some());
}

#[test]
fn parse_multiple_commands() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Register)]
        #[command(UpdateEmail: email)]
        #[command(Deactivate, requires_id)]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 3);
    assert_eq!(cmds[0].name.to_string(), "Register");
    assert_eq!(cmds[1].name.to_string(), "UpdateEmail");
    assert_eq!(cmds[2].name.to_string(), "Deactivate");
}

#[test]
fn parse_kind_hint() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Delete, requires_id, kind = "delete")]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert_eq!(cmds[0].kind, CommandKindHint::Delete);
}

#[test]
fn struct_name_generation() {
    let cmd = CommandDef::new(Ident::new("Register", Span::call_site()));
    assert_eq!(cmd.struct_name("User").to_string(), "RegisterUser");
}

#[test]
fn handler_method_name_generation() {
    let cmd = CommandDef::new(Ident::new("UpdateEmail", Span::call_site()));
    assert_eq!(cmd.handler_method_name().to_string(), "handle_update_email");
}

#[test]
fn parse_source_update() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Modify, source = "update")]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert_eq!(cmds[0].source, CommandSource::Update);
    assert!(cmds[0].requires_id);
    assert_eq!(cmds[0].kind, CommandKindHint::Update);
}

#[test]
fn parse_source_none() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Ping, source = "none")]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert_eq!(cmds[0].source, CommandSource::None);
}

#[test]
fn parse_source_create_explicit() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Register, source = "create")]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert_eq!(cmds[0].source, CommandSource::Create);
}

#[test]
fn parse_kind_create() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Register, kind = "create")]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert_eq!(cmds[0].kind, CommandKindHint::Create);
}

#[test]
fn parse_kind_update() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Modify, kind = "update")]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert_eq!(cmds[0].kind, CommandKindHint::Update);
}

#[test]
fn parse_kind_custom() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Process, kind = "custom")]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert_eq!(cmds[0].kind, CommandKindHint::Custom);
}

#[test]
fn parse_trailing_comma() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Register,)]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert_eq!(cmds[0].name.to_string(), "Register");
}

#[test]
fn parse_invalid_source_returns_empty() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Test, source = "invalid")]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert!(cmds.is_empty());
}

#[test]
fn parse_invalid_kind_returns_empty() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Test, kind = "invalid")]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert!(cmds.is_empty());
}

#[test]
fn parse_unknown_option_returns_empty() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Test, unknown_option)]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert!(cmds.is_empty());
}

#[test]
fn ignores_non_command_attributes() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[derive(Debug)]
        #[entity(table = "users")]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert!(cmds.is_empty());
}

#[test]
fn parse_security_bearer() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(AdminDelete, requires_id, security = "admin")]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert_eq!(cmds[0].security(), Some("admin"));
    assert!(!cmds[0].is_public());
}

#[test]
fn parse_security_none() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(PublicList, security = "none")]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert!(cmds[0].is_public());
    assert!(cmds[0].has_security_override());
}

#[test]
fn default_no_security_override() {
    let input: syn::DeriveInput = syn::parse_quote! {
        #[command(Register)]
        struct User {}
    };
    let cmds = parse_command_attrs(&input.attrs);
    assert_eq!(cmds.len(), 1);
    assert!(!cmds[0].has_security_override());
    assert!(!cmds[0].is_public());
    assert_eq!(cmds[0].security(), None);
}