DefaultModelProcessorTest.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.impl.model;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;

import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.xml.ModelXmlFactory;
import org.apache.maven.api.services.xml.XmlReaderException;
import org.apache.maven.api.services.xml.XmlReaderRequest;
import org.apache.maven.api.spi.ModelParser;
import org.apache.maven.api.spi.ModelParserException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * Tests for {@link DefaultModelProcessor}.
 */
class DefaultModelProcessorTest {

    @TempDir
    Path tempDir;

    @Test
    void testDetailedErrorMessageWithMultipleParsers() throws IOException {
        // Create a test POM file
        Path pomFile = tempDir.resolve("pom.yaml");
        Files.writeString(pomFile, "invalid: yaml: content:");

        // Create mock parsers that will fail
        ModelParser yamlParser = mock(ModelParser.class);
        when(yamlParser.locateAndParse(any(), any()))
                .thenThrow(new ModelParserException(
                        "YAML parsing failed", 5, 10, new RuntimeException("Invalid YAML syntax")));

        ModelParser tomlParser = mock(ModelParser.class);
        when(tomlParser.locateAndParse(any(), any()))
                .thenThrow(new ModelParserException("TOML parsing failed", 3, 7, null));

        // Create mock XML factory that will also fail
        ModelXmlFactory xmlFactory = mock(ModelXmlFactory.class);
        when(xmlFactory.read(any(XmlReaderRequest.class)))
                .thenThrow(new XmlReaderException("XML parsing failed", null, null));

        // Create processor with the mock parsers
        DefaultModelProcessor processor =
                new DefaultModelProcessor(xmlFactory, Map.of("yaml", yamlParser, "toml", tomlParser));

        // Create request
        XmlReaderRequest request =
                XmlReaderRequest.builder().path(pomFile).strict(true).build();

        // Execute and verify
        IOException exception = assertThrows(IOException.class, () -> processor.read(request));

        String message = exception.getMessage();

        // Verify the message contains information about all parsers
        assertTrue(message.contains("Unable to parse POM"), "Message should mention unable to parse POM");
        assertTrue(message.contains(pomFile.toString()), "Message should contain the POM file path");
        assertTrue(message.contains("Tried 2 parsers"), "Message should mention 2 parsers were tried");
        assertTrue(message.contains("YAML parsing failed"), "Message should contain YAML parser error");
        assertTrue(message.contains("at line 5, column 10"), "Message should contain YAML line/column info");
        assertTrue(message.contains("Invalid YAML syntax"), "Message should contain YAML cause message");
        assertTrue(message.contains("TOML parsing failed"), "Message should contain TOML parser error");
        assertTrue(message.contains("at line 3, column 7"), "Message should contain TOML line/column info");
        assertTrue(message.contains("default) XML reader also failed"), "Message should mention XML reader failure");
        assertTrue(message.contains("XML parsing failed"), "Message should contain XML error message");

        // Verify suppressed exceptions are still attached
        assertEquals(3, exception.getSuppressed().length, "Should have 3 suppressed exceptions");
    }

    @Test
    void testDetailedErrorMessageWithSingleParser() throws IOException {
        Path pomFile = tempDir.resolve("pom.json");
        Files.writeString(pomFile, "{invalid json}");

        ModelParser jsonParser = mock(ModelParser.class);
        when(jsonParser.locateAndParse(any(), any())).thenThrow(new ModelParserException("JSON parsing failed"));

        ModelXmlFactory xmlFactory = mock(ModelXmlFactory.class);
        when(xmlFactory.read(any(XmlReaderRequest.class)))
                .thenThrow(new XmlReaderException("Not valid XML", null, null));

        DefaultModelProcessor processor = new DefaultModelProcessor(xmlFactory, Map.of("json", jsonParser));

        XmlReaderRequest request =
                XmlReaderRequest.builder().path(pomFile).strict(true).build();

        IOException exception = assertThrows(IOException.class, () -> processor.read(request));

        String message = exception.getMessage();
        assertTrue(message.contains("Tried 1 parser:"), "Message should mention 1 parser (singular)");
        assertTrue(message.contains("JSON parsing failed"), "Message should contain JSON parser error");
        assertTrue(message.contains("Not valid XML"), "Message should contain XML error");
    }

    @Test
    void testSuccessfulParsingDoesNotThrowException() throws Exception {
        Path pomFile = tempDir.resolve("pom.yaml");
        Files.writeString(pomFile, "valid: yaml");

        Model mockModel = mock(Model.class);
        when(mockModel.withPomFile(any())).thenReturn(mockModel);

        ModelParser yamlParser = mock(ModelParser.class);
        when(yamlParser.locateAndParse(any(), any())).thenReturn(Optional.of(mockModel));

        ModelXmlFactory xmlFactory = mock(ModelXmlFactory.class);

        DefaultModelProcessor processor = new DefaultModelProcessor(xmlFactory, Map.of("yaml", yamlParser));

        XmlReaderRequest request =
                XmlReaderRequest.builder().path(pomFile).strict(true).build();

        Model result = processor.read(request);
        assertNotNull(result);
        verify(xmlFactory, never()).read(any(XmlReaderRequest.class));
    }
}