GAVUtilsTest.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.io.StringReader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
import org.jdom2.Document;
import org.jdom2.input.SAXBuilder;
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 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.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Unit tests for the {@link GAVUtils} utility class.
* Tests GAV extraction, computation, and parent resolution functionality.
*/
@DisplayName("GAVUtils")
class GAVUtilsTest {
private SAXBuilder saxBuilder;
@BeforeEach
void setUp() {
saxBuilder = new SAXBuilder();
}
private UpgradeContext createMockContext() {
return TestUtils.createMockContext();
}
@Nested
@DisplayName("GAV Extraction")
class GAVExtractionTests {
@Test
@DisplayName("should extract GAV from complete POM")
void shouldExtractGAVFromCompletePOM() throws Exception {
String pomXml = PomBuilder.create()
.groupId("com.example")
.artifactId("test-project")
.version("1.0.0")
.build();
Document document = saxBuilder.build(new StringReader(pomXml));
UpgradeContext context = createMockContext();
GAV gav = GAVUtils.extractGAVWithParentResolution(context, document);
assertNotNull(gav);
assertEquals("com.example", gav.groupId());
assertEquals("test-project", gav.artifactId());
assertEquals("1.0.0", gav.version());
}
@Test
@DisplayName("should extract GAV with parent inheritance")
void shouldExtractGAVWithParentInheritance() throws Exception {
String pomXml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>child-project</artifactId>
<!-- groupId and version inherited from parent -->
</project>
""";
Document document = saxBuilder.build(new StringReader(pomXml));
UpgradeContext context = createMockContext();
GAV gav = GAVUtils.extractGAVWithParentResolution(context, document);
assertNotNull(gav);
assertEquals("com.example", gav.groupId());
assertEquals("child-project", gav.artifactId());
assertEquals("1.0.0", gav.version());
}
@Test
@DisplayName("should handle partial parent inheritance")
void shouldHandlePartialParentInheritance() throws Exception {
String pomXml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
</parent>
<groupId>com.example.child</groupId>
<artifactId>child-project</artifactId>
<version>2.0.0</version>
</project>
""";
Document document = saxBuilder.build(new StringReader(pomXml));
UpgradeContext context = createMockContext();
GAV gav = GAVUtils.extractGAVWithParentResolution(context, document);
assertNotNull(gav);
assertEquals("com.example.child", gav.groupId());
assertEquals("child-project", gav.artifactId());
assertEquals("2.0.0", gav.version());
}
@ParameterizedTest
@MethodSource("provideInvalidGAVScenarios")
@DisplayName("should return null for invalid GAV scenarios")
void shouldReturnNullForInvalidGAVScenarios(
String groupId, String artifactId, String version, String description) throws Exception {
String pomXml = PomBuilder.create()
.groupId(groupId)
.artifactId(artifactId)
.version(version)
.build();
Document document = saxBuilder.build(new StringReader(pomXml));
UpgradeContext context = createMockContext();
GAV gav = GAVUtils.extractGAVWithParentResolution(context, document);
assertNull(gav, description);
}
private static Stream<Arguments> provideInvalidGAVScenarios() {
return Stream.of(
Arguments.of(
null, "incomplete-project", null, "Should return null for missing groupId and version"),
Arguments.of("com.example", null, "1.0.0", "Should return null for missing artifactId"),
Arguments.of(null, null, "1.0.0", "Should return null for missing groupId and artifactId"),
Arguments.of("com.example", "test-project", null, "Should return null for missing version"),
Arguments.of("", "test-project", "1.0.0", "Should return null for empty groupId"),
Arguments.of("com.example", "", "1.0.0", "Should return null for empty artifactId"),
Arguments.of("com.example", "test-project", "", "Should return null for empty version"));
}
}
@Nested
@DisplayName("GAV Computation")
class GAVComputationTests {
@Test
@DisplayName("should compute GAVs from multiple POMs")
void shouldComputeGAVsFromMultiplePOMs() throws Exception {
String parentPomXml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
</project>
""";
String childPomXml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>parent-project</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>child-project</artifactId>
</project>
""";
Document parentDoc = saxBuilder.build(new StringReader(parentPomXml));
Document childDoc = saxBuilder.build(new StringReader(childPomXml));
Map<Path, Document> pomMap = new HashMap<>();
pomMap.put(Paths.get("/project/pom.xml"), parentDoc);
pomMap.put(Paths.get("/project/child/pom.xml"), childDoc);
UpgradeContext context = createMockContext();
Set<GAV> gavs = GAVUtils.computeAllGAVs(context, pomMap);
assertEquals(2, gavs.size());
assertTrue(gavs.contains(new GAV("com.example", "parent-project", "1.0.0")));
assertTrue(gavs.contains(new GAV("com.example", "child-project", "1.0.0")));
}
@Test
@DisplayName("should handle empty POM map")
void shouldHandleEmptyPOMMap() {
UpgradeContext context = createMockContext();
Map<Path, Document> pomMap = new HashMap<>();
Set<GAV> gavs = GAVUtils.computeAllGAVs(context, pomMap);
assertNotNull(gavs);
assertTrue(gavs.isEmpty(), "Expected collection to be empty but had " + gavs.size() + " elements: " + gavs);
}
@Test
@DisplayName("should deduplicate identical GAVs")
void shouldDeduplicateIdenticalGAVs() throws Exception {
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>com.example</groupId>
<artifactId>duplicate-project</artifactId>
<version>1.0.0</version>
</project>
""";
Document doc1 = saxBuilder.build(new StringReader(pomXml));
Document doc2 = saxBuilder.build(new StringReader(pomXml));
Map<Path, Document> pomMap = new HashMap<>();
pomMap.put(Paths.get("/project/pom1.xml"), doc1);
pomMap.put(Paths.get("/project/pom2.xml"), doc2);
UpgradeContext context = createMockContext();
Set<GAV> gavs = GAVUtils.computeAllGAVs(context, pomMap);
assertEquals(1, gavs.size());
assertTrue(gavs.contains(new GAV("com.example", "duplicate-project", "1.0.0")));
}
@Test
@DisplayName("should skip POMs with incomplete GAVs")
void shouldSkipPOMsWithIncompleteGAVs() throws Exception {
String validPomXml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>valid-project</artifactId>
<version>1.0.0</version>
</project>
""";
String invalidPomXml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<artifactId>invalid-project</artifactId>
<!-- Missing groupId and version -->
</project>
""";
Document validDoc = saxBuilder.build(new StringReader(validPomXml));
Document invalidDoc = saxBuilder.build(new StringReader(invalidPomXml));
Map<Path, Document> pomMap = new HashMap<>();
pomMap.put(Paths.get("/project/valid.xml"), validDoc);
pomMap.put(Paths.get("/project/invalid.xml"), invalidDoc);
UpgradeContext context = createMockContext();
Set<GAV> gavs = GAVUtils.computeAllGAVs(context, pomMap);
assertEquals(1, gavs.size());
assertTrue(gavs.contains(new GAV("com.example", "valid-project", "1.0.0")));
}
}
@Nested
@DisplayName("Edge Cases")
class EdgeCases {
@Test
@DisplayName("should handle POM with only whitespace elements")
void shouldHandlePOMWithWhitespaceElements() throws Exception {
String pomXml = PomBuilder.create()
.groupId(" ") // whitespace-only groupId
.artifactId("test-project")
.version("1.0.0")
.build();
Document document = saxBuilder.build(new StringReader(pomXml));
UpgradeContext context = createMockContext();
GAV gav = GAVUtils.extractGAVWithParentResolution(context, document);
// Should handle whitespace-only groupId as invalid
assertNull(gav, "GAV should be null for whitespace-only groupId");
}
@Test
@DisplayName("should handle POM with empty elements")
void shouldHandlePOMWithEmptyElements() throws Exception {
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></groupId>
<artifactId>test-project</artifactId>
<version>1.0.0</version>
</project>
""";
Document document = saxBuilder.build(new StringReader(pomXml));
UpgradeContext context = createMockContext();
GAV gav = GAVUtils.extractGAVWithParentResolution(context, document);
assertNull(gav, "GAV should be null for empty groupId");
}
@Test
@DisplayName("should handle POM with special characters in GAV")
void shouldHandlePOMWithSpecialCharacters() throws Exception {
String pomXml = PomBuilder.create()
.groupId("com.example-test_group")
.artifactId("test-project.artifact")
.version("1.0.0-SNAPSHOT")
.build();
Document document = saxBuilder.build(new StringReader(pomXml));
UpgradeContext context = createMockContext();
GAV gav = GAVUtils.extractGAVWithParentResolution(context, document);
assertNotNull(gav, "GAV should be valid for special characters");
assertEquals("com.example-test_group", gav.groupId());
assertEquals("test-project.artifact", gav.artifactId());
assertEquals("1.0.0-SNAPSHOT", gav.version());
}
@Test
@DisplayName("should handle deeply nested parent inheritance")
void shouldHandleDeeplyNestedParentInheritance() throws Exception {
String pomXml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>grandparent</artifactId>
<version>1.0.0</version>
<relativePath>../../grandparent/pom.xml</relativePath>
</parent>
<artifactId>child-project</artifactId>
</project>
""";
Document document = saxBuilder.build(new StringReader(pomXml));
UpgradeContext context = createMockContext();
GAV gav = GAVUtils.extractGAVWithParentResolution(context, document);
assertNotNull(gav, "GAV should be resolved from parent");
assertEquals("com.example", gav.groupId());
assertEquals("child-project", gav.artifactId());
assertEquals("1.0.0", gav.version());
}
@Test
@DisplayName("should handle large number of POMs efficiently")
void shouldHandleLargeNumberOfPOMsEfficiently() throws Exception {
// Create a large number of POM documents for performance testing
Map<Path, Document> largePomMap = new HashMap<>();
for (int i = 0; i < 100; i++) {
Path pomPath = Paths.get("module" + i + "/pom.xml");
String pomContent = PomBuilder.create()
.groupId("com.example")
.artifactId("module" + i)
.version("1.0.0")
.build();
Document document = saxBuilder.build(new StringReader(pomContent));
largePomMap.put(pomPath, document);
}
UpgradeContext context = createMockContext();
long startTime = System.currentTimeMillis();
Set<GAV> gavs = GAVUtils.computeAllGAVs(context, largePomMap);
long endTime = System.currentTimeMillis();
// Performance assertion - should complete within reasonable time
long duration = endTime - startTime;
assertTrue(duration < 5000, "GAV computation should complete within 5 seconds for 100 POMs");
// Verify correctness
assertNotNull(gavs, "GAV set should not be null");
assertEquals(100, gavs.size(), "Should have computed GAVs for all 100 POMs");
}
}
}