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

borinquenkid pushed a commit to branch chore/clean-grails-datamapping-async
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 0b123f71c150d83f21ba3fc844455d1297acfa0e
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Fri Aug 21 17:30:10 2026 -0500

    Add test coverage for grails-datamapping-async and type decorator closures
    
    The module had no test infrastructure at all, so add the Spock dependency
    and specs for GormAsyncStaticApi and AsyncQuery covering task()/list()/
    count() delegation and the new-session decorator wrapping. AsyncQuery was
    previously untested anywhere in the repo.
    
    Also give both classes' promise decorator closures explicit parameter
    types (Closure<?> callable, Object[] args) instead of relying on implicit
    inference, fixing an IDE "cannot infer argument types" inspection on the
    generic PromiseDecorator SAM coercion.
    
    Co-Authored-By: Claude Sonnet 5 <[email protected]>
---
 grails-datamapping-async/build.gradle              |   4 +
 .../grails/datastore/gorm/async/AsyncQuery.groovy  |   4 +-
 .../datastore/gorm/async/GormAsyncStaticApi.groovy |   4 +-
 .../datastore/gorm/async/AsyncQuerySpec.groovy     |  91 ++++++++++++++++
 .../gorm/async/GormAsyncStaticApiSpec.groovy       | 115 +++++++++++++++++++++
 5 files changed, 214 insertions(+), 4 deletions(-)

diff --git a/grails-datamapping-async/build.gradle 
b/grails-datamapping-async/build.gradle
index 52cd2cb781..a82ff5d811 100644
--- a/grails-datamapping-async/build.gradle
+++ b/grails-datamapping-async/build.gradle
@@ -70,6 +70,10 @@ dependencies {
     }
 
     compileOnly 'org.apache.groovy:groovy'
