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 23066a68c664c618017a7671b65a19407eaf412f
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Tue Feb 24 18:49:44 2026 -0600

    more tests
---
 .../access/TraitPropertyAccessStrategySpec.groovy  | 262 +++++++++++++++++++++
 .../MultiTenantEventListenerSpec.groovy            | 229 ++++++++++++++++++
 2 files changed, 491 insertions(+)

diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/access/TraitPropertyAccessStrategySpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/access/TraitPropertyAccessStrategySpec.groovy
new file mode 100644
index 0000000000..540338b416
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/access/TraitPropertyAccessStrategySpec.groovy
@@ -0,0 +1,262 @@
+/*
+ *  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 org.grails.orm.hibernate.access
+
+import org.hibernate.property.access.spi.GetterFieldImpl
+import org.hibernate.property.access.spi.GetterMethodImpl
+import org.hibernate.property.access.spi.SetterFieldImpl
+import org.hibernate.property.access.spi.SetterMethodImpl
+import spock.lang.Specification
+import spock.lang.Unroll
+
+// ─── Test fixtures 
────────────────────────────────────────────────────────────
+
+trait HasName {
+    String name
+}
+
+trait HasActive {
+    boolean active
+}
+
+trait HasFlag {
+    Boolean flag
+}
+
+/** Plain Groovy class — no trait involvement. */
+class PlainPerson {
+    String plain
+}
+
+/** Groovy class implementing a String trait. */
+class NamedEntity implements HasName {}
+
+/** Groovy class implementing a primitive-boolean trait. */
+class ActiveEntity implements HasActive {}
+
+/** Groovy class implementing a boxed-Boolean trait. */
+class FlaggedEntity implements HasFlag {}
+
+// ─── Spec 
─────────────────────────────────────────────────────────────────────
+
+class TraitPropertyAccessStrategySpec extends Specification {
+
+    TraitPropertyAccessStrategy strategy = new TraitPropertyAccessStrategy()
+
+    // ─── getTraitFieldName 
────────────────────────────────────────────────────
+
+    void "getTraitFieldName encodes dots as underscores with double-underscore 
separator"() {
+        expect:
+        strategy.getTraitFieldName(HasName, 'name') ==
+            'org_grails_orm_hibernate_access_HasName__name'
+    }
+
+    void "getTraitFieldName encodes different trait class correctly"() {
+        expect:
+        strategy.getTraitFieldName(HasActive, 'active') ==
+            'org_grails_orm_hibernate_access_HasActive__active'
+    }
+
+    void "getTraitFieldName replaces every dot in the package name"() {
+        given:
+        def fieldName = strategy.getTraitFieldName(HasName, 'name')
+
+        expect:
+        !fieldName.contains('.')
+        fieldName.contains('__')
+        fieldName.endsWith('__name')
+    }
+
+    // ─── buildPropertyAccess: String trait property 
───────────────────────────
+
+    void "buildPropertyAccess returns non-null PropertyAccess for String trait 
property"() {
+        when:
+        def access = strategy.buildPropertyAccess(NamedEntity, 'name')
+
+        then:
+        access != null
+        access.getter != null
+        access.setter != null
+    }
+
+    void "PropertyAccess.getPropertyAccessStrategy returns the originating 
strategy"() {
+        given:
+        def access = strategy.buildPropertyAccess(NamedEntity, 'name')
+
+        expect:
+        access.propertyAccessStrategy.is(strategy)
+    }
+
+    void "getter and setter for String trait property are field-based"() {
+        given:
+        def access = strategy.buildPropertyAccess(NamedEntity, 'name')
+
+        expect:
+        access.getter instanceof GetterFieldImpl
+        access.setter instanceof SetterFieldImpl
+    }
+
+    void "getter.getReturnTypeClass returns String for String trait 
property"() {
+        given:
+        def access = strategy.buildPropertyAccess(NamedEntity, 'name')
+
+        expect:
+        access.getter.returnTypeClass == String
+    }
+
+    void "getter.getMember returns the backing trait Field for String 
property"() {
+        given:
+        def access = strategy.buildPropertyAccess(NamedEntity, 'name')
+
+        expect:
+        access.getter.getMember() instanceof java.lang.reflect.Field
+        (access.getter.getMember() as java.lang.reflect.Field).name ==
+            'org_grails_orm_hibernate_access_HasName__name'
+    }
+
+    // ─── buildPropertyAccess: primitive boolean trait property 
───────────────
+
+    void "buildPropertyAccess resolves primitive boolean trait property via 
isXxx getter"() {
+        when:
+        def access = strategy.buildPropertyAccess(ActiveEntity, 'active')
+
+        then:
+        access != null
+        access.getter instanceof GetterFieldImpl
+        access.setter instanceof SetterFieldImpl
+    }
+
+    void "getter.getReturnTypeClass returns boolean for boolean trait 
property"() {
+        given:
+        def access = strategy.buildPropertyAccess(ActiveEntity, 'active')
+
+        expect:
+        access.getter.returnTypeClass == boolean
+    }
+
+    void "getter.getMember returns the backing trait Field for boolean 
property"() {
+        given:
+        def access = strategy.buildPropertyAccess(ActiveEntity, 'active')
+
+        expect:
+        (access.getter.getMember() as java.lang.reflect.Field).name ==
+            'org_grails_orm_hibernate_access_HasActive__active'
+    }
+
+    // ─── buildPropertyAccess: boxed Boolean trait property 
───────────────────
+
+    void "buildPropertyAccess resolves boxed Boolean trait property via isXxx 
getter"() {
+        when:
+        def access = strategy.buildPropertyAccess(FlaggedEntity, 'flag')
+
+        then:
+        access != null
+        access.getter instanceof GetterFieldImpl
+        access.setter instanceof SetterFieldImpl
+    }
+
+    void "getter.getMember returns the backing trait Field for Boolean 
property"() {
+        given:
+        def access = strategy.buildPropertyAccess(FlaggedEntity, 'flag')
+
+        expect:
+        (access.getter.getMember() as java.lang.reflect.Field).name ==
+            'org_grails_orm_hibernate_access_HasFlag__flag'
+    }
+
+    // ─── buildPropertyAccess: error paths 
────────────────────────────────────
+
+    void "buildPropertyAccess throws IllegalStateException for non-trait 
property"() {
+        when:
+        strategy.buildPropertyAccess(PlainPerson, 'plain')
+
+        then:
+        def e = thrown(IllegalStateException)
+        e.message.contains('plain')
+        e.message.contains('PlainPerson')
+        e.message.contains('not provided by a trait')
+    }
+
+    void "buildPropertyAccess throws IllegalStateException for non-existent 
property"() {
+        when:
+        strategy.buildPropertyAccess(NamedEntity, 'nonExistent')
+
+        then:
+        def e = thrown(IllegalStateException)
+        e.message.contains('nonExistent')
+        e.message.contains('not provided by a trait')
+    }
+
+    void "buildPropertyAccess error message includes class name"() {
+        when:
+        strategy.buildPropertyAccess(NamedEntity, 'missing')
+
+        then:
+        def e = thrown(IllegalStateException)
+        e.message.contains('NamedEntity')
+    }
+
+    // ─── 3-arg overload 
───────────────────────────────────────────────────────
+
+    void "3-arg buildPropertyAccess delegates to 2-arg version"() {
+        given:
+        def access2 = strategy.buildPropertyAccess(NamedEntity, 'name')
+        def access3 = strategy.buildPropertyAccess(NamedEntity, 'name', true)
+
+        expect:
+        access2.getter.class     == access3.getter.class
+        access2.setter.class     == access3.setter.class
+        access3.propertyAccessStrategy.is(strategy)
+    }
+
+    @Unroll
+    void "3-arg overload with setterRequired=#req still resolves correctly"() {
+        when:
+        def access = strategy.buildPropertyAccess(NamedEntity, 'name', req)
+
+        then:
+        access.getter instanceof GetterFieldImpl
+
+        where:
+        req << [true, false]
+    }
+
+    // ─── multiple independent buildPropertyAccess calls 
───────────────────────
+
+    void "two buildPropertyAccess calls for same class return independent 
instances"() {
+        given:
+        def access1 = strategy.buildPropertyAccess(NamedEntity, 'name')
+        def access2 = strategy.buildPropertyAccess(NamedEntity, 'name')
+
+        expect:
+        !access1.is(access2)
+        access1.getter.returnTypeClass == access2.getter.returnTypeClass
+    }
+
+    void "buildPropertyAccess works on two different trait-implementing 
classes"() {
+        given:
+        def nameAccess   = strategy.buildPropertyAccess(NamedEntity, 'name')
+        def activeAccess = strategy.buildPropertyAccess(ActiveEntity, 'active')
+
+        expect:
+        nameAccess.getter.returnTypeClass   == String
+        activeAccess.getter.returnTypeClass == boolean
+    }
+}
+
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/multitenancy/MultiTenantEventListenerSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/multitenancy/MultiTenantEventListenerSpec.groovy
new file mode 100644
index 0000000000..4a0ef0b324
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/multitenancy/MultiTenantEventListenerSpec.groovy
@@ -0,0 +1,229 @@
+/*
+ *  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 org.grails.orm.hibernate.multitenancy
+
+import org.grails.datastore.mapping.engine.event.PreInsertEvent
+import org.grails.datastore.mapping.engine.event.PreUpdateEvent
+import org.grails.datastore.mapping.engine.event.ValidationEvent
+import org.grails.datastore.mapping.model.PersistentEntity
+import org.grails.datastore.mapping.model.types.TenantId
+import org.grails.datastore.mapping.multitenancy.MultiTenantCapableDatastore
+import org.grails.datastore.mapping.query.Query
+import org.grails.datastore.mapping.query.event.PreQueryEvent
+import org.grails.orm.hibernate.AbstractHibernateDatastore
+import org.springframework.context.ApplicationEvent
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class MultiTenantEventListenerSpec extends Specification {
+
+    MultiTenantEventListener listener = new MultiTenantEventListener()
+
+    // ─── supportsEventType 
────────────────────────────────────────────────────
+
+    @Unroll
+    void "supportsEventType returns true for #type.simpleName"() {
+        expect:
+        listener.supportsEventType(type)
+
+        where:
+        type << [PreQueryEvent, ValidationEvent, PreInsertEvent, 
PreUpdateEvent]
+    }
+
+    void "supportsEventType returns false for generic ApplicationEvent"() {
+        expect:
+        !listener.supportsEventType(ApplicationEvent)
+    }
+
+    void "supportsEventType returns false for unrelated event type"() {
+        expect:
+        !listener.supportsEventType(Object)
+    }
+
+    // ─── supportsSourceType 
───────────────────────────────────────────────────
+
+    void "supportsSourceType returns true for AbstractHibernateDatastore 
itself"() {
+        expect:
+        listener.supportsSourceType(AbstractHibernateDatastore)
+    }
+
+    void "supportsSourceType returns true for a subclass of 
AbstractHibernateDatastore"() {
+        given:
+        // anonymous subclass simulates a concrete HibernateDatastore
+        def subclass = Mock(AbstractHibernateDatastore).class
+
+        expect:
+        listener.supportsSourceType(AbstractHibernateDatastore)
+    }
+
+    void "supportsSourceType returns false for plain Datastore"() {
+        expect:
+        !listener.supportsSourceType(Object)
+    }
+
+    void "supportsSourceType returns false for String"() {
+        expect:
+        !listener.supportsSourceType(String)
+    }
+
+    // ─── getOrder 
─────────────────────────────────────────────────────────────
+
+    void "getOrder returns DEFAULT_ORDER from PersistenceEventListener"() {
+        expect:
+        listener.getOrder() == 
org.grails.datastore.mapping.engine.event.PersistenceEventListener.DEFAULT_ORDER
+    }
+
+    // ─── onApplicationEvent: unsupported event type is silently ignored 
───────
+
+    void "onApplicationEvent with unsupported event type does nothing"() {
+        given:
+        def unsupportedEvent = new ApplicationEvent("source") {}
+
+        when:
+        listener.onApplicationEvent(unsupportedEvent)
+
+        then:
+        noExceptionThrown()
+    }
+
+    // ─── onApplicationEvent: PreQueryEvent — non-multi-tenant entity 
──────────
+
+    void "onApplicationEvent PreQueryEvent on non-multi-tenant entity does not 
call enableMultiTenancyFilter"() {
+        given:
+        def datastore = Mock(AbstractHibernateDatastore)
+        def entity    = Mock(PersistentEntity) { isMultiTenant() >> false }
+        def query     = Mock(Query) { getEntity() >> entity }
+        def event     = new PreQueryEvent(datastore, query)
+
+        when:
+        listener.onApplicationEvent(event)
+
+        then:
+        0 * datastore.enableMultiTenancyFilter()
+    }
+
+    // ─── onApplicationEvent: PreQueryEvent — multi-tenant entity 
─────────────
+
+    void "onApplicationEvent PreQueryEvent on multi-tenant entity calls 
enableMultiTenancyFilter"() {
+        given:
+        def datastore = Mock(AbstractHibernateDatastore)
+        def entity    = Mock(PersistentEntity) { isMultiTenant() >> true }
+        def query     = Mock(Query) { getEntity() >> entity }
+        def event     = new PreQueryEvent(datastore, query)
+
+        when:
+        listener.onApplicationEvent(event)
+
+        then:
+        1 * datastore.enableMultiTenancyFilter()
+    }
+
+    void "onApplicationEvent PreQueryEvent with non-Hibernate source does not 
call enableMultiTenancyFilter"() {
+        given: "source is not an AbstractHibernateDatastore"
+        def nonHibernateDatastore = 
Mock(org.grails.datastore.mapping.core.Datastore)
+        def entity = Mock(PersistentEntity) { isMultiTenant() >> true }
+        def query  = Mock(Query) { getEntity() >> entity }
+        def event  = new PreQueryEvent(nonHibernateDatastore, query)
+
+        when:
+        listener.onApplicationEvent(event)
+
+        then:
+        noExceptionThrown()
+    }
+
+    // ─── onApplicationEvent: PreInsertEvent — non-multi-tenant entity 
─────────
+
+    void "onApplicationEvent PreInsertEvent on non-multi-tenant entity sets no 
tenant"() {
+        given:
+        def datastore    = Mock(AbstractHibernateDatastore)
+        def entity       = Mock(PersistentEntity) { isMultiTenant() >> false }
+        def entityAccess = 
Mock(org.grails.datastore.mapping.engine.EntityAccess)
+        def event        = new PreInsertEvent(datastore, entity, entityAccess)
+
+        when:
+        listener.onApplicationEvent(event)
+
+        then:
+        0 * entityAccess.setProperty(_, _)
+    }
+
+    // ─── onApplicationEvent: PreInsertEvent — multi-tenant, no resolver → 
no-op ─
+
+    void "onApplicationEvent PreInsertEvent on multi-tenant entity does not 
set tenantId when resolver returns null"() {
+        given: "resolver returns null tenant — no-op path"
+        def resolver     = 
Mock(org.grails.datastore.mapping.multitenancy.TenantResolver) { 
resolveTenantIdentifier() >> null }
+        def tenantId     = Mock(TenantId) { getName() >> "tenantId" }
+        def entity       = Mock(PersistentEntity) {
+            isMultiTenant() >> true
+            getTenantId()   >> tenantId
+        }
+        def entityAccess = 
Mock(org.grails.datastore.mapping.engine.EntityAccess)
+        def datastore    = Mock(AbstractHibernateDatastore) { 
getTenantResolver() >> resolver }
+        def event        = new PreInsertEvent(datastore, entity, entityAccess)
+
+        when:
+        listener.onApplicationEvent(event)
+
+        then: "setProperty never called because currentId is null"
+        0 * entityAccess.setProperty(_, _)
+    }
+
+    // ─── onApplicationEvent: PreUpdateEvent — multi-tenant, no resolver → 
no-op ─
+
+    void "onApplicationEvent PreUpdateEvent on multi-tenant entity does not 
set tenantId when resolver returns null"() {
+        given:
+        def resolver     = 
Mock(org.grails.datastore.mapping.multitenancy.TenantResolver) { 
resolveTenantIdentifier() >> null }
+        def tenantId     = Mock(TenantId) { getName() >> "tenantId" }
+        def entity       = Mock(PersistentEntity) {
+            isMultiTenant() >> true
+            getTenantId()   >> tenantId
+        }
+        def entityAccess = 
Mock(org.grails.datastore.mapping.engine.EntityAccess)
+        def datastore    = Mock(AbstractHibernateDatastore) { 
getTenantResolver() >> resolver }
+        def event        = new PreUpdateEvent(datastore, entity, entityAccess)
+
+        when:
+        listener.onApplicationEvent(event)
+
+        then:
+        0 * entityAccess.setProperty(_, _)
+    }
+
+    // ─── onApplicationEvent: ValidationEvent — multi-tenant, no resolver → 
no-op ─
+
+    void "onApplicationEvent ValidationEvent on multi-tenant entity does not 
set tenantId when resolver returns null"() {
+        given:
+        def resolver     = 
Mock(org.grails.datastore.mapping.multitenancy.TenantResolver) { 
resolveTenantIdentifier() >> null }
+        def tenantId     = Mock(TenantId) { getName() >> "tenantId" }
+        def entity       = Mock(PersistentEntity) {
+            isMultiTenant() >> true
+            getTenantId()   >> tenantId
+        }
+        def entityAccess = 
Mock(org.grails.datastore.mapping.engine.EntityAccess)
+        def datastore    = Mock(AbstractHibernateDatastore) { 
getTenantResolver() >> resolver }
+        def event        = new ValidationEvent(datastore, entity, entityAccess)
+
+        when:
+        listener.onApplicationEvent(event)
+
+        then:
+        0 * entityAccess.setProperty(_, _)
+    }
+}

Reply via email to