ModelUpgradeStrategyTest.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.maven.cling.invoker.mvnup.goals;

import java.io.StringReader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

import org.apache.maven.api.cli.mvnup.UpgradeOptions;
import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jdom2.input.SAXBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
 * Unit tests for the {@link ModelUpgradeStrategy} class.
 * Tests Maven model version upgrades and namespace transformations.
 */
@DisplayName("ModelUpgradeStrategy")
class ModelUpgradeStrategyTest {

    private ModelUpgradeStrategy strategy;
    private SAXBuilder saxBuilder;

    @BeforeEach
    void setUp() {
        strategy = new ModelUpgradeStrategy();
        saxBuilder = new SAXBuilder();
    }

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

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

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

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

        @ParameterizedTest
        @MethodSource("provideApplicabilityScenarios")
        @DisplayName("should determine applicability based on options")
        void shouldDetermineApplicabilityBasedOnOptions(
                Boolean all, String model, boolean expectedApplicable, String description) {
            UpgradeContext context = TestUtils.createMockContext(TestUtils.createOptions(all, null, null, null, model));

            boolean isApplicable = strategy.isApplicable(context);

            assertEquals(expectedApplicable, isApplicable, description);
        }

        private static Stream<Arguments> provideApplicabilityScenarios() {
            return Stream.of(
                    Arguments.of(null, "4.1.0", true, "Should be applicable when --model=4.1.0 is specified"),
                    Arguments.of(true, null, true, "Should be applicable when --all is specified"),
                    Arguments.of(true, "4.0.0", true, "Should be applicable when --all is specified (overrides model)"),
                    Arguments.of(null, null, false, "Should not be applicable by default"),
                    Arguments.of(false, null, false, "Should not be applicable when --all=false"),
                    Arguments.of(null, "4.0.0", false, "Should not be applicable for same version (4.0.0)"),
                    Arguments.of(false, "4.1.0", true, "Should be applicable for model upgrade even when --all=false"));
        }

