@@ -1616,6 +1616,177 @@ pub struct Model {
16161616 assert ! ( output. contains( "name" ) ) ;
16171617}
16181618
1619+ #[ test]
1620+ fn test_derive_response_base_name_handles_known_suffixes_and_fallback ( ) {
1621+ assert_eq ! ( derive_response_base_name( "UserResponse" ) , "User" ) ;
1622+ assert_eq ! ( derive_response_base_name( "UserRequest" ) , "User" ) ;
1623+ assert_eq ! ( derive_response_base_name( "UserSchema" ) , "User" ) ;
1624+ assert_eq ! ( derive_response_base_name( "User" ) , "User" ) ;
1625+ }
1626+
1627+ #[ test]
1628+ fn test_find_same_file_struct_metadata_reads_test_fixture_from_current_module ( ) {
1629+ let storage: HashMap < String , StructMetadata > = HashMap :: new ( ) ;
1630+ let metadata = find_same_file_struct_metadata ( "__VesperaSameFileLookupFixture" , & storage)
1631+ . expect ( "fixture should be found in schema_macro/mod.rs" ) ;
1632+
1633+ assert_eq ! ( metadata. name, "__VesperaSameFileLookupFixture" ) ;
1634+ assert ! (
1635+ metadata
1636+ . definition
1637+ . contains( "__VesperaSameFileLookupFixture" )
1638+ ) ;
1639+ assert ! ( metadata. definition. contains( "value" ) ) ;
1640+ }
1641+
1642+ #[ test]
1643+ fn test_has_derive_ignores_non_derive_attrs_and_detects_requested_derive ( ) {
1644+ let struct_item: syn:: ItemStruct = syn:: parse_str (
1645+ r#"
1646+ #[serde(rename_all = "camelCase")]
1647+ #[derive(Clone, Debug)]
1648+ struct Sample {
1649+ value: i32,
1650+ }
1651+ "# ,
1652+ )
1653+ . unwrap ( ) ;
1654+
1655+ assert ! ( has_derive( & struct_item, "Clone" ) ) ;
1656+ assert ! ( !has_derive( & struct_item, "Deserialize" ) ) ;
1657+ }
1658+
1659+ #[ test]
1660+ fn test_build_named_struct_field_assignments_rejects_tuple_structs ( ) {
1661+ let struct_item: syn:: ItemStruct = syn:: parse_str ( "struct TupleDto(String);" ) . unwrap ( ) ;
1662+ let source_expr = quote ! ( source) ;
1663+ let error = build_named_struct_field_assignments ( & struct_item, & source_expr) . unwrap_err ( ) ;
1664+ assert ! ( error. to_string( ) . contains( "named-field struct" ) ) ;
1665+ }
1666+
1667+ #[ test]
1668+ fn test_build_proxy_fields_rejects_tuple_structs ( ) {
1669+ let struct_item: syn:: ItemStruct = syn:: parse_str ( "struct TupleDto(String);" ) . unwrap ( ) ;
1670+ let error = build_proxy_fields ( & struct_item) . unwrap_err ( ) ;
1671+ assert ! ( error. to_string( ) . contains( "named-field struct" ) ) ;
1672+ }
1673+
1674+ #[ test]
1675+ fn test_build_proxy_to_dto_assignments_rejects_tuple_structs ( ) {
1676+ let struct_item: syn:: ItemStruct = syn:: parse_str ( "struct TupleDto(String);" ) . unwrap ( ) ;
1677+ let error = build_proxy_to_dto_assignments ( & struct_item) . unwrap_err ( ) ;
1678+ assert ! ( error. to_string( ) . contains( "named-field struct" ) ) ;
1679+ }
1680+
1681+ #[ test]
1682+ fn test_build_clone_assignments_rejects_tuple_structs ( ) {
1683+ let struct_item: syn:: ItemStruct = syn:: parse_str ( "struct TupleDto(String);" ) . unwrap ( ) ;
1684+ let error = build_clone_assignments ( & struct_item) . unwrap_err ( ) ;
1685+ assert ! ( error. to_string( ) . contains( "named-field struct" ) ) ;
1686+ }
1687+
1688+ #[ test]
1689+ fn test_maybe_generate_same_file_relation_override_returns_none_when_dto_is_missing ( ) {
1690+ let rel_info = RelationFieldInfo {
1691+ field_name : syn:: Ident :: new ( "user" , proc_macro2:: Span :: call_site ( ) ) ,
1692+ relation_type : "HasOne" . to_string ( ) ,
1693+ schema_path : quote ! ( crate :: models:: user:: Schema ) ,
1694+ is_optional : true ,
1695+ inline_type_info : None ,
1696+ relation_enum : None ,
1697+ fk_column : None ,
1698+ via_rel : None ,
1699+ } ;
1700+
1701+ let storage: HashMap < String , StructMetadata > = HashMap :: new ( ) ;
1702+ let new_type_name = syn:: Ident :: new ( "ArticleResponse" , proc_macro2:: Span :: call_site ( ) ) ;
1703+
1704+ let result =
1705+ maybe_generate_same_file_relation_override ( & new_type_name, "user" , & rel_info, & storage)
1706+ . expect ( "missing dto should not error" ) ;
1707+ assert ! ( result. is_none( ) ) ;
1708+ }
1709+
1710+ #[ test]
1711+ fn test_maybe_generate_same_file_relation_override_returns_none_for_invalid_model_type ( ) {
1712+ let rel_info = RelationFieldInfo {
1713+ field_name : syn:: Ident :: new ( "user" , proc_macro2:: Span :: call_site ( ) ) ,
1714+ relation_type : "HasOne" . to_string ( ) ,
1715+ schema_path : quote ! ( ?) ,
1716+ is_optional : true ,
1717+ inline_type_info : None ,
1718+ relation_enum : None ,
1719+ fk_column : None ,
1720+ via_rel : None ,
1721+ } ;
1722+
1723+ let storage = to_storage ( vec ! [ create_test_struct_metadata(
1724+ "UserInArticle" ,
1725+ "struct UserInArticle { id: i32 }" ,
1726+ ) ] ) ;
1727+ let new_type_name = syn:: Ident :: new ( "ArticleResponse" , proc_macro2:: Span :: call_site ( ) ) ;
1728+
1729+ let result =
1730+ maybe_generate_same_file_relation_override ( & new_type_name, "user" , & rel_info, & storage)
1731+ . expect ( "invalid model type should not error" ) ;
1732+ assert ! ( result. is_none( ) ) ;
1733+ }
1734+
1735+ #[ test]
1736+ fn test_generate_schema_type_code_normal_mode_relation_rename_and_custom_name ( ) {
1737+ let storage = to_storage ( vec ! [ create_test_struct_metadata(
1738+ "Model" ,
1739+ r#"#[sea_orm(table_name = "articles")]
1740+ pub struct Model {
1741+ pub id: i32,
1742+ pub name: String,
1743+ pub owner: HasOne<super::user::Entity>
1744+ }"# ,
1745+ ) ] ) ;
1746+
1747+ let tokens = quote ! (
1748+ ArticleResponse from Model ,
1749+ name = "CustomArticleSchema" ,
1750+ rename = [ ( "name" , "display_name" ) ]
1751+ ) ;
1752+ let input: SchemaTypeInput = syn:: parse2 ( tokens) . unwrap ( ) ;
1753+ let result = generate_schema_type_code ( & input, & storage) ;
1754+
1755+ assert ! ( result. is_ok( ) ) ;
1756+ let ( tokens, metadata) = result. unwrap ( ) ;
1757+ let output = tokens. to_string ( ) ;
1758+ assert ! ( output. contains( "display_name" ) ) ;
1759+ assert ! ( output. contains( "owner" ) ) ;
1760+ assert ! ( output. contains( "Clone" ) ) ;
1761+ assert ! ( output. contains( "CustomArticleSchema" ) ) ;
1762+ assert_eq ! ( metadata. unwrap( ) . name, "CustomArticleSchema" ) ;
1763+ }
1764+
1765+ #[ test]
1766+ fn test_generate_schema_type_code_multipart_with_add_and_custom_name ( ) {
1767+ let storage = to_storage ( vec ! [ create_test_struct_metadata(
1768+ "Upload" ,
1769+ "pub struct Upload { pub id: i32, pub name: String }" ,
1770+ ) ] ) ;
1771+
1772+ let tokens = quote ! (
1773+ UploadForm from Upload ,
1774+ multipart,
1775+ name = "UploadFormSchema" ,
1776+ add = [ ( "extra" : String ) ]
1777+ ) ;
1778+ let input: SchemaTypeInput = syn:: parse2 ( tokens) . unwrap ( ) ;
1779+ let result = generate_schema_type_code ( & input, & storage) ;
1780+
1781+ assert ! ( result. is_ok( ) ) ;
1782+ let ( tokens, metadata) = result. unwrap ( ) ;
1783+ let output = tokens. to_string ( ) ;
1784+ assert ! ( output. contains( "vespera :: Multipart" ) ) ;
1785+ assert ! ( output. contains( "extra" ) ) ;
1786+ assert ! ( output. contains( "UploadFormSchema" ) ) ;
1787+ assert_eq ! ( metadata. unwrap( ) . name, "UploadFormSchema" ) ;
1788+ }
1789+
16191790// ============================================================
16201791// Tests for BelongsTo/HasOne circular reference inline types
16211792// ============================================================
0 commit comments