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

jamesfredley pushed a commit to branch fix/8.0.x-merge-sb4-fallout
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 13354e28b0d2dbf5052f38225d7cf318beadf855
Author: James Fredley <[email protected]>
AuthorDate: Thu May 21 19:33:01 2026 -0400

    Repair graphql multi-datastore-app integration tests on Grails 8 stack
    
    The merge from 7.2.x moved this example into the main settings.gradle
    for the first time on 8.0.x. The forward port surfaced three
    independent regressions that together made :integrationTest fail with
    "no tests discovered" and then with broken assertions once discovery
    was restored.
    
    1) Spock test discovery. FooIntegrationSpec and BarIntegrationSpec
       were declared as `class X implements GraphQLSpec` without extending
       spock.lang.Specification, so JUnit's Spock engine never recognised
       them as tests. With nothing to run, the Grails @Integration
       bootstrapper never even tried to start a Spring context and
       Gradle 8.3+ raised "no tests discovered" rather than running them.
       Both specs are now `class X extends Specification implements
       GraphQLSpec` to match the sibling graphql examples.
    
    2) Logback configuration format. The app shipped a logback.groovy.
       Logback's Groovy DSL was removed in logback-classic 1.5; on the
       8.0.x stack that file is now read by the XML parser and aborts
       with SAXParseException "Content is not allowed in prolog". Convert
       the file to an equivalent logback.xml.
    
    3) MongoDB infrastructure. The application.yml binds MongoDB at
       localhost:27017 with no fallback. The main Build Grails-Core CI
       job and local builds do not start a MongoDB, so the Spring context
       could not initialise even after test discovery was fixed. Add
       integrationTestImplementation
       'org.apache.grails.testing:grails-testing-support-mongodb' so a
       MongoDB testcontainer is started for the integration test JVM and
       the host/port are injected via grails.mongodb.host.
    
    4) GORM datastore lookup. Both specs asserted
       `session.datastore instanceof HibernateDatastore / MongoDatastore`
       inside a withNewSession closure. On the modern GORM/Hibernate
       stack the closure receives org.hibernate.internal.SessionImpl which
       does not expose a `datastore` property; the assertion blew up with
       MissingPropertyException. Replace with GormEnhancer.findStaticApi
       which is the public entry point that survives the upgrade.
    
    5) graphql-java 25 scalar serialisation. MyGraphQLCustomizer declared
       Coercing<ObjectId, ObjectId> and returned an ObjectId from
       serialize. graphql-java 25 no longer toString()'s the returned
       scalar value during serialisation; it serialises the bean's
       structured properties (timestamp/date) instead. The custom scalar
       is changed to Coercing<ObjectId, String> and serialize now returns
       the 24 character hex form, matching the existing scalar
       description and what BarIntegrationSpec parses with new ObjectId.
    
    After these changes :integrationTest passes 2/2 locally on the merge
    commit with my other fixes applied.
    
    Assisted-by: claude-code:claude-opus-4-7
---
 .../grails-multi-datastore-app/build.gradle        |  4 ++
 .../grails-app/conf/logback.groovy                 | 55 ----------------------
 .../grails-app/conf/logback.xml                    | 37 +++++++++++++++
 .../groovy/myapp/BarIntegrationSpec.groovy         | 11 +++--
 .../groovy/myapp/FooIntegrationSpec.groovy         | 10 ++--
 .../main/groovy/myapp/MyGraphQLCustomizer.groovy   | 10 ++--
 6 files changed, 62 insertions(+), 65 deletions(-)

