CompatibilityWarningTest.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 eu.maveniverse.domtrip.Document;
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.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

/**
 * Tests for Maven 4 compatibility warnings emitted by {@link CompatibilityFixStrategy}.
 * These cover warning-only checks that detect patterns known to fail with Maven 4
 * but which cannot be auto-fixed.
 */
@DisplayName("CompatibilityFixStrategy Warnings")
class CompatibilityWarningTest {

    private CompatibilityFixStrategy strategy;

    @BeforeEach
    void setUp() {
        strategy = new CompatibilityFixStrategy();
    }

    private UpgradeContext createMockContext() {
        return TestUtils.createMockContext();
    }

    @Nested
    @DisplayName("Incompatible Plugin Warnings (#12432)")
    class IncompatiblePluginWarningTests {

        @Test
        @DisplayName("should not warn about gmavenplus-plugin (now handled by PluginUpgradeStrategy)")
        void shouldNotWarnAboutGmavenplusPlugin() throws Exception {
            // gmavenplus-plugin was fixed in 4.2.0 (groovy/GMavenPlus#328) and is now
            // handled as a plugin upgrade rather than a permanent incompatibility warning
            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.codehaus.gmavenplus</groupId>
                                <artifactId>gmavenplus-plugin</artifactId>
                                <version>4.1.1</version>
                            </plugin>
                        </plugins>
                    </build>
                </project>
                """;

            Document document = Document.of(pomXml);
            Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);

            UpgradeContext context = createMockContext();
            strategy.doApply(context, pomMap);

            verify(context.logger, never()).warn(argThat(msg -> msg.contains("Known Maven 4 incompatibility")));
        }

        @Test
        @DisplayName("should not warn about compatible plugins")
        void shouldNotWarnAboutCompatiblePlugins() 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>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.13.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();
            strategy.doApply(context, pomMap);

            verify(context.logger, never()).warn(argThat(msg -> msg.contains("Known Maven 4 incompatibility")));
        }
    }

    @Nested
    @DisplayName("Property-Interpolated Module Path Warnings (#12434)")
    class PropertyInterpolatedModulePathTests {

        @Test
        @DisplayName("should warn about property expressions in module paths")
        void shouldWarnAboutPropertyExpressionsInModulePaths() 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>test</groupId>
                    <artifactId>test</artifactId>
                    <version>1.0.0</version>
                    <packaging>pom</packaging>
                    <modules>
                        <module>core</module>
                        <module>spark-${spark.compat.version}</module>
                        <module>v${spark.major.version}/mixed-spark</module>
                    </modules>
                </project>
                """;

            Document document = Document.of(pomXml);
            Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);

            UpgradeContext context = createMockContext();
            strategy.doApply(context, pomMap);

            verify(context.logger, atLeastOnce())
                    .warn(argThat(msg ->
                            msg.contains("spark-${spark.compat.version}") && msg.contains("property expression")));
        }

        @Test
        @DisplayName("should not warn about static module paths")
        void shouldNotWarnAboutStaticModulePaths() 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>test</groupId>
                    <artifactId>test</artifactId>
                    <version>1.0.0</version>
                    <packaging>pom</packaging>
                    <modules>
                        <module>core</module>
                        <module>web</module>
                    </modules>
                </project>
                """;

            Document document = Document.of(pomXml);
            Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);

            UpgradeContext context = createMockContext();
            strategy.doApply(context, pomMap);

            verify(context.logger, never()).warn(argThat(msg -> msg.contains("property expression")));
        }

        @Test
        @DisplayName("should warn about property expressions in profile module paths")
        void shouldWarnAboutPropertyExpressionsInProfileModulePaths() 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>test</groupId>
                    <artifactId>test</artifactId>
                    <version>1.0.0</version>
                    <packaging>pom</packaging>
                    <profiles>
                        <profile>
                            <id>spark-3.5</id>
                            <modules>
                                <module>spark-${spark.version}</module>
                            </modules>
                        </profile>
                    </profiles>
                </project>
                """;

