RemoteResolutionGateTest.java

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

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;

import eu.maveniverse.domtrip.Document;
import org.apache.maven.api.settings.Mirror;
import org.apache.maven.api.settings.Proxy;
import org.apache.maven.api.settings.Settings;
import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
import org.junit.jupiter.api.DisplayName;
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.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * Tests for the remote-resolution gate in {@link AbstractUpgradeStrategy}.
 *
 * <p>mvnup builds effective models with a standalone resolver session that cannot honor
 * mirrors, proxies or offline mode from the operator's settings. The gate must skip
 * remote-model-dependent work whenever such a posture is configured, instead of silently
 * resolving from public repositories.</p>
 */
@DisplayName("Remote resolution gate")
class RemoteResolutionGateTest {

    private static Settings settingsWithRedirectingMirror() {
        return Settings.newBuilder()
                .mirrors(List.of(Mirror.newBuilder()
                        .id("corp-mirror")
                        .mirrorOf("*")
                        .url("https://mirror.corp.example/maven2")
                        .build()))
                .build();
    }

    @Test
    @DisplayName("no reason when settings were never loaded (test/embedded use)")
    void reasonIsNullWhenSettingsWereNotLoaded() {
        UpgradeContext context = TestUtils.createMockContext();
        context.effectiveSettings = null;
        assertNull(AbstractUpgradeStrategy.remoteResolutionUnsupportedReason(context));
    }

    @Test
    @DisplayName("no reason for empty effective settings")
    void reasonIsNullForEmptySettings() {
        UpgradeContext context = TestUtils.createMockContext();
        context.effectiveSettings = Settings.newInstance();
        assertNull(AbstractUpgradeStrategy.remoteResolutionUnsupportedReason(context));
    }

    @Test
    @DisplayName("no reason when only blocked mirrors are configured (default http blocker)")
    void reasonIsNullWhenOnlyBlockedMirrorsAreConfigured() {
        UpgradeContext context = TestUtils.createMockContext();
        context.effectiveSettings = Settings.newBuilder()
                .mirrors(List.of(Mirror.newBuilder()
                        .id("maven-default-http-blocker")
                        .mirrorOf("external:http:*")
                        .url("http://0.0.0.0/")
                        .blocked(true)
                        .build()))
                .build();
        assertNull(AbstractUpgradeStrategy.remoteResolutionUnsupportedReason(context));
    }

    @Test
    @DisplayName("redirecting mirror disables remote resolution")
    void reasonIsNonNullForRedirectingMirror() {
        UpgradeContext context = TestUtils.createMockContext();
        context.effectiveSettings = settingsWithRedirectingMirror();
        assertNotNull(AbstractUpgradeStrategy.remoteResolutionUnsupportedReason(context));
    }

    @Test
    @DisplayName("active proxy disables remote resolution")
    void reasonIsNonNullForActiveProxy() {
        UpgradeContext context = TestUtils.createMockContext();
        context.effectiveSettings = Settings.newBuilder()
                .proxies(List.of(Proxy.newBuilder()
                        .id("corp-proxy")
                        .activeString("true")
                        .host("proxy.corp.example")
                        .build()))
                .build();
        assertNotNull(AbstractUpgradeStrategy.remoteResolutionUnsupportedReason(context));
    }

    @Test
    @DisplayName("offline mode disables remote resolution")
    void reasonIsNonNullForOfflineMode() {
        UpgradeContext context = TestUtils.createMockContext();
        context.effectiveSettings = Settings.newBuilder().offline(true).build();
        assertNotNull(AbstractUpgradeStrategy.remoteResolutionUnsupportedReason(context));
    }

    @Test
    @DisplayName("compatibility fixes must not comment out elements when the posture cannot be honored")
    void compatibilityFixDoesNotCommentOutWhenPostureCannotBeHonored() {
        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>
                <dependencyManagement>
                    <dependencies>
                        <dependency>
                            <groupId>com.google.guava</groupId>
                            <artifactId>guava</artifactId>
                            <version>${guava-version}</version>
                        </dependency>
                    </dependencies>
                </dependencyManagement>
            </project>
            """;

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

        UpgradeContext context = TestUtils.createMockContext();
        // Operator routes all repository traffic through a mirror: the standalone resolver
        // cannot honor it, so no effective model is available and the property cannot be
        // proven undefined. The fix must be skipped rather than commenting out a possibly
        // valid dependency.
        context.effectiveSettings = settingsWithRedirectingMirror();

        CompatibilityFixStrategy strategy = new CompatibilityFixStrategy();
        UpgradeResult result = strategy.doApply(context, pomMap);

        assertTrue(result.success(), "Strategy should succeed while skipping the unverifiable fix");
        assertEquals(0, result.modifiedCount(), "No POM should be modified");

        String xml = DomUtils.toXml(document);
        assertFalse(xml.contains("mvnup: commented out"), "Nothing may be commented out");
        assertTrue(xml.contains("${guava-version}"), "The dependency must be left untouched");
    }
}