This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch chore/clean-grails-datamapping-support in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit fcb7ac8f2d7711b86afddb26918e2bb6777aa066 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Fri Aug 21 13:55:53 2026 -0500 Add missing test coverage for grails-datamapping-support and dedupe ConfigSupport usage Move GrailsVersionSpec to grails-datastore-core, where GrailsVersion actually lives, and make its current-version assertions hermetic instead of relying on an incidental transitive dependency on grails-bootstrap. Add specs for previously-untested classes in grails-datamapping-support: ConfigSupport, AggregatePersistenceContextInterceptor, EntityProxyHandlerAdapter, ProxyHandlerAdapter, and GormTransformer (including its @Canonical rejection and getKnownEntityNames). Replace the hibernate5/hibernate7 HibernateGrailsPlugin's inlined copy of ConfigSupport.prepareConfig's config-conversion logic with a call to the shared method, removing a duplicated code fragment. Add an integration spec per module driving doWithSpring() through the real plugin-manager invocation path to verify both the Hibernate bean wiring and the config conversion. Co-Authored-By: Claude Sonnet 5 <[email protected]> --- .../plugin/hibernate/HibernateGrailsPlugin.groovy | 15 +- .../hibernate/HibernateGrailsPluginSpec.groovy | 87 +++++++++ .../plugin/hibernate/HibernateGrailsPlugin.groovy | 15 +- .../hibernate/HibernateGrailsPluginSpec.groovy | 87 +++++++++ .../compiler/gorm/GormTransformerSpec.groovy | 90 ++++++++++ .../gorm/plugin/support/ConfigSupportSpec.groovy | 55 ++++++ .../proxy/EntityProxyHandlerAdapterSpec.groovy | 129 ++++++++++++++ .../gorm/proxy/ProxyHandlerAdapterSpec.groovy | 112 ++++++++++++ ...gregatePersistenceContextInterceptorSpec.groovy | 196 +++++++++++++++++++++ .../core/grailsversion/GrailsVersionSpec.groovy | 14 +- 10 files changed, 764 insertions(+), 36 deletions(-) diff --git a/grails-data-hibernate5/grails-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy b/grails-data-hibernate5/grails-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy index 366da3bc46..fb23f13cfc 100644 --- a/grails-data-hibernate5/grails-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy +++ b/grails-data-hibernate5/grails-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy @@ -23,8 +23,6 @@ import groovy.transform.CompileStatic import org.springframework.beans.factory.support.BeanDefinitionRegistry import org.springframework.context.ConfigurableApplicationContext -import org.springframework.core.convert.converter.Converter -import org.springframework.core.convert.support.ConfigurableConversionService import org.springframework.core.env.PropertyResolver import grails.config.Config @@ -33,8 +31,8 @@ import grails.core.GrailsClass import grails.orm.bootstrap.HibernateDatastoreSpringInitializer import grails.plugins.Plugin import grails.util.Environment -import org.grails.config.PropertySourcesConfig import org.grails.core.artefact.DomainClassArtefactHandler +import org.grails.datastore.gorm.plugin.support.ConfigSupport /** * Plugin that integrates Hibernate into a Grails application @@ -72,16 +70,7 @@ class HibernateGrailsPlugin extends Plugin { GrailsApplication grailsApplication = grailsApplication Config config = grailsApplication.config - if (config instanceof PropertySourcesConfig) { - ConfigurableConversionService conversionService = applicationContext.getEnvironment().getConversionService() - conversionService.addConverter(new Converter<String, Class>() { - @Override - Class convert(String source) { - Class.forName(source) - } - }) - ((PropertySourcesConfig) config).setConversionService(conversionService) - } + ConfigSupport.prepareConfig(config, applicationContext) def domainClasses = grailsApplication.getArtefacts(DomainClassArtefactHandler.TYPE) .collect() { GrailsClass cls -> cls.clazz } diff --git a/grails-data-hibernate5/grails-plugin/src/test/groovy/grails/plugin/hibernate/HibernateGrailsPluginSpec.groovy b/grails-data-hibernate5/grails-plugin/src/test/groovy/grails/plugin/hibernate/HibernateGrailsPluginSpec.groovy new file mode 100644 index 0000000000..f0b43e8711 --- /dev/null +++ b/grails-data-hibernate5/grails-plugin/src/test/groovy/grails/plugin/hibernate/HibernateGrailsPluginSpec.groovy @@ -0,0 +1,87 @@ +/* + * 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.plugin.hibernate + +import grails.core.DefaultGrailsApplication +import grails.gorm.annotation.Entity +import org.grails.config.PropertySourcesConfig +import org.grails.plugins.DefaultGrailsPlugin +import org.grails.spring.DefaultRuntimeSpringConfiguration +import org.grails.spring.RuntimeSpringConfiguration +import org.hibernate.SessionFactory +import org.hibernate.dialect.H2Dialect +import org.springframework.context.support.GenericApplicationContext +import org.springframework.transaction.PlatformTransactionManager +import spock.lang.AutoCleanup +import spock.lang.Specification + +/** + * Drives {@link HibernateGrailsPlugin#doWithSpring()} the same way the real {@code GrailsPluginManager} does: via + * {@link org.grails.plugins.DefaultGrailsPlugin#doWithRuntimeConfiguration}, followed by merging the accumulated + * bean definitions into a fresh {@link GenericApplicationContext} and refreshing it - exactly what + * {@link RuntimeSpringConfiguration#registerBeansWithContext} exists for, and how a real Grails Boot application + * merges plugin-registered beans into the application's own context. Refreshing the plugin's own internal + * {@code GrailsApplicationContext} directly doesn't work here: it renames Spring's "environment" bean to + * "springEnvironment" (a long-standing workaround for GRAILS-7851) which breaks the SpEL datasource expressions + * that {@code HibernateDatastoreConnectionSourcesRegistrar} registers. + */ +class HibernateGrailsPluginSpec extends Specification { + + @AutoCleanup + GenericApplicationContext applicationContext + + void "doWithSpring registers the Hibernate beans and prepares the config for class conversion"() { + given: "a Grails application with one domain class and an H2 test datasource" + def app = new DefaultGrailsApplication([PluginSpecBook] as Class[], getClass().classLoader) + app.initialise() + def config = new PropertySourcesConfig((Map<String, Object>) [ + 'dataSource.url' : 'jdbc:h2:mem:hibernateGrailsPluginSpec;LOCK_TIMEOUT=10000', + 'dataSource.dialect' : H2Dialect.name, + 'hibernate.hbm2ddl.auto': 'create-drop', + 'some.class' : PluginSpecBook.name + ]) + app.setConfig(config) + + and: "the plugin driven the same way the real GrailsPluginManager drives it" + def grailsPlugin = new DefaultGrailsPlugin(HibernateGrailsPlugin, app) + RuntimeSpringConfiguration springConfig = new DefaultRuntimeSpringConfiguration() + grailsPlugin.applicationContext = springConfig.unrefreshedApplicationContext + + expect: "the class value cannot be resolved before doWithSpring runs" + config.getProperty('some.class', Class) == null + + when: "the plugin is configured, then its beans merged into the application's real context" + grailsPlugin.doWithRuntimeConfiguration(springConfig) + applicationContext = new GenericApplicationContext() + springConfig.registerBeansWithContext(applicationContext) + applicationContext.refresh() + + then: "the Hibernate beans registered by the plugin are present" + applicationContext.getBean('sessionFactory', SessionFactory).metamodel.entities.size() == 1 + applicationContext.getBean(PlatformTransactionManager) + + and: "the config can now resolve a String value as a Class" + config.getProperty('some.class', Class) == PluginSpecBook + } +} + +@Entity +class PluginSpecBook { + String title +} diff --git a/grails-data-hibernate7/grails-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy b/grails-data-hibernate7/grails-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy index b758e72302..427d5da6a5 100644 --- a/grails-data-hibernate7/grails-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy +++ b/grails-data-hibernate7/grails-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy @@ -23,8 +23,6 @@ import groovy.transform.CompileStatic import org.springframework.beans.factory.support.BeanDefinitionRegistry import org.springframework.context.ConfigurableApplicationContext -import org.springframework.core.convert.converter.Converter -import org.springframework.core.convert.support.ConfigurableConversionService import org.springframework.core.env.PropertyResolver import grails.config.Config @@ -33,8 +31,8 @@ import grails.core.GrailsClass import grails.orm.bootstrap.HibernateDatastoreSpringInitializer import grails.plugins.Plugin import grails.util.Environment -import org.grails.config.PropertySourcesConfig import org.grails.core.artefact.DomainClassArtefactHandler +import org.grails.datastore.gorm.plugin.support.ConfigSupport /** * Plugin that integrates Hibernate into a Grails application @@ -72,16 +70,7 @@ class HibernateGrailsPlugin extends Plugin { GrailsApplication grailsApplication = grailsApplication Config config = grailsApplication.config - if (config instanceof PropertySourcesConfig) { - ConfigurableConversionService conversionService = applicationContext.getEnvironment().getConversionService() - conversionService.addConverter(new Converter<String, Class>() { - @Override - Class convert(String source) { - Class.forName(source) - } - }) - ((PropertySourcesConfig) config).setConversionService(conversionService) - } + ConfigSupport.prepareConfig(config, applicationContext) def domainClasses = grailsApplication.getArtefacts(DomainClassArtefactHandler.TYPE) .collect() { GrailsClass cls -> cls.clazz } diff --git a/grails-data-hibernate7/grails-plugin/src/test/groovy/grails/plugin/hibernate/HibernateGrailsPluginSpec.groovy b/grails-data-hibernate7/grails-plugin/src/test/groovy/grails/plugin/hibernate/HibernateGrailsPluginSpec.groovy new file mode 100644 index 0000000000..f0b43e8711 --- /dev/null +++ b/grails-data-hibernate7/grails-plugin/src/test/groovy/grails/plugin/hibernate/HibernateGrailsPluginSpec.groovy @@ -0,0 +1,87 @@ +/* + * 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.plugin.hibernate + +import grails.core.DefaultGrailsApplication +import grails.gorm.annotation.Entity +import org.grails.config.PropertySourcesConfig +import org.grails.plugins.DefaultGrailsPlugin +import org.grails.spring.DefaultRuntimeSpringConfiguration +import org.grails.spring.RuntimeSpringConfiguration +import org.hibernate.SessionFactory +import org.hibernate.dialect.H2Dialect +import org.springframework.context.support.GenericApplicationContext +import org.springframework.transaction.PlatformTransactionManager +import spock.lang.AutoCleanup +import spock.lang.Specification + +/** + * Drives {@link HibernateGrailsPlugin#doWithSpring()} the same way the real {@code GrailsPluginManager} does: via + * {@link org.grails.plugins.DefaultGrailsPlugin#doWithRuntimeConfiguration}, followed by merging the accumulated + * bean definitions into a fresh {@link GenericApplicationContext} and refreshing it - exactly what + * {@link RuntimeSpringConfiguration#registerBeansWithContext} exists for, and how a real Grails Boot application + * merges plugin-registered beans into the application's own context. Refreshing the plugin's own internal + * {@code GrailsApplicationContext} directly doesn't work here: it renames Spring's "environment" bean to + * "springEnvironment" (a long-standing workaround for GRAILS-7851) which breaks the SpEL datasource expressions + * that {@code HibernateDatastoreConnectionSourcesRegistrar} registers. + */ +class HibernateGrailsPluginSpec extends Specification { + + @AutoCleanup + GenericApplicationContext applicationContext + + void "doWithSpring registers the Hibernate beans and prepares the config for class conversion"() { + given: "a Grails application with one domain class and an H2 test datasource" + def app = new DefaultGrailsApplication([PluginSpecBook] as Class[], getClass().classLoader) + app.initialise() + def config = new PropertySourcesConfig((Map<String, Object>) [ + 'dataSource.url' : 'jdbc:h2:mem:hibernateGrailsPluginSpec;LOCK_TIMEOUT=10000', + 'dataSource.dialect' : H2Dialect.name, + 'hibernate.hbm2ddl.auto': 'create-drop', + 'some.class' : PluginSpecBook.name + ]) + app.setConfig(config) + + and: "the plugin driven the same way the real GrailsPluginManager drives it" + def grailsPlugin = new DefaultGrailsPlugin(HibernateGrailsPlugin, app) + RuntimeSpringConfiguration springConfig = new DefaultRuntimeSpringConfiguration() + grailsPlugin.applicationContext = springConfig.unrefreshedApplicationContext + + expect: "the class value cannot be resolved before doWithSpring runs" + config.getProperty('some.class', Class) == null + + when: "the plugin is configured, then its beans merged into the application's real context" + grailsPlugin.doWithRuntimeConfiguration(springConfig) + applicationContext = new GenericApplicationContext() + springConfig.registerBeansWithContext(applicationContext) + applicationContext.refresh() + + then: "the Hibernate beans registered by the plugin are present" + applicationContext.getBean('sessionFactory', SessionFactory).metamodel.entities.size() == 1 + applicationContext.getBean(PlatformTransactionManager) + + and: "the config can now resolve a String value as a Class" + config.getProperty('some.class', Class) == PluginSpecBook + } +} + +@Entity +class PluginSpecBook { + String title +} diff --git a/grails-datamapping-support/src/test/groovy/org/grails/compiler/gorm/GormTransformerSpec.groovy b/grails-datamapping-support/src/test/groovy/org/grails/compiler/gorm/GormTransformerSpec.groovy new file mode 100644 index 0000000000..ad6afd1f5f --- /dev/null +++ b/grails-datamapping-support/src/test/groovy/org/grails/compiler/gorm/GormTransformerSpec.groovy @@ -0,0 +1,90 @@ +/* + * 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.compiler.gorm + +import org.codehaus.groovy.ast.ClassNode +import org.codehaus.groovy.classgen.GeneratorContext +import org.codehaus.groovy.control.CompilationFailedException +import org.codehaus.groovy.control.CompilationUnit +import org.codehaus.groovy.control.MultipleCompilationErrorsException +import org.codehaus.groovy.control.Phases +import org.codehaus.groovy.control.SourceUnit +import org.grails.core.artefact.DomainClassArtefactHandler +import spock.lang.Specification + +/** + * Exercises {@link GormTransformer} the same way the production global transform drives it: the injector's + * {@code performInjection} is invoked directly on the parsed {@link ClassNode} during canonicalization, then + * compilation continues to class generation. + */ +class GormTransformerSpec extends Specification { + + void "getArtefactTypes returns the domain class artefact type"() { + expect: + new GormTransformer().artefactTypes == [DomainClassArtefactHandler.TYPE] as String[] + } + + void "a class marked with @Canonical fails compilation"() { + when: + compile('CanonicalBook', ''' + @groovy.transform.Canonical + class CanonicalBook { + String title + } + ''') + + then: + MultipleCompilationErrorsException e = thrown() + e.message.contains('@groovy.transform.Canonical') + } + + void "getKnownEntityNames includes a class after performInjection has visited it"() { + given: + String className = 'GormTransformerSpecKnownEntity' + + when: + compileToPhase(className, """ + class ${className} { + String name + } + """, Phases.CANONICALIZATION) + + then: + GormTransformer.getKnownEntityNames().contains(className) + } + + private void compile(String className, String source) { + compileToPhase(className, source, Phases.CLASS_GENERATION) + } + + private void compileToPhase(String className, String source, int phase) { + CompilationUnit cu = new CompilationUnit(new GroovyClassLoader()) + cu.addSource(className, source) + GormTransformer transformer = new GormTransformer() + cu.addPhaseOperation(new CompilationUnit.PrimaryClassNodeOperation() { + @Override + void call(SourceUnit src, GeneratorContext context, ClassNode cn) throws CompilationFailedException { + if (cn.nameWithoutPackage == className) { + transformer.performInjection(src, cn) + } + } + }, Phases.CANONICALIZATION) + cu.compile(phase) + } +} diff --git a/grails-datamapping-support/src/test/groovy/org/grails/datastore/gorm/plugin/support/ConfigSupportSpec.groovy b/grails-datamapping-support/src/test/groovy/org/grails/datastore/gorm/plugin/support/ConfigSupportSpec.groovy new file mode 100644 index 0000000000..9b0e127fb0 --- /dev/null +++ b/grails-datamapping-support/src/test/groovy/org/grails/datastore/gorm/plugin/support/ConfigSupportSpec.groovy @@ -0,0 +1,55 @@ +/* + * 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.plugin.support + +import org.grails.config.PropertySourcesConfig +import org.springframework.context.support.StaticApplicationContext +import org.springframework.core.env.PropertyResolver +import spock.lang.Specification + +class ConfigSupportSpec extends Specification { + + void "prepareConfig registers a String to Class converter so config values can be resolved as classes"() { + given: + def config = new PropertySourcesConfig(['some.class': String.name]) + def applicationContext = new StaticApplicationContext() + applicationContext.refresh() + + expect: "the class value cannot be resolved before the config is prepared" + config.getProperty('some.class', Class) == null + + when: + ConfigSupport.prepareConfig(config, applicationContext) + + then: + config.getProperty('some.class', Class) == String + } + + void "prepareConfig does nothing when the config is not a PropertySourcesConfig"() { + given: + def config = Mock(PropertyResolver) + def applicationContext = Mock(org.springframework.context.ConfigurableApplicationContext) + + when: + ConfigSupport.prepareConfig(config, applicationContext) + + then: + 0 * applicationContext._ + } +} diff --git a/grails-datamapping-support/src/test/groovy/org/grails/datastore/gorm/proxy/EntityProxyHandlerAdapterSpec.groovy b/grails-datamapping-support/src/test/groovy/org/grails/datastore/gorm/proxy/EntityProxyHandlerAdapterSpec.groovy new file mode 100644 index 0000000000..0c6c6e304d --- /dev/null +++ b/grails-datamapping-support/src/test/groovy/org/grails/datastore/gorm/proxy/EntityProxyHandlerAdapterSpec.groovy @@ -0,0 +1,129 @@ +/* + * 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.proxy + +import grails.core.support.proxy.EntityProxyHandler +import org.grails.datastore.mapping.engine.AssociationQueryExecutor +import spock.lang.Specification + +class EntityProxyHandlerAdapterSpec extends Specification { + + def proxyHandler = Mock(EntityProxyHandler) + def adapter = new EntityProxyHandlerAdapter(proxyHandler) + + void "isProxy delegates to the wrapped proxy handler"() { + given: + def target = new Object() + + when: + def result = adapter.isProxy(target) + + then: + 1 * proxyHandler.isProxy(target) >> true + result + } + + void "isInitialized delegates to the wrapped proxy handler"() { + given: + def target = new Object() + + when: + def result = adapter.isInitialized(target) + + then: + 1 * proxyHandler.isInitialized(target) >> true + result + } + + void "isInitialized with an association name delegates to the wrapped proxy handler"() { + given: + def target = new Object() + + when: + def result = adapter.isInitialized(target, "author") + + then: + 1 * proxyHandler.isInitialized(target, "author") >> false + !result + } + + void "unwrap delegates to unwrapIfProxy on the wrapped proxy handler"() { + given: + def proxy = new Object() + def unwrapped = new Object() + + when: + def result = adapter.unwrap(proxy) + + then: + 1 * proxyHandler.unwrapIfProxy(proxy) >> unwrapped + result.is(unwrapped) + } + + void "getIdentifier delegates to getProxyIdentifier on the wrapped proxy handler"() { + given: + def target = new Object() + + when: + def result = adapter.getIdentifier(target) + + then: + 1 * proxyHandler.getProxyIdentifier(target) >> 42L + result == 42L + } + + void "getProxiedClass delegates to the wrapped proxy handler"() { + given: + def target = new Object() + + when: + def result = adapter.getProxiedClass(target) + + then: + 1 * proxyHandler.getProxiedClass(target) >> String + result == String + } + + void "initialize delegates to the wrapped proxy handler"() { + given: + def target = new Object() + + when: + adapter.initialize(target) + + then: + 1 * proxyHandler.initialize(target) + } + + void "createProxy with a session, type and key is not supported"() { + when: + adapter.createProxy(null, String, "id") + + then: + thrown(UnsupportedOperationException) + } + + void "createProxy with a session, association query executor and key is not supported"() { + when: + adapter.createProxy(null, (AssociationQueryExecutor) null, "id") + + then: + thrown(UnsupportedOperationException) + } +} diff --git a/grails-datamapping-support/src/test/groovy/org/grails/datastore/gorm/proxy/ProxyHandlerAdapterSpec.groovy b/grails-datamapping-support/src/test/groovy/org/grails/datastore/gorm/proxy/ProxyHandlerAdapterSpec.groovy new file mode 100644 index 0000000000..d05e404405 --- /dev/null +++ b/grails-datamapping-support/src/test/groovy/org/grails/datastore/gorm/proxy/ProxyHandlerAdapterSpec.groovy @@ -0,0 +1,112 @@ +/* + * 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.proxy + +import org.grails.datastore.mapping.proxy.ProxyHandler +import spock.lang.Specification + +class ProxyHandlerAdapterSpec extends Specification { + + def delegate = Mock(ProxyHandler) + def adapter = new ProxyHandlerAdapter(delegate) + + void "getProxyIdentifier delegates to getIdentifier on the wrapped proxy handler"() { + given: + def target = new Object() + + when: + def result = adapter.getProxyIdentifier(target) + + then: + 1 * delegate.getIdentifier(target) >> 42L + result == 42L + } + + void "getProxiedClass delegates to the wrapped proxy handler"() { + given: + def target = new Object() + + when: + def result = adapter.getProxiedClass(target) + + then: + 1 * delegate.getProxiedClass(target) >> String + result == String + } + + void "isProxy delegates to the wrapped proxy handler"() { + given: + def target = new Object() + + when: + def result = adapter.isProxy(target) + + then: + 1 * delegate.isProxy(target) >> true + result + } + + void "unwrapIfProxy delegates to unwrap on the wrapped proxy handler"() { + given: + def proxy = new Object() + def unwrapped = new Object() + + when: + def result = adapter.unwrapIfProxy(proxy) + + then: + 1 * delegate.unwrap(proxy) >> unwrapped + result.is(unwrapped) + } + + void "isInitialized delegates to the wrapped proxy handler"() { + given: + def target = new Object() + + when: + def result = adapter.isInitialized(target) + + then: + 1 * delegate.isInitialized(target) >> true + result + } + + void "initialize delegates to the wrapped proxy handler"() { + given: + def target = new Object() + + when: + adapter.initialize(target) + + then: + 1 * delegate.initialize(target) + } + + void "isInitialized with an association name delegates to the wrapped proxy handler"() { + given: + def target = new Object() + + when: + def result = adapter.isInitialized(target, "author") + + then: + 1 * delegate.isInitialized(target, "author") >> false + !result + } +} diff --git a/grails-datamapping-support/src/test/groovy/org/grails/datastore/gorm/support/AggregatePersistenceContextInterceptorSpec.groovy b/grails-datamapping-support/src/test/groovy/org/grails/datastore/gorm/support/AggregatePersistenceContextInterceptorSpec.groovy new file mode 100644 index 0000000000..ef9276d253 --- /dev/null +++ b/grails-datamapping-support/src/test/groovy/org/grails/datastore/gorm/support/AggregatePersistenceContextInterceptorSpec.groovy @@ -0,0 +1,196 @@ +/* + * 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.support + +import grails.persistence.support.PersistenceContextInterceptor +import spock.lang.Specification + +class AggregatePersistenceContextInterceptorSpec extends Specification { + + void "isOpen returns false when there are no interceptors"() { + given: + def interceptor = new AggregatePersistenceContextInterceptor([]) + + expect: + !interceptor.isOpen() + } + + void "isOpen returns true if at least one interceptor is open"() { + given: + def closed = Mock(PersistenceContextInterceptor) { + isOpen() >> false + } + def open = Mock(PersistenceContextInterceptor) { + isOpen() >> true + } + def interceptor = new AggregatePersistenceContextInterceptor([closed, open]) + + expect: + interceptor.isOpen() + } + + void "isOpen returns false when every interceptor is closed"() { + given: + def first = Mock(PersistenceContextInterceptor) { + isOpen() >> false + } + def second = Mock(PersistenceContextInterceptor) { + isOpen() >> false + } + def interceptor = new AggregatePersistenceContextInterceptor([first, second]) + + expect: + !interceptor.isOpen() + } + + void "destroy only destroys interceptors that are open"() { + given: + def open = Mock(PersistenceContextInterceptor) { + isOpen() >> true + } + def closed = Mock(PersistenceContextInterceptor) { + isOpen() >> false + } + def interceptor = new AggregatePersistenceContextInterceptor([open, closed]) + + when: + interceptor.destroy() + + then: + 1 * open.destroy() + 0 * closed.destroy() + } + + void "destroy swallows an exception from one interceptor and still destroys the rest"() { + given: + def failing = Mock(PersistenceContextInterceptor) { + isOpen() >> true + destroy() >> { throw new RuntimeException("boom") } + } + def healthy = Mock(PersistenceContextInterceptor) { + isOpen() >> true + } + def interceptor = new AggregatePersistenceContextInterceptor([failing, healthy]) + + when: + interceptor.destroy() + + then: + noExceptionThrown() + 1 * healthy.destroy() + } + + void "reconnect delegates to every interceptor"() { + given: + def first = Mock(PersistenceContextInterceptor) + def second = Mock(PersistenceContextInterceptor) + def interceptor = new AggregatePersistenceContextInterceptor([first, second]) + + when: + interceptor.reconnect() + + then: + 1 * first.reconnect() + 1 * second.reconnect() + } + + void "clear delegates to every interceptor"() { + given: + def first = Mock(PersistenceContextInterceptor) + def second = Mock(PersistenceContextInterceptor) + def interceptor = new AggregatePersistenceContextInterceptor([first, second]) + + when: + interceptor.clear() + + then: + 1 * first.clear() + 1 * second.clear() + } + + void "disconnect delegates to every interceptor"() { + given: + def first = Mock(PersistenceContextInterceptor) + def second = Mock(PersistenceContextInterceptor) + def interceptor = new AggregatePersistenceContextInterceptor([first, second]) + + when: + interceptor.disconnect() + + then: + 1 * first.disconnect() + 1 * second.disconnect() + } + + void "flush delegates to every interceptor"() { + given: + def first = Mock(PersistenceContextInterceptor) + def second = Mock(PersistenceContextInterceptor) + def interceptor = new AggregatePersistenceContextInterceptor([first, second]) + + when: + interceptor.flush() + + then: + 1 * first.flush() + 1 * second.flush() + } + + void "init delegates to every interceptor"() { + given: + def first = Mock(PersistenceContextInterceptor) + def second = Mock(PersistenceContextInterceptor) + def interceptor = new AggregatePersistenceContextInterceptor([first, second]) + + when: + interceptor.init() + + then: + 1 * first.init() + 1 * second.init() + } + + void "setReadOnly delegates to every interceptor"() { + given: + def first = Mock(PersistenceContextInterceptor) + def second = Mock(PersistenceContextInterceptor) + def interceptor = new AggregatePersistenceContextInterceptor([first, second]) + + when: + interceptor.setReadOnly() + + then: + 1 * first.setReadOnly() + 1 * second.setReadOnly() + } + + void "setReadWrite delegates to every interceptor"() { + given: + def first = Mock(PersistenceContextInterceptor) + def second = Mock(PersistenceContextInterceptor) + def interceptor = new AggregatePersistenceContextInterceptor([first, second]) + + when: + interceptor.setReadWrite() + + then: + 1 * first.setReadWrite() + 1 * second.setReadWrite() + } +} diff --git a/grails-datamapping-support/src/test/groovy/org/grails/datastore/mapping/core/grailsversion/GrailsVersionSpec.groovy b/grails-datastore-core/src/test/groovy/org/grails/datastore/mapping/core/grailsversion/GrailsVersionSpec.groovy similarity index 92% rename from grails-datamapping-support/src/test/groovy/org/grails/datastore/mapping/core/grailsversion/GrailsVersionSpec.groovy rename to grails-datastore-core/src/test/groovy/org/grails/datastore/mapping/core/grailsversion/GrailsVersionSpec.groovy index 05deff071f..c383b9ceb4 100644 --- a/grails-datamapping-support/src/test/groovy/org/grails/datastore/mapping/core/grailsversion/GrailsVersionSpec.groovy +++ b/grails-datastore-core/src/test/groovy/org/grails/datastore/mapping/core/grailsversion/GrailsVersionSpec.groovy @@ -26,17 +26,11 @@ import spock.lang.Unroll */ class GrailsVersionSpec extends Specification { - @Unroll - void "isAtLeast(#requiredVersion) => #expected"(String requiredVersion, boolean expected) { + void "isAtLeast(requiredVersion) and isAtLeastMajorMinor(major, minor) return false when the current Grails version cannot be resolved"() { expect: - expected == GrailsVersion.isAtLeast(requiredVersion) - - where: - requiredVersion | expected - "3.2.0" | true - "3.1.0" | true - "3.3.0" | true - "99.9.9" | false + GrailsVersion.current == null + !GrailsVersion.isAtLeast("0.0.1") + !GrailsVersion.isAtLeastMajorMinor(0, 1) } @Unroll
