From ddab851407118407507893f4b73c8fdae6b80fa7 Mon Sep 17 00:00:00 2001 From: Stefan Yonkov Date: Thu, 23 Oct 2025 22:11:42 +0300 Subject: [PATCH 1/6] update dependencies and refactor code --- multiapps-common/pom.xml | 2 +- multiapps-mta/pom.xml | 2 +- multiapps-mta/src/main/java/module-info.java | 2 +- .../multiapps/mta/model/Version.java | 27 ++----- .../mta/parsers/PartialVersionConverter.java | 48 ------------- .../parsers/PartialVersionConverterTest.java | 72 ------------------- pom.xml | 31 ++++---- 7 files changed, 24 insertions(+), 160 deletions(-) delete mode 100644 multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/parsers/PartialVersionConverter.java delete mode 100644 multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/parsers/PartialVersionConverterTest.java diff --git a/multiapps-common/pom.xml b/multiapps-common/pom.xml index 1591c0a7..bb748592 100644 --- a/multiapps-common/pom.xml +++ b/multiapps-common/pom.xml @@ -29,7 +29,7 @@ commons-lang3 - com.fasterxml.jackson.core + tools.jackson.core jackson-databind diff --git a/multiapps-mta/pom.xml b/multiapps-mta/pom.xml index f82c0afd..5b64cf0d 100644 --- a/multiapps-mta/pom.xml +++ b/multiapps-mta/pom.xml @@ -21,7 +21,7 @@ commons-collections4 - com.vdurmont + org.semver4j semver4j diff --git a/multiapps-mta/src/main/java/module-info.java b/multiapps-mta/src/main/java/module-info.java index 1a092d01..e337fdb6 100644 --- a/multiapps-mta/src/main/java/module-info.java +++ b/multiapps-mta/src/main/java/module-info.java @@ -29,7 +29,7 @@ requires org.apache.commons.collections4; requires org.apache.commons.io; requires org.apache.commons.lang3; - requires semver4j; + requires org.semver4j; requires org.apache.commons.compress; requires static java.compiler; diff --git a/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/model/Version.java b/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/model/Version.java index f8f2efc3..3a797227 100644 --- a/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/model/Version.java +++ b/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/model/Version.java @@ -4,16 +4,12 @@ import org.cloudfoundry.multiapps.common.ParsingException; import org.cloudfoundry.multiapps.mta.Messages; -import org.cloudfoundry.multiapps.mta.parsers.PartialVersionConverter; -import com.vdurmont.semver4j.Semver; -import com.vdurmont.semver4j.Semver.SemverType; -import com.vdurmont.semver4j.SemverException; +import org.semver4j.Semver; +import org.semver4j.SemverException; public class Version implements Comparable { - private static final PartialVersionConverter PARTIAL_VERSION_CONVERTER = new PartialVersionConverter(); - private final Semver version; private Version(Semver version) { @@ -32,21 +28,12 @@ public int getPatch() { return version.getPatch(); } - public String getBuild() { - return version.getBuild(); - } - - public String[] getSuffixTokens() { - return version.getSuffixTokens(); - } - public static Version parseVersion(String versionString) { - try { - String fullVersionString = PARTIAL_VERSION_CONVERTER.convertToFullVersionString(versionString); - return new Version(new Semver(fullVersionString, SemverType.NPM)); - } catch (SemverException e) { - throw new ParsingException(e, Messages.UNABLE_TO_PARSE_VERSION, versionString); + var version = Semver.coerce(versionString); + if (version == null) { + throw new ParsingException(MessageFormat.format(Messages.UNABLE_TO_PARSE_VERSION, versionString)); } + return new Version(version); } @Override @@ -56,7 +43,7 @@ public int hashCode() { @Override public String toString() { - return version.getValue(); + return version.toString(); } @Override diff --git a/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/parsers/PartialVersionConverter.java b/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/parsers/PartialVersionConverter.java deleted file mode 100644 index 22059356..00000000 --- a/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/parsers/PartialVersionConverter.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.cloudfoundry.multiapps.mta.parsers; - -import com.vdurmont.semver4j.Semver; -import com.vdurmont.semver4j.Semver.SemverType; - -public class PartialVersionConverter { - - private static final String VERSION_STRING_TEMPLATE = "%s.%s.%s%s%s"; - private static final String VERSION_SUFFIX_STRING_TEMPLATE = "-%s"; - private static final String VERSION_BUILD_STRING_TEMPLATE = "+%s"; - private static final String DEFAULT_VERSION_SUFFIX = ""; - - public String convertToFullVersionString(String partialVersionString) { - Semver partialVersion = new Semver(partialVersionString, SemverType.LOOSE); - Integer majorVersion = partialVersion.getMajor(); - Integer minorVersion = partialVersion.getMinor(); - Integer patchVersion = partialVersion.getPatch(); - - if (minorVersion == null) { - minorVersion = 0; - } - if (patchVersion == null) { - patchVersion = 0; - } - return buildVersionString(majorVersion, minorVersion, patchVersion, partialVersion.getSuffixTokens(), partialVersion.getBuild()); - } - - private String buildVersionString(int major, int minor, int patch, String[] suffixTokens, String buildVersion) { - String formattedSuffixTokens = formatSuffixTokens(suffixTokens); - String formattedBuildVersion = formatBuildVersion(buildVersion); - return String.format(VERSION_STRING_TEMPLATE, major, minor, patch, formattedSuffixTokens, formattedBuildVersion); - } - - private String formatSuffixTokens(String[] suffixTokens) { - if (suffixTokens.length == 0) { - return DEFAULT_VERSION_SUFFIX; - } - return String.format(VERSION_SUFFIX_STRING_TEMPLATE, String.join(".", suffixTokens)); - } - - private String formatBuildVersion(String buildVersion) { - if (buildVersion == null) { - return DEFAULT_VERSION_SUFFIX; - } - return String.format(VERSION_BUILD_STRING_TEMPLATE, buildVersion); - } - -} diff --git a/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/parsers/PartialVersionConverterTest.java b/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/parsers/PartialVersionConverterTest.java deleted file mode 100644 index 5a539d72..00000000 --- a/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/parsers/PartialVersionConverterTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.cloudfoundry.multiapps.mta.parsers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.stream.Stream; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import com.vdurmont.semver4j.SemverException; - -class PartialVersionConverterTest { - - private final PartialVersionConverter partialVersionConverter = new PartialVersionConverter(); - - @ParameterizedTest - @MethodSource - void testConvertWithInvalidVersions(String versionString, String expectedExceptionMessage) { - SemverException exception = assertThrows(SemverException.class, - () -> partialVersionConverter.convertToFullVersionString(versionString)); - - assertEquals(expectedExceptionMessage, exception.getMessage()); - } - - static Stream testConvertWithInvalidVersions() { - return Stream.of( -// @formatter:off - Arguments.of("1.0.0-beta+", "The build cannot be empty."), - Arguments.of("3.a", "Invalid version (no minor version): 3.a"), - Arguments.of("a.b.c", "Invalid version (no major version): a.b.c"), - Arguments.of( "", "Invalid version (no major version): "), - Arguments.of("[ 2.0, 2.1 ]", "Invalid version (no major version): [ 2.0, 2.1 ]") -// @formatter:on - ); - } - - @ParameterizedTest - @MethodSource - void testConvertWithValidVersions(String versionString, String expectedResult) { - String fullVersionString = partialVersionConverter.convertToFullVersionString(versionString); - - assertEquals(expectedResult, fullVersionString); - } - - static Stream testConvertWithValidVersions() { - return Stream.of( -// @formatter:off - // Full version: - Arguments.of("1.0.0", "1.0.0"), - // Partial version with minor version: - Arguments.of("2.1", "2.1.0"), - // Partial version with patch version: - Arguments.of("2", "2.0.0"), - // Full version with suffix tokens: - Arguments.of("1.9.0-SHAPSHOT", "1.9.0-SHAPSHOT"), - // Partial version with suffix tokens: - Arguments.of("1.9-SHAPSHOT", "1.9.0-SHAPSHOT"), - // Partial version with suffix tokens: - Arguments.of("1-SHAPSHOT", "1.0.0-SHAPSHOT"), - // Full version with suffix tokens and build information: - Arguments.of("1.2.0-beta+exp.sha.5114f85", "1.2.0-beta+exp.sha.5114f85"), - // Partial version with suffix tokens and build information: - Arguments.of("1.2-beta+exp.sha.5114f85", "1.2.0-beta+exp.sha.5114f85"), - // Partial version with suffix tokens and build information: - Arguments.of("1-beta+exp.sha.5114f85", "1.0.0-beta+exp.sha.5114f85") -// @formatter:on - ); - } - -} diff --git a/pom.xml b/pom.xml index 9511c1d5..f170df3f 100644 --- a/pom.xml +++ b/pom.xml @@ -18,20 +18,18 @@ cloudfoundry ../multiapps-coverage/target/site/jacoco-aggregate/jacoco.xml - 5.13.4 - 1.13.2 - 5.18.0 - 3.18.0 + 6.0.0 + 5.20.0 + 3.19.0 4.5.0 2.20.0 - 2.4 - - - 3.1.0 - 2.10.1 - 2.19.2 - 4.0.5 - 4.0.2 + 2.5 + 6.0.0 + 2.11.6 + 3.0.1 + 2.20.0 + 4.0.6 + 4.0.4 1.28.0 @@ -217,7 +215,7 @@ org.junit.platform junit-platform-launcher - ${junit-platform.version} + ${junit.version} test @@ -248,14 +246,14 @@ - com.fasterxml.jackson.core + tools.jackson.core jackson-databind ${jackson.version} com.fasterxml.jackson.datatype jackson-datatype-jsr310 - ${jackson.version} + ${jackson.datatype.jsr310.version} @@ -281,9 +279,8 @@ snakeyaml ${snakeyaml.version} - - com.vdurmont + org.semver4j semver4j ${semver4j.version} From fd1ae4cb9ac5c4e1af9f8421b54148704839121a Mon Sep 17 00:00:00 2001 From: Stefan Yonkov Date: Thu, 6 Nov 2025 14:52:13 +0200 Subject: [PATCH 2/6] add unit tests --- .../multiapps/mta/model/VersionTest.java | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java diff --git a/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java b/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java new file mode 100644 index 00000000..06b984d8 --- /dev/null +++ b/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java @@ -0,0 +1,145 @@ +package org.cloudfoundry.multiapps.mta.model; + +import java.text.MessageFormat; + +import org.cloudfoundry.multiapps.common.ParsingException; +import org.cloudfoundry.multiapps.mta.Messages; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class VersionTest { + + @Nested + @DisplayName("parseVersion() tests") + class ParseVersionTests { + + @Test + @DisplayName("should parse a standard semantic version successfully") + void testParseStandardVersion() { + Version version = Version.parseVersion("1.2.3"); + + assertEquals(1, version.getMajor()); + assertEquals(2, version.getMinor()); + assertEquals(3, version.getPatch()); + assertEquals("1.2.3", version.toString()); + } + + @Test + @DisplayName("should parse versions with build metadata and timestamps") + void testParseComplexVersions() { + Version v1 = Version.parseVersion("0.0.1-20251105063220+e303076b5d51da7f0f3a10cd69de018ac0a3853d"); + Version v2 = Version.parseVersion("1.2.0-20251105043948+cb754700fb069b3367c869938ac6beb396c800e4"); + Version v3 = Version.parseVersion("1.0.0-SNAPSHOT-20240116224612+200df4832787863cc2f94d998c6cbcd711518933"); + + assertNotNull(v1); + assertNotNull(v2); + assertNotNull(v3); + + // Check major/minor/patch extracted properly + assertEquals(0, v1.getMajor()); + assertEquals(1, v1.getPatch()); + assertEquals(1, v2.getMajor()); + assertEquals(2, v2.getMinor()); + assertEquals(0, v3.getPatch()); + } + + @Test + @DisplayName("should coerce loose versions like '1', '1.2', or 'v1.2.3'") + void testParseCoercibleVersions() { + assertDoesNotThrow(() -> Version.parseVersion("1")); + assertDoesNotThrow(() -> Version.parseVersion("1.2")); + assertDoesNotThrow(() -> Version.parseVersion("v1.2.3")); + } + + @Test + @DisplayName("should throw ParsingException for invalid version strings") + void testInvalidVersionThrowsException() { + String invalidVersion = "not-a-version"; + + ParsingException exception = assertThrows(ParsingException.class, + () -> Version.parseVersion(invalidVersion)); + + assertTrue(exception.getMessage() + .contains(MessageFormat.format(Messages.UNABLE_TO_PARSE_VERSION, invalidVersion))); + } + + @Test + @DisplayName("should throw ParsingException for empty or null input") + void testEmptyOrNullThrowsException() { + assertThrows(ParsingException.class, () -> Version.parseVersion("")); + assertThrows(ParsingException.class, () -> Version.parseVersion(null)); + } + } + + @Nested + @DisplayName("compareTo() and equality tests") + class CompareTests { + + @Test + @DisplayName("should correctly compare different versions") + void testVersionComparison() { + Version v1 = Version.parseVersion("1.2.3"); + Version v2 = Version.parseVersion("1.3.0"); + Version v3 = Version.parseVersion("1.2.3"); + + assertTrue(v1.compareTo(v2) < 0); + assertTrue(v2.compareTo(v1) > 0); + assertEquals(0, v1.compareTo(v3)); + } + + @Test + @DisplayName("should maintain consistent equals/hashCode contracts") + void testEqualsAndHashCode() { + Version v1 = Version.parseVersion("2.0.0"); + Version v2 = Version.parseVersion("2.0.0"); + Version v3 = Version.parseVersion("2.0.1"); + + assertEquals(v1.hashCode(), v2.hashCode()); + assertNotEquals(v1.hashCode(), v3.hashCode()); + assertEquals(0, v1.compareTo(v2)); + } + } + + @Nested + @DisplayName("toString() tests") + class ToStringTests { + + @Test + @DisplayName("should return string representation matching Semver") + void testToStringRepresentation() { + String raw = "3.4.5-alpha+build"; + Version version = Version.parseVersion(raw); + + assertTrue(version.toString().startsWith("3.4.5")); + assertEquals(raw, version.toString()); + } + } + + @Nested + @DisplayName("edge cases and robustness") + class EdgeCases { + + @Test + @DisplayName("should handle large version numbers gracefully") + void testLargeVersionNumbers() { + Version version = Version.parseVersion("9999.9999.9999"); + assertEquals(9999, version.getMajor()); + } + + @Test + @DisplayName("should handle pre-release versions correctly") + void testPreReleaseVersion() { + Version version = Version.parseVersion("1.0.0-alpha"); + assertEquals(1, version.getMajor()); + assertTrue(version.toString().contains("alpha")); + } + } +} From 858014129c2c746e6e6071b37fef04503523437b Mon Sep 17 00:00:00 2001 From: Stefan Yonkov Date: Fri, 7 Nov 2025 17:37:43 +0200 Subject: [PATCH 3/6] fix comments Vankata D --- .../multiapps/mta/model/Version.java | 3 +- .../multiapps/mta/model/VersionTest.java | 87 +++++++++++++++++-- pom.xml | 4 +- 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/model/Version.java b/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/model/Version.java index 3a797227..67eaf16b 100644 --- a/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/model/Version.java +++ b/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/model/Version.java @@ -68,7 +68,8 @@ public boolean equals(Object obj) { public boolean satisfies(String requirement) { try { - return version.satisfies(requirement); + // The second parameter is preventing the default behavior of Semver to treat the suffix as a pre-release tag. + return version.satisfies(requirement, true); } catch (SemverException e) { throw new IllegalArgumentException(MessageFormat.format(Messages.UNABLE_TO_PARSE_VERSION_REQUIREMENT, requirement)); } diff --git a/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java b/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java index 06b984d8..8e1a77f6 100644 --- a/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java +++ b/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java @@ -8,8 +8,9 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -54,9 +55,20 @@ void testParseComplexVersions() { @Test @DisplayName("should coerce loose versions like '1', '1.2', or 'v1.2.3'") void testParseCoercibleVersions() { - assertDoesNotThrow(() -> Version.parseVersion("1")); - assertDoesNotThrow(() -> Version.parseVersion("1.2")); - assertDoesNotThrow(() -> Version.parseVersion("v1.2.3")); + Version v1 = Version.parseVersion("1"); + assertEquals(1, v1.getMajor()); + assertEquals(0, v1.getMinor()); + assertEquals(0, v1.getPatch()); + + Version v2 = Version.parseVersion("1.2"); + assertEquals(1, v2.getMajor()); + assertEquals(2, v2.getMinor()); + assertEquals(0, v2.getPatch()); + + Version v3 = Version.parseVersion("v1.2.3"); + assertEquals(1, v3.getMajor()); + assertEquals(2, v3.getMinor()); + assertEquals(3, v3.getPatch()); } @Test @@ -64,9 +76,10 @@ void testParseCoercibleVersions() { void testInvalidVersionThrowsException() { String invalidVersion = "not-a-version"; - ParsingException exception = assertThrows(ParsingException.class, + Exception exception = assertThrows(ParsingException.class, () -> Version.parseVersion(invalidVersion)); + assertInstanceOf(ParsingException.class, exception); assertTrue(exception.getMessage() .contains(MessageFormat.format(Messages.UNABLE_TO_PARSE_VERSION, invalidVersion))); } @@ -118,7 +131,6 @@ void testToStringRepresentation() { String raw = "3.4.5-alpha+build"; Version version = Version.parseVersion(raw); - assertTrue(version.toString().startsWith("3.4.5")); assertEquals(raw, version.toString()); } } @@ -142,4 +154,67 @@ void testPreReleaseVersion() { assertTrue(version.toString().contains("alpha")); } } + + @Nested + @DisplayName("satisfies() behavior") + class SatisfiesBehavior { + + @Test + @DisplayName("simple comparisons: greater/less/equal") + void testSimpleComparisons() { + Version v = Version.parseVersion("3.1.4"); + + assertTrue(v.satisfies(">3.0.0")); + assertTrue(v.satisfies(">=3.1.4")); + assertFalse(v.satisfies("<3.0.0")); + assertFalse(v.satisfies("<=3.1.3")); + } + + @Test + @DisplayName("composite ranges (space separated)") + void testCompositeRanges() { + Version v = Version.parseVersion("3.1.4"); + + assertTrue(v.satisfies(">=3.1.4 <4.0.0")); + assertTrue(v.satisfies(">=3.0.0 <=3.1.4")); + assertFalse(v.satisfies(">=3.2.0 <4.0.0")); + } + + @Test + @DisplayName("caret and tilde ranges") + void testCaretAndTilde() { + Version v = Version.parseVersion("1.3.5"); + + assertTrue(v.satisfies("^1.3.0")); + assertTrue(v.satisfies("~1.3.0")); + assertFalse(v.satisfies("^2.0.0")); + } + + @Test + @DisplayName("malformed or non-matching requirement strings are treated as non-matching (false)") + void testMalformedRequirementsReturnFalse() { + Version v = Version.parseVersion("3.1.4"); + + assertFalse(v.satisfies("not-a-requirement")); + assertFalse(v.satisfies("pesho3.1.4")); + assertFalse(v.satisfies("3.1.4.2.3.4.5")); + } + + @Test + @DisplayName("null requirement propagates NullPointerException") + void testNullRequirement() { + Version v = Version.parseVersion("3.1.4"); + assertThrows(NullPointerException.class, () -> v.satisfies(null)); + } + + @Test + @DisplayName("use versions with build metadata and timestamps for positive results") + void testSatisfiesWithComplexVersions() { + Version v = Version.parseVersion("1.2.3-20251105063220+e303076b5d51da7f0f3a10cd69de018ac0a3853d"); + + assertTrue(v.satisfies(">=1.2.0")); + assertFalse(v.satisfies(">1.2.3")); + assertFalse(v.satisfies("=1.2.3")); + } + } } diff --git a/pom.xml b/pom.xml index f170df3f..3684387f 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ cloudfoundry ../multiapps-coverage/target/site/jacoco-aggregate/jacoco.xml - 6.0.0 + 6.0.1 5.20.0 3.19.0 4.5.0 @@ -27,7 +27,7 @@ 6.0.0 2.11.6 3.0.1 - 2.20.0 + 2.20.1 4.0.6 4.0.4 1.28.0 From 93845498768fb349c0e587922ea3ce5ba8bc6a4d Mon Sep 17 00:00:00 2001 From: Stefan Yonkov Date: Mon, 10 Nov 2025 16:59:44 +0200 Subject: [PATCH 4/6] fix comments Kris --- .../multiapps/mta/model/VersionTest.java | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java b/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java index 8e1a77f6..db01b3a8 100644 --- a/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java +++ b/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java @@ -18,6 +18,7 @@ class VersionTest { + @Nested @DisplayName("parseVersion() tests") class ParseVersionTests { @@ -27,9 +28,7 @@ class ParseVersionTests { void testParseStandardVersion() { Version version = Version.parseVersion("1.2.3"); - assertEquals(1, version.getMajor()); - assertEquals(2, version.getMinor()); - assertEquals(3, version.getPatch()); + assertVersionParts(version, 1, 2, 3); assertEquals("1.2.3", version.toString()); } @@ -45,30 +44,22 @@ void testParseComplexVersions() { assertNotNull(v3); // Check major/minor/patch extracted properly - assertEquals(0, v1.getMajor()); - assertEquals(1, v1.getPatch()); - assertEquals(1, v2.getMajor()); - assertEquals(2, v2.getMinor()); - assertEquals(0, v3.getPatch()); + assertVersionParts(v1, 0, 0, 1); + assertVersionParts(v2, 1, 2, 0); + assertVersionParts(v3, 1, 0, 0); } @Test @DisplayName("should coerce loose versions like '1', '1.2', or 'v1.2.3'") void testParseCoercibleVersions() { Version v1 = Version.parseVersion("1"); - assertEquals(1, v1.getMajor()); - assertEquals(0, v1.getMinor()); - assertEquals(0, v1.getPatch()); + assertVersionParts(v1, 1, 0, 0); Version v2 = Version.parseVersion("1.2"); - assertEquals(1, v2.getMajor()); - assertEquals(2, v2.getMinor()); - assertEquals(0, v2.getPatch()); + assertVersionParts(v2, 1, 2, 0); Version v3 = Version.parseVersion("v1.2.3"); - assertEquals(1, v3.getMajor()); - assertEquals(2, v3.getMinor()); - assertEquals(3, v3.getPatch()); + assertVersionParts(v3, 1, 2, 3); } @Test @@ -215,6 +206,16 @@ void testSatisfiesWithComplexVersions() { assertTrue(v.satisfies(">=1.2.0")); assertFalse(v.satisfies(">1.2.3")); assertFalse(v.satisfies("=1.2.3")); + + // Additional checks: composite ranges and boundaries + assertTrue(v.satisfies(">=1.2.0 <2.0.0")); + assertFalse(v.satisfies(">1.2.3 <2.0.0")); } } + + private void assertVersionParts(Version version, int major, int minor, int patch) { + assertEquals(major, version.getMajor()); + assertEquals(minor, version.getMinor()); + assertEquals(patch, version.getPatch()); + } } From a95a50e3d1271477481ecb422805cf7cd258fe3e Mon Sep 17 00:00:00 2001 From: Stefan Yonkov Date: Tue, 11 Nov 2025 16:53:15 +0200 Subject: [PATCH 5/6] Update on comments, refactor of the Test class --- .../multiapps/mta/model/VersionTest.java | 142 ++++++++---------- 1 file changed, 63 insertions(+), 79 deletions(-) diff --git a/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java b/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java index db01b3a8..7a22fdeb 100644 --- a/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java +++ b/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java @@ -1,16 +1,16 @@ package org.cloudfoundry.multiapps.mta.model; -import java.text.MessageFormat; +import java.util.stream.Stream; -import org.cloudfoundry.multiapps.common.ParsingException; -import org.cloudfoundry.multiapps.mta.Messages; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -18,22 +18,17 @@ class VersionTest { + public static final String UNABLE_TO_PARSE_VERSION = "Unable to parse version"; + // --------------------------------------------------------------------------------------------- + // PARSING AND NORMALIZATION TESTS + // --------------------------------------------------------------------------------------------- @Nested - @DisplayName("parseVersion() tests") + @DisplayName("parseVersion() and normalization behavior") class ParseVersionTests { @Test - @DisplayName("should parse a standard semantic version successfully") - void testParseStandardVersion() { - Version version = Version.parseVersion("1.2.3"); - - assertVersionParts(version, 1, 2, 3); - assertEquals("1.2.3", version.toString()); - } - - @Test - @DisplayName("should parse versions with build metadata and timestamps") + @DisplayName("should parse complex versions with build metadata and timestamps") void testParseComplexVersions() { Version v1 = Version.parseVersion("0.0.1-20251105063220+e303076b5d51da7f0f3a10cd69de018ac0a3853d"); Version v2 = Version.parseVersion("1.2.0-20251105043948+cb754700fb069b3367c869938ac6beb396c800e4"); @@ -43,52 +38,59 @@ void testParseComplexVersions() { assertNotNull(v2); assertNotNull(v3); - // Check major/minor/patch extracted properly assertVersionParts(v1, 0, 0, 1); assertVersionParts(v2, 1, 2, 0); assertVersionParts(v3, 1, 0, 0); } - @Test - @DisplayName("should coerce loose versions like '1', '1.2', or 'v1.2.3'") - void testParseCoercibleVersions() { - Version v1 = Version.parseVersion("1"); - assertVersionParts(v1, 1, 0, 0); - - Version v2 = Version.parseVersion("1.2"); - assertVersionParts(v2, 1, 2, 0); - - Version v3 = Version.parseVersion("v1.2.3"); - assertVersionParts(v3, 1, 2, 3); - } - - @Test - @DisplayName("should throw ParsingException for invalid version strings") - void testInvalidVersionThrowsException() { - String invalidVersion = "not-a-version"; - - Exception exception = assertThrows(ParsingException.class, - () -> Version.parseVersion(invalidVersion)); - - assertInstanceOf(ParsingException.class, exception); - assertTrue(exception.getMessage() - .contains(MessageFormat.format(Messages.UNABLE_TO_PARSE_VERSION, invalidVersion))); - } - - @Test - @DisplayName("should throw ParsingException for empty or null input") - void testEmptyOrNullThrowsException() { - assertThrows(ParsingException.class, () -> Version.parseVersion("")); - assertThrows(ParsingException.class, () -> Version.parseVersion(null)); + @ParameterizedTest(name = "Partial version \"{0}\" should normalize to \"{1}\"") + @MethodSource + void testNormalizePartialVersions(String input, String expected) { + Version version = Version.parseVersion(input); + assertEquals(expected, version.toString()); + } + + static Stream testNormalizePartialVersions() { + return Stream.of( + Arguments.of("1.0.0", "1.0.0"), + Arguments.of("2.1", "2.1.0"), + Arguments.of("2", "2.0.0"), + Arguments.of("1.9.0-SNAPSHOT", "1.9.0-SNAPSHOT"), + Arguments.of("1.9-SNAPSHOT", "1.9.0"), + Arguments.of("1-SNAPSHOT", "1.0.0"), + Arguments.of("1.2.0-beta+exp.sha.5114f85", "1.2.0-beta+exp.sha.5114f85"), + Arguments.of("1.2-beta+exp.sha.5114f85", "1.2.0"), + Arguments.of("1-beta+exp.sha.5114f85", "1.0.0"), + Arguments.of("v1.2.3", "1.2.3") + ); + } + + @ParameterizedTest(name = "Invalid version \"{0}\" should throw SemverException or ParsingException") + @MethodSource + void testInvalidVersions(String invalid, String expectedMessage) { + Exception ex = assertThrows(Exception.class, () -> Version.parseVersion(invalid)); + assertTrue(ex.getMessage() + .contains(expectedMessage)); + } + + static Stream testInvalidVersions() { + return Stream.of( + Arguments.of("a.b.c", UNABLE_TO_PARSE_VERSION), + Arguments.of("", UNABLE_TO_PARSE_VERSION), + Arguments.of("null", UNABLE_TO_PARSE_VERSION), + Arguments.of("not-a-version", UNABLE_TO_PARSE_VERSION) + ); } } + // --------------------------------------------------------------------------------------------- + // COMPARISON AND EQUALITY TESTS + // --------------------------------------------------------------------------------------------- @Nested - @DisplayName("compareTo() and equality tests") + @DisplayName("compareTo() and equality behavior") class CompareTests { @Test - @DisplayName("should correctly compare different versions") void testVersionComparison() { Version v1 = Version.parseVersion("1.2.3"); Version v2 = Version.parseVersion("1.3.0"); @@ -100,58 +102,52 @@ void testVersionComparison() { } @Test - @DisplayName("should maintain consistent equals/hashCode contracts") void testEqualsAndHashCode() { Version v1 = Version.parseVersion("2.0.0"); Version v2 = Version.parseVersion("2.0.0"); Version v3 = Version.parseVersion("2.0.1"); + assertEquals(v1, v2); + assertNotEquals(v1, v3); assertEquals(v1.hashCode(), v2.hashCode()); assertNotEquals(v1.hashCode(), v3.hashCode()); - assertEquals(0, v1.compareTo(v2)); } } + // --------------------------------------------------------------------------------------------- + // TO STRING & EDGE CASE TESTS + // --------------------------------------------------------------------------------------------- @Nested - @DisplayName("toString() tests") - class ToStringTests { + class ToStringAndEdgeCases { @Test - @DisplayName("should return string representation matching Semver") void testToStringRepresentation() { String raw = "3.4.5-alpha+build"; Version version = Version.parseVersion(raw); - assertEquals(raw, version.toString()); } - } - - @Nested - @DisplayName("edge cases and robustness") - class EdgeCases { @Test - @DisplayName("should handle large version numbers gracefully") void testLargeVersionNumbers() { Version version = Version.parseVersion("9999.9999.9999"); assertEquals(9999, version.getMajor()); } @Test - @DisplayName("should handle pre-release versions correctly") void testPreReleaseVersion() { Version version = Version.parseVersion("1.0.0-alpha"); - assertEquals(1, version.getMajor()); - assertTrue(version.toString().contains("alpha")); + assertTrue(version.toString() + .contains("alpha")); } } + // --------------------------------------------------------------------------------------------- + // SATISFIES() RANGE CHECK TESTS + // --------------------------------------------------------------------------------------------- @Nested - @DisplayName("satisfies() behavior") class SatisfiesBehavior { @Test - @DisplayName("simple comparisons: greater/less/equal") void testSimpleComparisons() { Version v = Version.parseVersion("3.1.4"); @@ -162,52 +158,40 @@ void testSimpleComparisons() { } @Test - @DisplayName("composite ranges (space separated)") void testCompositeRanges() { Version v = Version.parseVersion("3.1.4"); - assertTrue(v.satisfies(">=3.1.4 <4.0.0")); assertTrue(v.satisfies(">=3.0.0 <=3.1.4")); assertFalse(v.satisfies(">=3.2.0 <4.0.0")); } @Test - @DisplayName("caret and tilde ranges") void testCaretAndTilde() { Version v = Version.parseVersion("1.3.5"); - assertTrue(v.satisfies("^1.3.0")); assertTrue(v.satisfies("~1.3.0")); assertFalse(v.satisfies("^2.0.0")); } @Test - @DisplayName("malformed or non-matching requirement strings are treated as non-matching (false)") void testMalformedRequirementsReturnFalse() { Version v = Version.parseVersion("3.1.4"); - assertFalse(v.satisfies("not-a-requirement")); assertFalse(v.satisfies("pesho3.1.4")); assertFalse(v.satisfies("3.1.4.2.3.4.5")); } @Test - @DisplayName("null requirement propagates NullPointerException") void testNullRequirement() { Version v = Version.parseVersion("3.1.4"); assertThrows(NullPointerException.class, () -> v.satisfies(null)); } @Test - @DisplayName("use versions with build metadata and timestamps for positive results") void testSatisfiesWithComplexVersions() { Version v = Version.parseVersion("1.2.3-20251105063220+e303076b5d51da7f0f3a10cd69de018ac0a3853d"); - assertTrue(v.satisfies(">=1.2.0")); assertFalse(v.satisfies(">1.2.3")); - assertFalse(v.satisfies("=1.2.3")); - - // Additional checks: composite ranges and boundaries assertTrue(v.satisfies(">=1.2.0 <2.0.0")); assertFalse(v.satisfies(">1.2.3 <2.0.0")); } @@ -218,4 +202,4 @@ private void assertVersionParts(Version version, int major, int minor, int patch assertEquals(minor, version.getMinor()); assertEquals(patch, version.getPatch()); } -} +} \ No newline at end of file From 3ba4b9eb11f0cfc0b3c9e06d0120ef8e4274992f Mon Sep 17 00:00:00 2001 From: Stefan Yonkov Date: Wed, 12 Nov 2025 09:12:16 +0200 Subject: [PATCH 6/6] fix comments --- .../multiapps/mta/model/VersionTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java b/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java index 7a22fdeb..c3a0d3d5 100644 --- a/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java +++ b/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/model/VersionTest.java @@ -18,7 +18,7 @@ class VersionTest { - public static final String UNABLE_TO_PARSE_VERSION = "Unable to parse version"; + private static final String UNABLE_TO_PARSE_VERSION = "Unable to parse version"; // --------------------------------------------------------------------------------------------- // PARSING AND NORMALIZATION TESTS @@ -43,6 +43,12 @@ void testParseComplexVersions() { assertVersionParts(v3, 1, 0, 0); } + private void assertVersionParts(Version version, int major, int minor, int patch) { + assertEquals(major, version.getMajor()); + assertEquals(minor, version.getMinor()); + assertEquals(patch, version.getPatch()); + } + @ParameterizedTest(name = "Partial version \"{0}\" should normalize to \"{1}\"") @MethodSource void testNormalizePartialVersions(String input, String expected) { @@ -196,10 +202,4 @@ void testSatisfiesWithComplexVersions() { assertFalse(v.satisfies(">1.2.3 <2.0.0")); } } - - private void assertVersionParts(Version version, int major, int minor, int patch) { - assertEquals(major, version.getMajor()); - assertEquals(minor, version.getMinor()); - assertEquals(patch, version.getPatch()); - } } \ No newline at end of file