From da9f3cce1a462e36fe08f4b080586c72d5eebbcb Mon Sep 17 00:00:00 2001 From: Evan Sarkar Date: Sat, 28 Mar 2026 06:01:23 +0530 Subject: [PATCH] Add equals/hashCode to MigrationResult Implement value-based equality for MigrationResult based on success status, source/target versions, domain, and duration. Fixes #222 Signed-off-by: Evan Sarkar --- .../spring/service/MigrationResult.java | 45 ++++++++++- .../spring/service/MigrationResultTest.java | 77 +++++++++++++++++++ 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/aether-datafixers-spring-boot-starter/src/main/java/de/splatgames/aether/datafixers/spring/service/MigrationResult.java b/aether-datafixers-spring-boot-starter/src/main/java/de/splatgames/aether/datafixers/spring/service/MigrationResult.java index e46b4d4..7ed9b9c 100644 --- a/aether-datafixers-spring-boot-starter/src/main/java/de/splatgames/aether/datafixers/spring/service/MigrationResult.java +++ b/aether-datafixers-spring-boot-starter/src/main/java/de/splatgames/aether/datafixers/spring/service/MigrationResult.java @@ -29,6 +29,7 @@ import org.jetbrains.annotations.Nullable; import java.time.Duration; +import java.util.Objects; import java.util.Optional; /** @@ -120,8 +121,8 @@ * shared between threads without synchronization.

* *

Equality and Hashing

- *

This class does not override {@code equals()} and {@code hashCode()}. - * Each instance is unique and identity-based comparison is used.

+ *

Two {@code MigrationResult} instances are considered equal if they have the same + * success status, source and target versions, domain, and duration.

* * @author Erik Pförtner * @see MigrationService @@ -429,6 +430,46 @@ public int getVersionSpan() { return Math.abs(this.toVersion.getVersion() - this.fromVersion.getVersion()); } + /** + * Indicates whether some other object is "equal to" this migration result. + * + *

Two {@code MigrationResult} instances are considered equal if and only if they + * have the same success status, source version, target version, domain, and duration.

+ * + * @param obj the reference object with which to compare; may be {@code null} + * @return {@code true} if this result is equal to the specified object; {@code false} otherwise + * @see #hashCode() + */ + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof MigrationResult other)) { + return false; + } + return this.success == other.success + && Objects.equals(this.fromVersion, other.fromVersion) + && Objects.equals(this.toVersion, other.toVersion) + && Objects.equals(this.domain, other.domain) + && Objects.equals(this.duration, other.duration); + } + + /** + * Returns a hash code value for this migration result. + * + *

The hash code is computed based on the success status, source version, target version, + * domain, and duration. This implementation satisfies the general contract of + * {@link Object#hashCode()}.

+ * + * @return a hash code value for this migration result + * @see #equals(Object) + */ + @Override + public int hashCode() { + return Objects.hash(this.success, this.fromVersion, this.toVersion, this.domain, this.duration); + } + /** * Returns a human-readable string representation of this result. * diff --git a/aether-datafixers-spring-boot-starter/src/test/java/de/splatgames/aether/datafixers/spring/service/MigrationResultTest.java b/aether-datafixers-spring-boot-starter/src/test/java/de/splatgames/aether/datafixers/spring/service/MigrationResultTest.java index e973996..1680d57 100644 --- a/aether-datafixers-spring-boot-starter/src/test/java/de/splatgames/aether/datafixers/spring/service/MigrationResultTest.java +++ b/aether-datafixers-spring-boot-starter/src/test/java/de/splatgames/aether/datafixers/spring/service/MigrationResultTest.java @@ -237,6 +237,83 @@ void getDurationReturnsMigrationDuration() { } } + @Nested + @DisplayName("equals and hashCode") + class EqualsAndHashCode { + + @Test + @DisplayName("equals() returns true for same values") + void equalsReturnsTrueForSameValues() { + TaggedDynamic data = mock(TaggedDynamic.class); + + MigrationResult a = MigrationResult.success( + data, FROM_VERSION, TO_VERSION, DOMAIN, DURATION + ); + MigrationResult b = MigrationResult.success( + data, FROM_VERSION, TO_VERSION, DOMAIN, DURATION + ); + + assertThat(a).isEqualTo(b); + } + + @Test + @DisplayName("equals() returns false for different success status") + void equalsReturnsFalseForDifferentSuccess() { + TaggedDynamic data = mock(TaggedDynamic.class); + + MigrationResult success = MigrationResult.success( + data, FROM_VERSION, TO_VERSION, DOMAIN, DURATION + ); + MigrationResult failure = MigrationResult.failure( + FROM_VERSION, TO_VERSION, DOMAIN, DURATION, new RuntimeException("error") + ); + + assertThat(success).isNotEqualTo(failure); + } + + @Test + @DisplayName("equals() returns false for different versions") + void equalsReturnsFalseForDifferentVersions() { + TaggedDynamic data = mock(TaggedDynamic.class); + + MigrationResult a = MigrationResult.success( + data, FROM_VERSION, TO_VERSION, DOMAIN, DURATION + ); + MigrationResult b = MigrationResult.success( + data, FROM_VERSION, new DataVersion(300), DOMAIN, DURATION + ); + + assertThat(a).isNotEqualTo(b); + } + + @Test + @DisplayName("hashCode() is consistent for equal objects") + void hashCodeConsistentForEqualObjects() { + TaggedDynamic data = mock(TaggedDynamic.class); + + MigrationResult a = MigrationResult.success( + data, FROM_VERSION, TO_VERSION, DOMAIN, DURATION + ); + MigrationResult b = MigrationResult.success( + data, FROM_VERSION, TO_VERSION, DOMAIN, DURATION + ); + + assertThat(a.hashCode()).isEqualTo(b.hashCode()); + } + + @Test + @DisplayName("equals() returns false for null") + void equalsReturnsFalseForNull() { + TaggedDynamic data = mock(TaggedDynamic.class); + + MigrationResult result = MigrationResult.success( + data, FROM_VERSION, TO_VERSION, DOMAIN, DURATION + ); + + assertThat(result).isNotEqualTo(null); + } + } + @Nested @DisplayName("toString") class ToStringMethod {