        @Test
        @DisplayName("should handle conflicting option combinations")
        void shouldHandleConflictingOptionCombinations() {
            // Test case where multiple conflicting options are set
            UpgradeContext context = TestUtils.createMockContext(TestUtils.createOptions(
                    true, // --all
                    false, // --infer (conflicts with --all)
                    false, // --fix-model (conflicts with --all)
                    false, // --plugins (conflicts with --all)
                    "4.0.0" // --model (conflicts with --all)
                    ));

            // --all should take precedence and make strategy applicable
            assertTrue(
                    strategy.isApplicable(context),
                    "Strategy should be applicable when --all is set, regardless of other options");
        }
    }

    @Nested
    @DisplayName("Model Version Upgrades")
    class ModelVersionUpgradeTests {

        @ParameterizedTest
        @MethodSource("provideUpgradeScenarios")
        @DisplayName("should handle various model version upgrade scenarios")
        void shouldHandleVariousModelVersionUpgradeScenarios(
                String initialNamespace,
                String initialModelVersion,
                String targetModelVersion,
                String expectedNamespace,
                String expectedModelVersion,
                int expectedModifiedCount,
                String description)
                throws Exception {

            String pomXml = PomBuilder.create()
                    .namespace(initialNamespace)
                    .modelVersion(initialModelVersion)
                    .groupId("test")
                    .artifactId("test")
                    .version("1.0.0")
                    .build();

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

            UpgradeContext context = createMockContext(TestUtils.createOptionsWithModelVersion(targetModelVersion));

            UpgradeResult result = strategy.apply(context, pomMap);

            assertTrue(result.success(), "Model upgrade should succeed: " + description);
            assertEquals(expectedModifiedCount, result.modifiedCount(), description);

            // Verify the model version and namespace
            Element root = document.getRootElement();
            assertEquals(expectedNamespace, root.getNamespaceURI(), "Namespace should be updated: " + description);

            Element modelVersionElement = root.getChild("modelVersion", root.getNamespace());
            if (expectedModelVersion != null) {
                assertNotNull(modelVersionElement, "Model version should exist: " + description);
                assertEquals(
                        expectedModelVersion,
                        modelVersionElement.getTextTrim(),
                        "Model version should be correct: " + description);
            }
        }

        private static Stream<Arguments> provideUpgradeScenarios() {
            return Stream.of(
                    Arguments.of(
                            "http://maven.apache.org/POM/4.0.0",
                            "4.0.0",
                            "4.1.0",
                            "http://maven.apache.org/POM/4.1.0",
                            "4.1.0",
                            1,
                            "Should upgrade from 4.0.0 to 4.1.0"),
                    Arguments.of(
                            "http://maven.apache.org/POM/4.1.0",
                            "4.1.0",
                            "4.1.0",
                            "http://maven.apache.org/POM/4.1.0",
                            "4.1.0",
                            0,
                            "Should not modify when already at target version"),
                    Arguments.of(
                            "http://maven.apache.org/POM/4.0.0",
                            null,
                            "4.1.0",
                            "http://maven.apache.org/POM/4.1.0",
                            "4.1.0",
                            1,
                            "Should add model version when missing"));
        }
    }

    @Nested
    @DisplayName("Namespace Updates")
    class NamespaceUpdateTests {

        @Test
        @DisplayName("should update namespace recursively")
        void shouldUpdateNamespaceRecursively() 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>
                    <dependencies>
                        <dependency>
                            <groupId>test</groupId>
                            <artifactId>test</artifactId>
                            <version>1.0.0</version>
                        </dependency>
                    </dependencies>
                </project>
                """;

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

            // Create context with --model-version=4.1.0 option to trigger namespace update
            UpgradeOptions options = mock(UpgradeOptions.class);
            when(options.modelVersion()).thenReturn(Optional.of("4.1.0"));
            when(options.all()).thenReturn(Optional.empty());
            UpgradeContext context = createMockContext(options);

            UpgradeResult result = strategy.apply(context, pomMap);

            assertTrue(result.success(), "Model upgrade should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have upgraded namespace");

            // Verify namespace was updated recursively
            Element root = document.getRootElement();
            Namespace newNamespace = Namespace.getNamespace("http://maven.apache.org/POM/4.1.0");
            assertEquals(newNamespace, root.getNamespace());

            // Verify child elements namespace updated recursively
            Element dependencies = root.getChild("dependencies", newNamespace);
            assertNotNull(dependencies);
            assertEquals(newNamespace, dependencies.getNamespace());

            Element dependency = dependencies.getChild("dependency", newNamespace);
            assertNotNull(dependency);
            assertEquals(newNamespace, dependency.getNamespace());

            Element groupId = dependency.getChild("groupId", newNamespace);
            assertNotNull(groupId);
            assertEquals(newNamespace, groupId.getNamespace());
        }

        @Test
        @DisplayName("should convert modules to subprojects in 4.1.0")
        void shouldConvertModulesToSubprojectsIn410() 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>
                    <modules>
                        <module>module1</module>
                        <module>module2</module>
                    </modules>
                </project>
                """;

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

            // Create context with --model-version=4.1.0 option to trigger module conversion
            UpgradeOptions options = mock(UpgradeOptions.class);
            when(options.modelVersion()).thenReturn(Optional.of("4.1.0"));
            when(options.all()).thenReturn(Optional.empty());
            UpgradeContext context = createMockContext(options);

            UpgradeResult result = strategy.apply(context, pomMap);

            assertTrue(result.success(), "Model upgrade should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have converted modules to subprojects");

            // Verify modules element was renamed to subprojects
            Element root = document.getRootElement();
            Namespace namespace = root.getNamespace();
            assertNull(root.getChild("modules", namespace));
            Element subprojects = root.getChild("subprojects", namespace);
            assertNotNull(subprojects);

            // Verify module elements were renamed to subproject
            assertEquals(0, subprojects.getChildren("module", namespace).size());
            assertEquals(2, subprojects.getChildren("subproject", namespace).size());

            assertEquals(
                    "module1",
                    subprojects.getChildren("subproject", namespace).get(0).getText());
            assertEquals(
                    "module2",
                    subprojects.getChildren("subproject", namespace).get(1).getText());
        }
    }

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

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

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

    @Nested
    @DisplayName("Phase Upgrades")
    class PhaseUpgradeTests {

        @Test
        @DisplayName("should upgrade deprecated phases to Maven 4 equivalents in 4.1.0")
        void shouldUpgradeDeprecatedPhasesIn410() throws Exception {
            Document document = createDocumentWithDeprecatedPhases();
            Map<Path, Document> pomMap = Map.of(Paths.get("pom.xml"), document);

            // Create context with --model-version=4.1.0 option to trigger phase upgrade
            UpgradeOptions options = mock(UpgradeOptions.class);
            when(options.modelVersion()).thenReturn(Optional.of("4.1.0"));
            when(options.all()).thenReturn(Optional.empty());
            UpgradeContext context = createMockContext(options);

            UpgradeResult result = strategy.apply(context, pomMap);

            assertTrue(result.success(), "Model upgrade should succeed");
            assertTrue(result.modifiedCount() > 0, "Should have upgraded phases");

            // Verify phases were upgraded
            verifyCleanPluginPhases(document);
            verifyFailsafePluginPhases(document);
            verifySitePluginPhases(document);
            verifyPluginManagementPhases(document);
            verifyProfilePhases(document);
        }

        private Document createDocumentWithDeprecatedPhases() throws Exception {
            String pomXml =
                    """
                <?xml version="1.0" encoding="UTF-8"?>
                <project xmlns="http://maven.apache.org/POM/4.0.0">
                    <modelVersion>4.0.0</modelVersion>
                    <groupId>com.example</groupId>
                    <artifactId>test-project</artifactId>
                    <version>1.0.0</version>
                    <build>
                        <plugins>
                            <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-clean-plugin</artifactId>
                                <version>3.2.0</version>
                                <executions>
                                    <execution>
                                        <id>pre-clean-test</id>
                                        <phase>pre-clean</phase>
                                        <goals>
                                            <goal>clean</goal>
                                        </goals>
                                    </execution>
                                    <execution>
                                        <id>post-clean-test</id>
                                        <phase>post-clean</phase>
                                        <goals>
                                            <goal>clean</goal>
                                        </goals>
                                    </execution>
                                </executions>
                            </plugin>
                            <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-failsafe-plugin</artifactId>
                                <version>3.0.0-M7</version>
                                <executions>
                                    <execution>
                                        <id>pre-integration-test-setup</id>
                                        <phase>pre-integration-test</phase>
                                        <goals>
                                            <goal>integration-test</goal>
                                        </goals>
                                    </execution>
                                    <execution>
                                        <id>post-integration-test-cleanup</id>
                                        <phase>post-integration-test</phase>
                                        <goals>
                                            <goal>verify</goal>
                                        </goals>
                                    </execution>
                                </executions>
                            </plugin>
                            <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-site-plugin</artifactId>
                                <version>3.12.1</version>
                                <executions>
                                    <execution>
                                        <id>pre-site-setup</id>
                                        <phase>pre-site</phase>
                                        <goals>
                                            <goal>site</goal>
                                        </goals>
                                    </execution>
                                    <execution>
                                        <id>post-site-cleanup</id>
                                        <phase>post-site</phase>
                                        <goals>
                                            <goal>deploy</goal>
                                        </goals>
                                    </execution>
                                </executions>
                            </plugin>
                        </plugins>
                        <pluginManagement>
                            <plugins>
                                <plugin>
                                    <groupId>org.apache.maven.plugins</groupId>
                                    <artifactId>maven-compiler-plugin</artifactId>
                                    <version>3.11.0</version>
                                    <executions>
                                        <execution>
                                            <id>pre-clean-compile</id>
                                            <phase>pre-clean</phase>
                                            <goals>
                                                <goal>compile</goal>
                                            </goals>
                                        </execution>
                                    </executions>
                                </plugin>
                            </plugins>
                        </pluginManagement>
                    </build>
                    <profiles>
                        <profile>
                            <id>test-profile</id>
                            <build>
                                <plugins>
                                    <plugin>
                                        <groupId>org.apache.maven.plugins</groupId>
                                        <artifactId>maven-antrun-plugin</artifactId>
                                        <version>3.1.0</version>
                                        <executions>
                                            <execution>
                                                <id>profile-pre-integration-test</id>
                                                <phase>pre-integration-test</phase>
                                                <goals>
                                                    <goal>run</goal>
                                                </goals>
                                            </execution>
                                        </executions>
                                    </plugin>
                                </plugins>
                            </build>
                        </profile>
                    </profiles>
                </project>
                """;

            return saxBuilder.build(new StringReader(pomXml));
        }

        private void verifyCleanPluginPhases(Document document) {
            Element root = document.getRootElement();
            Namespace namespace = root.getNamespace();
            Element build = root.getChild("build", namespace);
            Element plugins = build.getChild("plugins", namespace);

            Element cleanPlugin = plugins.getChildren("plugin", namespace).stream()
                    .filter(p -> "maven-clean-plugin"
                            .equals(p.getChild("artifactId", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(cleanPlugin);

            Element cleanExecutions = cleanPlugin.getChild("executions", namespace);
            Element preCleanExecution = cleanExecutions.getChildren("execution", namespace).stream()
                    .filter(e ->
                            "pre-clean-test".equals(e.getChild("id", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(preCleanExecution);
            assertEquals(
                    "before:clean",
                    preCleanExecution.getChild("phase", namespace).getText());

            Element postCleanExecution = cleanExecutions.getChildren("execution", namespace).stream()
                    .filter(e ->
                            "post-clean-test".equals(e.getChild("id", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(postCleanExecution);
            assertEquals(
                    "after:clean",
                    postCleanExecution.getChild("phase", namespace).getText());
        }

        private void verifyFailsafePluginPhases(Document document) {
            Element root = document.getRootElement();
            Namespace namespace = root.getNamespace();
            Element build = root.getChild("build", namespace);
            Element plugins = build.getChild("plugins", namespace);

            Element failsafePlugin = plugins.getChildren("plugin", namespace).stream()
                    .filter(p -> "maven-failsafe-plugin"
                            .equals(p.getChild("artifactId", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(failsafePlugin);

            Element failsafeExecutions = failsafePlugin.getChild("executions", namespace);
            Element preIntegrationExecution = failsafeExecutions.getChildren("execution", namespace).stream()
                    .filter(e -> "pre-integration-test-setup"
                            .equals(e.getChild("id", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(preIntegrationExecution);
            assertEquals(
                    "before:integration-test",
                    preIntegrationExecution.getChild("phase", namespace).getText());

            Element postIntegrationExecution = failsafeExecutions.getChildren("execution", namespace).stream()
                    .filter(e -> "post-integration-test-cleanup"
                            .equals(e.getChild("id", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(postIntegrationExecution);
            assertEquals(
                    "after:integration-test",
                    postIntegrationExecution.getChild("phase", namespace).getText());
        }

        private void verifySitePluginPhases(Document document) {
            Element root = document.getRootElement();
            Namespace namespace = root.getNamespace();
            Element build = root.getChild("build", namespace);
            Element plugins = build.getChild("plugins", namespace);

            Element sitePlugin = plugins.getChildren("plugin", namespace).stream()
                    .filter(p -> "maven-site-plugin"
                            .equals(p.getChild("artifactId", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(sitePlugin);

            Element siteExecutions = sitePlugin.getChild("executions", namespace);
            Element preSiteExecution = siteExecutions.getChildren("execution", namespace).stream()
                    .filter(e ->
                            "pre-site-setup".equals(e.getChild("id", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(preSiteExecution);
            assertEquals(
                    "before:site", preSiteExecution.getChild("phase", namespace).getText());

            Element postSiteExecution = siteExecutions.getChildren("execution", namespace).stream()
                    .filter(e -> "post-site-cleanup"
                            .equals(e.getChild("id", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(postSiteExecution);
            assertEquals(
                    "after:site", postSiteExecution.getChild("phase", namespace).getText());
        }

        private void verifyPluginManagementPhases(Document document) {
            Element root = document.getRootElement();
            Namespace namespace = root.getNamespace();
            Element build = root.getChild("build", namespace);
            Element pluginManagement = build.getChild("pluginManagement", namespace);
            Element managedPlugins = pluginManagement.getChild("plugins", namespace);
            Element compilerPlugin = managedPlugins.getChildren("plugin", namespace).stream()
                    .filter(p -> "maven-compiler-plugin"
                            .equals(p.getChild("artifactId", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(compilerPlugin);

            Element compilerExecutions = compilerPlugin.getChild("executions", namespace);
            Element preCleanCompileExecution = compilerExecutions.getChildren("execution", namespace).stream()
                    .filter(e -> "pre-clean-compile"
                            .equals(e.getChild("id", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(preCleanCompileExecution);
            assertEquals(
                    "before:clean",
                    preCleanCompileExecution.getChild("phase", namespace).getText());
        }

        private void verifyProfilePhases(Document document) {
            Element root = document.getRootElement();
            Namespace namespace = root.getNamespace();
            Element profiles = root.getChild("profiles", namespace);
            Element profile = profiles.getChild("profile", namespace);
            Element profileBuild = profile.getChild("build", namespace);
            Element profilePlugins = profileBuild.getChild("plugins", namespace);
            Element antrunPlugin = profilePlugins.getChildren("plugin", namespace).stream()
                    .filter(p -> "maven-antrun-plugin"
                            .equals(p.getChild("artifactId", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(antrunPlugin);

            Element antrunExecutions = antrunPlugin.getChild("executions", namespace);
            Element profilePreIntegrationExecution = antrunExecutions.getChildren("execution", namespace).stream()
                    .filter(e -> "profile-pre-integration-test"
                            .equals(e.getChild("id", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(profilePreIntegrationExecution);
            assertEquals(
                    "before:integration-test",
                    profilePreIntegrationExecution.getChild("phase", namespace).getText());
        }

        @Test
        @DisplayName("should not upgrade phases when upgrading to 4.0.0")
        void shouldNotUpgradePhasesWhenUpgradingTo400() throws Exception {
            String pomXml =
                    """
                <?xml version="1.0" encoding="UTF-8"?>
                <project xmlns="http://maven.apache.org/POM/4.0.0">
                    <modelVersion>4.0.0</modelVersion>
                    <groupId>com.example</groupId>
                    <artifactId>test-project</artifactId>
                    <version>1.0.0</version>
                    <build>
                        <plugins>
                            <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-clean-plugin</artifactId>
                                <version>3.2.0</version>
                                <executions>
                                    <execution>
                                        <id>pre-clean-test</id>
                                        <phase>pre-clean</phase>
                                        <goals>
                                            <goal>clean</goal>
                                        </goals>
                                    </execution>
                                </executions>
                            </plugin>
                        </plugins>
                    </build>
                </project>
                """;

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

            // Create context with --model-version=4.0.0 option (no phase upgrade)
            UpgradeOptions options = mock(UpgradeOptions.class);
            when(options.modelVersion()).thenReturn(Optional.of("4.0.0"));
            when(options.all()).thenReturn(Optional.empty());
            UpgradeContext context = createMockContext(options);

            UpgradeResult result = strategy.apply(context, pomMap);

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

            // Verify phases were NOT upgraded (should remain as pre-clean)
            Element root = document.getRootElement();
            Namespace namespace = root.getNamespace();
            Element build = root.getChild("build", namespace);
            Element plugins = build.getChild("plugins", namespace);
            Element cleanPlugin = plugins.getChild("plugin", namespace);
            Element executions = cleanPlugin.getChild("executions", namespace);
            Element execution = executions.getChild("execution", namespace);
            Element phase = execution.getChild("phase", namespace);

            assertEquals("pre-clean", phase.getText(), "Phase should remain as pre-clean for 4.0.0");
        }

        @Test
        @DisplayName("should preserve non-deprecated phases")
        void shouldPreserveNonDeprecatedPhases() throws Exception {
            String pomXml =
                    """
                <?xml version="1.0" encoding="UTF-8"?>
                <project xmlns="http://maven.apache.org/POM/4.0.0">
                    <modelVersion>4.0.0</modelVersion>
                    <groupId>com.example</groupId>
                    <artifactId>test-project</artifactId>
                    <version>1.0.0</version>
                    <build>
                        <plugins>
                            <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-compiler-plugin</artifactId>
                                <version>3.11.0</version>
                                <executions>
                                    <execution>
                                        <id>compile-test</id>
                                        <phase>compile</phase>
                                        <goals>
                                            <goal>compile</goal>
                                        </goals>
                                    </execution>
                                    <execution>
                                        <id>test-compile-test</id>
                                        <phase>test-compile</phase>
                                        <goals>
                                            <goal>testCompile</goal>
                                        </goals>
                                    </execution>
                                    <execution>
                                        <id>package-test</id>
                                        <phase>package</phase>
                                        <goals>
                                            <goal>compile</goal>
                                        </goals>
                                    </execution>
                                </executions>
                            </plugin>
                        </plugins>
                    </build>
                </project>
                """;

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

            // Create context with --model-version=4.1.0 option
            UpgradeOptions options = mock(UpgradeOptions.class);
            when(options.modelVersion()).thenReturn(Optional.of("4.1.0"));
            when(options.all()).thenReturn(Optional.empty());
            UpgradeContext context = createMockContext(options);

            UpgradeResult result = strategy.apply(context, pomMap);

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

            // Verify non-deprecated phases were preserved
            Element root = document.getRootElement();
            Namespace namespace = root.getNamespace();
            Element build = root.getChild("build", namespace);
            Element plugins = build.getChild("plugins", namespace);
            Element compilerPlugin = plugins.getChild("plugin", namespace);
            Element executions = compilerPlugin.getChild("executions", namespace);

            Element compileExecution = executions.getChildren("execution", namespace).stream()
                    .filter(e ->
                            "compile-test".equals(e.getChild("id", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(compileExecution);
            assertEquals(
                    "compile", compileExecution.getChild("phase", namespace).getText());

            Element testCompileExecution = executions.getChildren("execution", namespace).stream()
                    .filter(e -> "test-compile-test"
                            .equals(e.getChild("id", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(testCompileExecution);
            assertEquals(
                    "test-compile",
                    testCompileExecution.getChild("phase", namespace).getText());

            Element packageExecution = executions.getChildren("execution", namespace).stream()
                    .filter(e ->
                            "package-test".equals(e.getChild("id", namespace).getText()))
                    .findFirst()
                    .orElse(null);
            assertNotNull(packageExecution);
            assertEquals(
                    "package", packageExecution.getChild("phase", namespace).getText());
        }
    }

    @Nested
    @DisplayName("Downgrade Handling")
    class DowngradeHandlingTests {

        @Test
        @DisplayName("should fail with error when attempting downgrade from 4.1.0 to 4.0.0")
        void shouldFailWhenAttemptingDowngrade() throws Exception {
            String pomXml =
                    """
                <?xml version="1.0" encoding="UTF-8"?>
                <project xmlns="http://maven.apache.org/POM/4.1.0">
                    <modelVersion>4.1.0</modelVersion>
                    <groupId>com.example</groupId>
                    <artifactId>test-project</artifactId>
                    <version>1.0.0</version>
                </project>
                """;

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

            UpgradeContext context = TestUtils.createMockContext(TestUtils.createOptionsWithModelVersion("4.0.0"));

            UpgradeResult result = strategy.apply(context, pomMap);

            // Should have errors (not just warnings)
            assertTrue(result.errorCount() > 0, "Downgrade should result in errors");
            assertFalse(result.success(), "Downgrade should not be successful");
            assertEquals(1, result.errorCount(), "Should have exactly one error");
        }

        @Test
        @DisplayName("should succeed when upgrading from 4.0.0 to 4.1.0")
        void shouldSucceedWhenUpgrading() throws Exception {
            String pomXml =
                    """
                <?xml version="1.0" encoding="UTF-8"?>
                <project xmlns="http://maven.apache.org/POM/4.0.0">
                    <modelVersion>4.0.0</modelVersion>
                    <groupId>com.example</groupId>
                    <artifactId>test-project</artifactId>
                    <version>1.0.0</version>
                </project>
                """;

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

            UpgradeContext context = TestUtils.createMockContext(TestUtils.createOptionsWithModelVersion("4.1.0"));

            UpgradeResult result = strategy.apply(context, pomMap);

            // Should succeed
            assertTrue(result.success(), "Valid upgrade should be successful");
            assertEquals(0, result.errorCount(), "Should have no errors");
            assertEquals(1, result.modifiedCount(), "Should have modified one POM");
        }
    }
}