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") }) }
