This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch test/abstract-datastore-initializer in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 42612d37550bc748580b06cd073380329408f559 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Thu Aug 13 14:36:47 2026 -0500 Close test coverage gaps in the Hibernate5/7 and MongoDB datastore initializers Adds tests for branches of the three production AbstractDatastoreInitializer subclasses that were previously unexercised: the OSIV interceptor registration branch and configureForDataSource(DataSource) for both Hibernate5 and Hibernate7, Hibernate7's IllegalStateException guard when hibernateDatastore fails to register, and the Map/Collection<Class> constructor form for both. For MongoDB, covers the mongo != null branch (reusing a pre-existing MongoClient) in both configure() and getBeanDefinitions(), the package-scanning constructor, and adds a new Docker-free MongoDbDataStoreSpringInitializerUnitSpec covering the isMappedClass/ collectMappedClasses mixed-entity filtering and the deprecated setters that the Docker-backed spec never reaches. grails-data-neo4j was excluded: it is not part of the root build, its README states it hasn't been updated for the current release, and its standalone build is broken, so no tests could be written or verified there. Co-Authored-By: Claude Sonnet 5 <[email protected]> --- .../HibernateDatastoreSpringInitializerSpec.groovy | 65 +++++++++++ .../HibernateDatastoreSpringInitializerSpec.groovy | 80 ++++++++++++++ .../MongoDbDataStoreSpringInitializerSpec.groovy | 46 ++++++++ ...ongoDbDataStoreSpringInitializerUnitSpec.groovy | 120 +++++++++++++++++++++ 4 files changed, 311 insertions(+) diff --git a/grails-data-hibernate5/grails-plugin/src/test/groovy/grails/orm/bootstrap/HibernateDatastoreSpringInitializerSpec.groovy b/grails-data-hibernate5/grails-plugin/src/test/groovy/grails/orm/bootstrap/HibernateDatastoreSpringInitializerSpec.groovy index 88fb4c628d..f2e542589b 100644 --- a/grails-data-hibernate5/grails-plugin/src/test/groovy/grails/orm/bootstrap/HibernateDatastoreSpringInitializerSpec.groovy +++ b/grails-data-hibernate5/grails-plugin/src/test/groovy/grails/orm/bootstrap/HibernateDatastoreSpringInitializerSpec.groovy @@ -19,17 +19,28 @@ package grails.orm.bootstrap import grails.gorm.annotation.Entity +import org.grails.orm.hibernate.HibernateDatastore import org.hibernate.Session import org.hibernate.SessionFactory import org.hibernate.dialect.H2Dialect +import org.springframework.beans.factory.support.RootBeanDefinition +import org.springframework.context.ConfigurableApplicationContext +import org.springframework.context.support.GenericApplicationContext +import org.springframework.jdbc.datasource.DriverManagerDataSource import org.springframework.transaction.PlatformTransactionManager +import spock.lang.AutoCleanup import spock.lang.Specification +import javax.sql.DataSource + /** * Created by graemerocher on 29/01/14. */ class HibernateDatastoreSpringInitializerSpec extends Specification{ + @AutoCleanup + ConfigurableApplicationContext applicationContext + void "Test configure multiple data sources"() { given:"An initializer instance" Map config = [ @@ -90,6 +101,60 @@ class HibernateDatastoreSpringInitializerSpec extends Specification{ } } + + void "Test the Map/Collection<Class> constructor bootstraps GORM"() { + given: "An initializer built from a Collection of persistent classes" + def datastoreInitializer = new HibernateDatastoreSpringInitializer( + ['dataSource.url': 'jdbc:h2:mem:collectionCtor;LOCK_TIMEOUT=10000', 'hibernate.hbm2ddl.auto': 'create'], + [Person] as Collection<Class>) + + when: "the application is configured" + applicationContext = (ConfigurableApplicationContext) datastoreInitializer.configure() + + then: "GORM is bootstrapped with the given entity" + applicationContext.getBean(HibernateDatastore).mappingContext.getPersistentEntity(Person.name) != null + Person.withNewSession { Person.count() == 0 } + } + + void "Test configureForDataSource bootstraps GORM around a pre-existing DataSource"() { + given: "a DataSource created ahead of time" + def dataSource = new DriverManagerDataSource('jdbc:h2:mem:configureForDataSource;LOCK_TIMEOUT=10000', 'sa', '') + dataSource.driverClassName = 'org.h2.Driver' + def datastoreInitializer = new HibernateDatastoreSpringInitializer(['hibernate.hbm2ddl.auto': 'create'], Person) + + when: "the initializer is configured around that DataSource" + applicationContext = (ConfigurableApplicationContext) datastoreInitializer.configureForDataSource(dataSource) + + then: "the pre-existing DataSource is registered and reused rather than a new one being built" + applicationContext.getBean(HibernateDatastoreSpringInitializer.DEFAULT_DATA_SOURCE_NAME, DataSource).is(dataSource) + Person.withNewSession { Person.count() == 0 } + } + + void "Test the OSIV interceptor is registered when the registry is a web application"() { + given: "a registry that signals it belongs to a web application" + def datastoreInitializer = new HibernateDatastoreSpringInitializer(['dataSource.url': 'jdbc:h2:mem:osivEnabled;LOCK_TIMEOUT=10000'], Person) + def registry = new GenericApplicationContext() + registry.registerBeanDefinition('grailsControllerHelper', new RootBeanDefinition(Object)) + + when: + datastoreInitializer.configureForBeanDefinitionRegistry(registry) + registry.refresh() + applicationContext = registry + + then: + registry.containsBean('openSessionInViewInterceptor') + } + + void "Test the OSIV interceptor is not registered for a non-web application registry"() { + given: "An initializer instance configured against a plain, non-web registry" + def datastoreInitializer = new HibernateDatastoreSpringInitializer(['dataSource.url': 'jdbc:h2:mem:osivDisabled;LOCK_TIMEOUT=10000'], Person) + + when: + applicationContext = (ConfigurableApplicationContext) datastoreInitializer.configure() + + then: + !applicationContext.containsBean('openSessionInViewInterceptor') + } } @Entity class Person { diff --git a/grails-data-hibernate7/grails-plugin/src/test/groovy/grails/orm/bootstrap/HibernateDatastoreSpringInitializerSpec.groovy b/grails-data-hibernate7/grails-plugin/src/test/groovy/grails/orm/bootstrap/HibernateDatastoreSpringInitializerSpec.groovy index 9549d17d68..afa580120f 100644 --- a/grails-data-hibernate7/grails-plugin/src/test/groovy/grails/orm/bootstrap/HibernateDatastoreSpringInitializerSpec.groovy +++ b/grails-data-hibernate7/grails-plugin/src/test/groovy/grails/orm/bootstrap/HibernateDatastoreSpringInitializerSpec.groovy @@ -23,11 +23,17 @@ import org.grails.orm.hibernate.HibernateDatastore import org.hibernate.Session import org.hibernate.SessionFactory import org.hibernate.dialect.H2Dialect +import org.springframework.beans.factory.support.BeanDefinitionRegistry +import org.springframework.beans.factory.support.RootBeanDefinition import org.springframework.context.ConfigurableApplicationContext +import org.springframework.context.support.GenericApplicationContext +import org.springframework.jdbc.datasource.DriverManagerDataSource import org.springframework.transaction.PlatformTransactionManager import spock.lang.AutoCleanup import spock.lang.Specification +import javax.sql.DataSource + /** * Created by graemerocher on 29/01/14. */ @@ -93,6 +99,80 @@ class HibernateDatastoreSpringInitializerSpec extends Specification{ return true } } + + void "Test the Map/Collection<Class> constructor bootstraps GORM"() { + given: "An initializer built from a Collection of persistent classes" + def datastoreInitializer = new HibernateDatastoreSpringInitializer( + ['dataSource.url': 'jdbc:h2:mem:collectionCtor;LOCK_TIMEOUT=10000', 'hibernate.hbm2ddl.auto': 'create'], + [Person] as Collection<Class>) + + when: "the application is configured" + applicationContext = (ConfigurableApplicationContext) datastoreInitializer.configure() + + then: "GORM is bootstrapped with the given entity" + applicationContext.getBean(HibernateDatastore).mappingContext.getPersistentEntity(Person.name) != null + Person.withNewSession { Person.count() == 0 } + } + + void "Test configureForDataSource bootstraps GORM around a pre-existing DataSource"() { + given: "a DataSource created ahead of time" + def dataSource = new DriverManagerDataSource('jdbc:h2:mem:configureForDataSource;LOCK_TIMEOUT=10000', 'sa', '') + dataSource.driverClassName = 'org.h2.Driver' + def datastoreInitializer = new HibernateDatastoreSpringInitializer(['hibernate.hbm2ddl.auto': 'create'], Person) + + when: "the initializer is configured around that DataSource" + applicationContext = (ConfigurableApplicationContext) datastoreInitializer.configureForDataSource(dataSource) + + then: "the pre-existing DataSource is registered and reused rather than a new one being built" + applicationContext.getBean(HibernateDatastoreSpringInitializer.DEFAULT_DATA_SOURCE_NAME, DataSource).is(dataSource) + Person.withNewSession { Person.count() == 0 } + } + + void "Test configureForBeanDefinitionRegistry throws when the hibernateDatastore bean was not registered"() { + given: "an initializer whose bean definitions never register hibernateDatastore" + def datastoreInitializer = new HibernateDatastoreSpringInitializer([:], Person) { + @Override + Closure getBeanDefinitions(BeanDefinitionRegistry beanDefinitionRegistry) { + { -> } + } + } + def registry = new GenericApplicationContext() + + when: + datastoreInitializer.configureForBeanDefinitionRegistry(registry) + + then: + thrown(IllegalStateException) + + cleanup: + registry.close() + } + + void "Test the OSIV interceptor is registered when the registry is a web application"() { + given: "a registry that signals it belongs to a web application" + def datastoreInitializer = new HibernateDatastoreSpringInitializer(['dataSource.url': 'jdbc:h2:mem:osivEnabled;LOCK_TIMEOUT=10000'], Person) + def registry = new GenericApplicationContext() + registry.registerBeanDefinition('grailsControllerHelper', new RootBeanDefinition(Object)) + + when: + datastoreInitializer.configureForBeanDefinitionRegistry(registry) + registry.refresh() + applicationContext = registry + + then: + registry.containsBean('openSessionInViewInterceptor') + } + + void "Test the OSIV interceptor is not registered for a non-web application registry"() { + given: "An initializer instance configured against a plain, non-web registry" + def datastoreInitializer = new HibernateDatastoreSpringInitializer(['dataSource.url': 'jdbc:h2:mem:osivDisabled;LOCK_TIMEOUT=10000'], Person) + + when: + applicationContext = (ConfigurableApplicationContext) datastoreInitializer.configure() + + then: + !applicationContext.containsBean('openSessionInViewInterceptor') + } } @Entity class Person { diff --git a/grails-data-mongodb/core/src/test/groovy/grails/mongodb/bootstrap/MongoDbDataStoreSpringInitializerSpec.groovy b/grails-data-mongodb/core/src/test/groovy/grails/mongodb/bootstrap/MongoDbDataStoreSpringInitializerSpec.groovy index e2666d9567..a764e9688a 100644 --- a/grails-data-mongodb/core/src/test/groovy/grails/mongodb/bootstrap/MongoDbDataStoreSpringInitializerSpec.groovy +++ b/grails-data-mongodb/core/src/test/groovy/grails/mongodb/bootstrap/MongoDbDataStoreSpringInitializerSpec.groovy @@ -19,6 +19,7 @@ package grails.mongodb.bootstrap import com.mongodb.client.MongoClient +import com.mongodb.client.MongoClients import grails.mongodb.MongoEntity import grails.mongodb.geo.Point import grails.persistence.Entity @@ -26,6 +27,7 @@ import org.apache.grails.testing.mongo.AutoStartedMongoSpec import org.bson.Document import org.grails.datastore.gorm.mongo.Birthday import org.grails.datastore.gorm.mongo.BirthdayCodec +import org.grails.datastore.mapping.core.DatastoreUtils import org.grails.datastore.mapping.engine.types.AbstractMappingAwareCustomTypeMarshaller import org.grails.datastore.mapping.model.MappingContext import org.grails.datastore.mapping.model.PersistentProperty @@ -108,6 +110,50 @@ class MongoDbDataStoreSpringInitializerSpec extends AutoStartedMongoSpec { mongoDatastore.destroy() } + void "Test configure reuses a pre-existing MongoClient instead of creating a new one"() { + given: "a MongoClient created ahead of time" + def mongoClient = MongoClients.create("mongodb://${mongoHost}:${mongoPort}".toString()) + def initializer = makeInitializer([ + (MongoSettings.SETTING_DATABASE_NAME): 'foo', + ], Person) + initializer.setMongoClient(mongoClient) + + when: "the initializer is configured" + def applicationContext = initializer.configure() + + then: "the pre-existing client is registered and reused rather than a new one being built" + applicationContext.getBean('mongo', MongoClient).is(mongoClient) + applicationContext.getBean(MongoDatastore).getMongoClient().is(mongoClient) + + cleanup: + applicationContext.getBean(MongoDatastore).destroy() + mongoClient.close() + } + + void "Test the package-scanning constructor discovers entities via classpath scan"() { + given: "an initializer configured with a package name rather than explicit classes" + def resolver = DatastoreUtils.createPropertyResolver([ + (MongoSettings.SETTING_HOST): mongoHost, + (MongoSettings.SETTING_PORT): mongoPort, + ]) + def initializer = new MongoDbDataStoreSpringInitializer(resolver, Person.package.name) { + @Override + protected Map<String, Class<?>> loadDataServices(String secondaryDatastore = null) { + [:] + } + } + + when: "the application is configured" + def applicationContext = initializer.configure() + def mongoDatastore = applicationContext.getBean(MongoDatastore) + + then: "the Person entity declared in the scanned package was discovered and mapped" + mongoDatastore.mappingContext.getPersistentEntity(Person.name) != null + + cleanup: + mongoDatastore.destroy() + } + @Issue('GPMONGODB-339') @Ignore // The MongoDB API for this test has been altered / removed with no apparent replacement for getting the number of pooled connections in use diff --git a/grails-data-mongodb/core/src/test/groovy/grails/mongodb/bootstrap/MongoDbDataStoreSpringInitializerUnitSpec.groovy b/grails-data-mongodb/core/src/test/groovy/grails/mongodb/bootstrap/MongoDbDataStoreSpringInitializerUnitSpec.groovy new file mode 100644 index 0000000000..c708ac5c4e --- /dev/null +++ b/grails-data-mongodb/core/src/test/groovy/grails/mongodb/bootstrap/MongoDbDataStoreSpringInitializerUnitSpec.groovy @@ -0,0 +1,120 @@ +/* + * 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 grails.mongodb.bootstrap + +import com.mongodb.MongoClientSettings +import com.mongodb.client.MongoClient +import grails.mongodb.MongoEntity +import spock.lang.Specification + +/** + * Pure unit coverage for {@link MongoDbDataStoreSpringInitializer} that does not require a + * running MongoDB instance, covering the {@code isMappedClass} override and the deprecated + * bean-style setters that {@link MongoDbDataStoreSpringInitializerSpec} does not reach. + */ +class MongoDbDataStoreSpringInitializerUnitSpec extends Specification { + + void 'isMappedClass and collectMappedClasses discriminate MongoEntity classes from unrelated ones for a secondary datastore'() { + given: + def initializer = new MongoDbDataStoreSpringInitializer([MappedThing, UnmappedThing]) + initializer.setSecondaryDatastore(true) + + expect: + initializer.isMappedClass('mongo', MappedThing) + !initializer.isMappedClass('mongo', UnmappedThing) + initializer.collectMappedClasses('mongo') == [MappedThing] + } + + void 'setMongoBeanName updates the mongo bean name'() { + given: + def initializer = new MongoDbDataStoreSpringInitializer() + + when: + initializer.setMongoBeanName('customMongo') + + then: + initializer.mongoBeanName == 'customMongo' + } + + void 'setMongoOptionsBeanName updates the mongo options bean name'() { + given: + def initializer = new MongoDbDataStoreSpringInitializer() + + when: + initializer.setMongoOptionsBeanName('customMongoOptions') + + then: + initializer.mongoOptionsBeanName == 'customMongoOptions' + } + + void 'setDatabaseName updates the database name'() { + given: + def initializer = new MongoDbDataStoreSpringInitializer() + + when: + initializer.setDatabaseName('customDb') + + then: + initializer.databaseName == 'customDb' + } + + void 'setDefaultMapping updates the default mapping closure'() { + given: + def initializer = new MongoDbDataStoreSpringInitializer() + def mapping = { -> } + + when: + initializer.setDefaultMapping(mapping) + + then: + initializer.defaultMapping.is(mapping) + } + + void 'setMongoOptions updates the mongo client settings'() { + given: + def initializer = new MongoDbDataStoreSpringInitializer() + def settings = MongoClientSettings.builder().build() + + when: + initializer.setMongoOptions(settings) + + then: + initializer.mongoOptions.is(settings) + } + + void 'setMongoClient records the pre-existing client to reuse'() { + given: + def initializer = new MongoDbDataStoreSpringInitializer() + def client = Mock(MongoClient) + + when: + initializer.setMongoClient(client) + + then: + initializer.mongo.is(client) + } +} + +class MappedThing implements MongoEntity<MappedThing> { + Long id +} + +class UnmappedThing { + static mapWith = 'sql' +}