diff --git 
a/grails-test-examples/graphql/grails-multi-datastore-app/build.gradle 
b/grails-test-examples/graphql/grails-multi-datastore-app/build.gradle
index f019e87c56..fe1aaa30b6 100644
--- a/grails-test-examples/graphql/grails-multi-datastore-app/build.gradle
+++ b/grails-test-examples/graphql/grails-multi-datastore-app/build.gradle
@@ -70,6 +70,10 @@ dependencies {
 
     testImplementation 'org.apache.grails:grails-testing-support-datamapping'
     testImplementation 'org.apache.grails:grails-testing-support-web'
+
+    // Auto-start a MongoDB testcontainer before integration tests (overrides 
the
+    // localhost:27017 host in application.yml via grails.mongodb.host system 
property).
+    integrationTestImplementation 
'org.apache.grails.testing:grails-testing-support-mongodb'
 }
 
 apply {
diff --git 
a/grails-test-examples/graphql/grails-multi-datastore-app/grails-app/conf/logback.groovy
 
b/grails-test-examples/graphql/grails-multi-datastore-app/grails-app/conf/logback.groovy
deleted file mode 100644
index b803df96c8..0000000000
--- 
a/grails-test-examples/graphql/grails-multi-datastore-app/grails-app/conf/logback.groovy
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- *  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.
- */
-
-import grails.util.BuildSettings
-import grails.util.Environment
-import org.springframework.boot.logging.logback.ColorConverter
-import 
org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter
-
-import java.nio.charset.Charset
-
-conversionRule 'clr', ColorConverter
-conversionRule 'wex', WhitespaceThrowableProxyConverter
-
-// See http://logback.qos.ch/manual/groovy.html for details on configuration
-appender('STDOUT', ConsoleAppender) {
-    encoder(PatternLayoutEncoder) {
-        charset = Charset.forName('UTF-8')
-
-        pattern =
-                '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} ' + // Date
-                        '%clr(%5p) ' + // Log level
-                        '%clr(---){faint} %clr([%15.15t]){faint} ' + // Thread
-                        '%clr(%-40.40logger{39}){cyan} %clr(:){faint} ' + // 
Logger
-                        '%m%n%wex' // Message
-    }
-}
-
-def targetDir = BuildSettings.TARGET_DIR
-if (Environment.isDevelopmentMode() && targetDir != null) {
-    appender("FULL_STACKTRACE", FileAppender) {
-        file = "${targetDir}/stacktrace.log"
-        append = true
-        encoder(PatternLayoutEncoder) {
-            pattern = "%level %logger - %msg%n"
-        }
-    }
-    logger("StackTrace", ERROR, ['FULL_STACKTRACE'], false)
-}
-root(ERROR, ['STDOUT'])
diff --git 
a/grails-test-examples/graphql/grails-multi-datastore-app/grails-app/conf/logback.xml
 
b/grails-test-examples/graphql/grails-multi-datastore-app/grails-app/conf/logback.xml
new file mode 100644
index 0000000000..ca693c3d9e
--- /dev/null
+++ 
b/grails-test-examples/graphql/grails-multi-datastore-app/grails-app/conf/logback.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+     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
+
+       http://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.
+
+-->
+<configuration>
+
+    <conversionRule conversionWord="clr" 
class="org.springframework.boot.logging.logback.ColorConverter" />
+    <conversionRule conversionWord="wex" 
class="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"
 />
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <charset>UTF-8</charset>
+            <pattern>%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) 
%clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} 
%clr(:){faint} %m%n%wex</pattern>
+        </encoder>
+    </appender>
+
+    <root level="error">
+        <appender-ref ref="STDOUT" />
+    </root>
+</configuration>
diff --git 
a/grails-test-examples/graphql/grails-multi-datastore-app/src/integration-test/groovy/myapp/BarIntegrationSpec.groovy
 
b/grails-test-examples/graphql/grails-multi-datastore-app/src/integration-test/groovy/myapp/BarIntegrationSpec.groovy
index d1f5c96f4c..9e91ed3569 100644
--- 
a/grails-test-examples/graphql/grails-multi-datastore-app/src/integration-test/groovy/myapp/BarIntegrationSpec.groovy
+++ 
b/grails-test-examples/graphql/grails-multi-datastore-app/src/integration-test/groovy/myapp/BarIntegrationSpec.groovy
@@ -21,12 +21,13 @@ package myapp
 
 import grails.testing.mixin.integration.Integration
 import org.bson.types.ObjectId
+import org.grails.datastore.gorm.GormEnhancer
 import org.grails.datastore.mapping.mongo.MongoDatastore
 import org.grails.gorm.graphql.plugin.testing.GraphQLSpec
-
+import spock.lang.Specification
 
 @Integration