+
+    testImplementation 'org.spockframework:spock-core'
+
+    testRuntimeOnly 'org.slf4j:slf4j-nop' // Prevents warning about missing 
slf4j implementation during tests
 }
 
 apply {
diff --git 
a/grails-datamapping-async/src/main/groovy/org/grails/datastore/gorm/async/AsyncQuery.groovy
 
b/grails-datamapping-async/src/main/groovy/org/grails/datastore/gorm/async/AsyncQuery.groovy
index 84ddbb2bb2..f33c172a4c 100644
--- 
a/grails-datamapping-async/src/main/groovy/org/grails/datastore/gorm/async/AsyncQuery.groovy
+++ 
b/grails-datamapping-async/src/main/groovy/org/grails/datastore/gorm/async/AsyncQuery.groovy
@@ -32,8 +32,8 @@ class AsyncQuery<E> implements PromiseDecoratorProvider {
     /**
      * Wraps each promise in a new persistence session
      */
-    private List<PromiseDecorator> decorators = [ { Closure callable ->
-        return { args ->
+    private List<PromiseDecorator> decorators = [ { Closure<?> callable ->
+        return { Object[] args ->
             gormOperations.persistentClass.withNewSession {
                 callable.call(*args)
             }
diff --git 
a/grails-datamapping-async/src/main/groovy/org/grails/datastore/gorm/async/GormAsyncStaticApi.groovy
 
b/grails-datamapping-async/src/main/groovy/org/grails/datastore/gorm/async/GormAsyncStaticApi.groovy
index 7687d42faf..655895a74b 100644
--- 
a/grails-datamapping-async/src/main/groovy/org/grails/datastore/gorm/async/GormAsyncStaticApi.groovy
+++ 
b/grails-datamapping-async/src/main/groovy/org/grails/datastore/gorm/async/GormAsyncStaticApi.groovy
@@ -37,8 +37,8 @@ class GormAsyncStaticApi<D> implements 
PromiseDecoratorProvider {
     /**
      * Wraps each promise in a new persistence session
      */
-    private List<PromiseDecorator> decorators = [ { Closure callable ->
-        return { args -> staticApi.withNewSession { callable.call(*args) } }
+    private List<PromiseDecorator> decorators = [ { Closure<?> callable ->
+        return { Object[] args -> staticApi.withNewSession { 
callable.call(*args) } }
     } as PromiseDecorator ]
 
     GormAsyncStaticApi(GormStaticApi<D> staticApi) {
diff --git 
a/grails-datamapping-async/src/test/groovy/org/grails/datastore/gorm/async/AsyncQuerySpec.groovy
 
b/grails-datamapping-async/src/test/groovy/org/grails/datastore/gorm/async/AsyncQuerySpec.groovy
new file mode 100644
index 0000000000..15184a32e9
--- /dev/null
+++ 
b/grails-datamapping-async/src/test/groovy/org/grails/datastore/gorm/async/AsyncQuerySpec.groovy
@@ -0,0 +1,91 @@
+/*
+ *  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.async
+
+import grails.async.Promise
+import grails.async.Promises
+import org.grails.async.factory.SynchronousPromiseFactory
+import org.grails.datastore.gorm.query.GormOperations
+import spock.lang.Specification
+
+class AsyncQuerySpec extends Specification {
+
+    private def originalPromiseFactory
+
+    void setup() {
+        originalPromiseFactory = Promises.promiseFactory
+        Promises.promiseFactory = new SynchronousPromiseFactory()
+        AsyncQuerySpecEntity.newSessionCallCount = 0
+    }
+
+    void cleanup() {
+        Promises.promiseFactory = originalPromiseFactory
+    }
+
+    void "list delegates to the wrapped GORM operations asynchronously within 
a new session"() {
+        given:
+        def gormOperations = Mock(GormOperations) {
+            getPersistentClass() >> AsyncQuerySpecEntity
+        }
+        def query = new AsyncQuery(gormOperations)
+
+        when:
+        Promise<List> promise = query.list()
+
+        then:
+        1 * gormOperations.list() >> ["a", "b"]
+        promise.get() == ["a", "b"]
+        AsyncQuerySpecEntity.newSessionCallCount == 1
+    }
+
+    void "count delegates to the wrapped GORM operations asynchronously within 
a new session"() {
+        given:
+        def gormOperations = Mock(GormOperations) {
+            getPersistentClass() >> AsyncQuerySpecEntity
+        }
+        def query = new AsyncQuery(gormOperations)
+
+        when:
+        Promise<Number> promise = query.count()
+
+        then:
+        1 * gormOperations.count() >> 3
+        promise.get() == 3
+        AsyncQuerySpecEntity.newSessionCallCount == 1
+    }
+
+    void "getDecorators returns a single decorator that wraps calls in a new 
session"() {
+        given:
+        def gormOperations = Mock(GormOperations)
+        def query = new AsyncQuery(gormOperations)
+
+        expect:
+        query.decorators.size() == 1
+    }
+}
+
+class AsyncQuerySpecEntity {
+
+    static int newSessionCallCount = 0
+
+    static withNewSession(Closure callable) {
+        newSessionCallCount++
+        callable.call()
+    }
+}
diff --git 
a/grails-datamapping-async/src/test/groovy/org/grails/datastore/gorm/async/GormAsyncStaticApiSpec.groovy
 
b/grails-datamapping-async/src/test/groovy/org/grails/datastore/gorm/async/GormAsyncStaticApiSpec.groovy
new file mode 100644
index 0000000000..bb5ab3632b
--- /dev/null
+++ 
b/grails-datamapping-async/src/test/groovy/org/grails/datastore/gorm/async/GormAsyncStaticApiSpec.groovy
@@ -0,0 +1,115 @@
+/*
+ *  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.async
+
+import grails.async.Promise
+import grails.async.Promises
+import org.grails.async.factory.SynchronousPromiseFactory
+import org.grails.datastore.gorm.GormStaticApi
+import org.grails.datastore.mapping.model.PersistentEntity
+import spock.lang.Specification
+
+class GormAsyncStaticApiSpec extends Specification {
+
+    private def originalPromiseFactory
+
+    void setup() {
+        originalPromiseFactory = Promises.promiseFactory
+        Promises.promiseFactory = new SynchronousPromiseFactory()
+    }
+
+    void cleanup() {
+        Promises.promiseFactory = originalPromiseFactory
+    }
+
+    void "task runs the closure inside a new session and returns its result 
via a promise"() {
+        given:
+        def entity = Mock(PersistentEntity) {
+            getJavaClass() >> String
+        }
+        def staticApi = Mock(GormStaticApi) {
+            getGormPersistentEntity() >> entity
+        }
+        def api = new GormAsyncStaticApi(staticApi)
+
+        when:
+        Promise<String> promise = api.task { "done" }
+
+        then:
+        1 * staticApi.withNewSession(_) >> { Closure c -> c.call() }
+        promise.get() == "done"
+    }
+
+    void "task does not invoke the closure when withNewSession is never 
called"() {
+        given:
+        def entity = Mock(PersistentEntity) {
+            getJavaClass() >> String
+        }
+        def staticApi = Mock(GormStaticApi) {
+            getGormPersistentEntity() >> entity
+        }
+        def api = new GormAsyncStaticApi(staticApi)
+
+        when:
+        Promise<String> promise = api.task { "unreached" }
+
+        then:
+        1 * staticApi.withNewSession(_) >> null
+        promise.get() == null
+    }
+
+    void "list delegates to the static API asynchronously within a new 
session"() {
+        given:
+        def staticApi = Mock(GormStaticApi) {
+            withNewSession(_) >> { Closure c -> c.call() }
+        }
+        def api = new GormAsyncStaticApi(staticApi)
+
+        when:
+        Promise<List> promise = api.list()
+
+        then:
+        1 * staticApi.list() >> ["a", "b"]
+        promise.get() == ["a", "b"]
+    }
+
+    void "count delegates to the static API asynchronously within a new 
session"() {
+        given:
+        def staticApi = Mock(GormStaticApi) {
+            withNewSession(_) >> { Closure c -> c.call() }
+        }
+        def api = new GormAsyncStaticApi(staticApi)
+
+        when:
+        Promise<Integer> promise = api.count()
+
+        then:
+        1 * staticApi.count() >> 5
+        promise.get() == 5
+    }
+
+    void "getDecorators returns a single decorator that wraps calls in a new 
session"() {
+        given:
+        def staticApi = Mock(GormStaticApi)
+        def api = new GormAsyncStaticApi(staticApi)
+
+        expect:
+        api.decorators.size() == 1
+    }
+}

Reply via email to