            Document document = Document.of(pomXml);
            Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);

            UpgradeContext context = createMockContext();
            strategy.doApply(context, pomMap);

            verify(context.logger, atLeastOnce())
                    .warn(argThat(msg -> msg.contains("spark-${spark.version}") && msg.contains("profile")));
        }
    }

    @Nested
    @DisplayName("CI-Friendly Missing Dependency Version Warnings (#12435)")
    class CiFriendlyDependencyVersionTests {

        @Test
        @DisplayName("should warn about versionless dependencies in CI-friendly project")
        void shouldWarnAboutVersionlessDependenciesInCiFriendlyProject() 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>org.example</groupId>
                        <artifactId>parent</artifactId>
                        <version>${revision}</version>
                    </parent>
                    <artifactId>child</artifactId>
                    <dependencies>
                        <dependency>
                            <groupId>org.springframework</groupId>
                            <artifactId>spring-context</artifactId>
                        </dependency>
                        <dependency>
                            <groupId>io.protostuff</groupId>
                            <artifactId>protostuff-core</artifactId>
                        </dependency>
                    </dependencies>
                </project>
                """;

            Document document = Document.of(pomXml);
            Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);

            UpgradeContext context = createMockContext();
            strategy.doApply(context, pomMap);

            verify(context.logger, atLeastOnce())
                    .warn(argThat(msg -> msg.contains("CI-friendly")
                            && msg.contains("${revision}")
                            && msg.contains("2 dependencies")));
        }

        @Test
        @DisplayName("should not warn when parent version is not CI-friendly")
        void shouldNotWarnWhenParentVersionNotCiFriendly() 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>org.example</groupId>
                        <artifactId>parent</artifactId>
                        <version>1.0.0</version>
                    </parent>
                    <artifactId>child</artifactId>
                    <dependencies>
                        <dependency>
                            <groupId>org.springframework</groupId>
                            <artifactId>spring-context</artifactId>
                        </dependency>
                    </dependencies>
                </project>
                """;

            Document document = Document.of(pomXml);
            Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);

            UpgradeContext context = createMockContext();
            strategy.doApply(context, pomMap);

            verify(context.logger, never()).warn(argThat(msg -> msg.contains("CI-friendly")));
        }

        @Test
        @DisplayName("should not warn when dependencies have explicit versions")
        void shouldNotWarnWhenDependenciesHaveVersions() 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>org.example</groupId>
                        <artifactId>parent</artifactId>
                        <version>${revision}</version>
                    </parent>
                    <artifactId>child</artifactId>
                    <dependencies>
                        <dependency>
                            <groupId>org.springframework</groupId>
                            <artifactId>spring-context</artifactId>
                            <version>6.0.0</version>
                        </dependency>
                    </dependencies>
                </project>
                """;

            Document document = Document.of(pomXml);
            Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);

            UpgradeContext context = createMockContext();
            strategy.doApply(context, pomMap);

            verify(context.logger, never()).warn(argThat(msg -> msg.contains("CI-friendly")));
        }

        @Test
        @DisplayName("should handle ${changelist} as CI-friendly expression")
        void shouldHandleChangelistAsCiFriendly() 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>org.example</groupId>
                        <artifactId>parent</artifactId>
                        <version>1.0.0${changelist}</version>
                    </parent>
                    <artifactId>child</artifactId>
                    <dependencies>
                        <dependency>
                            <groupId>commons-io</groupId>
                            <artifactId>commons-io</artifactId>
                        </dependency>
                    </dependencies>
                </project>
                """;

            Document document = Document.of(pomXml);
            Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);

            UpgradeContext context = createMockContext();
            strategy.doApply(context, pomMap);

            verify(context.logger, atLeastOnce()).warn(argThat(msg -> msg.contains("CI-friendly")));
        }

        @Test
        @DisplayName("should not warn when no parent element exists")
        void shouldNotWarnWhenNoParentExists() 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>test</groupId>
                    <artifactId>test</artifactId>
                    <version>1.0.0</version>
                    <dependencies>
                        <dependency>
                            <groupId>org.springframework</groupId>
                            <artifactId>spring-context</artifactId>
                        </dependency>
                    </dependencies>
                </project>
                """;

            Document document = Document.of(pomXml);
            Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);

            UpgradeContext context = createMockContext();
            strategy.doApply(context, pomMap);

            verify(context.logger, never()).warn(argThat(msg -> msg.contains("CI-friendly")));
        }
    }
}