ReportTest.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.mvnval;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.ModelProblem;
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.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * Unit tests for the {@link Report} record.
 * Tests the invariants the compact constructor enforces and the clean/dirty distinction.
 */
@DisplayName("Report")
class ReportTest {

    private static final Path POM = Path.of("pom.xml");

    @Nested
    @DisplayName("Problems")
    class ProblemTests {

        @Test
        @DisplayName("should report a problem once when two validation phases both found it")
        void shouldReportRepeatedProblemOnce() {
            // The parent version check sits in both validateFileModel and validateRawModel, so
            // the model builder hands the same finding over twice.
            ModelProblem problem =
                    TestUtils.problem(BuilderProblem.Severity.WARNING, "'parent.version' is LATEST", "pom.xml", 3, 66);
            ModelProblem again =
                    TestUtils.problem(BuilderProblem.Severity.WARNING, "'parent.version' is LATEST", "pom.xml", 3, 66);

            assertEquals(1, Report.of(POM, List.of(problem, again)).problems().size());
        }

        @Test
        @DisplayName("should keep two problems that differ only in where they sit")
        void shouldKeepProblemsAtDifferentPlaces() {
            ModelProblem first = TestUtils.problem(BuilderProblem.Severity.WARNING, "same words", "pom.xml", 3, 1);
            ModelProblem second = TestUtils.problem(BuilderProblem.Severity.WARNING, "same words", "pom.xml", 9, 1);

            assertEquals(2, Report.of(POM, List.of(first, second)).problems().size());
        }
    }

    @Nested
    @DisplayName("State")
    class StateTests {

        @Test
        @DisplayName("should be clean when there is neither a failure nor a problem")
        void shouldBeCleanWhenNeitherFailureNorProblem() {
            assertTrue(Report.of(POM, List.of()).clean(), "a report with no problems should be clean");
        }

        @Test
        @DisplayName("should not be clean when a problem was reported")
        void shouldNotBeCleanWhenProblemReported() {
            Report report = Report.of(
                    POM, List.of(TestUtils.problem(BuilderProblem.Severity.WARNING, "warn", "pom.xml", 1, 1)));
            assertFalse(report.clean(), "a report carrying a problem should not be clean");
        }

        @Test
        @DisplayName("should not be clean when the file could not be validated")
        void shouldNotBeCleanWhenValidationFailed() {
            assertFalse(Report.failed(POM, "not a readable file").clean(), "a failed report should not be clean");
        }
    }

    @Nested
    @DisplayName("Invariants")
    class InvariantTests {

        @Test
        @DisplayName("should reject a failure that also carries problems")
        void shouldRejectFailureCarryingProblems() {
            List<ModelProblem> problems =
                    List.of(TestUtils.problem(BuilderProblem.Severity.ERROR, "boom", "pom.xml", 1, 1));
            assertThrows(
                    IllegalArgumentException.class,
                    () -> new Report(POM, problems, "not a readable file"),
                    "a failed report must not also carry problems");
        }

        @Test
        @DisplayName("should copy the problem list so later changes cannot leak in")
        void shouldCopyProblemList() {
            List<ModelProblem> mutable = new ArrayList<>();
            mutable.add(TestUtils.problem(BuilderProblem.Severity.ERROR, "first", "pom.xml", 1, 1));
            Report report = Report.of(POM, mutable);

            mutable.add(TestUtils.problem(BuilderProblem.Severity.ERROR, "second", "pom.xml", 2, 2));

            assertEquals(1, report.problems().size(), "the report should not see additions made after it was built");
        }
    }
}