jdaugherty commented on code in PR #16191: URL: https://github.com/apache/grails-core/pull/16191#discussion_r4146277922
########## grails-datamapping-support/src/test/groovy/org/grails/compiler/gorm/GormTransformerSpec.groovy: ########## @@ -0,0 +1,148 @@ +/* + * 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 java.lang.reflect.Modifier +import java.security.CodeSource + +import grails.gorm.annotation.Entity +import groovy.transform.Canonical +import org.codehaus.groovy.ast.AnnotationNode +import org.codehaus.groovy.ast.ClassHelper +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.CompilerConfiguration +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 org.grails.datastore.gorm.GormEntity +import org.springframework.core.Ordered +import spock.lang.Specification +import spock.lang.Unroll + +/** + * Exercises {@link GormTransformer} through every entry point the compiler uses to drive a class injector: the + * URL based {@code shouldInject} check plus the three-argument {@code performInjection} that the global artefact + * transform invokes on each matching class, the two-argument variant, and {@code performInjectionOnAnnotatedClass} + * used for explicitly annotated artefacts. For the entity transformation the injector is invoked on the parsed + * {@link ClassNode} during semantic analysis, the phase the global Grails class injector transform runs at, then + * compilation continues through class generation so the resulting class can be inspected. The {@code @Canonical} + * rejection is driven on a hand-built class node because Groovy expands that meta-annotation into the annotations it + * collects before any transform in the same phase sees the class. + */ +class GormTransformerSpec extends Specification { + + private static final Map<String, Closure<Void>> ENTRY_POINTS = [ + 'performInjection(source, context, classNode)' : { GormTransformer transformer, SourceUnit source, GeneratorContext context, ClassNode classNode -> + transformer.performInjection(source, context, classNode) + }, + 'performInjection(source, classNode)' : { GormTransformer transformer, SourceUnit source, GeneratorContext context, ClassNode classNode -> + transformer.performInjection(source, classNode) + }, + 'performInjectionOnAnnotatedClass(source, classNode)': { GormTransformer transformer, SourceUnit source, GeneratorContext context, ClassNode classNode -> + transformer.performInjectionOnAnnotatedClass(source, classNode) + } + ] + + void "getArtefactTypes returns the domain class artefact type"() { + expect: + new GormTransformer().artefactTypes == [DomainClassArtefactHandler.TYPE] as String[] + } + + void "getOrder runs the transformer before every other class injector"() { + expect: + new GormTransformer().order == Ordered.HIGHEST_PRECEDENCE + } + + @Unroll + void "shouldInject is #expected for #description"() { + expect: + new GormTransformer().shouldInject(url) == expected + + where: + description | url | expected + 'a source file under grails-app/domain' | new URL('file:/app/grails-app/domain/example/Book.groovy') | true + 'a source file under another grails-app folder' | new URL('file:/app/grails-app/services/example/BookService.groovy') | false + 'a source file outside grails-app' | new URL('file:/app/src/main/groovy/example/Book.groovy') | false + 'a null url' | null | false + } + + @Unroll + void "#entryPoint rejects a class marked with @Canonical"() { + given: "a class node that still carries the @Canonical meta-annotation" + SourceUnit source = SourceUnit.create('CanonicalBook.groovy', 'class CanonicalBook { String title }') + ClassNode classNode = new ClassNode('CanonicalBook', Modifier.PUBLIC, ClassHelper.OBJECT_TYPE) + classNode.addAnnotation(new AnnotationNode(ClassHelper.make(Canonical))) + + when: + ENTRY_POINTS[entryPoint].call(new GormTransformer(), source, null, classNode) + + then: + MultipleCompilationErrorsException e = thrown() + e.message.contains('Class [CanonicalBook] is marked with @groovy.transform.Canonical which is not supported for GORM entities.') + !GormTransformer.getKnownEntityNames().contains('CanonicalBook') + + where: + entryPoint << ENTRY_POINTS.keySet() + } + + @Unroll + void "#entryPoint turns a plain class into a GORM entity and records it in getKnownEntityNames"() { Review Comment: Fixed: the previous `getKnownEntityNames` test only compiled to `CANONICALIZATION`, so it never proved that `performInjection` produces a usable class. Production (`GrailsAwareInjectionOperation`) calls the three-argument `performInjection(source, context, classNode)`, and `ArtefactTypeAstTransformation` calls `performInjectionOnAnnotatedClass`; the spec only covered the two-argument overload. All three entry points are now exercised, and the compiled class is asserted to implement `GormEntity` and carry `@grails.gorm.annotation.Entity`. `shouldInject(URL)` and `getOrder()` are covered as well. ########## grails-datamapping-support/src/test/groovy/org/grails/compiler/gorm/GormTransformerSpec.groovy: ########## @@ -0,0 +1,148 @@ +/* + * 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 java.lang.reflect.Modifier +import java.security.CodeSource + +import grails.gorm.annotation.Entity +import groovy.transform.Canonical +import org.codehaus.groovy.ast.AnnotationNode +import org.codehaus.groovy.ast.ClassHelper +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.CompilerConfiguration +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 org.grails.datastore.gorm.GormEntity +import org.springframework.core.Ordered +import spock.lang.Specification +import spock.lang.Unroll + +/** + * Exercises {@link GormTransformer} through every entry point the compiler uses to drive a class injector: the + * URL based {@code shouldInject} check plus the three-argument {@code performInjection} that the global artefact + * transform invokes on each matching class, the two-argument variant, and {@code performInjectionOnAnnotatedClass} + * used for explicitly annotated artefacts. For the entity transformation the injector is invoked on the parsed + * {@link ClassNode} during semantic analysis, the phase the global Grails class injector transform runs at, then + * compilation continues through class generation so the resulting class can be inspected. The {@code @Canonical} + * rejection is driven on a hand-built class node because Groovy expands that meta-annotation into the annotations it + * collects before any transform in the same phase sees the class. + */ +class GormTransformerSpec extends Specification { + + private static final Map<String, Closure<Void>> ENTRY_POINTS = [ + 'performInjection(source, context, classNode)' : { GormTransformer transformer, SourceUnit source, GeneratorContext context, ClassNode classNode -> + transformer.performInjection(source, context, classNode) + }, + 'performInjection(source, classNode)' : { GormTransformer transformer, SourceUnit source, GeneratorContext context, ClassNode classNode -> + transformer.performInjection(source, classNode) + }, + 'performInjectionOnAnnotatedClass(source, classNode)': { GormTransformer transformer, SourceUnit source, GeneratorContext context, ClassNode classNode -> + transformer.performInjectionOnAnnotatedClass(source, classNode) + } + ] + + void "getArtefactTypes returns the domain class artefact type"() { + expect: + new GormTransformer().artefactTypes == [DomainClassArtefactHandler.TYPE] as String[] + } + + void "getOrder runs the transformer before every other class injector"() { + expect: + new GormTransformer().order == Ordered.HIGHEST_PRECEDENCE + } + + @Unroll + void "shouldInject is #expected for #description"() { + expect: + new GormTransformer().shouldInject(url) == expected + + where: + description | url | expected + 'a source file under grails-app/domain' | new URL('file:/app/grails-app/domain/example/Book.groovy') | true + 'a source file under another grails-app folder' | new URL('file:/app/grails-app/services/example/BookService.groovy') | false + 'a source file outside grails-app' | new URL('file:/app/src/main/groovy/example/Book.groovy') | false + 'a null url' | null | false + } + + @Unroll + void "#entryPoint rejects a class marked with @Canonical"() { + given: "a class node that still carries the @Canonical meta-annotation" + SourceUnit source = SourceUnit.create('CanonicalBook.groovy', 'class CanonicalBook { String title }') + ClassNode classNode = new ClassNode('CanonicalBook', Modifier.PUBLIC, ClassHelper.OBJECT_TYPE) + classNode.addAnnotation(new AnnotationNode(ClassHelper.make(Canonical))) + + when: + ENTRY_POINTS[entryPoint].call(new GormTransformer(), source, null, classNode) + + then: + MultipleCompilationErrorsException e = thrown() + e.message.contains('Class [CanonicalBook] is marked with @groovy.transform.Canonical which is not supported for GORM entities.') + !GormTransformer.getKnownEntityNames().contains('CanonicalBook') + + where: + entryPoint << ENTRY_POINTS.keySet() + } + + @Unroll + void "#entryPoint turns a plain class into a GORM entity and records it in getKnownEntityNames"() { + given: + String className = "GormTransformerSpecEntity${index}" + + when: + Class<?> compiled = compile(className, """ + class ${className} { + String name + } + """, ENTRY_POINTS[entryPoint]) + + then: + GormEntity.isAssignableFrom(compiled) + compiled.isAnnotationPresent(Entity) + GormTransformer.getKnownEntityNames().contains(className) + + where: + [index, entryPoint] << ENTRY_POINTS.keySet().withIndex().collect { String name, int i -> [i, name] } + } + + private static Class<?> compile(String className, String source, Closure<Void> inject) { + GormTransformer transformer = new GormTransformer() + GroovyClassLoader classLoader = new GroovyClassLoader(GormTransformerSpec.classLoader) { + @Override + protected CompilationUnit createCompilationUnit(CompilerConfiguration config, CodeSource codeSource) { + CompilationUnit unit = super.createCompilationUnit(config, codeSource) + unit.addPhaseOperation(new CompilationUnit.IPrimaryClassNodeOperation() { + @Override + void call(SourceUnit src, GeneratorContext context, ClassNode classNode) throws CompilationFailedException { + if (classNode.nameWithoutPackage == className) { + inject.call(transformer, src, context, classNode) + } + } + }, Phases.SEMANTIC_ANALYSIS) Review Comment: Fixed: the hook moved from `CANONICALIZATION` to `SEMANTIC_ANALYSIS`, which is the phase `GlobalGrailsClassInjectorTransformation` (default `@GroovyASTTransformation` phase) runs at. Hooking at `CANONICALIZATION` via `addPhaseOperation` appends after Groovy's built-in `TraitComposer.doExtendTraits` operation, so the `GormEntity` trait added by the injector is never composed and the class fails to compile. The class loader based helper also lets the test load and inspect the generated class. ########## grails-datamapping-support/src/test/groovy/org/grails/compiler/gorm/GormTransformerSpec.groovy: ########## @@ -0,0 +1,148 @@ +/* + * 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 java.lang.reflect.Modifier +import java.security.CodeSource + +import grails.gorm.annotation.Entity +import groovy.transform.Canonical +import org.codehaus.groovy.ast.AnnotationNode +import org.codehaus.groovy.ast.ClassHelper +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.CompilerConfiguration +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 org.grails.datastore.gorm.GormEntity +import org.springframework.core.Ordered +import spock.lang.Specification +import spock.lang.Unroll + +/** + * Exercises {@link GormTransformer} through every entry point the compiler uses to drive a class injector: the + * URL based {@code shouldInject} check plus the three-argument {@code performInjection} that the global artefact + * transform invokes on each matching class, the two-argument variant, and {@code performInjectionOnAnnotatedClass} + * used for explicitly annotated artefacts. For the entity transformation the injector is invoked on the parsed + * {@link ClassNode} during semantic analysis, the phase the global Grails class injector transform runs at, then + * compilation continues through class generation so the resulting class can be inspected. The {@code @Canonical} + * rejection is driven on a hand-built class node because Groovy expands that meta-annotation into the annotations it + * collects before any transform in the same phase sees the class. + */ +class GormTransformerSpec extends Specification { + + private static final Map<String, Closure<Void>> ENTRY_POINTS = [ + 'performInjection(source, context, classNode)' : { GormTransformer transformer, SourceUnit source, GeneratorContext context, ClassNode classNode -> + transformer.performInjection(source, context, classNode) + }, + 'performInjection(source, classNode)' : { GormTransformer transformer, SourceUnit source, GeneratorContext context, ClassNode classNode -> + transformer.performInjection(source, classNode) + }, + 'performInjectionOnAnnotatedClass(source, classNode)': { GormTransformer transformer, SourceUnit source, GeneratorContext context, ClassNode classNode -> + transformer.performInjectionOnAnnotatedClass(source, classNode) + } + ] + + void "getArtefactTypes returns the domain class artefact type"() { + expect: + new GormTransformer().artefactTypes == [DomainClassArtefactHandler.TYPE] as String[] + } + + void "getOrder runs the transformer before every other class injector"() { + expect: + new GormTransformer().order == Ordered.HIGHEST_PRECEDENCE + } + + @Unroll + void "shouldInject is #expected for #description"() { + expect: + new GormTransformer().shouldInject(url) == expected + + where: + description | url | expected + 'a source file under grails-app/domain' | new URL('file:/app/grails-app/domain/example/Book.groovy') | true + 'a source file under another grails-app folder' | new URL('file:/app/grails-app/services/example/BookService.groovy') | false + 'a source file outside grails-app' | new URL('file:/app/src/main/groovy/example/Book.groovy') | false + 'a null url' | null | false + } + + @Unroll + void "#entryPoint rejects a class marked with @Canonical"() { Review Comment: Fixed: the original `@Canonical` test was a false positive. The compile did fail, but not because of the injector's check. The injector was hooked at `CANONICALIZATION`, which is after Groovy's `TraitComposer.doExtendTraits` operation, so the added `GormEntity` trait was never composed and compilation failed with `Can't have an abstract method in a non-abstract class ... 'void clearErrors()' must be implemented`. That error echoes the offending source line, which happened to be `@groovy.transform.Canonical`, so `e.message.contains('@groovy.transform.Canonical')` passed. Meanwhile the injector never saw the annotation at all, because Groovy's annotation-collector visitor had already expanded `@Canonical` earlier in `SEMANTIC_ANALYSIS`. The test now builds a class node that still carries `@Canonical`, calls each `performInjection` entry point directly, and asserts the injector's own error message, plus that the class was not recorded as a known entity. ########## grails-datamapping-support/src/test/groovy/org/grails/datastore/mapping/core/grailsversion/GrailsVersionCurrentSpec.groovy: ########## @@ -0,0 +1,45 @@ +/* + * 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.mapping.core.grailsversion + +import spock.lang.Specification + +/** + * Covers the branch of {@link GrailsVersion#getCurrent()} that resolves the running Grails version. The version is + * read from the manifest of the artifact that ships {@code grails.util.BuildSettings}, which reaches this module's + * runtime classpath through its declared dependency on {@code grails-core}. The hermetic + * {@code GrailsVersionSpec} in {@code grails-datastore-core} covers the unresolved branch. + */ +class GrailsVersionCurrentSpec extends Specification { Review Comment: Added: moving `GrailsVersionSpec` to `grails-datastore-core` dropped the only coverage of the branch of `GrailsVersion.getCurrent()` that actually resolves a version (the old `isAtLeast("3.2.0") == true` rows). That branch is what `HibernateDatastoreConnectionSourcesRegistrar` and `HibernateConnectionSourceFactory` rely on through `isAtLeastMajorMinor(3, 3)`. This spec keeps it covered in `grails-datamapping-support`, where `grails-gradle-model` (which ships `grails.util.BuildSettings`) is on the runtime classpath through the module's declared `grails-core` dependency rather than incidentally. ########## grails-datastore-core/src/test/groovy/org/grails/datastore/mapping/core/grailsversion/GrailsVersionSpec.groovy: ########## @@ -51,11 +45,11 @@ class GrailsVersionSpec extends Specification { "3.3.0-SNAPSHOT" | 3 | 3 | true "3.3.0-SNAPSHOT" | 3 | 4 | false "3.3.0-SNAPSHOT" | 3 | 2 | true - "3.3.0-SNAPSHOT" | 3 | 2 | true + "3.3.0-SNAPSHOT" | 2 | 9 | true } @Unroll - void "test isAtLeast(#version, #requiredVersion) => expected"(String version, + void "test isAtLeast(#version, #requiredVersion) => #expected"(String version, Review Comment: Fixed: `=> expected` was a literal, so every unrolled iteration had the same name. Now `#expected`. ########## grails-datastore-core/src/test/groovy/org/grails/datastore/mapping/core/grailsversion/GrailsVersionSpec.groovy: ########## @@ -51,11 +45,11 @@ class GrailsVersionSpec extends Specification { "3.3.0-SNAPSHOT" | 3 | 3 | true "3.3.0-SNAPSHOT" | 3 | 4 | false "3.3.0-SNAPSHOT" | 3 | 2 | true - "3.3.0-SNAPSHOT" | 3 | 2 | true + "3.3.0-SNAPSHOT" | 2 | 9 | true Review Comment: Fixed: this row duplicated the one above it (`3.3.0-SNAPSHOT | 3 | 2 | true`). Replaced with a lower-major case so the `version.major > majorVersion` branch has a row of its own for a 3.x version. ########## 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 Review Comment: Confirmed on `8.1.x`: `grails.util.BuildSettings` now lives in `grails-gradle-model`, which reaches the root build only via `grails-bootstrap`. `grails-datastore-core` depends on neither, so this assertion holds and the unresolved branch is what this module can test. ########## grails-datamapping-support/src/test/groovy/org/grails/datastore/gorm/plugin/support/ConfigSupportSpec.groovy: ########## @@ -0,0 +1,56 @@ +/* + * 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.ConfigurableApplicationContext +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(ConfigurableApplicationContext) Review Comment: Fixed: replaced the inline fully qualified `org.springframework.context.ConfigurableApplicationContext` with an import, matching the rest of the file. ########## 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, Mock(AssociationQueryExecutor), "id") Review Comment: Fixed: `(AssociationQueryExecutor) null` carries no type at runtime in dynamic Groovy, so overload selection between `createProxy(Session, Class, Serializable)` and `createProxy(Session, AssociationQueryExecutor, K)` depended on Groovy's null-dispatch rules, and both overloads throw the same exception anyway. A `Mock(AssociationQueryExecutor)` makes the intended overload unambiguous. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
