This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch merge-hibernate6 in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 7d6d589efcca60c045b1111763e491a1132e1a7b Author: Walter Duque de Estrada <wbdu...@mac.com> AuthorDate: Wed Sep 3 18:23:04 2025 -0500 Validation --- .../orm/hibernate/AbstractHibernateDatastore.java | 12 ++ .../hibernate/support/HibernateRuntimeUtils.groovy | 25 +-- .../groovy/grails/gorm/specs/ValidationSpec.groovy | 171 ------------------- .../data/testing/tck/tests/ValidationSpec.groovy | 186 +++++++++++++++++++-- 4 files changed, 200 insertions(+), 194 deletions(-) diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateDatastore.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateDatastore.java index 458dd4832b..f59114e96a 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateDatastore.java +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/AbstractHibernateDatastore.java @@ -271,6 +271,18 @@ public abstract class AbstractHibernateDatastore extends AbstractDatastore imple return autoTimestampEventListener; } + @Override + public boolean hasCurrentSession() { + // Consider a session present only if a bound session exists and is connected + org.grails.datastore.mapping.transactions.SessionHolder sessionHolder = + (org.grails.datastore.mapping.transactions.SessionHolder) + org.springframework.transaction.support.TransactionSynchronizationManager.getResource(this); + if (sessionHolder == null) { + return false; + } + return sessionHolder.getValidatedSession() != null; + } + /** * @return The data source name being used */ diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/support/HibernateRuntimeUtils.groovy b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/support/HibernateRuntimeUtils.groovy index 9046518e90..edd3afde28 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/support/HibernateRuntimeUtils.groovy +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/support/HibernateRuntimeUtils.groovy @@ -51,16 +51,21 @@ class HibernateRuntimeUtils { def errors = new ValidationErrors(target) Errors originalErrors = isGormValidateable ? ((GormValidateable)target).getErrors() : (Errors) mc.getProperty(target, GormProperties.ERRORS) - for (Object o in originalErrors.fieldErrors) { - FieldError fe = (FieldError)o - if (fe.isBindingFailure()) { - errors.addError(new FieldError(fe.getObjectName(), - fe.field, - fe.rejectedValue, - fe.bindingFailure, - fe.codes, - fe.arguments, - fe.defaultMessage)) + // Copy binding failures and any existing object-level errors + for (Object o in originalErrors.allErrors) { + if (o instanceof FieldError) { + FieldError fe = (FieldError)o + if (fe.isBindingFailure()) { + errors.addError(new FieldError(fe.getObjectName(), + fe.field, + fe.rejectedValue, + fe.bindingFailure, + fe.codes, + fe.arguments, + fe.defaultMessage)) + } + } else { + errors.addError((org.springframework.validation.ObjectError) o) } } diff --git a/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/ValidationSpec.groovy b/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/ValidationSpec.groovy deleted file mode 100644 index 9478aad07f..0000000000 --- a/grails-data-hibernate6/core/src/test/groovy/grails/gorm/specs/ValidationSpec.groovy +++ /dev/null @@ -1,171 +0,0 @@ -package grails.gorm.specs - -import org.apache.grails.data.hibernate6.core.GrailsDataHibernate6TckManager -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.ClassWithListArgBeforeValidate -import org.apache.grails.data.testing.tck.domains.ClassWithNoArgBeforeValidate -import org.apache.grails.data.testing.tck.domains.ClassWithOverloadedBeforeValidate -import org.apache.grails.data.testing.tck.domains.TestEntity -import org.springframework.transaction.support.TransactionSynchronizationManager - -/** - * Tests validation semantics. - */ -class ValidationSpec extends GrailsDataTckSpec<GrailsDataHibernate6TckManager> { - - void setupSpec() { - manager.addAllDomainClasses([ClassWithListArgBeforeValidate, ClassWithNoArgBeforeValidate, - ClassWithOverloadedBeforeValidate]) - } - - void "Test validate() method"() { - // test assumes name cannot be blank - given: - def t - - when: - t = new TestEntity(name: "") - boolean validationResult = t.validate() - def errors = t.errors - - then: - !validationResult - t.hasErrors() - errors != null - errors.hasErrors() - - when: - t.clearErrors() - - then: - !t.hasErrors() - } - - - void "Test that validate is called on save()"() { - given: - def t - - when: - t = new TestEntity(name: "") - - then: - t.save() == null - t.hasErrors() == true - 0 == TestEntity.count() - - when: - t.clearErrors() - t.name = "Bob" - t.age = 45 - t.child = new ChildEntity(name: "Fred") - t = t.save() - - then: - t != null - 1 == TestEntity.count() - } - - void "Test beforeValidate gets called on save()"() { - given: - def entityWithNoArgBeforeValidateMethod - def entityWithListArgBeforeValidateMethod - def entityWithOverloadedBeforeValidateMethod - - when: - entityWithNoArgBeforeValidateMethod = new ClassWithNoArgBeforeValidate() - entityWithListArgBeforeValidateMethod = new ClassWithListArgBeforeValidate() - entityWithOverloadedBeforeValidateMethod = new ClassWithOverloadedBeforeValidate() - entityWithNoArgBeforeValidateMethod.save() - entityWithListArgBeforeValidateMethod.save() - entityWithOverloadedBeforeValidateMethod.save() - - then: - 1 == entityWithNoArgBeforeValidateMethod.noArgCounter - 1 == entityWithListArgBeforeValidateMethod.listArgCounter - 1 == entityWithOverloadedBeforeValidateMethod.noArgCounter - 0 == entityWithOverloadedBeforeValidateMethod.listArgCounter - } - - void "Test beforeValidate gets called on validate()"() { - given: - def entityWithNoArgBeforeValidateMethod - def entityWithListArgBeforeValidateMethod - def entityWithOverloadedBeforeValidateMethod - - when: - entityWithNoArgBeforeValidateMethod = new ClassWithNoArgBeforeValidate() - entityWithListArgBeforeValidateMethod = new ClassWithListArgBeforeValidate() - entityWithOverloadedBeforeValidateMethod = new ClassWithOverloadedBeforeValidate() - entityWithNoArgBeforeValidateMethod.validate() - entityWithListArgBeforeValidateMethod.validate() - entityWithOverloadedBeforeValidateMethod.validate() - - then: - 1 == entityWithNoArgBeforeValidateMethod.noArgCounter - 1 == entityWithListArgBeforeValidateMethod.listArgCounter - 1 == entityWithOverloadedBeforeValidateMethod.noArgCounter - 0 == entityWithOverloadedBeforeValidateMethod.listArgCounter - } - - void "Test beforeValidate gets called on validate() and passing a list of field names to validate"() { - given: - def entityWithNoArgBeforeValidateMethod - def entityWithListArgBeforeValidateMethod - def entityWithOverloadedBeforeValidateMethod - - when: - entityWithNoArgBeforeValidateMethod = new ClassWithNoArgBeforeValidate() - entityWithListArgBeforeValidateMethod = new ClassWithListArgBeforeValidate() - entityWithOverloadedBeforeValidateMethod = new ClassWithOverloadedBeforeValidate() - entityWithNoArgBeforeValidateMethod.validate(['name']) - entityWithListArgBeforeValidateMethod.validate(['name']) - entityWithOverloadedBeforeValidateMethod.validate(['name']) - - then: - 1 == entityWithNoArgBeforeValidateMethod.noArgCounter - 1 == entityWithListArgBeforeValidateMethod.listArgCounter - 0 == entityWithOverloadedBeforeValidateMethod.noArgCounter - 1 == entityWithOverloadedBeforeValidateMethod.listArgCounter - ['name'] == entityWithOverloadedBeforeValidateMethod.propertiesPassedToBeforeValidate - } - - void "Test that validate works without a bound Session"() { - - given: - def t - - when: - manager.session.disconnect() - def resource - if (TransactionSynchronizationManager.hasResource(manager.session.datastore.sessionFactory)) { - resource = TransactionSynchronizationManager.unbindResource(manager.session.datastore.sessionFactory) - } - - t = new TestEntity(name: "") - - then: - TransactionSynchronizationManager.getResource(manager.session.datastore.sessionFactory) == null - t.save() == null - t.hasErrors() == true - - when: - TransactionSynchronizationManager.bindResource(manager.session.datastore.sessionFactory, resource) - - then: - 1 == t.errors.allErrors.size() - 0 == TestEntity.count() - - when: - t.clearErrors() - t.name = "Bob" - t.age = 45 - t.child = new ChildEntity(name: "Fred") - t = t.save(flush: true) - - then: - t != null - 1 == TestEntity.count() - } -} diff --git a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/ValidationSpec.groovy b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/ValidationSpec.groovy index 8761d93b3e..a38e5b5885 100644 --- a/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/ValidationSpec.groovy +++ b/grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/ValidationSpec.groovy @@ -18,8 +18,8 @@ */ package org.apache.grails.data.testing.tck.tests -import spock.lang.IgnoreIf -import spock.lang.PendingFeatureIf +import grails.gorm.transactions.Rollback +import org.springframework.transaction.support.TransactionSynchronizationManager import spock.lang.Unroll import org.springframework.validation.Validator @@ -41,11 +41,165 @@ class ValidationSpec extends GrailsDataTckSpec { void setupSpec() { manager.addAllDomainClasses([ClassWithListArgBeforeValidate, ClassWithNoArgBeforeValidate, - ClassWithOverloadedBeforeValidate, TestEntity, ChildEntity, Task]) + ClassWithOverloadedBeforeValidate, TestEntity,Task]) + } + + @Rollback + void "Test validate() method"() { + // test assumes name cannot be blank + given: + def t + + when: + t = new TestEntity(name: "") + boolean validationResult = t.validate() + def errors = t.errors + + then: + !validationResult + t.hasErrors() + errors != null + errors.hasErrors() + + when: + t.clearErrors() + + then: + !t.hasErrors() + } + + + @Rollback + void "Test that validate is called on save()"() { + given: + def t + + when: + t = new TestEntity(name: "") + + then: + t.save() == null + t.hasErrors() == true + 0 == TestEntity.count() + + when: + t.clearErrors() + t.name = "Bob" + t.age = 45 + t.child = new ChildEntity(name: "Fred") + t = t.save() + + then: + t != null + 1 == TestEntity.count() + } + + @Rollback + void "Test beforeValidate gets called on save()"() { + given: + def entityWithNoArgBeforeValidateMethod + def entityWithListArgBeforeValidateMethod + def entityWithOverloadedBeforeValidateMethod + + when: + entityWithNoArgBeforeValidateMethod = new ClassWithNoArgBeforeValidate() + entityWithListArgBeforeValidateMethod = new ClassWithListArgBeforeValidate() + entityWithOverloadedBeforeValidateMethod = new ClassWithOverloadedBeforeValidate() + entityWithNoArgBeforeValidateMethod.save() + entityWithListArgBeforeValidateMethod.save() + entityWithOverloadedBeforeValidateMethod.save() + + then: + 1 == entityWithNoArgBeforeValidateMethod.noArgCounter + 1 == entityWithListArgBeforeValidateMethod.listArgCounter + 1 == entityWithOverloadedBeforeValidateMethod.noArgCounter + 0 == entityWithOverloadedBeforeValidateMethod.listArgCounter + } + + void "Test beforeValidate gets called on validate()"() { + given: + def entityWithNoArgBeforeValidateMethod + def entityWithListArgBeforeValidateMethod + def entityWithOverloadedBeforeValidateMethod + + when: + entityWithNoArgBeforeValidateMethod = new ClassWithNoArgBeforeValidate() + entityWithListArgBeforeValidateMethod = new ClassWithListArgBeforeValidate() + entityWithOverloadedBeforeValidateMethod = new ClassWithOverloadedBeforeValidate() + entityWithNoArgBeforeValidateMethod.validate() + entityWithListArgBeforeValidateMethod.validate() + entityWithOverloadedBeforeValidateMethod.validate() + + then: + 1 == entityWithNoArgBeforeValidateMethod.noArgCounter + 1 == entityWithListArgBeforeValidateMethod.listArgCounter + 1 == entityWithOverloadedBeforeValidateMethod.noArgCounter + 0 == entityWithOverloadedBeforeValidateMethod.listArgCounter + } + + void "Test beforeValidate gets called on validate() and passing a list of field names to validate"() { + given: + def entityWithNoArgBeforeValidateMethod + def entityWithListArgBeforeValidateMethod + def entityWithOverloadedBeforeValidateMethod + + when: + entityWithNoArgBeforeValidateMethod = new ClassWithNoArgBeforeValidate() + entityWithListArgBeforeValidateMethod = new ClassWithListArgBeforeValidate() + entityWithOverloadedBeforeValidateMethod = new ClassWithOverloadedBeforeValidate() + entityWithNoArgBeforeValidateMethod.validate(['name']) + entityWithListArgBeforeValidateMethod.validate(['name']) + entityWithOverloadedBeforeValidateMethod.validate(['name']) + + then: + 1 == entityWithNoArgBeforeValidateMethod.noArgCounter + 1 == entityWithListArgBeforeValidateMethod.listArgCounter + 0 == entityWithOverloadedBeforeValidateMethod.noArgCounter + 1 == entityWithOverloadedBeforeValidateMethod.listArgCounter + ['name'] == entityWithOverloadedBeforeValidateMethod.propertiesPassedToBeforeValidate + } + + @Rollback + void "Test that validate works without a bound Session"() { + + given: + def t + + when: + manager.session.disconnect() + def resource + if (TransactionSynchronizationManager.hasResource(manager.session.datastore.sessionFactory)) { + resource = TransactionSynchronizationManager.unbindResource(manager.session.datastore.sessionFactory) + } + + t = new TestEntity(name: "") + + then: + TransactionSynchronizationManager.getResource(manager.session.datastore.sessionFactory) == null + t.save() == null + t.hasErrors() == true + + when: + TransactionSynchronizationManager.bindResource(manager.session.datastore.sessionFactory, resource) + + then: + 1 == t.errors.allErrors.size() + 0 == TestEntity.count() + + when: + t.clearErrors() + t.name = "Bob" + t.age = 45 + t.child = new ChildEntity(name: "Fred") + t = t.save(flush: true) + + then: + t != null + 1 == TestEntity.count() } // Hibernate did not originally have this test and it fails for it - @PendingFeatureIf({ System.getProperty('hibernate5.gorm.suite') }) + @Rollback void 'Test validating an object that has had values rejected with an ObjectError'() { given: def t = new TestEntity(name: 'someName') @@ -61,7 +215,7 @@ class ValidationSpec extends GrailsDataTckSpec { } // Hibernate did not originally have this test and it fails for it - @PendingFeatureIf({ System.getProperty('hibernate5.gorm.suite') }) + @Rollback void 'Test disable validation'() { // test assumes name cannot be blank given: @@ -79,6 +233,7 @@ class ValidationSpec extends GrailsDataTckSpec { errors.hasErrors() when: + t = new TestEntity(name: '', child: new ChildEntity(name: 'child')) t.save(validate: false, flush: true) then: @@ -86,6 +241,7 @@ class ValidationSpec extends GrailsDataTckSpec { !t.hasErrors() } + @Rollback void 'Test validate() method'() { // test assumes name cannot be blank given: @@ -109,6 +265,7 @@ class ValidationSpec extends GrailsDataTckSpec { !t.hasErrors() } + @Rollback void 'Test that validate is called on save()'() { given: @@ -134,6 +291,7 @@ class ValidationSpec extends GrailsDataTckSpec { 1 == TestEntity.count() } + @Rollback void 'Test beforeValidate gets called on save()'() { given: def entityWithNoArgBeforeValidateMethod @@ -155,6 +313,7 @@ class ValidationSpec extends GrailsDataTckSpec { 0 == entityWithOverloadedBeforeValidateMethod.listArgCounter } + @Rollback void 'Test beforeValidate gets called on validate()'() { given: def entityWithNoArgBeforeValidateMethod @@ -176,6 +335,7 @@ class ValidationSpec extends GrailsDataTckSpec { 0 == entityWithOverloadedBeforeValidateMethod.listArgCounter } + @Rollback void 'Test beforeValidate gets called on validate() and passing a list of field names to validate'() { given: def entityWithNoArgBeforeValidateMethod @@ -198,24 +358,22 @@ class ValidationSpec extends GrailsDataTckSpec { ['name'] == entityWithOverloadedBeforeValidateMethod.propertiesPassedToBeforeValidate } - @IgnoreIf({ - Boolean.getBoolean('neo4j.gorm.suite') || // neo4j requires a transaction present for inserts - System.getProperty('hibernate5.gorm.suite') // Hibernate has a custom version of this test - }) + @Unroll void 'Test that validate works without a bound Session'() { given: def t + def initialCount = TestEntity.count() when: manager.session.disconnect() t = new TestEntity(name: '') then: - !manager.session.datastore.hasCurrentSession() + !manager.session.isConnected() t.save() == null t.hasErrors() == true 1 == t.errors.allErrors.size() - 0 == TestEntity.count() + TestEntity.count() == initialCount when: t.clearErrors() @@ -225,11 +383,13 @@ class ValidationSpec extends GrailsDataTckSpec { t = t.save(flush: true) then: - !manager.session.datastore.hasCurrentSession() + !manager.session.isConnected() t != null - 1 == TestEntity.count() + TestEntity.count() == initialCount + 1 } + + @Unroll void 'Two parameter validate is called on entity validator if it implements Validator interface'() { given: def mockValidator = Mock(Validator)