This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch 8.0.x-hibernate7 in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 668ae12c00b6c9287ec802934c43580107bd1e02 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Sat Feb 21 18:27:50 2026 -0600 Added IdentityEnumType tests --- build.gradle | 5 +- grails-data-hibernate7/core/build.gradle | 28 +++ .../grails/gorm/specs/IdentityEnumTypeSpec.groovy | 204 +++++++++++++++++++++ 3 files changed, 236 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c05bbf2b18..33d6ace4a1 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,10 @@ import java.time.format.DateTimeFormatter plugins { // This makes the plugin available to all subprojects and scripts like java-config.gradle - id "com.diffplug.spotless" version "6.25.0" apply false + id "com.diffplug.spotless" version "8.2.1" apply false + + // Declare additional code analysis plugins globally (available but not applied yet) + id "com.github.spotbugs" version "6.4.8" apply false } ext { diff --git a/grails-data-hibernate7/core/build.gradle b/grails-data-hibernate7/core/build.gradle index 04e20741e4..3135abff6b 100644 --- a/grails-data-hibernate7/core/build.gradle +++ b/grails-data-hibernate7/core/build.gradle @@ -17,10 +17,15 @@ * under the License. */ + +import com.github.spotbugs.snom.Effort +import com.github.spotbugs.snom.Confidence plugins { id 'groovy' id 'java-library' id 'jacoco' + id 'com.github.spotbugs' + id 'pmd' } version = projectVersion @@ -147,3 +152,26 @@ jacocoTestReport { html.required = true } } + +spotbugs { + effort = Effort.valueOf('MAX') + reportLevel = Confidence.valueOf('HIGH')// or Confidence.MEDIUM, Confidence.LOW + // other settings... +} + +tasks.withType(com.github.spotbugs.snom.SpotBugsTask).configureEach { + reports { + xml.enabled = false + html.enabled = true // → nice HTML report in build/reports/spotbugs + } +} + +pmd { + consoleOutput = true +// ruleSets = ['category/java/bestpractices.xml', 'category/java/errorprone.xml', 'category/java/security.xml'] + // toolVersion = '7.0.0' // optional: override default PMD version if needed +} + +tasks.named('check') { + dependsOn 'spotbugsMain', 'pmdMain' +} diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/IdentityEnumTypeSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/IdentityEnumTypeSpec.groovy index 24b0d4d75d..f86a750e02 100644 --- a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/IdentityEnumTypeSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/IdentityEnumTypeSpec.groovy @@ -22,9 +22,14 @@ import grails.gorm.annotation.Entity import grails.gorm.transactions.Rollback import jakarta.persistence.Enumerated import jakarta.persistence.EnumType +import org.grails.orm.hibernate.cfg.IdentityEnumType +import org.hibernate.MappingException +import org.hibernate.engine.spi.SharedSessionContractImplementor import javax.sql.DataSource +import java.sql.PreparedStatement import java.sql.ResultSet +import java.sql.Types /** * Created by graemerocher on 16/11/16. @@ -60,6 +65,186 @@ class IdentityEnumTypeSpec extends HibernateGormDatastoreSpec { resultSet.getString(1) == "X__TWO" FooWithEnum.first().mySuperValue == XEnum.X__TWO } + + // ── Direct unit tests for IdentityEnumType ──────────────────────────────── + + def "setParameterValues initializes enumClass and bidiMap"() { + given: + def type = new IdentityEnumType() + def props = new Properties() + props.setProperty(IdentityEnumType.PARAM_ENUM_CLASS, IdentityStatusEnum.name) + + when: + type.setParameterValues(props) + + then: + type.returnedClass() == IdentityStatusEnum + type.sqlTypes() != null + type.sqlTypes().length > 0 + } + + def "setParameterValues throws MappingException for enum without getId method"() { + given: + def type = new IdentityEnumType() + def props = new Properties() + props.setProperty(IdentityEnumType.PARAM_ENUM_CLASS, PlainEnum.name) + + when: + type.setParameterValues(props) + + then: + thrown(MappingException) + } + + def "getBidiEnumMap caches the same instance across calls"() { + when: + def map1 = IdentityEnumType.getBidiEnumMap(IdentityStatusEnum) + def map2 = IdentityEnumType.getBidiEnumMap(IdentityStatusEnum) + + then: + map1.is(map2) + } + + def "equals uses identity comparison"() { + given: + def type = new IdentityEnumType() + + expect: + type.equals(IdentityStatusEnum.ACTIVE, IdentityStatusEnum.ACTIVE) + !type.equals(IdentityStatusEnum.ACTIVE, IdentityStatusEnum.INACTIVE) + !type.equals(null, IdentityStatusEnum.ACTIVE) + } + + def "hashCode delegates to the object"() { + given: + def type = new IdentityEnumType() + def val = IdentityStatusEnum.ACTIVE + + expect: + type.hashCode(val) == val.hashCode() + } + + def "deepCopy returns the same object reference"() { + given: + def type = new IdentityEnumType() + def val = IdentityStatusEnum.ACTIVE + + expect: + type.deepCopy(val).is(val) + } + + def "isMutable returns false"() { + expect: + !new IdentityEnumType().isMutable() + } + + def "disassemble returns the value as Serializable"() { + given: + def type = new IdentityEnumType() + def val = IdentityStatusEnum.ACTIVE + + expect: + type.disassemble(val).is(val) + } + + def "assemble returns the cached value unchanged"() { + given: + def type = new IdentityEnumType() + def val = IdentityStatusEnum.ACTIVE + + expect: + type.assemble(val, null).is(val) + } + + def "replace returns the original value"() { + given: + def type = new IdentityEnumType() + + expect: + type.replace(IdentityStatusEnum.ACTIVE, IdentityStatusEnum.INACTIVE, null).is(IdentityStatusEnum.ACTIVE) + } + + def "nullSafeSet with null value sets SQL null on PreparedStatement"() { + given: + def type = new IdentityEnumType() + def props = new Properties() + props.setProperty(IdentityEnumType.PARAM_ENUM_CLASS, IdentityStatusEnum.name) + type.setParameterValues(props) + def stmt = Mock(PreparedStatement) + def session = Mock(SharedSessionContractImplementor) + + when: + type.nullSafeSet(stmt, null, 1, session) + + then: + 1 * stmt.setNull(1, type.sqlTypes()[0]) + } + + def "nullSafeSet with non-null value writes the enum id to PreparedStatement"() { + given: + def type = new IdentityEnumType() + def props = new Properties() + props.setProperty(IdentityEnumType.PARAM_ENUM_CLASS, IdentityStatusEnum.name) + type.setParameterValues(props) + def stmt = Mock(PreparedStatement) + def session = Mock(SharedSessionContractImplementor) + + when: + type.nullSafeSet(stmt, IdentityStatusEnum.ACTIVE, 1, session) + + then: + // The String id "A" is written via setString + 1 * stmt.setString(1, "A") + 0 * stmt.setNull(_, _) + } + + def "nullSafeGet returns null when ResultSet value is null"() { + given: + def type = new IdentityEnumType() + def props = new Properties() + props.setProperty(IdentityEnumType.PARAM_ENUM_CLASS, IdentityStatusEnum.name) + type.setParameterValues(props) + def rs = Mock(ResultSet) + rs.getObject(1, IdentityStatusEnum) >> null + rs.wasNull() >> true + + when: + def result = type.nullSafeGet(rs, 1, Mock(SharedSessionContractImplementor), null) + + then: + result == null + } + + def "nullSafeGet returns the enum value from ResultSet"() { + given: + def type = new IdentityEnumType() + def props = new Properties() + props.setProperty(IdentityEnumType.PARAM_ENUM_CLASS, IdentityStatusEnum.name) + type.setParameterValues(props) + def rs = Mock(ResultSet) + rs.getObject(1, IdentityStatusEnum) >> IdentityStatusEnum.INACTIVE + rs.wasNull() >> false + + when: + def result = type.nullSafeGet(rs, 1, Mock(SharedSessionContractImplementor), null) + + then: + result == IdentityStatusEnum.INACTIVE + } + + def "getBidiEnumMap logs warning for duplicate enum ids and still returns a map"() { + when: + def map = IdentityEnumType.getBidiEnumMap(DuplicateIdEnum) + + then: + noExceptionThrown() + map != null + } + + def "getSqlType returns 0"() { + expect: + new IdentityEnumType().getSqlType() == 0 + } } @Entity @@ -109,3 +294,22 @@ enum XEnum { name } } + +/** Enum with a String id — used for direct IdentityEnumType unit tests. */ +enum IdentityStatusEnum { + ACTIVE("A"), INACTIVE("I") + final String id + IdentityStatusEnum(String id) { this.id = id } +} + +/** Plain enum with no getId — should cause MappingException in setParameterValues. */ +enum PlainEnum { + ONE, TWO +} + +/** Enum with duplicate ids — triggers the warn path in BidiEnumMap. */ +enum DuplicateIdEnum { + X("same"), Y("same") + final String id + DuplicateIdEnum(String id) { this.id = id } +}
