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

Reply via email to