-class BarIntegrationSpec implements GraphQLSpec {
+class BarIntegrationSpec extends Specification implements GraphQLSpec {
 
     void "test a bar can be created"() {
         when:
@@ -43,8 +44,10 @@ class BarIntegrationSpec implements GraphQLSpec {
         """)
         Map obj = resp.body().data.barCreate
 
-        then:
+        then: 'bar is created in the Mongo datastore with a valid ObjectId'
         new ObjectId((String) obj.id)
-        Bar.withNewSession { session -> session.datastore instanceof 
MongoDatastore }
+        // GORM no longer surfaces `datastore` as a property on the Mongo 
session;
+        // reach it through GormEnhancer's static API instead.
+        GormEnhancer.findStaticApi(Bar).datastore instanceof MongoDatastore
     }
 }
diff --git 
a/grails-test-examples/graphql/grails-multi-datastore-app/src/integration-test/groovy/myapp/FooIntegrationSpec.groovy
 
b/grails-test-examples/graphql/grails-multi-datastore-app/src/integration-test/groovy/myapp/FooIntegrationSpec.groovy
index 1af9526a1a..05ee9c0dee 100644
--- 
a/grails-test-examples/graphql/grails-multi-datastore-app/src/integration-test/groovy/myapp/FooIntegrationSpec.groovy
+++ 
b/grails-test-examples/graphql/grails-multi-datastore-app/src/integration-test/groovy/myapp/FooIntegrationSpec.groovy
@@ -20,11 +20,13 @@
 package myapp
 
 import grails.testing.mixin.integration.Integration
+import org.grails.datastore.gorm.GormEnhancer
 import org.grails.gorm.graphql.plugin.testing.GraphQLSpec
 import org.grails.orm.hibernate.HibernateDatastore
+import spock.lang.Specification
 
 @Integration
-class FooIntegrationSpec implements GraphQLSpec {
+class FooIntegrationSpec extends Specification implements GraphQLSpec {
 
     void "test a foo can be created"() {
         when:
@@ -41,8 +43,10 @@ class FooIntegrationSpec implements GraphQLSpec {
         """)
         Map obj = resp.body().data.fooCreate
 
-        then:
+        then: 'foo is created in the Hibernate datastore'
         obj.id == 1
-        Foo.withNewSession { session -> session.datastore instanceof 
HibernateDatastore }
+        // GORM no longer surfaces `datastore` as a property on the Hibernate
+        // SessionImpl; reach it through GormEnhancer's static API instead.
+        GormEnhancer.findStaticApi(Foo).datastore instanceof HibernateDatastore
     }
 }
diff --git 
a/grails-test-examples/graphql/grails-multi-datastore-app/src/main/groovy/myapp/MyGraphQLCustomizer.groovy
 
b/grails-test-examples/graphql/grails-multi-datastore-app/src/main/groovy/myapp/MyGraphQLCustomizer.groovy
index d6f93058d8..6d1812af5a 100644
--- 
a/grails-test-examples/graphql/grails-multi-datastore-app/src/main/groovy/myapp/MyGraphQLCustomizer.groovy
+++ 
b/grails-test-examples/graphql/grails-multi-datastore-app/src/main/groovy/myapp/MyGraphQLCustomizer.groovy
@@ -33,7 +33,7 @@ class MyGraphQLCustomizer extends GraphQLPostProcessor {
     @Override
     void doWith(GraphQLTypeManager typeManager) {
         typeManager.registerType(ObjectId, GraphQLScalarType.newScalar()
-                .name("ObjectId").description("Hex representation of a Mongo 
object id").coercing(new Coercing<ObjectId, ObjectId>() {
+                .name("ObjectId").description("Hex representation of a Mongo 
object id").coercing(new Coercing<ObjectId, String>() {
 
             protected Optional<ObjectId> convert(Object input) {
                 if (input instanceof ObjectId) {
@@ -45,9 +45,13 @@ class MyGraphQLCustomizer extends GraphQLPostProcessor {
                 }
             }
 
+            // graphql-java 25 no longer invokes toString on returned scalar 
values during
+            // serialization; the Coercing must produce the wire 
representation itself.
+            // Returning a String (hex) ensures the GraphQL response carries 
the 24-char
+            // hex form rather than ObjectId's structured property bag.
             @Override
-            ObjectId serialize(Object input) {
-                convert(input).orElseThrow({
+            String serialize(Object input) {
+                convert(input).map { it.toString() }.orElseThrow({
                     throw new CoercingSerializeException("Could not convert 
${input.class.name} to an ObjectId")
                 })
             }

Reply via email to