This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 8.0.x-hibernate7
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit f49f35a63487f09e6be5643e684fc44b1e9ab891
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Tue Feb 24 05:49:07 2026 -0600

    HibernateMappingBuilder tested
---
 .../hibernate/HibernateMappingBuilder.groovy       | 209 ++++++-----
 .../mapping/HibernateMappingBuilderSpec.groovy     | 402 +++++++++++++++++++++
 2 files changed, 521 insertions(+), 90 deletions(-)

diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateMappingBuilder.groovy
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateMappingBuilder.groovy
index cb991a1439..f037f480a8 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateMappingBuilder.groovy
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/hibernate/HibernateMappingBuilder.groovy
@@ -100,7 +100,7 @@ class HibernateMappingBuilder implements 
MappingConfigurationBuilder<Mapping, Pr
         }
     }
 
-    void hibernateCustomUserType(Map args) {
+    void hibernateCustomUserType(Map<String, Object> args) {
         if (args.type && (args['class'] instanceof Class)) {
             mapping.userTypes[(Class)args['class']] = args.type.toString()
         }
@@ -232,32 +232,34 @@ class HibernateMappingBuilder implements 
MappingConfigurationBuilder<Mapping, Pr
         mapping.cache = new CacheConfig(enabled: shouldCache)
     }
 
-    void id(Map args) {
+    void id(Map<String, Object> args) {
         if (args.composite) {
             mapping.identity = new CompositeIdentity(propertyNames: (String[]) 
args.composite)
             if (args.compositeClass) {
                 (mapping.identity as CompositeIdentity).compositeClass = 
(Class) args.compositeClass
             }
         } else {
-            if (args?.generator) {
-                ((Identity) mapping.identity).generator = 
args.remove('generator').toString()
+            Object generatorVal = args.remove('generator')
+            if (generatorVal != null) {
+                ((Identity) mapping.identity).generator = 
generatorVal.toString()
             }
-            if (args?.name) {
-                ((Identity) mapping.identity).name = 
args.remove('name').toString()
+            Object nameVal = args.remove('name')
+            if (nameVal != null) {
+                ((Identity) mapping.identity).name = nameVal.toString()
             }
-            if (args?.params) {
-                Map params = (Map) args.remove('params')
+            Object paramsVal = args.remove('params')
+            if (paramsVal instanceof Map) {
                 Map<String, String> stringParams = [:]
-                params.each { k, v -> stringParams[k.toString()] = 
v?.toString() }
+                ((Map<Object, Object>) paramsVal).each { k, v -> 
stringParams[k.toString()] = v?.toString() }
                 ((Identity) mapping.identity).params = stringParams
             }
         }
-        if (args?.natural) {
-            Object naturalArgs = args.remove('natural')
-            Object propertyNames = naturalArgs instanceof Map ? ((Map) 
naturalArgs).remove('properties') : naturalArgs
+        Object naturalVal = args.remove('natural')
+        if (naturalVal != null) {
+            Object propertyNames = naturalVal instanceof Map ? ((Map<String, 
Object>) naturalVal).remove('properties') : naturalVal
             if (propertyNames) {
                 NaturalId ni = new NaturalId()
-                ni.mutable = (naturalArgs instanceof Map) && ((Map) 
naturalArgs).mutable ?: false
+                ni.mutable = (naturalVal instanceof Map) && ((Map<String, 
Object>) naturalVal).mutable ?: false
                 if (propertyNames instanceof List) {
                     ni.propertyNames = (List<String>) propertyNames
                 } else {
@@ -281,7 +283,7 @@ class HibernateMappingBuilder implements 
MappingConfigurationBuilder<Mapping, Pr
     /**
      * Internal logic for building property configurations.
      */
-    protected void handlePropertyInternal(String name, Map namedArgs, Closure 
subClosure) {
+    protected void handlePropertyInternal(String name, Map<String, Object> 
namedArgs, Closure subClosure) {
         PropertyConfig newConfig = new PropertyConfig()
         if (defaultConstraints != null && namedArgs.containsKey('shared')) {
             PropertyConfig sharedConstraints = 
mapping.columns.get(namedArgs.shared.toString())
@@ -296,39 +298,46 @@ class HibernateMappingBuilder implements 
MappingConfigurationBuilder<Mapping, Pr
         }
 
         PropertyConfig property = mapping.columns[name] ?: newConfig
-        property.name = namedArgs.name?.toString() ?: property.name
-        property.generator = namedArgs.generator?.toString() ?: 
property.generator
-        property.formula = namedArgs.formula?.toString() ?: property.formula
-        property.accessType = namedArgs.accessType instanceof AccessType ? 
(AccessType)namedArgs.accessType : property.accessType
-        property.type = namedArgs.type ?: property.type
-        property.setLazy(namedArgs.lazy instanceof Boolean ? 
(Boolean)namedArgs.lazy : property.getLazy())
-        property.insertable = namedArgs.insertable instanceof Boolean ? 
(Boolean)namedArgs.insertable : property.insertable
-        property.updatable = (namedArgs.updateable != null ? 
namedArgs.updateable : namedArgs.updatable) instanceof Boolean ? 
(Boolean)(namedArgs.updateable ?: namedArgs.updatable) : property.updatable
-        property.cascade = namedArgs.cascade?.toString() ?: property.cascade
-        property.cascadeValidate = namedArgs.cascadeValidate instanceof 
Boolean ? (Boolean)namedArgs.cascadeValidate : property.cascadeValidate
-        property.sort = namedArgs.sort?.toString() ?: property.sort
-        property.order = namedArgs.order?.toString() ?: property.order
-        property.batchSize = namedArgs.batchSize instanceof Integer ? 
(Integer)namedArgs.batchSize : property.batchSize
-        property.ignoreNotFound = namedArgs.ignoreNotFound instanceof Boolean 
? (Boolean)namedArgs.ignoreNotFound : property.ignoreNotFound
+        Object nameVal = namedArgs.name
+        if (nameVal != null) property.name = nameVal.toString()
+        Object genVal = namedArgs.generator
+        if (genVal != null) property.generator = genVal.toString()
+        Object formulaVal = namedArgs.formula
+        if (formulaVal != null) property.formula = formulaVal.toString()
+        if (namedArgs.accessType instanceof AccessType) property.accessType = 
(AccessType) namedArgs.accessType
+        Object typeVal = namedArgs.type
+        if (typeVal != null) property.type = typeVal
+        if (namedArgs.lazy instanceof Boolean) property.setLazy((Boolean) 
namedArgs.lazy)
+        if (namedArgs.insertable instanceof Boolean) property.insertable = 
(Boolean) namedArgs.insertable
+        Object updateableVal = namedArgs.updateable != null ? 
namedArgs.updateable : namedArgs.updatable
+        if (updateableVal instanceof Boolean) property.updatable = (Boolean) 
updateableVal
+        Object cascadeVal = namedArgs.cascade
+        if (cascadeVal != null) property.cascade = cascadeVal.toString()
+        if (namedArgs.cascadeValidate instanceof Boolean) 
property.cascadeValidate = (Boolean) namedArgs.cascadeValidate
+        Object sortVal = namedArgs.sort
+        if (sortVal != null) property.sort = sortVal.toString()
+        Object orderVal = namedArgs.order
+        if (orderVal != null) property.order = orderVal.toString()
+        if (namedArgs.batchSize instanceof Integer) property.batchSize = 
(Integer) namedArgs.batchSize
+        if (namedArgs.ignoreNotFound instanceof Boolean) 
property.ignoreNotFound = (Boolean) namedArgs.ignoreNotFound
         if (namedArgs.params instanceof Map) {
             Properties typeProps = new Properties()
-            ((Map<Object, Object>)namedArgs.params).each { k, v -> 
typeProps.put(k, v) }
+            ((Map<Object, Object>) namedArgs.params).each { Object k, Object v 
-> typeProps.put(k, v) }
             property.typeParams = typeProps
         }
 
-        if (namedArgs.unique instanceof Boolean) 
property.setUnique((boolean)(Boolean)namedArgs.unique)
-        else if (namedArgs.unique instanceof String) 
property.setUnique((String)namedArgs.unique)
-        else if (namedArgs.unique instanceof List) 
property.setUnique((List<String>)namedArgs.unique)
-        property.nullable = namedArgs.nullable instanceof Boolean ? 
(Boolean)namedArgs.nullable : property.nullable
-        property.maxSize = namedArgs.maxSize instanceof Number ? 
(Number)namedArgs.maxSize : property.maxSize
-        property.minSize = namedArgs.minSize instanceof Number ? 
(Number)namedArgs.minSize : property.minSize
-
+        Object uniqueVal = namedArgs.unique
+        if (uniqueVal instanceof Boolean) 
property.setUnique((boolean)(Boolean) uniqueVal)
+        else if (uniqueVal instanceof String) property.setUnique((String) 
uniqueVal)
+        else if (uniqueVal instanceof List) property.setUnique((List<String>) 
uniqueVal)
+        if (namedArgs.nullable instanceof Boolean) property.nullable = 
(Boolean) namedArgs.nullable
+        if (namedArgs.maxSize instanceof Number) property.maxSize = (Number) 
namedArgs.maxSize
+        if (namedArgs.minSize instanceof Number) property.minSize = (Number) 
namedArgs.minSize
         if (namedArgs.size instanceof IntRange) property.size = (IntRange) 
namedArgs.size
-        property.max = namedArgs.max instanceof Comparable ? (Comparable) 
namedArgs.max : property.max
-        property.min = namedArgs.min instanceof Comparable ? (Comparable) 
namedArgs.min : property.min
-        property.range = namedArgs.range instanceof ObjectRange ? 
(ObjectRange) namedArgs.range : null
-        property.inList = namedArgs.inList instanceof List ? (List) 
namedArgs.inList : property.inList
-
+        if (namedArgs.max instanceof Comparable) property.max = (Comparable) 
namedArgs.max
+        if (namedArgs.min instanceof Comparable) property.min = (Comparable) 
namedArgs.min
+        if (namedArgs.range instanceof ObjectRange) property.range = 
(ObjectRange) namedArgs.range
+        if (namedArgs.inList instanceof List) property.inList = (List) 
namedArgs.inList
         if (namedArgs.scale instanceof Integer) property.scale = (Integer) 
namedArgs.scale
 
         if (namedArgs.fetch) {
@@ -346,34 +355,46 @@ class HibernateMappingBuilder implements 
MappingConfigurationBuilder<Mapping, Pr
             ColumnConfig cc = property.columns ? property.columns[0] : new 
ColumnConfig()
             if (!property.columns) property.columns << cc
 
-            if (namedArgs["column"]) cc.name = namedArgs["column"].toString()
-            if (namedArgs["sqlType"]) cc.sqlType = 
namedArgs["sqlType"].toString()
-            if (namedArgs["enumType"]) cc.enumType = 
namedArgs["enumType"].toString()
-            if (namedArgs["index"]) cc.index = namedArgs["index"].toString()
-            if (namedArgs["unique"]) cc.unique = namedArgs["unique"]
-            if (namedArgs["read"]) cc.read = namedArgs["read"].toString()
-            if (namedArgs["write"]) cc.write = namedArgs["write"].toString()
-            if (namedArgs.defaultValue) cc.defaultValue = 
namedArgs.defaultValue.toString()
-            if (namedArgs.comment) cc.comment = namedArgs.comment.toString()
-            if (namedArgs["length"] instanceof Integer) cc.length = 
(Integer)namedArgs["length"]
-            if (namedArgs["precision"] instanceof Integer) cc.precision = 
(Integer)namedArgs["precision"]
-            if (namedArgs["scale"] instanceof Integer) cc.scale = 
(Integer)namedArgs["scale"]
-
-            if (namedArgs.joinTable instanceof String) {
-                property.joinTable((String)namedArgs.joinTable)
-            } else if (namedArgs.joinTable instanceof Map) {
-                property.joinTable((Map)namedArgs.joinTable)
+            Object colVal = namedArgs["column"]
+            if (colVal) cc.name = colVal.toString()
+            Object sqlTypeVal = namedArgs["sqlType"]
+            if (sqlTypeVal) cc.sqlType = sqlTypeVal.toString()
+            Object enumTypeVal = namedArgs["enumType"]
+            if (enumTypeVal) cc.enumType = enumTypeVal.toString()
+            Object indexVal = namedArgs["index"]
+            if (indexVal) cc.index = indexVal.toString()
+            Object ccUniqueVal = namedArgs["unique"]
+            if (ccUniqueVal) cc.unique = ccUniqueVal instanceof Boolean ? 
(Boolean) ccUniqueVal : ccUniqueVal
+            Object readVal = namedArgs["read"]
+            if (readVal) cc.read = readVal.toString()
+            Object writeVal = namedArgs["write"]
+            if (writeVal) cc.write = writeVal.toString()
+            Object defaultVal = namedArgs.defaultValue
+            if (defaultVal) cc.defaultValue = defaultVal.toString()
+            Object commentVal = namedArgs.comment
+            if (commentVal) cc.comment = commentVal.toString()
+            if (namedArgs["length"] instanceof Integer) cc.length = (int) 
(Integer) namedArgs["length"]
+            if (namedArgs["precision"] instanceof Integer) cc.precision = 
(int) (Integer) namedArgs["precision"]
+            if (namedArgs["scale"] instanceof Integer) cc.scale = (int) 
(Integer) namedArgs["scale"]
+
+            Object joinTableVal = namedArgs.joinTable
+            if (joinTableVal instanceof String) {
+                property.joinTable((String) joinTableVal)
+            } else if (joinTableVal instanceof Map) {
+                property.joinTable((Map) joinTableVal)
             }
 
             if (namedArgs.indexColumn instanceof Map) {
-                Map icArgs = (Map)namedArgs.indexColumn
+                Map<String, Object> icArgs = (Map<String, Object>) 
namedArgs.indexColumn
                 PropertyConfig ic = new PropertyConfig()
                 ColumnConfig icc = new ColumnConfig()
-                if (icArgs.name) icc.name = icArgs.name.toString()
-                if (icArgs.type) icc.sqlType = icArgs.type.toString()
-                if (icArgs.length instanceof Integer) icc.length = 
(Integer)icArgs.length
+                Object icName = icArgs.name
+                if (icName) icc.name = icName.toString()
+                Object icType = icArgs.type
+                if (icType) icc.sqlType = icType.toString()
+                if (icArgs.length instanceof Integer) icc.length = (int) 
(Integer) icArgs.length
                 ic.columns << icc
-                ic.type = icArgs.type
+                ic.type = icType
                 property.indexColumn = ic
             }
         }
@@ -381,15 +402,18 @@ class HibernateMappingBuilder implements 
MappingConfigurationBuilder<Mapping, Pr
         // Cache association handling
         if (namedArgs.cache != null) {
             CacheConfig cc = new CacheConfig()
-            if (namedArgs.cache instanceof String && 
CacheConfig.USAGE_OPTIONS.contains(namedArgs.cache)) {
-                cc.usage = namedArgs.cache.toString()
+            Object cacheVal = namedArgs.cache
+            if (cacheVal instanceof String && 
CacheConfig.USAGE_OPTIONS.contains(cacheVal)) {
+                cc.usage = (String) cacheVal
                 property.cache = cc
-            } else if (namedArgs.cache == true) {
+            } else if (cacheVal == true) {
                 property.cache = cc
-            } else if (namedArgs.cache instanceof Map) {
-                Map cacheArgs = (Map) namedArgs.cache
-                cc.usage = cacheArgs.usage?.toString()
-                cc.include = cacheArgs.include?.toString()
+            } else if (cacheVal instanceof Map) {
+                Map<String, Object> cacheArgs = (Map<String, Object>) cacheVal
+                Object cacheUsage = cacheArgs.usage
+                if (cacheUsage != null) cc.usage = cacheUsage.toString()
+                Object cacheInclude = cacheArgs.include
+                if (cacheInclude != null) cc.include = cacheInclude.toString()
                 property.cache = cc
             }
         }
@@ -400,11 +424,13 @@ class HibernateMappingBuilder implements 
MappingConfigurationBuilder<Mapping, Pr
     void columns(@DelegatesTo(value = Object, strategy = 
Closure.DELEGATE_ONLY) Closure callable) {
         callable.resolveStrategy = Closure.DELEGATE_ONLY
         callable.delegate = new Object() {
-            def invokeMethod(String methodName, Object args) {
+            Object invokeMethod(String methodName, Object args) {
                 Object[] argsArray = (Object[]) args
-                Map namedArgs = (argsArray.length > 0 && argsArray[0] 
instanceof Map) ? (Map)argsArray[0] : [:]
-                Closure sub = (argsArray.length > 0 && 
argsArray[argsArray.length - 1] instanceof Closure) ? 
(Closure)argsArray[argsArray.length - 1] : null
+                int argc = argsArray.length
+                Map<String, Object> namedArgs = (argc > 0 && argsArray[0] 
instanceof Map) ? (Map<String, Object>) argsArray[0] : [:]
+                Closure sub = (argc > 0 && argsArray[argc - 1] instanceof 
Closure) ? (Closure) argsArray[argc - 1] : null
                 handlePropertyInternal(methodName, namedArgs, sub)
+                return null
             }
         }
         callable.call()
@@ -422,28 +448,31 @@ class HibernateMappingBuilder implements 
MappingConfigurationBuilder<Mapping, Pr
         mapping.comment = comment
     }
 
-    def methodMissing(String name, Object args) {
+    void methodMissing(String name, Object args) {
         if (methodMissingIncludes != null && 
!methodMissingIncludes.contains(name)) return
         if (methodMissingExcludes.contains(name)) return
 
         Object[] argsArray = (Object[]) args
-        boolean hasArgs = argsArray.length > 0
-        if (name == 'user-type' && hasArgs && argsArray[0] instanceof Map) {
-            hibernateCustomUserType((Map) argsArray[0])
-        } else if (name == 'importFrom' && hasArgs && argsArray[0] instanceof 
Class) {
+        int argc = argsArray.length
+        boolean hasArgs = argc > 0
+        Object firstArg = hasArgs ? argsArray[0] : null
+        Object lastArg = argc > 0 ? argsArray[argc - 1] : null
+
+        if (name == 'user-type' && hasArgs && firstArg instanceof Map) {
+            hibernateCustomUserType((Map<String, Object>) firstArg)
+        } else if (name == 'importFrom' && hasArgs && firstArg instanceof 
Class) {
             List<Closure> constraintsToImport = 
ClassPropertyFetcher.getStaticPropertyValuesFromInheritanceHierarchy(
-                (Class) argsArray[0], GormProperties.CONSTRAINTS, Closure)
+                (Class) firstArg, GormProperties.CONSTRAINTS, Closure)
             if (constraintsToImport) {
-                List<String> originalIncludes = this.methodMissingIncludes
-                List<String> originalExcludes = this.methodMissingExcludes
+                List<String> originalIncludes = methodMissingIncludes
+                List<String> originalExcludes = methodMissingExcludes
                 try {
-                    Object lastArg = argsArray[argsArray.length - 1]
                     if (lastArg instanceof Map) {
-                        Map argMap = (Map) lastArg
+                        Map<String, Object> argMap = (Map<String, Object>) 
lastArg
                         Object includes = argMap.get(INCLUDE_PARAM)
                         Object excludes = argMap.get(EXCLUDE_PARAM)
-                        if (includes instanceof List) 
this.methodMissingIncludes = (List<String>) includes
-                        if (excludes instanceof List) 
this.methodMissingExcludes = (List<String>) excludes
+                        if (includes instanceof List) methodMissingIncludes = 
(List<String>) includes
+                        if (excludes instanceof List) methodMissingExcludes = 
(List<String>) excludes
                     }
                     for (Closure callable in constraintsToImport) {
                         callable.delegate = this
@@ -451,13 +480,13 @@ class HibernateMappingBuilder implements 
MappingConfigurationBuilder<Mapping, Pr
                         callable.call()
                     }
                 } finally {
-                    this.methodMissingIncludes = originalIncludes
-                    this.methodMissingExcludes = originalExcludes
+                    methodMissingIncludes = originalIncludes
+                    methodMissingExcludes = originalExcludes
                 }
             }
-        } else if (hasArgs && (argsArray[0] instanceof Map || argsArray[0] 
instanceof Closure)) {
-            Map namedArgs = argsArray[0] instanceof Map ? (Map)argsArray[0] : 
[:]
-            Closure sub = argsArray[argsArray.length - 1] instanceof Closure ? 
(Closure)argsArray[argsArray.length - 1] : null
+        } else if (hasArgs && (firstArg instanceof Map || firstArg instanceof 
Closure)) {
+            Map<String, Object> namedArgs = firstArg instanceof Map ? 
(Map<String, Object>) firstArg : [:]
+            Closure sub = lastArg instanceof Closure ? (Closure) lastArg : null
             handlePropertyInternal(name, namedArgs, sub)
         }
     }
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/hibernate/mapping/HibernateMappingBuilderSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/hibernate/mapping/HibernateMappingBuilderSpec.groovy
new file mode 100644
index 0000000000..e72f31557a
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/hibernate/mapping/HibernateMappingBuilderSpec.groovy
@@ -0,0 +1,402 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package grails.gorm.hibernate.mapping
+
+import jakarta.persistence.AccessType
+import org.grails.orm.hibernate.cfg.CacheConfig
+import org.grails.orm.hibernate.cfg.Mapping
+import org.grails.orm.hibernate.cfg.PropertyConfig
+import 
org.grails.orm.hibernate.cfg.domainbinding.hibernate.HibernateMappingBuilder
+import org.hibernate.FetchMode
+import spock.lang.Specification
+
+/**
+ * Covers branches of {@link HibernateMappingBuilder} not exercised by
+ * {@link HibernateMappingBuilderTests}.
+ */
+class HibernateMappingBuilderSpec extends Specification {
+
+    private HibernateMappingBuilder builder(String name = 'Foo') {
+        new HibernateMappingBuilder(new Mapping(), name)
+    }
+
+    private Mapping evaluate(@DelegatesTo(HibernateMappingBuilder) Closure cl) 
{
+        builder().evaluate(cl)
+    }
+
+    // 
-------------------------------------------------------------------------
+    // autowire / tenantId
+    // 
-------------------------------------------------------------------------
+
+    def "autowire stores the value on the mapping"() {
+        expect:
+        evaluate { autowire true }.autowire
+        !evaluate { autowire false }.autowire
+    }
+
+    def "tenantId stores the property name"() {
+        expect:
+        evaluate { tenantId 'tenantId' }.getPropertyConfig('tenantId') != null
+    }
+
+    // 
-------------------------------------------------------------------------
+    // cache(String, Map)
+    // 
-------------------------------------------------------------------------
+
+    def "cache(String, Map) sets usage and include"() {
+        when:
+        Mapping m = evaluate { cache 'read-write', [include: 'all'] }
+
+        then:
+        m.cache.usage == 'read-write'
+        m.cache.include == 'all'
+    }
+
+    def "cache(String) with invalid usage still creates a CacheConfig with the 
default usage"() {
+        when:
+        Mapping m = evaluate { cache 'INVALID_USAGE' }
+
+        then:
+        m.cache != null
+        m.cache.usage == 'read-write'  // default preserved; INVALID_USAGE 
rejected
+    }
+
+    def "cache(Map) with invalid include still creates a CacheConfig with the 
default include"() {
+        when:
+        Mapping m = evaluate { cache usage: 'read-only', include: 
'INVALID_INCLUDE' }
+
+        then:
+        m.cache != null
+        m.cache.usage == 'read-only'
+        m.cache.include == 'all'  // default preserved; INVALID_INCLUDE 
rejected
+    }
+
+    // 
-------------------------------------------------------------------------
+    // hibernateCustomUserType
+    // 
-------------------------------------------------------------------------
+
+    def "hibernateCustomUserType registers a user type when args are valid"() {
+        when:
+        Mapping m = evaluate { 'user-type'(type: 'myType', 'class': String) }
+
+        then:
+        m.userTypes[String] == 'myType'
+    }
+
+    def "hibernateCustomUserType is a no-op when class is not a Class"() {
+        when:
+        Mapping m = evaluate { 'user-type'(type: 'myType', 'class': 
'notAClass') }
+
+        then:
+        m.userTypes.isEmpty()
+    }
+
+    def "hibernateCustomUserType is a no-op when type is absent"() {
+        when:
+        Mapping m = evaluate { 'user-type'('class': String) }
+
+        then:
+        m.userTypes.isEmpty()
+    }
+
+    // 
-------------------------------------------------------------------------
+    // includes() null-safety
+    // 
-------------------------------------------------------------------------
+
+    def "includes() with null closure does not throw"() {
+        when:
+        evaluate { includes(null) }
+
+        then:
+        noExceptionThrown()
+    }
+
+    // 
-------------------------------------------------------------------------
+    // sort / order null guards
+    // 
-------------------------------------------------------------------------
+
+    def "sort(null) is a no-op"() {
+        when:
+        Mapping m = evaluate { sort((String) null) }
+
+        then:
+        m.sort.name == null
+    }
+
+    def "order with invalid direction is a no-op"() {
+        when:
+        Mapping m = evaluate { order 'invalid' }
+
+        then:
+        m.sort.direction == null
+    }
+
+    def "batchSize(null) is a no-op and leaves batchSize as null"() {
+        when:
+        Mapping m = evaluate { batchSize null }
+
+        then:
+        m.batchSize == null
+    }
+
+    // 
-------------------------------------------------------------------------
+    // evaluate with context argument
+    // 
-------------------------------------------------------------------------
+
+    def "evaluate passes context to the closure"() {
+        given:
+        def b = builder()
+        Object captured = null
+
+        when:
+        b.evaluate({ Object ctx -> captured = ctx }, 'myContext')
+
+        then:
+        captured == 'myContext'
+    }
+
+    // 
-------------------------------------------------------------------------
+    // property(Map, String) — the 2-arg typed method
+    // 
-------------------------------------------------------------------------
+
+    def "property(Map, String) registers the property config"() {
+        when:
+        Mapping m = evaluate { property([nullable: true, column: 'my_col'], 
'myProp') }
+
+        then:
+        m.getPropertyConfig('myProp') != null
+        m.getPropertyConfig('myProp').nullable
+        m.getPropertyConfig('myProp').column == 'my_col'
+    }
+
+    // 
-------------------------------------------------------------------------
+    // handlePropertyInternal — uncovered branches
+    // 
-------------------------------------------------------------------------
+
+    def "property with accessType stores it"() {
+        when:
+        Mapping m = evaluate { myProp accessType: AccessType.FIELD }
+
+        then:
+        m.getPropertyConfig('myProp').accessType == AccessType.FIELD
+    }
+
+    def "property updateable alias is honoured"() {
+        when:
+        Mapping m = evaluate { myProp updateable: false }
+
+        then:
+        !m.getPropertyConfig('myProp').updatable
+    }
+
+    def "property params map is converted to Properties"() {
+        when:
+        Mapping m = evaluate { myProp params: [scale: '4', precision: '10'] }
+
+        then:
+        m.getPropertyConfig('myProp').typeParams instanceof Properties
+        m.getPropertyConfig('myProp').typeParams['scale'] == '4'
+    }
+
+    def "property unique as String creates a named unique constraint"() {
+        when:
+        Mapping m = evaluate { myProp unique: 'myGroup' }
+
+        then:
+        m.getPropertyConfig('myProp').isUniqueWithinGroup()
+    }
+
+    def "property unique as List creates a composite unique constraint"() {
+        when:
+        Mapping m = evaluate { myProp unique: ['a', 'b'] }
+
+        then:
+        m.getPropertyConfig('myProp').isUniqueWithinGroup()
+    }
+
+    def "property size as IntRange stores minSize and maxSize"() {
+        when:
+        Mapping m = evaluate { myProp size: (1..10) }
+
+        then:
+        m.getPropertyConfig('myProp').minSize == 1
+        m.getPropertyConfig('myProp').maxSize == 10
+    }
+
+    def "property range as ObjectRange stores min and max"() {
+        when:
+        // ObjectRange is used for non-primitive ranges; 'a'..'e' produces one
+        Mapping m = evaluate { myProp range: ('a'..'e') }
+
+        then:
+        m.getPropertyConfig('myProp').min == 'a'
+        m.getPropertyConfig('myProp').max == 'e'
+    }
+
+    def "property inList stores the list"() {
+        when:
+        Mapping m = evaluate { myProp inList: ['A', 'B', 'C'] }
+
+        then:
+        m.getPropertyConfig('myProp').inList == ['A', 'B', 'C']
+    }
+
+    def "property fetch with join string sets JOIN fetch mode"() {
+        when:
+        Mapping m = evaluate { myProp fetch: 'join' }
+
+        then:
+        m.getPropertyConfig('myProp').getFetchMode() == FetchMode.JOIN
+    }
+
+    def "property fetch with unknown string falls back to SELECT"() {
+        when:
+        Mapping m = evaluate { myProp fetch: 'eager' }
+
+        then:
+        m.getPropertyConfig('myProp').getFetchMode() == FetchMode.SELECT
+    }
+
+    def "property with sub-closure delegates to PropertyDefinitionDelegate"() {
+        when:
+        Mapping m = evaluate {
+            myProp {
+                column name: 'col_one'
+            }
+        }
+
+        then:
+        m.getPropertyConfig('myProp').columns[0].name == 'col_one'
+    }
+
+    def "property indexColumn map is applied"() {
+        when:
+        Mapping m = evaluate {
+            myProp indexColumn: [name: 'idx', type: 'integer', length: 10]
+        }
+
+        then:
+        PropertyConfig ic = m.getPropertyConfig('myProp').indexColumn
+        ic != null
+        ic.columns[0].name == 'idx'
+        ic.columns[0].length == 10
+    }
+
+    def "property cache as boolean true enables caching"() {
+        when:
+        Mapping m = evaluate { myProp cache: true }
+
+        then:
+        m.getPropertyConfig('myProp').cache instanceof CacheConfig
+    }
+
+    def "property cache as boolean false is a no-op"() {
+        when:
+        Mapping m = evaluate { myProp cache: false }
+
+        then:
+        m.getPropertyConfig('myProp').cache == null
+    }
+
+    def "property cache as Map sets usage and include"() {
+        when:
+        Mapping m = evaluate { myProp cache: [usage: 'read-only', include: 
'all'] }
+
+        then:
+        m.getPropertyConfig('myProp').cache.usage == 'read-only'
+        m.getPropertyConfig('myProp').cache.include == 'all'
+    }
+
+    def "property column sqlType is set"() {
+        when:
+        Mapping m = evaluate { myProp sqlType: 'text' }
+
+        then:
+        m.getPropertyConfig('myProp').sqlType == 'text'
+    }
+
+    def "property column read/write formulas are set"() {
+        when:
+        Mapping m = evaluate { myProp read: 'lower(col)', write: 'upper(?)' }
+
+        then:
+        m.getPropertyConfig('myProp').columns[0].read == 'lower(col)'
+        m.getPropertyConfig('myProp').columns[0].write == 'upper(?)'
+    }
+
+    def "property column defaultValue and comment are set"() {
+        when:
+        Mapping m = evaluate { myProp defaultValue: 'N/A', comment: 'a test 
column' }
+
+        then:
+        m.getPropertyConfig('myProp').columns[0].defaultValue == 'N/A'
+        m.getPropertyConfig('myProp').columns[0].comment == 'a test column'
+    }
+
+    // 
-------------------------------------------------------------------------
+    // methodMissing — filtering branches
+    // 
-------------------------------------------------------------------------
+
+    def "methodMissing skips properties in methodMissingExcludes via 
importFrom"() {
+        given: "a class whose constraints closure maps 'foos' and 'bars'"
+        def cl = new GroovyClassLoader().parseClass('''
+            class ImportSource {
+                static constraints = {
+                    foos(lazy: false)
+                    bars(lazy: true)
+                }
+            }
+        ''')
+
+        when: "importFrom with exclude:[bars]"
+        Mapping m = evaluate { importFrom(cl, [exclude: ['bars']]) }
+
+        then: "foos is mapped, bars is not"
+        m.getPropertyConfig('foos') != null
+        m.getPropertyConfig('bars') == null
+    }
+
+    def "methodMissing skips properties not in methodMissingIncludes via 
importFrom"() {
+        given:
+        def cl = new GroovyClassLoader().parseClass('''
+            class ImportSource2 {
+                static constraints = {
+                    foos(lazy: false)
+                    bars(lazy: true)
+                }
+            }
+        ''')
+
+        when: "importFrom with include:[bars]"
+        Mapping m = evaluate { importFrom(cl, [include: ['bars']]) }
+
+        then: "bars is mapped, foos is not"
+        m.getPropertyConfig('bars') != null
+        m.getPropertyConfig('foos') == null
+    }
+
+    def "methodMissing with no matching args signature is silently ignored"() {
+        when: "call with a plain String arg (no Map, no Closure)"
+        Mapping m = evaluate { myProp 'justAString' }
+
+        then:
+        noExceptionThrown()
+        m.getPropertyConfig('myProp') == null
+    }
+}


Reply via email to