You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by sb...@apache.org on 2026/09/04 08:24:30 UTC

(grails-core) 01/01: test(grails-datamapping-core): restore the transaction service specs lost in the 7.2.x merge

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

sbglasius pushed a commit to branch test/restore-transaction-service-specs-8.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 857c98a98ca236f1803d9643473b6b83cfd8bdec
Author: Søren Berg Glasius <so...@glasius.dk>
AuthorDate: Fri Sep 4 10:17:58 2026 +0200

    test(grails-datamapping-core): restore the transaction service specs lost in the 7.2.x merge
    
    The 7.2.x -> 8.0.x merge hit an add/add conflict on
    DefaultTransactionServiceSpec: both branches had independently created
    it. The resolution kept 8.0.x's version, which had a single test, and
    dropped 7.2.x's fourteen. 8.0.x went from 14 covered behaviours to 1.
    
    The drop was accidental, not a scoping decision. The resolution was
    half-applied: it took 7.2.x's four Spring transaction imports but not
    the test methods that use them, leaving `PlatformTransactionManager`,
    `TransactionDefinition`, `TransactionStatus` and
    `TransactionSystemException` imported and unused on 8.0.x. Pre-merge
    8.0.x had neither the imports nor the tests, so they arrived together
    and only half survived.
    
    Restore 7.2.x's suite and keep 8.0.x's accessor test, giving 15:
    
    - the full `withTransaction`/`withRollback`/`withNewTransaction` matrix
      across the Closure, TransactionDefinition and Map overloads, each with
      its "datastore does not support transactions" counterpart
    - `getDatastore/setDatastore round-trip`, covering the explicit
      `datastore` field 8.0.x added to DefaultTransactionService
    
    The two branches' DefaultTransactionService differ only by that added
    field and its accessors - every method the restored tests exercise is
    byte-identical between 7.2.x and 8.0.x, so the suite ports unchanged.
    7.2.x's property-style `transactionService.datastore = ...` resolves to
    the new setter.
    
    Also drop the class-level comment that said the overloads were out of
    scope. It is no longer true once the suite is restored, and it referred
    to "this PR" and "item 14" of a plan that is not in the repository.
    
    All 15 pass:
      ./gradlew :grails-datamapping-core:test --tests \
        "org.grails.datastore.gorm.services.DefaultTransactionServiceSpec"
    
    DefaultTenantServiceSpec, the other add/add in that merge, was resolved
    cleanly and is left alone.
---
 .../services/DefaultTransactionServiceSpec.groovy  | 173 +++++++++++++++++++--
 1 file changed, 160 insertions(+), 13 deletions(-)

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
index 1a95b2e973..5b81777a2b 100644
--- 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
@@ -24,27 +24,174 @@ 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
 
-/**
- * The diff only adds the explicit {@code datastore}/{@code getDatastore()}/{@code setDatastore()}
- * {@code Service} contract (previously implicit/inherited) - the rest of this class (the
- * {@code withTransaction}/{@code withRollback}/{@code withNewTransaction} overloads) predates this
- * PR and is already exercised via real {@code TransactionService} usage in the separate
- * {@code grails-datamapping-core-test} module (which doesn't count toward this module's own
- * coverage - see item 14's cross-module attribution note), so out of scope here.
- */
 class DefaultTransactionServiceSpec extends Specification {
 
-    void "getDatastore/setDatastore round-trip"() {
+    DefaultTransactionService transactionService = new DefaultTransactionService()
+
+    PlatformTransactionManager transactionManager = Mock(PlatformTransactionManager) {
+        getTransaction(_) >> Mock(TransactionStatus)
+    }
+
+    TransactionCapableDatastore transactionCapableDatastore = Mock(TransactionCapableDatastore) {
+        getTransactionManager() >> transactionManager
+    }
+
+    void 'getDatastore/setDatastore round-trip'() {
+        given:
+        Datastore datastore = Mock(Datastore)
+
+        when:
+        transactionService.setDatastore(datastore)
+
+        then:
+        transactionService.getDatastore().is(datastore)
+    }
+
+    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:
-        def service = new DefaultTransactionService()
-        def datastore = Mock(Datastore)
+        transactionService.datastore = Mock(Datastore)
+        TransactionDefinition definition = new CustomizableRollbackTransactionAttribute()
 
         when:
-        service.setDatastore(datastore)
+        transactionService.withNewTransaction(definition) { status -> 'done' }
 
         then:
-        service.getDatastore().is(datastore)
+        thrown(TransactionSystemException)
     }
 }