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

borinquenkid pushed a commit to branch test/document-datamapping-core-services
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit ea7581ca891a3468cd0a303a9b5624d26962c68e
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Sat Aug 15 18:52:30 2026 -0500

    Add unit test coverage for DefaultTenantService/DefaultTransactionService
    
    Both classes had zero test coverage in org.grails.datastore.gorm.services.
    Mock-based Spock specs cover all public methods including the
    multi-tenancy-mode/datastore-capability branch checks, bringing each
    class to 100% line/branch/method coverage.
    
    Co-Authored-By: Claude Sonnet 5 <[email protected]>
---
 .../gorm/services/DefaultTenantServiceSpec.groovy  | 205 +++++++++++++++++++++
 .../services/DefaultTransactionServiceSpec.groovy  | 186 +++++++++++++++++++
 2 files changed, 391 insertions(+)

diff --git 
a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/services/DefaultTenantServiceSpec.groovy
 
b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/services/DefaultTenantServiceSpec.groovy
new file mode 100644
index 0000000000..65f39221b2
--- /dev/null
+++ 
b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/services/DefaultTenantServiceSpec.groovy
@@ -0,0 +1,205 @@
+/*
+ *  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.datastore.gorm.services
+
+import org.grails.datastore.mapping.core.Datastore
+import org.grails.datastore.mapping.model.DatastoreConfigurationException
+import org.grails.datastore.mapping.multitenancy.AllTenantsResolver
+import org.grails.datastore.mapping.multitenancy.MultiTenancySettings
+import org.grails.datastore.mapping.multitenancy.MultiTenantCapableDatastore
+import org.grails.datastore.mapping.multitenancy.TenantResolver
+import spock.lang.Specification
+
+class DefaultTenantServiceSpec extends Specification {
+
+    DefaultTenantService tenantService = new DefaultTenantService()
+
+    void 'multiTenantDatastore throws when the datastore is not multi-tenant 
capable'() {
+        given:
+        tenantService.datastore = Mock(Datastore)
+
+        when:
+        tenantService.eachTenant {}
+
+        then:
+        DatastoreConfigurationException e = 
thrown(DatastoreConfigurationException)
+        e.message.contains('not Multi-Tenant capable')
+    }
+
+    void 'currentId throws when multi tenancy mode is NONE'() {
+        given:
+        MultiTenantCapableDatastore datastore = 
Mock(MultiTenantCapableDatastore)
+        datastore.multiTenancyMode >> 
MultiTenancySettings.MultiTenancyMode.NONE
+        tenantService.datastore = datastore
+
+        when:
+        tenantService.currentId()
+
+        then:
+        DatastoreConfigurationException e = 
thrown(DatastoreConfigurationException)
+        e.message.contains('not configured for Multi-Tenancy')
+    }
+
+    void 'currentId resolves the tenant id from the tenant resolver'() {
+        given:
+        TenantResolver tenantResolver = Mock(TenantResolver) {
+            resolveTenantIdentifier() >> 'tenant1'
+        }
+        MultiTenantCapableDatastore datastore = 
Mock(MultiTenantCapableDatastore)
+        datastore.multiTenancyMode >> 
MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR
+        datastore.tenantResolver >> tenantResolver
+        tenantService.datastore = datastore
+
+        expect:
+        tenantService.currentId() == 'tenant1'
+    }
+
+    void 'withoutId throws when multi tenancy mode is NONE'() {
+        given:
+        MultiTenantCapableDatastore datastore = 
Mock(MultiTenantCapableDatastore)
+        datastore.multiTenancyMode >> 
MultiTenancySettings.MultiTenancyMode.NONE
+        tenantService.datastore = datastore
+
+        when:
+        tenantService.withoutId { 'result' }
+
+        then:
+        thrown(DatastoreConfigurationException)
+    }
+
+    void 'withoutId with a shared connection mode executes the callable 
without a session'() {
+        given:
+        MultiTenantCapableDatastore datastore = 
Mock(MultiTenantCapableDatastore)
+        datastore.multiTenancyMode >> 
MultiTenancySettings.MultiTenancyMode.DISCRIMINATOR
+        tenantService.datastore = datastore
+
+        when:
+        String result = tenantService.withoutId { -> 'result' }
+
+        then:
+        result == 'result'
+        0 * datastore.withSession(_)
+        0 * datastore.withNewSession(_, _)
+    }
+
+    void 'withoutId with a non-shared connection mode executes within a new 
session'() {
+        given:
+        MultiTenantCapableDatastore datastore = 
Mock(MultiTenantCapableDatastore)
+        datastore.multiTenancyMode >> 
MultiTenancySettings.MultiTenancyMode.DATABASE
+        datastore.withNewSession(_, _) >> { args -> args[1].call('session') }
+        tenantService.datastore = datastore
+
+        expect:
+        tenantService.withoutId { 'result' } == 'result'
+    }
+
+    void 'withId throws when multi tenancy mode is NONE'() {
+        given:
+        MultiTenantCapableDatastore datastore = 
Mock(MultiTenantCapableDatastore)
+        datastore.multiTenancyMode >> 
MultiTenancySettings.MultiTenancyMode.NONE
+        tenantService.datastore = datastore
+
+        when:
+        tenantService.withId('tenant1') { 'result' }
+
+        then:
+        thrown(DatastoreConfigurationException)
+    }
+
+    void 'withId with a shared connection mode executes the callable 
directly'() {
+        given:
+        MultiTenantCapableDatastore datastore = 
Mock(MultiTenantCapableDatastore)
+        datastore.multiTenancyMode >> 
MultiTenancySettings.MultiTenancyMode.SCHEMA
+        tenantService.datastore = datastore
+
+        expect:
+        tenantService.withId('tenant1') { tenantId -> "result-$tenantId" } == 
'result-tenant1'
+    }
+
+    void 'withId with a non-shared connection mode executes within a new 
session for the tenant'() {
+        given:
+        MultiTenantCapableDatastore datastore = 
Mock(MultiTenantCapableDatastore)
+        datastore.multiTenancyMode >> 
MultiTenancySettings.MultiTenancyMode.DATABASE
+        datastore.withNewSession('tenant1', _) >> { args -> 
args[1].call('session') }
+        tenantService.datastore = datastore
+
+        expect:
+        tenantService.withId('tenant1') { 'result' } == 'result'
+    }
+
+    void 'withCurrent throws when multi tenancy mode is NONE'() {
+        given:
+        MultiTenantCapableDatastore datastore = 
Mock(MultiTenantCapableDatastore)
+        datastore.multiTenancyMode >> 
MultiTenancySettings.MultiTenancyMode.NONE
+        tenantService.datastore = datastore
+
+        when:
+        tenantService.withCurrent { 'result' }
+
+        then:
+        thrown(DatastoreConfigurationException)
+    }
+
+    void 'withCurrent executes the callable with the resolved current tenant 
id'() {
+        given:
+        TenantResolver tenantResolver = Mock(TenantResolver) {
+            resolveTenantIdentifier() >> 'tenant1'
+        }
+        MultiTenantCapableDatastore datastore = 
Mock(MultiTenantCapableDatastore)
+        datastore.multiTenancyMode >> 
MultiTenancySettings.MultiTenancyMode.SCHEMA
+        datastore.tenantResolver >> tenantResolver
+        tenantService.datastore = datastore
+
+        expect:
+        tenantService.withCurrent { tenantId -> "result-$tenantId" } == 
'result-tenant1'
+    }
+
+    void 'eachTenant throws when multi tenancy mode is NONE'() {
+        given:
+        MultiTenantCapableDatastore datastore = 
Mock(MultiTenantCapableDatastore)
+        datastore.multiTenancyMode >> 
MultiTenancySettings.MultiTenancyMode.NONE
+        tenantService.datastore = datastore
+
+        when:
+        tenantService.eachTenant {}
+
+        then:
+        thrown(UnsupportedOperationException)
+    }
+
+    void 'eachTenant with a shared connection mode invokes the callable for 
every resolved tenant id'() {
+        given:
+        AllTenantsResolver tenantResolver = Mock(AllTenantsResolver) {
+            resolveTenantIds() >> ['tenant1', 'tenant2']
+        }
+        MultiTenantCapableDatastore datastore = 
Mock(MultiTenantCapableDatastore)
+        datastore.multiTenancyMode >> 
MultiTenancySettings.MultiTenancyMode.SCHEMA
+        datastore.tenantResolver >> tenantResolver
+        tenantService.datastore = datastore
+
+        and:
+        List<Serializable> seen = []
+
+        when:
+        tenantService.eachTenant { tenantId -> seen << tenantId }
+
+        then:
+        seen == ['tenant1', 'tenant2']
+    }
+}
diff --git 
a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/services/DefaultTransactionServiceSpec.groovy
 
b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/services/DefaultTransactionServiceSpec.groovy
new file mode 100644
index 0000000000..99be3d643e
--- /dev/null
+++ 
b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/services/DefaultTransactionServiceSpec.groovy
@@ -0,0 +1,186 @@
+/*
+ *  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.datastore.gorm.services
+
+import org.springframework.transaction.PlatformTransactionManager
+import org.springframework.transaction.TransactionDefinition
+import org.springframework.transaction.TransactionStatus
+import org.springframework.transaction.TransactionSystemException
+
+import org.grails.datastore.mapping.core.Datastore
+import 
org.grails.datastore.mapping.transactions.CustomizableRollbackTransactionAttribute
+import org.grails.datastore.mapping.transactions.TransactionCapableDatastore
+import spock.lang.Specification
+
+class DefaultTransactionServiceSpec extends Specification {
+
+    DefaultTransactionService transactionService = new 
DefaultTransactionService()
+
+    PlatformTransactionManager transactionManager = 
Mock(PlatformTransactionManager) {
+        getTransaction(_) >> Mock(TransactionStatus)
+    }
+
+    TransactionCapableDatastore transactionCapableDatastore = 
Mock(TransactionCapableDatastore) {
+        getTransactionManager() >> transactionManager
+    }
+
+    void 'withTransaction(Closure) executes the callable within a 
transaction'() {
+        given:
+        transactionService.datastore = transactionCapableDatastore
+
+        when:
+        String result = transactionService.withTransaction { status -> 'done' }
+
+        then:
+        result == 'done'
+        1 * transactionManager.commit(_)
+    }
+
+    void 'withTransaction(Closure) throws when the datastore does not support 
transactions'() {
+        given:
+        transactionService.datastore = Mock(Datastore)
+
+        when:
+        transactionService.withTransaction { status -> 'done' }
+
+        then:
+        TransactionSystemException e = thrown(TransactionSystemException)
+        e.message.contains('does not support transactions')
+    }
+
+    void 'withRollback(Closure) executes the callable and rolls back'() {
+        given:
+        transactionService.datastore = transactionCapableDatastore
+
+        expect:
+        transactionService.withRollback { status -> 'done' } == 'done'
+    }
+
+    void 'withRollback(Closure) throws when the datastore does not support 
transactions'() {
+        given:
+        transactionService.datastore = Mock(Datastore)
+
+        when:
+        transactionService.withRollback { status -> 'done' }
+
+        then:
+        thrown(TransactionSystemException)
+    }
+
+    void 'withNewTransaction(Closure) executes the callable with 
PROPAGATION_REQUIRES_NEW'() {
+        given:
+        transactionService.datastore = transactionCapableDatastore
+
+        expect:
+        transactionService.withNewTransaction { status -> 'done' } == 'done'
+    }
+
+    void 'withNewTransaction(Closure) throws when the datastore does not 
support transactions'() {
+        given:
+        transactionService.datastore = Mock(Datastore)
+
+        when:
+        transactionService.withNewTransaction { status -> 'done' }
+
+        then:
+        thrown(TransactionSystemException)
+    }
+
+    void 'withTransaction(TransactionDefinition, Closure) executes the 
callable with the given definition'() {
+        given:
+        transactionService.datastore = transactionCapableDatastore
+        TransactionDefinition definition = new 
CustomizableRollbackTransactionAttribute()
+
+        expect:
+        transactionService.withTransaction(definition) { status -> 'done' } == 
'done'
+    }
+
+    void 'withTransaction(TransactionDefinition, Closure) throws when the 
datastore does not support transactions'() {
+        given:
+        transactionService.datastore = Mock(Datastore)
+        TransactionDefinition definition = new 
CustomizableRollbackTransactionAttribute()
+
+        when:
+        transactionService.withTransaction(definition) { status -> 'done' }
+
+        then:
+        thrown(TransactionSystemException)
+    }
+
+    void 'withTransaction(Map, Closure) builds a transaction definition from 
the map'() {
+        given:
+        transactionService.datastore = transactionCapableDatastore
+
+        expect:
+        transactionService.withTransaction([readOnly: true]) { status -> 
'done' } == 'done'
+    }
+
+    void 'withTransaction(Map, Closure) throws when the datastore does not 
support transactions'() {
+        given:
+        transactionService.datastore = Mock(Datastore)
+
+        when:
+        transactionService.withTransaction([readOnly: true]) { status -> 
'done' }
+
+        then:
+        thrown(TransactionSystemException)
+    }
+
+    void 'withRollback(TransactionDefinition, Closure) executes the callable 
and rolls back'() {
+        given:
+        transactionService.datastore = transactionCapableDatastore
+        TransactionDefinition definition = new 
CustomizableRollbackTransactionAttribute()
+
+        expect:
+        transactionService.withRollback(definition) { status -> 'done' } == 
'done'
+    }
+
+    void 'withRollback(TransactionDefinition, Closure) throws when the 
datastore does not support transactions'() {
+        given:
+        transactionService.datastore = Mock(Datastore)
+        TransactionDefinition definition = new 
CustomizableRollbackTransactionAttribute()
+
+        when:
+        transactionService.withRollback(definition) { status -> 'done' }
+
+        then:
+        thrown(TransactionSystemException)
+    }
+
+    void 'withNewTransaction(TransactionDefinition, Closure) forces 
PROPAGATION_REQUIRES_NEW'() {
+        given:
+        transactionService.datastore = transactionCapableDatastore
+        TransactionDefinition definition = new 
CustomizableRollbackTransactionAttribute()
+
+        expect:
+        transactionService.withNewTransaction(definition) { status -> 'done' } 
== 'done'
+    }
+
+    void 'withNewTransaction(TransactionDefinition, Closure) throws when the 
datastore does not support transactions'() {
+        given:
+        transactionService.datastore = Mock(Datastore)
+        TransactionDefinition definition = new 
CustomizableRollbackTransactionAttribute()
+
+        when:
+        transactionService.withNewTransaction(definition) { status -> 'done' }
+
+        then:
+        thrown(TransactionSystemException)
+    }
+}

Reply via email to