EnforcerVersionRangeStrategyTest.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.cling.invoker.mvnup.goals;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Optional;
import eu.maveniverse.domtrip.Document;
import org.apache.maven.api.cli.mvnup.UpgradeOptions;
import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
import org.junit.jupiter.api.BeforeEach;
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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Unit tests for the {@link EnforcerVersionRangeStrategy} class.
* Tests widening of RequireMavenVersion ranges to allow Maven 4.
*/
@DisplayName("EnforcerVersionRangeStrategy")
class EnforcerVersionRangeStrategyTest {
private EnforcerVersionRangeStrategy strategy;
@BeforeEach
void setUp() {
strategy = new EnforcerVersionRangeStrategy();
}
private UpgradeContext createMockContext() {
return TestUtils.createMockContext();
}
private UpgradeContext createMockContext(UpgradeOptions options) {
return TestUtils.createMockContext(options);
}
@Nested
@DisplayName("Applicability")
class ApplicabilityTests {
@Test
@DisplayName("should be applicable when --model option is true")
void shouldBeApplicableWhenModelOptionTrue() {
UpgradeOptions options = mock(UpgradeOptions.class);
when(options.model()).thenReturn(Optional.of(true));
when(options.all()).thenReturn(Optional.empty());
when(options.infer()).thenReturn(Optional.empty());
when(options.plugins()).thenReturn(Optional.empty());
when(options.modelVersion()).thenReturn(Optional.empty());
UpgradeContext context = createMockContext(options);
assertTrue(strategy.isApplicable(context));
}
@Test
@DisplayName("should be applicable when --all option is specified")
void shouldBeApplicableWhenAllOptionSpecified() {
UpgradeOptions options = mock(UpgradeOptions.class);
when(options.all()).thenReturn(Optional.of(true));
when(options.model()).thenReturn(Optional.empty());
when(options.infer()).thenReturn(Optional.empty());
when(options.plugins()).thenReturn(Optional.empty());
when(options.modelVersion()).thenReturn(Optional.empty());
UpgradeContext context = createMockContext(options);
assertTrue(strategy.isApplicable(context));
}
@Test
@DisplayName("should be applicable by default when no specific options provided")
void shouldBeApplicableByDefaultWhenNoSpecificOptions() {
UpgradeContext context = createMockContext(TestUtils.createDefaultOptions());
assertTrue(strategy.isApplicable(context));
}
}
@Nested
@DisplayName("Version Range Widening")
class VersionRangeWideningTests {
@Test
@DisplayName("should widen [3.8.8,4) to [3.8.8,5)")
void shouldWidenStandardRange() {
assertEquals("[3.8.8,5)", EnforcerVersionRangeStrategy.widenVersionRange("[3.8.8,4)"));
}
@Test
@DisplayName("should widen [3,4) to [3,5)")
void shouldWidenShortRange() {
assertEquals("[3,5)", EnforcerVersionRangeStrategy.widenVersionRange("[3,4)"));
}
@Test
@DisplayName("should widen (3.6.3,4) to (3.6.3,5)")
void shouldWidenExclusiveLowerBoundRange() {
assertEquals("(3.6.3,5)", EnforcerVersionRangeStrategy.widenVersionRange("(3.6.3,4)"));
}
@Test
@DisplayName("should widen [3.8.8,4.0) to [3.8.8,5)")
void shouldWidenRange4Dot0() {
assertEquals("[3.8.8,5)", EnforcerVersionRangeStrategy.widenVersionRange("[3.8.8,4.0)"));
}
@Test
@DisplayName("should widen [3.8.8,4.0.0) to [3.8.8,5)")
void shouldWidenRange4Dot0Dot0() {
assertEquals("[3.8.8,5)", EnforcerVersionRangeStrategy.widenVersionRange("[3.8.8,4.0.0)"));
}
@Test
@DisplayName("should not widen [3.8.8,) ��� open upper bound already allows 4.x")
void shouldNotWidenOpenUpperBound() {
assertNull(EnforcerVersionRangeStrategy.widenVersionRange("[3.8.8,)"));
}
@Test
@DisplayName("should not widen plain version 3.8.8")
void shouldNotWidenPlainVersion() {
assertNull(EnforcerVersionRangeStrategy.widenVersionRange("3.8.8"));
}
@Test
@DisplayName("should not widen [3.8.8,5) ��� already allows Maven 4")
void shouldNotWidenAlreadyWidened() {
assertNull(EnforcerVersionRangeStrategy.widenVersionRange("[3.8.8,5)"));
}
@Test
@DisplayName("should not widen [3.8.8,3.9) ��� upper bound is not at 4")
void shouldNotWidenNon4UpperBound() {
assertNull(EnforcerVersionRangeStrategy.widenVersionRange("[3.8.8,3.9)"));
}
}
@Nested
@DisplayName("Plugin Configuration")
class PluginConfigurationTests {
@Test
@DisplayName("should widen requireMavenVersion in top-level plugin configuration")
void shouldWidenInTopLevelConfiguration() {
String pomXml = """
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<configuration>
<rules>
<requireMavenVersion>
<version>[3.8.8,4)</version>
</requireMavenVersion>
</rules>
</configuration>
</plugin>
</plugins>
</build>
</project>
""";
Document document = Document.of(pomXml);
Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);
UpgradeContext context = createMockContext();
UpgradeResult result = strategy.doApply(context, pomMap);
assertTrue(result.success());
assertTrue(result.modifiedCount() > 0);
String xml = DomUtils.toXml(document);
assertTrue(xml.contains("[3.8.8,5)"), "Version range should be widened to [3.8.8,5)");
assertFalse(xml.contains("[3.8.8,4)"), "Original range should be replaced");
}
@Test
@DisplayName("should widen requireMavenVersion in execution configuration")
void shouldWidenInExecutionConfiguration() {
String pomXml = """
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce-maven</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireMavenVersion>
<version>[3,4)</version>
</requireMavenVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
""";
Document document = Document.of(pomXml);
Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);
UpgradeContext context = createMockContext();
UpgradeResult result = strategy.doApply(context, pomMap);
assertTrue(result.success());
assertTrue(result.modifiedCount() > 0);
String xml = DomUtils.toXml(document);
assertTrue(xml.contains("[3,5)"), "Version range should be widened to [3,5)");
}
@Test
@DisplayName("should widen requireMavenVersion in pluginManagement")
void shouldWidenInPluginManagement() {
String pomXml = """
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1.0.0</version>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<configuration>
<rules>
<requireMavenVersion>
<version>[3.6.3,4)</version>
</requireMavenVersion>
</rules>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
""";
Document document = Document.of(pomXml);
Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);
UpgradeContext context = createMockContext();
UpgradeResult result = strategy.doApply(context, pomMap);
assertTrue(result.success());
assertTrue(result.modifiedCount() > 0);
String xml = DomUtils.toXml(document);
assertTrue(xml.contains("[3.6.3,5)"), "Version range should be widened");
}
@Test
@DisplayName("should not modify enforcer plugin with compatible version range")
void shouldNotModifyCompatibleRange() {
String pomXml = """
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<configuration>
<rules>
<requireMavenVersion>
<version>[3.8.8,)</version>
</requireMavenVersion>
</rules>
</configuration>
</plugin>
</plugins>
</build>
</project>
""";
Document document = Document.of(pomXml);
Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);
UpgradeContext context = createMockContext();
UpgradeResult result = strategy.doApply(context, pomMap);
assertTrue(result.success());
assertEquals(0, result.modifiedCount(), "No changes expected for compatible range");
}
@Test
@DisplayName("should not modify non-enforcer plugins")
void shouldNotModifyNonEnforcerPlugins() {
String pomXml = """
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
</plugins>
</build>
</project>
""";
Document document = Document.of(pomXml);
Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);
UpgradeContext context = createMockContext();
UpgradeResult result = strategy.doApply(context, pomMap);
assertTrue(result.success());
assertEquals(0, result.modifiedCount(), "No changes expected for non-enforcer plugins");
}
@Test
@DisplayName("should handle enforcer plugin without explicit groupId")
void shouldHandleEnforcerWithoutGroupId() {
String pomXml = """
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<configuration>
<rules>
<requireMavenVersion>
<version>[3.8.8,4)</version>
</requireMavenVersion>
</rules>
</configuration>
</plugin>
</plugins>
</build>
</project>
""";
Document document = Document.of(pomXml);
Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);
UpgradeContext context = createMockContext();
UpgradeResult result = strategy.doApply(context, pomMap);
assertTrue(result.success());
assertTrue(result.modifiedCount() > 0, "Should handle enforcer plugin without explicit groupId");
String xml = DomUtils.toXml(document);
assertTrue(xml.contains("[3.8.8,5)"));
}
}
@Nested
@DisplayName("Profile-Scoped")
class ProfileScopedTests {
@Test
@DisplayName("should widen requireMavenVersion in profile-scoped enforcer plugin")
void shouldWidenInProfileScopedPlugin() {
String pomXml = """
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1.0.0</version>
<profiles>
<profile>
<id>enforce</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<configuration>
<rules>
<requireMavenVersion>
<version>[3.8.8,4)</version>
</requireMavenVersion>
</rules>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
""";
Document document = Document.of(pomXml);
Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);
UpgradeContext context = createMockContext();
UpgradeResult result = strategy.doApply(context, pomMap);
assertTrue(result.success());
assertTrue(result.modifiedCount() > 0);
String xml = DomUtils.toXml(document);
assertTrue(xml.contains("[3.8.8,5)"), "Profile-scoped range should be widened");
}
}
@Nested
@DisplayName("Edge Cases")
class EdgeCaseTests {
@Test
@DisplayName("should handle POM with no build section")
void shouldHandlePomWithNoBuild() {
String pomXml = """
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1.0.0</version>
</project>
""";
Document document = Document.of(pomXml);
Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);
UpgradeContext context = createMockContext();
UpgradeResult result = strategy.doApply(context, pomMap);
assertTrue(result.success());
assertEquals(0, result.modifiedCount());
}
@Test
@DisplayName("should handle enforcer plugin with no rules")
void shouldHandleEnforcerWithNoRules() {
String pomXml = """
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<configuration>
</configuration>
</plugin>
</plugins>
</build>
</project>
""";
Document document = Document.of(pomXml);
Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);
UpgradeContext context = createMockContext();
UpgradeResult result = strategy.doApply(context, pomMap);
assertTrue(result.success());
assertEquals(0, result.modifiedCount());
}
@Test
@DisplayName("should widen both top-level and execution-level configurations")
void shouldWidenBothTopLevelAndExecutionLevel() {
String pomXml = """
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<configuration>
<rules>
<requireMavenVersion>
<version>[3.6.3,4)</version>
</requireMavenVersion>
</rules>
</configuration>
<executions>
<execution>
<id>enforce</id>
<configuration>
<rules>
<requireMavenVersion>
<version>[3.8.8,4)</version>
</requireMavenVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
""";
Document document = Document.of(pomXml);
Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);
UpgradeContext context = createMockContext();
UpgradeResult result = strategy.doApply(context, pomMap);
assertTrue(result.success());
assertTrue(result.modifiedCount() > 0);
String xml = DomUtils.toXml(document);
assertTrue(xml.contains("[3.6.3,5)"), "Top-level configuration range should be widened");
assertTrue(xml.contains("[3.8.8,5)"), "Execution-level configuration range should be widened");
assertFalse(xml.contains(",4)"), "No ranges with upper bound at 4 should remain");
}
}
}