This is an automated email from the ASF dual-hosted git repository. jamesfredley pushed a commit to branch refactor/remove-gorm-enhancer-from-tests in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit be27f2b6ca2134f62629f6d2e3e9c2a283ccedff Author: James Fredley <[email protected]> AuthorDate: Mon Feb 23 19:54:14 2026 -0500 refactor(tests): replace GormEnhancer.find*Api calls with public GORM APIs Tests were calling GormEnhancer.findStaticApi(), findInstanceApi(), and findValidationApi() to interact with GORM internals directly. Replace all usages with the same public APIs an end user would call. Changes per file: - WhereQueryConnectionRoutingSpec: use Domain."${connectionName}" named-datasource property + instance .save()/.delete() directly - FooIntegrationSpec / BarIntegrationSpec: use Domain.withNewSession to access the backing datastore - SaveWithFailOnErrorDefaultSpec: remove mutation of internal failOnError state; test per-call save(failOnError: true/false) behaviour instead - DeepValidateWithSaveSpec: drop GormInstanceApi mock; use real entity with entity.save(deepValidate: true/false) - DataServiceMultiTenantMultiDataSourceSpec: replace API-registration assertion with functional save/count via data service; replace GormEnhancer in MetricService with Metric.executeQuery/executeUpdate (safe because the service is @Transactional(connection='analytics')) - DataServiceMultiDataSourceSpec: add @Query deleteAll()/getTotalAmount() to ProductService; replace all GormEnhancer calls with data service and domain static methods - ValidationSpec (neo4j): use mappingContext.addEntityValidator(entity, null) to clear validators instead of GormEnhancer.findValidationApi() All affected tests pass. Assisted-by: Claude Code <[email protected]> --- .../groovy/myapp/BarIntegrationSpec.groovy | 3 +- .../groovy/myapp/FooIntegrationSpec.groovy | 3 +- .../DataServiceMultiDataSourceSpec.groovy | 42 +++++++----------- ...ataServiceMultiTenantMultiDataSourceSpec.groovy | 42 +++++++----------- .../groovy/grails/gorm/tests/ValidationSpec.groovy | 3 +- .../gorm/tests/DeepValidateWithSaveSpec.groovy | 38 ++++++++++------ .../gorm/SaveWithFailOnErrorDefaultSpec.groovy | 50 +++++++++++----------- .../tests/WhereQueryConnectionRoutingSpec.groovy | 36 +++++++--------- 8 files changed, 100 insertions(+), 117 deletions(-) diff --git a/grails-data-graphql/examples/grails-multi-datastore-app/src/integration-test/groovy/myapp/BarIntegrationSpec.groovy b/grails-data-graphql/examples/grails-multi-datastore-app/src/integration-test/groovy/myapp/BarIntegrationSpec.groovy index eb899dd8ef..0e63025413 100644 --- a/grails-data-graphql/examples/grails-multi-datastore-app/src/integration-test/groovy/myapp/BarIntegrationSpec.groovy +++ b/grails-data-graphql/examples/grails-multi-datastore-app/src/integration-test/groovy/myapp/BarIntegrationSpec.groovy @@ -21,7 +21,6 @@ package myapp import grails.test.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 @@ -46,6 +45,6 @@ class BarIntegrationSpec implements GraphQLSpec { then: new ObjectId((String) obj.id) - GormEnhancer.findStaticApi(Bar).datastore instanceof MongoDatastore + Bar.withNewSession { session -> session.datastore instanceof MongoDatastore } } } diff --git a/grails-data-graphql/examples/grails-multi-datastore-app/src/integration-test/groovy/myapp/FooIntegrationSpec.groovy b/grails-data-graphql/examples/grails-multi-datastore-app/src/integration-test/groovy/myapp/FooIntegrationSpec.groovy index 19e573f174..dacc444801 100644 --- a/grails-data-graphql/examples/grails-multi-datastore-app/src/integration-test/groovy/myapp/FooIntegrationSpec.groovy +++ b/grails-data-graphql/examples/grails-multi-datastore-app/src/integration-test/groovy/myapp/FooIntegrationSpec.groovy @@ -20,7 +20,6 @@ package myapp import grails.test.mixin.integration.Integration -import org.grails.datastore.gorm.GormEnhancer import org.grails.gorm.graphql.plugin.testing.GraphQLSpec import org.grails.orm.hibernate.HibernateDatastore @@ -44,6 +43,6 @@ class FooIntegrationSpec implements GraphQLSpec { then: obj.id == 1 - GormEnhancer.findStaticApi(Foo).datastore instanceof HibernateDatastore + Foo.withNewSession { session -> session.datastore instanceof HibernateDatastore } } } diff --git a/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/DataServiceMultiDataSourceSpec.groovy b/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/DataServiceMultiDataSourceSpec.groovy index e75be729a0..776a27d664 100644 --- a/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/DataServiceMultiDataSourceSpec.groovy +++ b/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/DataServiceMultiDataSourceSpec.groovy @@ -27,7 +27,6 @@ import grails.gorm.annotation.Entity import grails.gorm.services.Query import grails.gorm.services.Service import grails.gorm.transactions.Transactional -import org.grails.datastore.gorm.GormEnhancer import org.grails.datastore.mapping.core.DatastoreUtils import org.grails.orm.hibernate.HibernateDatastore @@ -79,22 +78,12 @@ class DataServiceMultiDataSourceSpec extends Specification { } void setup() { - def api = GormEnhancer.findStaticApi(Product, 'books') - api.withNewTransaction { - api.executeUpdate('delete from Product') - } + productService.deleteAll() } void "schema is created on the books datasource"() { - when: 'we query the books datasource for the product table' - def api = GormEnhancer.findStaticApi(Product, 'books') - def result = api.withNewTransaction { - api.executeQuery('SELECT 1 FROM Product p WHERE 1=0') - } - - then: 'no exception - table exists on books' - noExceptionThrown() - result != null + expect: 'Product table exists on books - count succeeds without exception' + productService.count() == 0 } void "save routes to books datasource"() { @@ -108,9 +97,7 @@ class DataServiceMultiDataSourceSpec extends Specification { saved.amount == 42 and: 'it exists on the books datasource' - GormEnhancer.findStaticApi(Product, 'books').withNewTransaction { - GormEnhancer.findStaticApi(Product, 'books').count() - } == 1 + productService.count() == 1 } void "get by ID routes to books datasource"() { @@ -190,19 +177,16 @@ class DataServiceMultiDataSourceSpec extends Specification { found.every { it.name == 'Duplicate' } } - void "GormEnhancer escape-hatch HQL works on books datasource"() { + void "@Query aggregate works on books datasource"() { given: 'products saved on books' productService.save(new Product(name: 'Foo', amount: 100)) productService.save(new Product(name: 'Bar', amount: 200)) - when: 'we run aggregate HQL through GormEnhancer' - def api = GormEnhancer.findStaticApi(Product, 'books') - def result = api.withNewTransaction { - api.executeQuery('SELECT SUM(p.amount) FROM Product p') - } + when: 'we run an aggregate @Query through the data service' + def total = productService.getTotalAmount() then: 'the aggregation reflects books data' - result[0] == 300 + total == 300 } void "save, get, and find round-trip through Data Service"() { @@ -245,9 +229,7 @@ class DataServiceMultiDataSourceSpec extends Specification { saved.amount == 42 and: 'it exists on the books datasource' - GormEnhancer.findStaticApi(Product, 'books').withNewTransaction { - GormEnhancer.findStaticApi(Product, 'books').count() - } == 1 + productDataService.count() == 1 } void "interface service: get by ID routes to books datasource"() { @@ -421,6 +403,12 @@ abstract class ProductService { abstract List<Product> findAllByName(String name) + @Query("delete from ${Product p} where 1=1") + abstract Number deleteAll() + + @Query("select sum(p.amount) from ${Product p}") + abstract Number getTotalAmount() + /** * Constructor-style save - GORM creates the entity from parameters. * Tests that SaveImplementer routes multi-arg saves through connection-aware API. diff --git a/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/DataServiceMultiTenantMultiDataSourceSpec.groovy b/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/DataServiceMultiTenantMultiDataSourceSpec.groovy index abeb5b3408..f4f01de431 100644 --- a/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/DataServiceMultiTenantMultiDataSourceSpec.groovy +++ b/grails-data-hibernate5/core/src/test/groovy/org/grails/orm/hibernate/connections/DataServiceMultiTenantMultiDataSourceSpec.groovy @@ -29,9 +29,7 @@ import grails.gorm.MultiTenant import grails.gorm.annotation.Entity import grails.gorm.services.Service import grails.gorm.transactions.Transactional -import org.grails.datastore.gorm.GormEnhancer import org.grails.datastore.gorm.GormEntity -import org.grails.datastore.gorm.GormStaticApi import org.grails.datastore.mapping.core.DatastoreUtils import org.grails.datastore.mapping.multitenancy.MultiTenancySettings import org.grails.datastore.mapping.multitenancy.resolvers.SystemPropertyTenantResolver @@ -49,7 +47,7 @@ import org.grails.orm.hibernate.HibernateDatastore * - Schema creation on the correct (analytics) datasource for MultiTenant domains * - save(), get(), delete(), count() with tenant isolation on secondary datasource * - findBy* dynamic finders with tenant isolation on secondary datasource - * - GormEnhancer escape-hatch for aggregate HQL on secondary datasource + * - Data Service aggregate HQL on secondary datasource * - Tenant isolation: same-named data under different tenants stays separate * * @see PartitionedMultiTenancySpec for basic DISCRIMINATOR multi-tenancy @@ -194,27 +192,23 @@ class DataServiceMultiTenantMultiDataSourceSpec extends Specification { found2.amount == 200 } - void "GormEnhancer resolves analytics qualifier for MultiTenant entity with explicit datasource"() { - when: 'Looking up the static API without specifying a connection' - def api = GormEnhancer.findStaticApi(Metric) + void "analytics datasource is registered and functional for MultiTenant entity"() { + when: 'A metric is saved via the data service (routes to analytics datasource)' + def saved = metricService.save(new Metric(name: 'registration-check', amount: 1)) - then: 'The API is registered and functional (schema exists on correct datasource)' - api != null - - when: 'Using the explicit analytics qualifier' - def analyticsApi = GormEnhancer.findStaticApi(Metric, 'analytics') - - then: 'The analytics API is also registered' - analyticsApi != null + then: 'Metric is persisted - analytics datasource is properly registered' + saved != null + saved.id != null + metricService.count() == 1 } - void "GormEnhancer aggregate HQL routes to analytics datasource"() { + void "aggregate HQL routes to analytics datasource via data service"() { given: 'Multiple metrics saved under tenant1' metricService.save(new Metric(name: 'alpha', amount: 10)) metricService.save(new Metric(name: 'beta', amount: 20)) metricService.save(new Metric(name: 'gamma', amount: 30)) - when: 'Using GormEnhancer for an aggregate query' + when: 'Running an aggregate query through the data service' def results = metricService.getTotalAmountAbove(15) then: 'The HQL executes against the analytics datasource' @@ -271,25 +265,21 @@ interface MetricDataService { @Transactional(connection = 'analytics') abstract class MetricService implements MetricDataService { - /** - * Statically compiled access to the analytics datasource via GormEnhancer. - */ - private GormStaticApi<Metric> getAnalyticsApi() { - GormEnhancer.findStaticApi(Metric, 'analytics') - } - /** * Delete all metrics for the current tenant from the analytics datasource. + * The @Transactional(connection = 'analytics') on this class ensures the + * executeUpdate routes to the analytics datasource. */ void deleteAll() { - analyticsApi.executeUpdate('delete from Metric') + Metric.executeUpdate('delete from Metric where 1=1') } /** - * Aggregate query - calculates total amount of metrics above a threshold. + * Aggregate query via domain class static API. + * Executes against analytics datasource via the active transaction. */ List getTotalAmountAbove(Integer minAmount) { - analyticsApi.executeQuery( + Metric.executeQuery( 'select sum(m.amount) from Metric m where m.amount > :minAmount', [minAmount: minAmount] ) diff --git a/grails-data-neo4j/grails-datastore-gorm-neo4j/src/test/groovy/grails/gorm/tests/ValidationSpec.groovy b/grails-data-neo4j/grails-datastore-gorm-neo4j/src/test/groovy/grails/gorm/tests/ValidationSpec.groovy index 24b0ec06bb..c9dd203cb5 100644 --- a/grails-data-neo4j/grails-datastore-gorm-neo4j/src/test/groovy/grails/gorm/tests/ValidationSpec.groovy +++ b/grails-data-neo4j/grails-datastore-gorm-neo4j/src/test/groovy/grails/gorm/tests/ValidationSpec.groovy @@ -19,7 +19,6 @@ package grails.gorm.tests -import org.grails.datastore.gorm.GormEnhancer import org.grails.datastore.gorm.validation.CascadingValidator import org.grails.datastore.mapping.model.PersistentEntity import org.springframework.validation.Validator @@ -38,7 +37,7 @@ class ValidationSpec extends GormDatastoreSpec { def setup() { for(cls in domainClasses) { setupValidator(cls) - GormEnhancer.findValidationApi(cls).validator = null + session.mappingContext.addEntityValidator(persistentEntityFor(cls), null) } } diff --git a/grails-datamapping-core-test/src/test/groovy/grails/gorm/tests/DeepValidateWithSaveSpec.groovy b/grails-datamapping-core-test/src/test/groovy/grails/gorm/tests/DeepValidateWithSaveSpec.groovy index f0ebdf37e9..4203167e41 100644 --- a/grails-datamapping-core-test/src/test/groovy/grails/gorm/tests/DeepValidateWithSaveSpec.groovy +++ b/grails-datamapping-core-test/src/test/groovy/grails/gorm/tests/DeepValidateWithSaveSpec.groovy @@ -18,29 +18,39 @@ */ package grails.gorm.tests +import org.apache.grails.data.testing.tck.base.GrailsDataTckSpec +import org.apache.grails.data.testing.tck.domains.ChildEntity import org.apache.grails.data.testing.tck.domains.TestEntity import org.apache.grails.data.simple.core.GrailsDataCoreTckManager -import org.apache.grails.data.testing.tck.base.GrailsDataTckSpec -import org.grails.datastore.gorm.GormEnhancer -import org.grails.datastore.gorm.GormInstanceApi -import org.grails.datastore.gorm.GormValidateable /** * Created by graemerocher on 16/02/2017. */ class DeepValidateWithSaveSpec extends GrailsDataTckSpec<GrailsDataCoreTckManager> { - void "test deep validate parameter"() { - given: - def validateable = Mock(GormValidateable) - validateable.hasErrors() >> true - def args = [deepValidate: true] + void "save with deepValidate: true succeeds for a valid entity"() { + given: "a valid TestEntity" + def entity = new TestEntity(name: 'testDeepValidate', age: 10, child: new ChildEntity(name: 'child')) + + when: "saved with deepValidate: true" + def saved = entity.save(deepValidate: true, flush: true) + + then: "the entity is persisted without errors" + saved != null + saved.id != null + !saved.hasErrors() + } + + void "save with deepValidate: false still saves a valid entity"() { + given: "a valid TestEntity" + def entity = new TestEntity(name: 'testShallowValidate', age: 10, child: new ChildEntity(name: 'child')) - when: - GormInstanceApi instanceApi = GormEnhancer.findInstanceApi(TestEntity) - instanceApi.save(validateable, [deepValidate: true]) + when: "saved with deepValidate: false" + def saved = entity.save(deepValidate: false, flush: true) - then: - 1 * validateable.validate(args) + then: "the entity is persisted without errors" + saved != null + saved.id != null + !saved.hasErrors() } } diff --git a/grails-datamapping-core-test/src/test/groovy/org/grails/datastore/gorm/SaveWithFailOnErrorDefaultSpec.groovy b/grails-datamapping-core-test/src/test/groovy/org/grails/datastore/gorm/SaveWithFailOnErrorDefaultSpec.groovy index f2af7a21e8..f14dbbb215 100644 --- a/grails-datamapping-core-test/src/test/groovy/org/grails/datastore/gorm/SaveWithFailOnErrorDefaultSpec.groovy +++ b/grails-datamapping-core-test/src/test/groovy/org/grails/datastore/gorm/SaveWithFailOnErrorDefaultSpec.groovy @@ -46,40 +46,42 @@ class SaveWithFailOnErrorDefaultSpec extends GrailsDataTckSpec<GrailsDataCoreTck context.addEntityValidator(entity, validator) } - void "test save with fail on error default"() { - when: "A product is saved with fail on error default true" - GormEnhancer.findInstanceApi(TestProduct).failOnError = true - def p = new TestProduct() - p.save() + void "save(failOnError: true) throws ValidationException on invalid entity"() { + when: "An invalid product is saved with failOnError: true" + new TestProduct().save(failOnError: true) - then: "Validation exception thrown" + then: "ValidationException is thrown" thrown(ValidationException) + } - when: "A product is saved with fail on error default false" - GormEnhancer.findInstanceApi(TestProduct).failOnError = false - p = new TestProduct() - def result = p.save() + void "save(failOnError: false) returns null on invalid entity"() { + when: "An invalid product is saved with failOnError: false" + def result = new TestProduct().save(failOnError: false) - then: "The save returns false" - !result + then: "null is returned instead of throwing" + result == null } - void "test override fail on error default"() { - when: "A product is saved with fail on error override to false" - GormEnhancer.findInstanceApi(TestProduct).failOnError = true - def p = new TestProduct() - def result = p.save(failOnError: false, flush: true) + void "save() default behaviour returns null on invalid entity"() { + when: "An invalid product is saved with no explicit failOnError arg" + def result = new TestProduct().save() - then: "The save returns false" - !result + then: "null is returned (default is false)" + result == null + } - when: "A product is saved with fail on error override to true" - GormEnhancer.findInstanceApi(TestProduct).failOnError = false - p = new TestProduct() - p.save(failOnError: true) + void "per-call failOnError: true overrides when entity is invalid"() { + when: "save with explicit failOnError: true on an invalid entity" + new TestProduct().save(failOnError: true) - then: "Validation exception thrown" + then: thrown(ValidationException) + + when: "save with explicit failOnError: false on an invalid entity" + def result = new TestProduct().save(failOnError: false, flush: true) + + then: + result == null } } diff --git a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/WhereQueryConnectionRoutingSpec.groovy b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/WhereQueryConnectionRoutingSpec.groovy index 03b05bd0d5..4659086a88 100644 --- a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/WhereQueryConnectionRoutingSpec.groovy +++ b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/WhereQueryConnectionRoutingSpec.groovy @@ -21,8 +21,6 @@ package org.apache.grails.data.testing.tck.tests import spock.lang.Issue import spock.lang.Requires -import org.grails.datastore.gorm.GormEnhancer - import org.apache.grails.data.testing.tck.base.GrailsDataTckSpec import org.apache.grails.data.testing.tck.domains.WhereRoutingItem import org.apache.grails.data.testing.tck.domains.WhereRoutingItemService @@ -112,28 +110,26 @@ class WhereQueryConnectionRoutingSpec extends GrailsDataTckSpec { } private void saveToConnection(String connectionName, String name, Double amount) { - def api = connectionName - ? GormEnhancer.findStaticApi(WhereRoutingItem, connectionName) - : GormEnhancer.findStaticApi(WhereRoutingItem) - api.withNewTransaction { - def instanceApi = connectionName - ? GormEnhancer.findInstanceApi(WhereRoutingItem, connectionName) - : GormEnhancer.findInstanceApi(WhereRoutingItem) - def item = new WhereRoutingItem(name: name, amount: amount) - instanceApi.save(item, [flush: true]) + def item = new WhereRoutingItem(name: name, amount: amount) + if (connectionName) { + WhereRoutingItem."${connectionName}".withNewTransaction { + item.save(flush: true) + } + } else { + WhereRoutingItem.withNewTransaction { + item.save(flush: true) + } } } private void deleteAllFromConnection(String connectionName) { - def api = connectionName - ? GormEnhancer.findStaticApi(WhereRoutingItem, connectionName) - : GormEnhancer.findStaticApi(WhereRoutingItem) - def instanceApi = connectionName - ? GormEnhancer.findInstanceApi(WhereRoutingItem, connectionName) - : GormEnhancer.findInstanceApi(WhereRoutingItem) - api.withNewTransaction { - for (item in api.list()) { - instanceApi.delete(item, [flush: true]) + if (connectionName) { + WhereRoutingItem."${connectionName}".withNewTransaction { + WhereRoutingItem."${connectionName}".list().each { it.delete(flush: true) } + } + } else { + WhereRoutingItem.withNewTransaction { + WhereRoutingItem.list().each { it.delete(flush: true) } } } }
