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

Reply via email to