PluginUpgradeStrategyTest.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.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Optional;

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

/**
 * Unit tests for the {@link PluginUpgradeStrategy} class. Tests plugin version upgrades, plugin management additions,
 * and Maven 4 compatibility.
 */
@DisplayName("PluginUpgradeStrategy")
class PluginUpgradeStrategyTest {

    private PluginUpgradeStrategy strategy;

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

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

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

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

    private UpgradeOptions createDefaultOptions() {
        return TestUtils.createDefaultOptions();
    }

    @Nested
    @DisplayName("Applicability")
    class ApplicabilityTests {

        @Test
        @DisplayName("should be applicable when --plugins option is true")
        void shouldBeApplicableWhenPluginsOptionTrue() {
            UpgradeOptions options = mock(UpgradeOptions.class);
            when(options.plugins()).thenReturn(Optional.of(true));
            when(options.all()).thenReturn(Optional.empty());

            UpgradeContext context = createMockContext(options);

            assertTrue(strategy.isApplicable(context), "Strategy should be applicable when --plugins 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.plugins()).thenReturn(Optional.empty());

            UpgradeContext context = 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 = createDefaultOptions();

            UpgradeContext context = createMockContext(options);

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

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

            UpgradeContext context = createMockContext(options);

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

    @Nested
    @DisplayName("Plugin Upgrades")
    class PluginUpgradeTests {

        @Test
        @DisplayName("should upgrade plugin version when below minimum")
        void shouldUpgradePluginVersionWhenBelowMinimum() throws Exception {
            String pomXml = PomBuilder.create()
                    .groupId("test")
                    .artifactId("test")
                    .version("1.0.0")
                    .plugin("org.apache.maven.plugins", "maven-compiler-plugin", "3.8.1")
                    .build();

            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(), "Plugin upgrade should succeed");
            // Note: POM may or may not be modified depending on whether upgrades are needed

            // Verify the plugin version was upgraded
            Editor editor = new Editor(document);
            Element root = editor.root();
            String version = root.path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);

            // The exact version depends on the plugin upgrades configuration
            assertNotNull(version, "Plugin should have a version");
        }

        @Test
        @DisplayName("should not modify plugin when version is already sufficient")
        void shouldNotModifyPluginWhenVersionAlreadySufficient() 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();
            UpgradeResult result = strategy.doApply(context, pomMap);

            assertTrue(result.success(), "Plugin upgrade should succeed");
            // POM might still be marked as modified due to other plugin management additions
        }

        @Test
        @DisplayName("should upgrade milestone version below release minimum")
        void shouldUpgradeMilestoneVersionBelowRelease() 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-enforcer-plugin</artifactId>
                                    <version>3.0.0-M1</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(), "Plugin upgrade should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have upgraded 3.0.0-M1 to 3.5.0");

