NashornCompatibilityStrategyTest.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.assertNotNull;
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 NashornCompatibilityStrategy} class.
 * Tests detection of antrun JavaScript usage and injection of standalone Nashorn dependency.
 *
 * @see <a href="https://github.com/apache/maven/issues/12988">#12988</a>
 */
@DisplayName("NashornCompatibilityStrategy")
class NashornCompatibilityStrategyTest {

    private NashornCompatibilityStrategy strategy;

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

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

    @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());

            UpgradeContext context = TestUtils.createMockContext(options);

            assertTrue(strategy.isApplicable(context), "Strategy should be applicable when --model is true");
        }

        @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());

            UpgradeContext context = TestUtils.createMockContext(options);

            assertTrue(strategy.isApplicable(context), "Strategy should be applicable when --all is specified");
        }

        @Test
        @DisplayName("should be applicable by default when no specific options provided")
        void shouldBeApplicableByDefaultWhenNoSpecificOptions() {
            UpgradeOptions options = TestUtils.createDefaultOptions();

            UpgradeContext context = TestUtils.createMockContext(options);

            assertTrue(strategy.isApplicable(context), "Strategy should be applicable by default");
        }

        @Test
        @DisplayName("should not be applicable when --model option is false")
        void shouldNotBeApplicableWhenModelOptionFalse() {
            UpgradeOptions options = mock(UpgradeOptions.class);
            when(options.model()).thenReturn(Optional.of(false));
            when(options.all()).thenReturn(Optional.empty());

            UpgradeContext context = TestUtils.createMockContext(options);

            assertFalse(strategy.isApplicable(context), "Strategy should not be applicable when --model is false");
        }
    }

    @Nested
    @DisplayName("Antrun JavaScript Detection and Nashorn Injection")
    class AntrunJavaScriptTests {

        @Test
        @DisplayName("should inject nashorn-core when antrun plugin uses JavaScript script")
        void shouldInjectNashornWhenAntrunUsesJavaScript() 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-antrun-plugin</artifactId>
                                <executions>
                                    <execution>
                                        <id>set-bree</id>
                                        <phase>initialize</phase>
                                        <configuration>
                                            <target>
                                                <script language="javascript"><![CDATA[
                                                    var System = java.lang.System;
                                                    var bree = "J2SE-1.5";
                                                    project.setProperty("sling.bree", bree);
                                                ]]></script>
                                            </target>
                                        </configuration>
                                        <goals>
                                            <goal>run</goal>
                                        </goals>
                                    </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(), "Strategy should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have modified the POM");

            String xml = DomUtils.toXml(document);
            assertTrue(xml.contains("org.openjdk.nashorn"), "Should contain nashorn groupId");
            assertTrue(xml.contains("nashorn-core"), "Should contain nashorn artifactId");
            assertTrue(xml.contains("15.7"), "Should contain nashorn version");
        }

        @Test
        @DisplayName("should inject nashorn-core when antrun plugin has no explicit groupId")
        void shouldInjectNashornWhenAntrunHasNoGroupId() 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>
                                <artifactId>maven-antrun-plugin</artifactId>
                                <executions>
                                    <execution>
                                        <configuration>
                                            <target>
                                                <script language="javascript"><![CDATA[
                                                    project.setProperty("foo", "bar");
                                                ]]></script>
                                            </target>
                                        </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(), "Strategy should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have modified the POM");

            String xml = DomUtils.toXml(document);
            assertTrue(xml.contains("nashorn-core"), "Should contain nashorn dependency");
        }

        @Test
        @DisplayName("should inject nashorn-core in pluginManagement")
        void shouldInjectNashornInPluginManagement() 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>
                        <pluginManagement>
                            <plugins>
                                <plugin>
                                    <groupId>org.apache.maven.plugins</groupId>
                                    <artifactId>maven-antrun-plugin</artifactId>
                                    <configuration>
                                        <target>
                                            <script language="javascript"><![CDATA[
                                                project.setProperty("foo", "bar");
                                            ]]></script>
                                        </target>
                                    </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(), "Strategy should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have modified the POM");

            String xml = DomUtils.toXml(document);
            assertTrue(xml.contains("nashorn-core"), "Should contain nashorn dependency");
        }

        @Test
        @DisplayName("should inject nashorn-core in profile build")
        void shouldInjectNashornInProfile() 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>
                    <profiles>
                        <profile>
                            <id>legacy</id>
                            <build>
                                <plugins>
                                    <plugin>
                                        <groupId>org.apache.maven.plugins</groupId>
                                        <artifactId>maven-antrun-plugin</artifactId>
                                        <executions>
                                            <execution>
                                                <configuration>
                                                    <target>
                                                        <script language="javascript"><![CDATA[
                                                            var x = 1;
                                                        ]]></script>
                                                    </target>
                                                </configuration>
                                            </execution>
                                        </executions>
                                    </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(), "Strategy should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have modified the POM");

            String xml = DomUtils.toXml(document);
            assertTrue(xml.contains("nashorn-core"), "Should contain nashorn dependency in profile");
        }

        @Test
        @DisplayName("should handle case-insensitive JavaScript language attribute")
        void shouldHandleCaseInsensitiveLanguage() 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-antrun-plugin</artifactId>
                                <configuration>
                                    <target>
                                        <script language="JavaScript"><![CDATA[
                                            project.setProperty("foo", "bar");
                                        ]]></script>
                                    </target>
                                </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(), "Strategy should succeed");
            assertTrue(result.modifiedCount() > 0, "Should detect JavaScript regardless of case");

            String xml = DomUtils.toXml(document);
            assertTrue(xml.contains("nashorn-core"), "Should contain nashorn dependency");
        }
    }

    @Nested
    @DisplayName("No-op Scenarios")
    class NoOpTests {

        @Test
        @DisplayName("should not modify POM without antrun plugin")
        void shouldNotModifyPomWithoutAntrun() 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.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(), "Strategy should succeed");
            assertEquals(0, result.modifiedCount(), "Should not have modified the POM");
        }

        @Test
        @DisplayName("should not modify POM when antrun plugin has no JavaScript scripts")
        void shouldNotModifyWhenNoJavaScript() 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-antrun-plugin</artifactId>
                                <executions>
                                    <execution>
                                        <configuration>
                                            <target>
                                                <echo message="Hello"/>
                                            </target>
                                        </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(), "Strategy should succeed");
            assertEquals(0, result.modifiedCount(), "Should not have modified the POM");

            String xml = DomUtils.toXml(document);
            assertFalse(xml.contains("nashorn"), "Should not contain nashorn dependency");
        }

        @Test
        @DisplayName("should not modify POM when antrun uses non-JavaScript script language")
        void shouldNotModifyWhenNonJavaScriptLanguage() 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-antrun-plugin</artifactId>
                                <configuration>
                                    <target>
                                        <script language="groovy"><![CDATA[
                                            println "Hello from Groovy"
                                        ]]></script>
                                    </target>
                                </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(), "Strategy should succeed");
            assertEquals(0, result.modifiedCount(), "Should not have modified the POM for Groovy");

            String xml = DomUtils.toXml(document);
            assertFalse(xml.contains("nashorn"), "Should not contain nashorn dependency");
        }

        @Test
        @DisplayName("should not modify POM when nashorn dependency already exists")
        void shouldNotModifyWhenNashornAlreadyPresent() 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-antrun-plugin</artifactId>
                                <dependencies>
                                    <dependency>
                                        <groupId>org.openjdk.nashorn</groupId>
                                        <artifactId>nashorn-core</artifactId>
                                        <version>15.7</version>
                                    </dependency>
                                </dependencies>
                                <configuration>
                                    <target>
                                        <script language="javascript"><![CDATA[
                                            project.setProperty("foo", "bar");
                                        ]]></script>
                                    </target>
                                </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(), "Strategy should succeed");
            assertEquals(0, result.modifiedCount(), "Should not have modified the POM");
        }

        @Test
        @DisplayName("should not modify POM without build section")
        void shouldNotModifyPomWithoutBuild() 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>
                </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(), "Strategy should succeed");
            assertEquals(0, result.modifiedCount(), "Should not have modified the POM");
        }

        @Test
        @DisplayName("should not inject for non-maven-plugins groupId antrun")
        void shouldNotInjectForNonMavenPluginsGroupId() 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>com.example</groupId>
                                <artifactId>maven-antrun-plugin</artifactId>
                                <configuration>
                                    <target>
                                        <script language="javascript"><![CDATA[
                                            var x = 1;
                                        ]]></script>
                                    </target>
                                </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(), "Strategy should succeed");
            assertEquals(0, result.modifiedCount(), "Should not inject for non-standard groupId");
        }
    }

    @Nested
    @DisplayName("Sling Parent POM Pattern")
    class SlingPatternTests {

        @Test
        @DisplayName("should handle the real Sling parent POM pattern with BREE script")
        void shouldHandleSlingBreePattern() 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>org.apache.sling</groupId>
                    <artifactId>sling</artifactId>
                    <version>22</version>
                    <packaging>pom</packaging>
                    <build>
                        <plugins>
                            <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-antrun-plugin</artifactId>
                                <executions>
                                    <execution>
                                        <id>set-bundle-required-execution-environment</id>
                                        <phase>initialize</phase>
                                        <goals>
                                            <goal>run</goal>
                                        </goals>
                                        <configuration>
                                            <exportAntProperties>true</exportAntProperties>
                                            <target>
                                                <script language="javascript"><![CDATA[
                                                    var System = java.lang.System;
                                                    var bree = "J2SE-1.5";
                                                    var slingJavaVersion = System.getProperty("sling.java.version");
                                                    if (slingJavaVersion == "6" || slingJavaVersion == "1.6") {
                                                        bree = "JavaSE-1.6";
                                                    } else if (slingJavaVersion == "7" || slingJavaVersion == "1.7") {
                                                        bree = "JavaSE-1.7";
                                                    } else if (slingJavaVersion == "8" || slingJavaVersion == "1.8") {
                                                        bree = "JavaSE-1.8";
                                                    }
                                                    project.setProperty("sling.bree", bree);
                                                ]]></script>
                                            </target>
                                        </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(), "Strategy should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have modified the Sling POM");

            String xml = DomUtils.toXml(document);
            assertTrue(xml.contains("org.openjdk.nashorn"), "Should contain nashorn groupId");
            assertTrue(xml.contains("nashorn-core"), "Should contain nashorn artifactId");
            assertTrue(xml.contains("15.7"), "Should contain nashorn version");
        }

        @Test
        @DisplayName("should handle antrun with existing dependencies and add nashorn")
        void shouldAddNashornToExistingDependencies() 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-antrun-plugin</artifactId>
                                <dependencies>
                                    <dependency>
                                        <groupId>some.other</groupId>
                                        <artifactId>lib</artifactId>
                                        <version>1.0</version>
                                    </dependency>
                                </dependencies>
                                <configuration>
                                    <target>
                                        <script language="javascript"><![CDATA[
                                            project.setProperty("foo", "bar");
                                        ]]></script>
                                    </target>
                                </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(), "Strategy should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have modified the POM");

            String xml = DomUtils.toXml(document);
            assertTrue(xml.contains("nashorn-core"), "Should contain nashorn dependency");
            assertTrue(xml.contains("some.other"), "Should preserve existing dependency");
        }
    }

    @Nested
    @DisplayName("Strategy Description")
    class StrategyDescriptionTests {

        @Test
        @DisplayName("should provide meaningful description")
        void shouldProvideMeaningfulDescription() {
            String description = strategy.getDescription();

            assertNotNull(description, "Description should not be null");
            assertFalse(description.trim().isEmpty(), "Description should not be empty");
            assertTrue(
                    description.toLowerCase().contains("nashorn")
                            || description.toLowerCase().contains("antrun")
                            || description.toLowerCase().contains("javascript"),
                    "Description should mention nashorn, antrun, or javascript");
        }
    }
}