            Editor editor = new Editor(document);
            Element root = editor.root();
            String version = root.path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("3.5.0", version, "3.0.0-M1 should be upgraded to 3.5.0");
        }

        @Test
        @DisplayName("should upgrade plugin in pluginManagement")
        void shouldUpgradePluginInPluginManagement() 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-enforcer-plugin</artifactId>
                                        <version>2.0.0</version>
                                    </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(), "Plugin upgrade should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have upgraded maven-enforcer-plugin");

            // Verify the version was upgraded
            Editor editor = new Editor(document);
            Element root = editor.root();
            String version = root.path("build", "pluginManagement", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("3.5.0", version);
        }

        @Test
        @DisplayName("should upgrade plugin with property version")
        void shouldUpgradePluginWithPropertyVersion() 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>
                        <properties>
                            <shade.plugin.version>3.0.0</shade.plugin.version>
                        </properties>
                        <build>
                            <plugins>
                                <plugin>
                                    <groupId>org.apache.maven.plugins</groupId>
                                    <artifactId>maven-shade-plugin</artifactId>
                                    <version>${shade.plugin.version}</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(), "Plugin upgrade should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have upgraded shade plugin property");

            // Verify the property was upgraded
            Editor editor = new Editor(document);
            Element root = editor.root();
            String version = root.path("properties", "shade.plugin.version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("3.5.0", version);
        }

        @Test
        @DisplayName("should upgrade surefire plugin when below minimum")
        void shouldUpgradeSurefirePluginWhenBelowMinimum() 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-surefire-plugin</artifactId>
                                    <version>3.1.2</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(), "Plugin upgrade should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have upgraded maven-surefire-plugin");

            Editor editor = new Editor(document);
            Element root = editor.root();
            String version = root.path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("3.5.2", version);
        }

        @Test
        @DisplayName("should upgrade failsafe plugin when below minimum")
        void shouldUpgradeFailsafePluginWhenBelowMinimum() 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-failsafe-plugin</artifactId>
                                    <version>3.1.2</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(), "Plugin upgrade should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have upgraded maven-failsafe-plugin");

            Editor editor = new Editor(document);
            Element root = editor.root();
            String version = root.path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("3.5.2", version);
        }

        @Test
        @DisplayName("should upgrade surefire-report plugin when below minimum")
        void shouldUpgradeSurefireReportPluginWhenBelowMinimum() 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-surefire-report-plugin</artifactId>
                                    <version>3.1.2</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(), "Plugin upgrade should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have upgraded maven-surefire-report-plugin");

            Editor editor = new Editor(document);
            Element root = editor.root();
            String version = root.path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("3.5.2", version);
        }

        @Test
        @DisplayName("should upgrade jaxb2-maven-plugin when below minimum")
        void shouldUpgradeJaxb2MavenPluginWhenBelowMinimum() 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.codehaus.mojo</groupId>
                                    <artifactId>jaxb2-maven-plugin</artifactId>
                                    <version>3.1.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(), "Plugin upgrade should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have upgraded jaxb2-maven-plugin");

            Editor editor = new Editor(document);
            Element root = editor.root();
            String version = root.path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("3.2.0", version);
        }

        @Test
        @DisplayName("should upgrade maven-war-plugin when below minimum")
        void shouldUpgradeWarPluginWhenBelowMinimum() throws Exception {
            Document doc = PomBuilder.create()
                    .plugin("org.apache.maven.plugins", "maven-war-plugin", "2.1.1")
                    .buildDocument();
            UpgradeResult result = strategy.doApply(createMockContext(), Map.of(Paths.get("pom.xml"), doc));

            assertTrue(result.success() && result.modifiedCount() > 0, "Should have upgraded maven-war-plugin");
            String version = new Editor(doc)
                    .root()
                    .path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("3.4.0", version, "maven-war-plugin should be upgraded to 3.4.0");
        }

        @Test
        @DisplayName("should upgrade maven-ear-plugin when below minimum")
        void shouldUpgradeEarPluginWhenBelowMinimum() throws Exception {
            Document doc = PomBuilder.create()
                    .plugin("org.apache.maven.plugins", "maven-ear-plugin", "3.2.0")
                    .buildDocument();
            UpgradeResult result = strategy.doApply(createMockContext(), Map.of(Paths.get("pom.xml"), doc));

            assertTrue(result.success() && result.modifiedCount() > 0, "Should have upgraded maven-ear-plugin");
            String version = new Editor(doc)
                    .root()
                    .path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("3.4.0", version, "maven-ear-plugin should be upgraded to 3.4.0");
        }

        @Test
        @DisplayName("should upgrade maven-bundle-plugin when below minimum")
        void shouldUpgradeBundlePluginWhenBelowMinimum() throws Exception {
            Document doc = PomBuilder.create()
                    .plugin("org.apache.felix", "maven-bundle-plugin", "4.2.1")
                    .buildDocument();
            UpgradeResult result = strategy.doApply(createMockContext(), Map.of(Paths.get("pom.xml"), doc));

            assertTrue(result.success() && result.modifiedCount() > 0, "Should have upgraded maven-bundle-plugin");
            String version = new Editor(doc)
                    .root()
                    .path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("5.1.1", version, "maven-bundle-plugin should be upgraded to 5.1.1");
        }

        @Test
        @DisplayName("should not upgrade maven-bundle-plugin when version is already sufficient")
        void shouldNotUpgradeBundlePluginWhenSufficient() throws Exception {
            Document doc = PomBuilder.create()
                    .plugin("org.apache.felix", "maven-bundle-plugin", "5.1.9")
                    .buildDocument();
            strategy.doApply(createMockContext(), Map.of(Paths.get("pom.xml"), doc));

            String version = new Editor(doc)
                    .root()
                    .path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("5.1.9", version, "maven-bundle-plugin 5.1.9 should not be downgraded");
        }

        @Test
        @DisplayName("should upgrade bnd-maven-plugin when below minimum")
        void shouldUpgradeBndMavenPluginWhenBelowMinimum() throws Exception {
            Document doc = PomBuilder.create()
                    .plugin("biz.aQute.bnd", "bnd-maven-plugin", "4.1.0")
                    .buildDocument();
            UpgradeResult result = strategy.doApply(createMockContext(), Map.of(Paths.get("pom.xml"), doc));

            assertTrue(result.success() && result.modifiedCount() > 0, "Should have upgraded bnd-maven-plugin");
            String version = new Editor(doc)
                    .root()
                    .path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("5.1.0", version, "bnd-maven-plugin should be upgraded to 5.1.0");
        }

        @Test
        @DisplayName("should not upgrade bnd-maven-plugin when version is already sufficient")
        void shouldNotUpgradeBndMavenPluginWhenSufficient() throws Exception {
            Document doc = PomBuilder.create()
                    .plugin("biz.aQute.bnd", "bnd-maven-plugin", "7.0.0")
                    .buildDocument();
            strategy.doApply(createMockContext(), Map.of(Paths.get("pom.xml"), doc));

            String version = new Editor(doc)
                    .root()
                    .path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("7.0.0", version, "bnd-maven-plugin 7.0.0 should not be downgraded");
        }

        @Test
        @DisplayName("should not upgrade when version is already higher")
        void shouldNotUpgradeWhenVersionAlreadyHigher() 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.codehaus.mojo</groupId>
                                    <artifactId>flatten-maven-plugin</artifactId>
                                    <version>1.3.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(), "Plugin upgrade should succeed");

            // Verify the version was not changed
            Editor editor = new Editor(document);
            Element root = editor.root();
            String version = root.path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("1.3.0", version);
        }

        @Test
        @DisplayName("should upgrade plugin without explicit groupId")
        void shouldUpgradePluginWithoutExplicitGroupId() 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-shade-plugin</artifactId>
                                    <version>3.1.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(), "Plugin upgrade should succeed");
            assertTrue(
                    result.modifiedCount() > 0,
                    "Should have upgraded maven-shade-plugin even without explicit groupId");

            // Verify the version was upgraded
            Editor editor = new Editor(document);
            Element root = editor.root();
            String version = root.path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals("3.5.0", version);
        }

        @Test
        @DisplayName("should not upgrade plugin without version")
        void shouldNotUpgradePluginWithoutVersion() 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.codehaus.mojo</groupId>
                                    <artifactId>exec-maven-plugin</artifactId>
                                    <!-- No version - inherited from parent or pluginManagement -->
                                </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(), "Plugin upgrade should succeed");
            // Note: POM might still be modified due to plugin management additions
        }

        @Test
        @DisplayName("should not upgrade when property is not found")
        void shouldNotUpgradeWhenPropertyNotFound() 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.codehaus.mojo</groupId>
                                    <artifactId>exec-maven-plugin</artifactId>
                                    <version>${exec.plugin.version}</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(), "Plugin upgrade should succeed");
            // Note: POM might still be modified due to plugin management additions
        }

        @Test
        @DisplayName("should upgrade plugin with property version defined in parent POM")
        void shouldUpgradePluginWithPropertyVersionInParentPom() throws Exception {
            // Simulates hbase pattern: root POM defines <exec.maven.version>3.1.0</exec.maven.version>
            // and submodule uses <version>${exec.maven.version}</version>
            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>org.example</groupId>
                        <artifactId>parent</artifactId>
                        <version>1.0.0</version>
                        <packaging>pom</packaging>
                        <properties>
                            <exec.maven.version>3.1.0</exec.maven.version>
                        </properties>
                        <modules>
                            <module>assembly</module>
                        </modules>
                    </project>
                    """;

            String submodulePomXml = """
                    <?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>assembly</artifactId>
                        <build>
                            <plugins>
                                <plugin>
                                    <groupId>org.codehaus.mojo</groupId>
                                    <artifactId>exec-maven-plugin</artifactId>
                                    <version>${exec.maven.version}</version>
                                </plugin>
                            </plugins>
                        </build>
                    </project>
                    """;

            Path tempDir = Files.createTempDirectory("mvnup-test-");
            try {
                Document parentDoc = Document.of(parentPomXml);
                Document submoduleDoc = Document.of(submodulePomXml);
                Path parentPomPath = tempDir.resolve("pom.xml");
                Path submodulePomPath = tempDir.resolve("assembly/pom.xml");
                Map<Path, Document> pomMap = Map.of(
                        parentPomPath, parentDoc,
                        submodulePomPath, submoduleDoc);

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

                assertTrue(result.success(), "Plugin upgrade should succeed");

                // The parent POM's property should be upgraded to 3.5.0
                Editor parentEditor = new Editor(parentDoc);
                String propertyValue = parentEditor
                        .root()
                        .path("properties", "exec.maven.version")
                        .map(Element::textContentTrimmed)
                        .orElse(null);
                assertEquals(
                        "3.5.0",
                        propertyValue,
                        "Parent POM property exec.maven.version should be upgraded from 3.1.0 to 3.5.0");

                // The submodule's version element should still reference the property
                Editor submoduleEditor = new Editor(submoduleDoc);
                String version = submoduleEditor
                        .root()
                        .path("build", "plugins", "plugin", "version")
                        .map(Element::textContentTrimmed)
                        .orElse(null);
                assertEquals(
                        "${exec.maven.version}",
                        version,
                        "Submodule should still reference the property, not a hardcoded version");
            } finally {
                try (var walk = Files.walk(tempDir)) {
                    walk.sorted(java.util.Comparator.reverseOrder()).forEach(p -> {
                        try {
                            Files.delete(p);
                        } catch (IOException ignored) {
                        }
                    });
                }
            }
        }

        @Test
        @DisplayName("should not emit spurious warning when property is already at target version")
        void shouldNotWarnWhenPropertyAlreadyAtTargetVersion() throws Exception {
            // Root POM has exec.maven.version=3.5.0 (already at target)
            // Submodule uses ${exec.maven.version} ��� no upgrade needed, no warning expected
            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>org.example</groupId>
                        <artifactId>parent</artifactId>
                        <version>1.0.0</version>
                        <packaging>pom</packaging>
                        <properties>
                            <exec.maven.version>3.5.0</exec.maven.version>
                        </properties>
                        <modules>
                            <module>assembly</module>
                        </modules>
                    </project>
                    """;

            String submodulePomXml = """
                    <?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>assembly</artifactId>
                        <build>
                            <plugins>
                                <plugin>
                                    <groupId>org.codehaus.mojo</groupId>
                                    <artifactId>exec-maven-plugin</artifactId>
                                    <version>${exec.maven.version}</version>
                                </plugin>
                            </plugins>
                        </build>
                    </project>
                    """;

            Path tempDir = Files.createTempDirectory("mvnup-test-");
            try {
                Document parentDoc = Document.of(parentPomXml);
                Document submoduleDoc = Document.of(submodulePomXml);
                Path parentPomPath = tempDir.resolve("pom.xml");
                Path submodulePomPath = tempDir.resolve("assembly/pom.xml");
                Map<Path, Document> pomMap = Map.of(
                        parentPomPath, parentDoc,
                        submodulePomPath, submoduleDoc);

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

                assertTrue(result.success(), "Plugin upgrade should succeed");

                // Property should not have been modified (already at target)
                Editor parentEditor = new Editor(parentDoc);
                String propertyValue = parentEditor
                        .root()
                        .path("properties", "exec.maven.version")
                        .map(Element::textContentTrimmed)
                        .orElse(null);
                assertEquals("3.5.0", propertyValue, "Property should remain at 3.5.0 (no upgrade needed)");

                // No spurious "not found" warning should have been emitted
                verify(context.logger, never()).warn(contains("not found"));
            } finally {
                try (var walk = Files.walk(tempDir)) {
                    walk.sorted(java.util.Comparator.reverseOrder()).forEach(p -> {
                        try {
                            Files.delete(p);
                        } catch (IOException ignored) {
                        }
                    });
                }
            }
        }
    }

    @Nested
    @DisplayName("Plugin Dependency Upgrades")
    class PluginDependencyUpgradeTests {

        @Test
        @DisplayName("should upgrade extra-enforcer-rules dependency when below minimum")
        void shouldUpgradeExtraEnforcerRulesDependency() 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-enforcer-plugin</artifactId>
                                    <version>3.5.0</version>
                                    <dependencies>
                                        <dependency>
                                            <groupId>org.codehaus.mojo</groupId>
                                            <artifactId>extra-enforcer-rules</artifactId>
                                            <version>1.0-beta-4</version>
                                        </dependency>
                                    </dependencies>
                                </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(), "Plugin dependency upgrade should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have upgraded extra-enforcer-rules");

            String xml = document.toXml();
            assertTrue(xml.contains("<version>1.4</version>"), "extra-enforcer-rules should be upgraded to 1.4");
            assertFalse(xml.contains("1.0-beta-4"), "Old version should be gone");
        }

        @Test
        @DisplayName("should not upgrade extra-enforcer-rules when version is already sufficient")
        void shouldNotUpgradeExtraEnforcerRulesWhenSufficient() 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-enforcer-plugin</artifactId>
                                    <version>3.5.0</version>
                                    <dependencies>
                                        <dependency>
                                            <groupId>org.codehaus.mojo</groupId>
                                            <artifactId>extra-enforcer-rules</artifactId>
                                            <version>1.8.0</version>
                                        </dependency>
                                    </dependencies>
                                </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);

            String xml = document.toXml();
            assertTrue(xml.contains("1.8.0"), "Version 1.8.0 should be preserved");
        }
    }

    @Nested
    @DisplayName("Plugin Management")
    class PluginManagementTests {

        @Test
        @DisplayName("should add pluginManagement before existing plugins section")
        void shouldAddPluginManagementBeforeExistingPluginsSection() 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.8.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 the structure
            Editor editor = new Editor(document);
            Element root = editor.root();
            Element buildElement = root.childElement("build").orElse(null);
            assertNotNull(buildElement, "Build element should exist");

            List<Element> buildChildren = buildElement.childElements().toList();

            // Find the indices of pluginManagement and plugins
            int pluginManagementIndex = -1;
            int pluginsIndex = -1;

            for (int i = 0; i < buildChildren.size(); i++) {
                Element child = buildChildren.get(i);
                if ("pluginManagement".equals(child.name())) {
                    pluginManagementIndex = i;
                } else if ("plugins".equals(child.name())) {
                    pluginsIndex = i;
                }
            }

            assertTrue(pluginsIndex >= 0, "plugins should be present");
            if (pluginManagementIndex >= 0) {
                assertTrue(
                        pluginManagementIndex < pluginsIndex,
                        "pluginManagement should come before plugins when both are present");
            }
        }
    }

    @Nested
    @DisplayName("Plugin Upgrade Configuration")
    class PluginUpgradeConfigurationTests {
        @Test
        @DisplayName("should have predefined plugin upgrades")
        void shouldHavePredefinedPluginUpgrades() throws Exception {
            List<PluginUpgrade> upgrades = PluginUpgradeStrategy.getPluginUpgrades();
            assertFalse(upgrades.isEmpty(), "Should have predefined plugin upgrades");

            for (String expected : List.of(
                    "maven-compiler-plugin",
                    "exec-maven-plugin",
                    "maven-surefire-plugin",
                    "maven-failsafe-plugin",
                    "maven-surefire-report-plugin",
                    "maven-war-plugin",
                    "maven-ear-plugin",
                    "maven-bundle-plugin",
                    "bnd-maven-plugin")) {
                assertTrue(
                        upgrades.stream().anyMatch(u -> expected.equals(u.artifactId())),
                        "Should include " + expected + " upgrade");
            }
        }

        @Test
        @DisplayName("should have valid plugin upgrade definitions")
        void shouldHaveValidPluginUpgradeDefinitions() {
            for (PluginUpgrade upgrade : PluginUpgradeStrategy.getPluginUpgrades()) {
                assertNotNull(upgrade.groupId(), "Plugin upgrade should have groupId");
                assertNotNull(upgrade.artifactId(), "Plugin upgrade should have artifactId");
                assertNotNull(upgrade.minVersion(), "Plugin upgrade should have minVersion");
            }
        }
    }

    @Nested
    @DisplayName("Inherited Plugin Detection")
    class InheritedPluginDetectionTests {
        @Test
        @DisplayName("should inject pluginManagement with comment for plugins inherited from remote parent")
        void shouldInjectPluginManagementWithCommentForRemoteParentPlugins() throws Exception {
            // org.apache:apache:23 defines maven-enforcer-plugin in pluginManagement and
            // build/plugins. A child POM that does NOT itself declare the plugin should
            // still get a pluginManagement override with a comment explaining it overrides
            // the parent, so that Maven 4 incompatible plugin versions get upgraded.
            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.apache</groupId>
                            <artifactId>apache</artifactId>
                            <version>23</version>
                        </parent>
                        <groupId>org.example</groupId>
                        <artifactId>test-child</artifactId>
                        <version>1.0.0-SNAPSHOT</version>
                    </project>
                    """;

            Path tempDir = Files.createTempDirectory("mvnup-test-");
            try {
                Files.createDirectories(tempDir.resolve(".mvn"));
                Path pomPath = tempDir.resolve("pom.xml");
                Files.writeString(pomPath, pomXml);

                Document document = Document.of(pomXml);
                Map<Path, Document> pomMap = Map.of(pomPath, document);

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

                assertTrue(result.success(), "Strategy should succeed");
                assertTrue(result.modifiedCount() > 0, "Should have modified POM for remote parent plugin upgrade");

                String xml = DomUtils.toXml(document);
                assertTrue(
                        xml.contains("<pluginManagement>"),
                        "Should inject pluginManagement for plugins from remote parent");
                assertTrue(
                        xml.contains("Override version inherited from parent"),
                        "Should add comment explaining the override");
                assertTrue(
                        xml.contains("<artifactId>maven-enforcer-plugin</artifactId>"),
                        "Should add pluginManagement for maven-enforcer-plugin");
                // Verify the comment is on its own line, not appended to the previous closing tag
                assertFalse(
                        xml.contains("</plugin><!--"), "Comment should be on its own line, not appended to </plugin>");
            } finally {
                try (var walk = Files.walk(tempDir)) {
                    walk.sorted(java.util.Comparator.reverseOrder()).forEach(p -> {
                        try {
                            Files.delete(p);
                        } catch (IOException ignored) {
                        }
                    });
                }
            }
        }

        @Test
        @DisplayName("should override remote parent plugin via pluginManagement with comment, not direct build/plugins")
        void shouldOverrideRemoteParentPluginViaPluginManagementWithComment() throws Exception {
            // org.apache:apache:23 has maven-enforcer-plugin in build/plugins WITHOUT
            // an explicit version ��� the version (1.4.1) comes from pluginManagement.
            // In this case, adding a pluginManagement override with a comment in the child
            // is sufficient; no direct build/plugins entry should be added for enforcer.
            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.apache</groupId>
                            <artifactId>apache</artifactId>
                            <version>23</version>
                        </parent>
                        <groupId>org.example</groupId>
                        <artifactId>test-child</artifactId>
                        <version>1.0.0-SNAPSHOT</version>
                    </project>
                    """;

            Path tempDir = Files.createTempDirectory("mvnup-test-");
            try {
                Files.createDirectories(tempDir.resolve(".mvn"));
                Path pomPath = tempDir.resolve("pom.xml");
                Files.writeString(pomPath, pomXml);
                Document document = Document.of(pomXml);
                Map<Path, Document> pomMap = Map.of(pomPath, document);

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

                assertTrue(result.success(), "Strategy should succeed");

                Editor editor = new Editor(document);
                Element root = editor.root();

                // Verify pluginManagement entry exists for enforcer
                Element pmPlugins =
                        root.path("build", "pluginManagement", "plugins").orElse(null);
                assertNotNull(pmPlugins, "Should have pluginManagement/plugins");
                boolean hasEnforcerInPM = pmPlugins
                        .childElements("plugin")
                        .anyMatch(p -> "maven-enforcer-plugin"
                                .equals(p.childElement("artifactId")
                                        .map(Element::textContentTrimmed)
                                        .orElse("")));
                assertTrue(hasEnforcerInPM, "Should have enforcer in pluginManagement");

                String xml = DomUtils.toXml(document);
                assertTrue(
                        xml.contains("Override version inherited from parent"),
                        "Should add comment explaining the override");
                // Verify the comment is on its own line, not appended to the previous closing tag
                assertFalse(
                        xml.contains("</plugin><!--"), "Comment should be on its own line, not appended to </plugin>");

                // Verify NO direct build/plugins entry for enforcer (PM override is sufficient)
                Element buildPlugins = root.childElement("build")
                        .flatMap(b -> b.childElement("plugins"))
                        .orElse(null);
                if (buildPlugins != null) {
                    boolean hasEnforcerInPlugins = buildPlugins
                            .childElements("plugin")
                            .anyMatch(p -> "maven-enforcer-plugin"
                                    .equals(p.childElement("artifactId")
                                            .map(Element::textContentTrimmed)
                                            .orElse("")));
                    assertFalse(
                            hasEnforcerInPlugins,
                            "Should NOT add enforcer in build/plugins when pluginManagement override suffices");
                }
            } finally {
                try (var walk = Files.walk(tempDir)) {
                    walk.sorted(java.util.Comparator.reverseOrder())
                            .map(Path::toFile)
                            .forEach(java.io.File::delete);
                }
            }
        }

        @Test
        @DisplayName("should not duplicate plugin in build/plugins when already locally declared")
        void shouldNotDuplicatePluginInBuildPluginsWhenAlreadyDeclared() 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.apache</groupId>
                            <artifactId>apache</artifactId>
                            <version>23</version>
                        </parent>
                        <groupId>org.example</groupId>
                        <artifactId>test-child</artifactId>
                        <version>1.0.0-SNAPSHOT</version>
                        <build>
                            <plugins>
                                <plugin>
                                    <groupId>org.apache.maven.plugins</groupId>
                                    <artifactId>maven-enforcer-plugin</artifactId>
                                    <version>3.0.0</version>
                                </plugin>
                            </plugins>
                        </build>
                    </project>
                    """;

            Document document = Document.of(pomXml);
            Path tempDir2 = Files.createTempDirectory("mvnup-test-");
            try {
                Files.createDirectories(tempDir2.resolve(".mvn"));
                Path pomPath = tempDir2.resolve("pom.xml");
                Files.writeString(pomPath, pomXml);
                document = Document.of(pomXml);
                Map<Path, Document> pomMap = Map.of(pomPath, document);

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

                assertTrue(result.success(), "Strategy should succeed");

                Editor editor = new Editor(document);
                Element root = editor.root();
                Element buildPlugins = root.childElement("build")
                        .flatMap(b -> b.childElement("plugins"))
                        .orElse(null);
                assertNotNull(buildPlugins, "Should have build/plugins section");

                long enforcerCount = buildPlugins
                        .childElements("plugin")
                        .filter(p -> "maven-enforcer-plugin"
                                .equals(p.childElement("artifactId")
                                        .map(Element::textContentTrimmed)
                                        .orElse("")))
                        .count();
                assertEquals(1, enforcerCount, "Should have exactly one maven-enforcer-plugin in build/plugins");

                String version = buildPlugins
                        .childElements("plugin")
                        .filter(p -> "maven-enforcer-plugin"
                                .equals(p.childElement("artifactId")
                                        .map(Element::textContentTrimmed)
                                        .orElse("")))
                        .findFirst()
                        .flatMap(p -> p.childElement("version"))
                        .map(Element::textContentTrimmed)
                        .orElse(null);
                assertEquals("3.5.0", version, "Existing enforcer-plugin version should be upgraded to 3.5.0");
            } finally {
                try (var walk = Files.walk(tempDir2)) {
                    walk.sorted(java.util.Comparator.reverseOrder())
                            .map(Path::toFile)
                            .forEach(java.io.File::delete);
                }
            }
        }

        @Test
        @DisplayName("should inject pluginManagement when plugin is locally declared without version")
        void shouldInjectPluginManagementForLocallyDeclaredPluginWithoutVersion() throws Exception {
            // Child POM explicitly declares maven-enforcer-plugin in build/plugins without
            // a version. The version comes from the remote parent's pluginManagement.
            // Since the child does declare the plugin, mvnup should add a pluginManagement
            // entry to override the inherited version.
            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.apache</groupId>
                            <artifactId>apache</artifactId>
                            <version>23</version>
                        </parent>
                        <groupId>org.example</groupId>
                        <artifactId>test-child</artifactId>
                        <version>1.0.0-SNAPSHOT</version>
                        <build>
                            <plugins>
                                <plugin>
                                    <groupId>org.apache.maven.plugins</groupId>
                                    <artifactId>maven-enforcer-plugin</artifactId>
                                </plugin>
                            </plugins>
                        </build>
                    </project>
                    """;

            Path tempDir = Files.createTempDirectory("mvnup-test-");
            try {
                Files.createDirectories(tempDir.resolve(".mvn"));
                Path pomPath = tempDir.resolve("pom.xml");
                Files.writeString(pomPath, pomXml);

                Document document = Document.of(pomXml);
                Map<Path, Document> pomMap = Map.of(pomPath, document);

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

                assertTrue(result.success(), "Strategy should succeed");
                assertTrue(
                        result.modifiedCount() > 0, "Should have added pluginManagement for locally declared plugin");

                String xml = DomUtils.toXml(document);
                assertTrue(
                        xml.contains("<pluginManagement>"), "Should add pluginManagement for locally declared plugin");
                assertTrue(
                        xml.contains("<artifactId>maven-enforcer-plugin</artifactId>"),
                        "Should add pluginManagement for maven-enforcer-plugin");
            } finally {
                try (var walk = Files.walk(tempDir)) {
                    walk.sorted(java.util.Comparator.reverseOrder()).forEach(p -> {
                        try {
                            Files.delete(p);
                        } catch (IOException ignored) {
                        }
                    });
                }
            }
        }
    }

    @Nested
    @DisplayName("Error Handling")
    class ErrorHandlingTests {

        @Test
        @DisplayName("should warn when effective model analysis fails for POM with unresolvable remote parent")
        void shouldWarnWhenEffectiveModelAnalysisFailsForUnresolvableRemoteParent() 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.nonexistent.test</groupId>
                            <artifactId>nonexistent-parent</artifactId>
                            <version>1.0.0</version>
                        </parent>
                        <artifactId>test-child</artifactId>
                    </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);

            assertNotNull(result, "Result should not be null");
            assertTrue(result.success(), "Strategy should succeed even when effective model analysis fails");
            assertTrue(result.processedPoms().contains(Paths.get("pom.xml")), "POM should be marked as processed");

            verify(context.logger, atLeastOnce())
                    .warn(argThat(msg -> msg.contains("Failed to analyze effective model")));
        }

        @Test
        @DisplayName("should handle malformed POM gracefully")
        void shouldHandleMalformedPOMGracefully() throws Exception {
            String malformedPomXml = """
                    <?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>
                                    <!-- Missing required elements -->
                                </plugin>
                            </plugins>
                        </build>
                    </project>
                    """;

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

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

            // Strategy should handle malformed POMs gracefully
            assertNotNull(result, "Result should not be null");
            assertTrue(result.processedPoms().contains(Paths.get("pom.xml")), "POM should be marked as processed");
        }
    }

    @Nested
    @DisplayName("Strategy Description")
    class StrategyDescriptionTests {
        @Test
        @DisplayName("should provide meaningful description")
        void shouldProvideMeaningfulDescription() {
            assertNotNull(strategy.getDescription(), "Description should not be null");
            assertFalse(strategy.getDescription().trim().isEmpty(), "Description should not be empty");
            assertTrue(
                    strategy.getDescription().toLowerCase().contains("plugin"), "Description should mention plugins");
        }
    }

    @Nested
    @DisplayName("XML Formatting")
    class XmlFormattingTests {
        @Test
        @DisplayName("should format pluginManagement with proper indentation")
        void shouldFormatPluginManagementWithProperIndentation() 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.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);

            // Convert to string to check formatting
            String result = DomUtils.toXml(document);

            // Check that the plugin version was upgraded
            assertTrue(result.contains("<version>3.11.0</version>"), "Plugin version should be upgraded to 3.11.0");

            // Verify that the XML formatting is correct - no malformed closing tags
            assertFalse(result.contains("</plugin></plugins>"), "Should not have malformed closing tags");
            assertFalse(result.contains("</plugins></pluginManagement>"), "Should not have malformed closing tags");

            // Check that proper indentation is maintained
            assertTrue(result.contains("    <build>"), "Build element should be properly indented");
            assertTrue(result.contains("        <plugins>"), "Plugins element should be properly indented");
            assertTrue(result.contains("            <plugin>"), "Plugin element should be properly indented");
        }

        @Test
        @DisplayName("should format pluginManagement with proper indentation when added")
        void shouldFormatPluginManagementWithProperIndentationWhenAdded() throws Exception {
            // Use a POM that will trigger pluginManagement addition by having a plugin without version
            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>
                                    <!-- No version - should trigger pluginManagement addition -->
                                </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);

            // Convert to string to check formatting
            String result = DomUtils.toXml(document);

            // If pluginManagement was added, verify proper formatting
            if (result.contains("<pluginManagement>")) {
                // Verify that the XML formatting is correct - no malformed closing tags
                assertFalse(result.contains("</plugin></plugins>"), "Should not have malformed closing tags");
                assertFalse(result.contains("</plugins></pluginManagement>"), "Should not have malformed closing tags");

                // Check that proper indentation is maintained for pluginManagement
                assertTrue(
                        result.contains("        <pluginManagement>"), "PluginManagement should be properly indented");
                assertTrue(
                        result.contains("            <plugins>"),
                        "Plugins in pluginManagement should be properly indented");
                assertTrue(
                        result.contains("        </pluginManagement>"),
                        "PluginManagement closing tag should be properly indented");
            }
        }
    }

    @Nested
    @DisplayName("Maven 4 Pre-release Version Detection")
    class Maven4PreReleaseTests {

        @Test
        @DisplayName("should detect 4.0.0 pre-release versions")
        void shouldDetectPreReleaseVersions() {
            assertTrue(PluginUpgradeStrategy.isMaven4PreRelease("4.0.0-beta-1"));
            assertTrue(PluginUpgradeStrategy.isMaven4PreRelease("4.0.0-beta-2"));
            assertTrue(PluginUpgradeStrategy.isMaven4PreRelease("4.0.0-alpha-1"));
            assertTrue(PluginUpgradeStrategy.isMaven4PreRelease("4.0.0-SNAPSHOT"));
            assertTrue(PluginUpgradeStrategy.isMaven4PreRelease("4.0.0-rc-1"));
        }

        @Test
        @DisplayName("should not detect non-pre-release versions")
        void shouldNotDetectNonPreReleaseVersions() {
            assertFalse(PluginUpgradeStrategy.isMaven4PreRelease("4.0.0"));
            assertFalse(PluginUpgradeStrategy.isMaven4PreRelease("3.5.0"));
            assertFalse(PluginUpgradeStrategy.isMaven4PreRelease("3.14.0"));
            assertFalse(PluginUpgradeStrategy.isMaven4PreRelease(null));
        }

        @Test
        @DisplayName("should upgrade beta-1 to latest pre-release, not downgrade to 3.x")
        void shouldUpgradeBetaToLatestPreRelease() throws Exception {
            Document doc = PomBuilder.create()
                    .plugin("org.apache.maven.plugins", "maven-compiler-plugin", "4.0.0-beta-1")
                    .buildDocument();
            strategy.doApply(createMockContext(), Map.of(Paths.get("pom.xml"), doc));
            String xml = DomUtils.toXml(doc);
            assertTrue(
                    xml.contains("<version>4.0.0-beta-4</version>"),
                    "Should upgrade to latest pre-release, not downgrade to 3.11.0");
        }

        @Test
        @DisplayName("should not downgrade when already at latest pre-release")
        void shouldNotDowngradeWhenAtLatestPreRelease() throws Exception {
            Document doc = PomBuilder.create()
                    .plugin("org.apache.maven.plugins", "maven-compiler-plugin", "4.0.0-beta-4")
                    .buildDocument();
            strategy.doApply(createMockContext(), Map.of(Paths.get("pom.xml"), doc));
            assertTrue(
                    DomUtils.toXml(doc).contains("<version>4.0.0-beta-4</version>"),
                    "Should keep latest pre-release unchanged");
        }

        @Test
        @DisplayName("should upgrade pre-release property to latest pre-release")
        void shouldUpgradePreReleaseProperty() throws Exception {
            Document doc = PomBuilder.create()
                    .property("compiler.version", "4.0.0-beta-1")
                    .plugin("org.apache.maven.plugins", "maven-compiler-plugin", "${compiler.version}")
                    .buildDocument();
            strategy.doApply(createMockContext(), Map.of(Paths.get("pom.xml"), doc));
            assertTrue(
                    DomUtils.toXml(doc).contains(">4.0.0-beta-4</"),
                    "Should upgrade property to latest pre-release, not 3.x");
        }

        @Test
        @DisplayName("should downgrade 4.0.0-beta-1 resources-plugin to stable 3.3.1")
        void shouldDowngradePreReleaseResourcesPluginToStable() throws Exception {
            // maven-resources-plugin 4.0.0-beta-1 has API incompatibilities at runtime with
            // Maven 4 rc-5 builds. Since there is no latestPreRelease for this plugin,
            // mvnup must downgrade to the stable minVersion (3.3.1).
            Document doc = PomBuilder.create()
                    .plugin("org.apache.maven.plugins", "maven-resources-plugin", "4.0.0-beta-1")
                    .buildDocument();
            strategy.doApply(createMockContext(), Map.of(Paths.get("pom.xml"), doc));
            assertTrue(
                    DomUtils.toXml(doc).contains("<version>3.3.1</version>"),
                    "maven-resources-plugin 4.0.0-beta-1 should be downgraded to stable 3.3.1");
        }

        @Test
        @DisplayName("should downgrade 4.0.0-beta-1 resources-plugin property to stable 3.3.1")
        void shouldDowngradePreReleaseResourcesPluginPropertyToStable() throws Exception {
            Document doc = PomBuilder.create()
                    .property("resources.version", "4.0.0-beta-1")
                    .plugin("org.apache.maven.plugins", "maven-resources-plugin", "${resources.version}")
                    .buildDocument();
            strategy.doApply(createMockContext(), Map.of(Paths.get("pom.xml"), doc));
            assertTrue(
                    DomUtils.toXml(doc).contains(">3.3.1</"),
                    "Property holding maven-resources-plugin 4.0.0-beta-1 should be downgraded to 3.3.1");
        }
    }

    @Nested
    @DisplayName("Plugin Migration")
    class PluginMigrationTests {

        @Test
        @DisplayName("should migrate org.scala-tools:maven-scala-plugin in build/plugins")
        void shouldMigrateScalaPluginInBuildPlugins() 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.scala-tools</groupId>
                                    <artifactId>maven-scala-plugin</artifactId>
                                    <version>2.15.2</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(), "Plugin migration should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have migrated maven-scala-plugin");

            Editor editor = new Editor(document);
            Element root = editor.root();
            Element pluginElement = root.path("build", "plugins", "plugin").orElse(null);
            assertNotNull(pluginElement, "Plugin element should exist");

            String groupId = pluginElement
                    .childElement("groupId")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            String artifactId = pluginElement
                    .childElement("artifactId")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            String version = pluginElement
                    .childElement("version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);

            assertEquals("net.alchim31.maven", groupId, "groupId should be migrated to net.alchim31.maven");
            assertEquals("scala-maven-plugin", artifactId, "artifactId should be migrated to scala-maven-plugin");
            assertEquals("4.9.5", version, "version should be set to 4.9.5");
        }

        @Test
        @DisplayName("should migrate org.scala-tools:maven-scala-plugin in pluginManagement")
        void shouldMigrateScalaPluginInPluginManagement() 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.scala-tools</groupId>
                                        <artifactId>maven-scala-plugin</artifactId>
                                        <version>2.15.2</version>
                                    </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(), "Plugin migration should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have migrated maven-scala-plugin in pluginManagement");

            Editor editor = new Editor(document);
            Element root = editor.root();
            Element pluginElement =
                    root.path("build", "pluginManagement", "plugins", "plugin").orElse(null);
            assertNotNull(pluginElement, "Plugin element should exist in pluginManagement");

            String groupId = pluginElement
                    .childElement("groupId")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            String artifactId = pluginElement
                    .childElement("artifactId")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            String version = pluginElement
                    .childElement("version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);

            assertEquals("net.alchim31.maven", groupId, "groupId should be migrated to net.alchim31.maven");
            assertEquals("scala-maven-plugin", artifactId, "artifactId should be migrated to scala-maven-plugin");
            assertEquals("4.9.5", version, "version should be set to 4.9.5");
        }

        @Test
        @DisplayName("should not modify POM without migratable plugins")
        void shouldNotModifyPomWithoutMigratablePlugins() 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>net.alchim31.maven</groupId>
                                    <artifactId>scala-maven-plugin</artifactId>
                                    <version>4.9.5</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 the plugin was not changed
            Editor editor = new Editor(document);
            Element root = editor.root();
            String groupId = root.path("build", "plugins", "plugin", "groupId")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            String artifactId = root.path("build", "plugins", "plugin", "artifactId")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            String version = root.path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);

            assertEquals("net.alchim31.maven", groupId, "groupId should remain net.alchim31.maven");
            assertEquals("scala-maven-plugin", artifactId, "artifactId should remain scala-maven-plugin");
            assertEquals("4.9.5", version, "version should remain 4.9.5");
        }

        @Test
        @DisplayName("should upgrade exec-maven-plugin in submodule with explicit version")
        void shouldUpgradeExecPluginInSubmodule() throws Exception {
            // Simulates hbase-assembly declaring exec-maven-plugin:3.1.0 explicitly
            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>org.example</groupId>
                        <artifactId>parent</artifactId>
                        <version>1.0.0</version>
                        <packaging>pom</packaging>
                        <modules>
                            <module>assembly</module>
                        </modules>
                    </project>
                    """;

            String submodulePomXml = """
                    <?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>assembly</artifactId>
                        <build>
                            <plugins>
                                <plugin>
                                    <groupId>org.codehaus.mojo</groupId>
                                    <artifactId>exec-maven-plugin</artifactId>
                                    <version>3.1.0</version>
                                </plugin>
                            </plugins>
                        </build>
                    </project>
                    """;

            Path tempDir = Files.createTempDirectory("mvnup-test-");
            try {
                Document parentDoc = Document.of(parentPomXml);
                Document submoduleDoc = Document.of(submodulePomXml);
                Path parentPomPath = tempDir.resolve("pom.xml");
                Path submodulePomPath = tempDir.resolve("assembly/pom.xml");
                Map<Path, Document> pomMap = Map.of(
                        parentPomPath, parentDoc,
                        submodulePomPath, submoduleDoc);

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

                assertTrue(result.success(), "Plugin upgrade should succeed");

                // The submodule's exec-maven-plugin should be upgraded to 3.5.0
                Editor editor = new Editor(submoduleDoc);
                String version = editor.root()
                        .path("build", "plugins", "plugin", "version")
                        .map(Element::textContentTrimmed)
                        .orElse(null);
                assertEquals("3.5.0", version, "exec-maven-plugin 3.1.0 should be upgraded to 3.5.0 in submodule");
                assertFalse(submoduleDoc.toXml().contains("3.1.0"), "Old version 3.1.0 should not remain");
            } finally {
                try (var walk = Files.walk(tempDir)) {
                    walk.sorted(java.util.Comparator.reverseOrder()).forEach(p -> {
                        try {
                            Files.delete(p);
                        } catch (IOException ignored) {
                        }
                    });
                }
            }
        }

        @Test
        @DisplayName("maven-jar-plugin upgrade target should be 3.4.1 not 3.5.0")
        void jarPluginTargetShouldBe341() throws Exception {
            // maven-jar-plugin 3.4.2 introduced two regressions (via maven-archiver 3.6.3):
            //   - SOURCE_DATE_EPOCH=0 / 1970-01-01 timestamps rejected (apache/maven-jar-plugin#595)
            //   - derived Automatic-Module-Name with hyphens causes build failure (apache/maven-jar-plugin#596)
            // 3.4.1 uses maven-archiver 3.6.2 which has neither issue.
            // Target 3.4.1 until #595/#596 are fixed and a clean 3.5.x ships.
            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-jar-plugin</artifactId>
                                    <version>3.3.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(), "Plugin upgrade should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have upgraded maven-jar-plugin");

            Editor editor = new Editor(document);
            String version = editor.root()
                    .path("build", "plugins", "plugin", "version")
                    .map(Element::textContentTrimmed)
                    .orElse(null);
            assertEquals(
                    "3.4.1",
                    version,
                    "maven-jar-plugin should be upgraded to 3.4.1 (3.4.2 has timestamp/module-name regressions,"
                            + " 3.5.0 has plexus-archiver modular JAR regression)");
        }

        @Test
        @DisplayName("maven-jar-plugin 3.4.2 should not be upgraded further")
        void jarPlugin342ShouldNotBeUpgraded() 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-jar-plugin</artifactId>
                                    <version>3.4.2</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);

            String xml = document.toXml();
            assertTrue(xml.contains("3.4.2"), "Version 3.4.2 should be preserved (already above min 3.4.1)");
        }

        @Test
        @DisplayName("should have predefined plugin migrations")
        void shouldHavePredefinedPluginMigrations() {
            List<PluginMigration> migrations = PluginUpgradeStrategy.getPluginMigrations();

            assertFalse(migrations.isEmpty(), "Should have predefined plugin migrations");

            boolean hasScalaPlugin = migrations.stream()
                    .anyMatch(m ->
                            "org.scala-tools".equals(m.oldGroupId()) && "maven-scala-plugin".equals(m.oldArtifactId()));

            assertTrue(hasScalaPlugin, "Should include org.scala-tools:maven-scala-plugin migration");

            // Verify migration target coordinates
            PluginMigration scalaMigration = migrations.stream()
                    .filter(m ->
                            "org.scala-tools".equals(m.oldGroupId()) && "maven-scala-plugin".equals(m.oldArtifactId()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(scalaMigration, "Scala plugin migration should exist");
            assertEquals("net.alchim31.maven", scalaMigration.newGroupId());
            assertEquals("scala-maven-plugin", scalaMigration.newArtifactId());
            assertEquals("4.9.5", scalaMigration.minVersion());
        }
    }
}