3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.scanner.externalissue.sarif;
22 import com.google.common.collect.MoreCollectors;
23 import java.nio.file.Path;
24 import java.util.Optional;
25 import org.apache.commons.lang.math.RandomUtils;
26 import org.junit.Before;
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.junit.runner.RunWith;
30 import org.mockito.Mock;
31 import org.mockito.junit.MockitoJUnitRunner;
32 import org.slf4j.event.Level;
33 import org.sonar.api.batch.sensor.internal.SensorContextTester;
34 import org.sonar.api.config.internal.MapSettings;
35 import org.sonar.api.testfixtures.log.LogAndArguments;
36 import org.sonar.api.testfixtures.log.LogTester;
37 import org.sonar.api.utils.log.LoggerLevel;
38 import org.sonar.core.sarif.Sarif210;
39 import org.sonar.core.sarif.SarifSerializer;
41 import static org.assertj.core.api.Assertions.assertThat;
42 import static org.mockito.Mockito.doThrow;
43 import static org.mockito.Mockito.mock;
44 import static org.mockito.Mockito.verify;
45 import static org.mockito.Mockito.when;
47 @RunWith(MockitoJUnitRunner.class)
48 public class SarifIssuesImportSensorTest {
50 private static final String FILE_1 = "path/to/sarif/file.sarif";
51 private static final String FILE_2 = "path/to/sarif/file2.sarif";
52 private static final String SARIF_REPORT_PATHS_PARAM = FILE_1 + "," + FILE_2;
55 private SarifSerializer sarifSerializer;
57 private Sarif210Importer sarifImporter;
59 private MapSettings sensorSettings;
62 public void before() {
63 sensorSettings = new MapSettings();
67 public final LogTester logTester = new LogTester();
69 private final SensorContextTester sensorContext = SensorContextTester.create(Path.of("."));
72 public void execute_single_files() {
73 sensorSettings.setProperty("sonar.sarifReportPaths", FILE_1);
75 ReportAndResults reportAndResults = mockReportAndResults(FILE_1);
77 SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
78 sensor.execute(sensorContext);
80 verify(sarifImporter).importSarif(reportAndResults.getSarifReport());
82 assertThat(logTester.logs(Level.INFO)).hasSize(1);
83 assertSummaryIsCorrectlyDisplayed(FILE_1, reportAndResults.getSarifImportResults());
87 public void execute_multiple_files() {
89 sensorSettings.setProperty("sonar.sarifReportPaths", SARIF_REPORT_PATHS_PARAM);
91 ReportAndResults reportAndResults1 = mockReportAndResults(FILE_1);
92 ReportAndResults reportAndResults2 = mockReportAndResults(FILE_2);
94 SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
95 sensor.execute(sensorContext);
97 verify(sarifImporter).importSarif(reportAndResults1.getSarifReport());
98 verify(sarifImporter).importSarif(reportAndResults2.getSarifReport());
100 assertSummaryIsCorrectlyDisplayed(FILE_1, reportAndResults1.getSarifImportResults());
101 assertSummaryIsCorrectlyDisplayed(FILE_2, reportAndResults2.getSarifImportResults());
105 public void skip_report_when_import_fails() {
106 sensorSettings.setProperty("sonar.sarifReportPaths", SARIF_REPORT_PATHS_PARAM);
108 ReportAndResults reportAndResults1 = mockReportAndResults(FILE_1);
109 ReportAndResults reportAndResults2 = mockReportAndResults(FILE_2);
111 doThrow(new NullPointerException("import failed")).when(sarifImporter).importSarif(reportAndResults1.getSarifReport());
113 SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
114 sensor.execute(sensorContext);
116 verify(sarifImporter).importSarif(reportAndResults2.getSarifReport());
117 assertThat(logTester.logs(Level.WARN)).contains("Failed to process SARIF report from file 'path/to/sarif/file.sarif', error: 'import failed'");
118 assertSummaryIsCorrectlyDisplayed(FILE_2, reportAndResults2.getSarifImportResults());
122 public void skip_report_when_deserialization_fails() {
123 sensorSettings.setProperty("sonar.sarifReportPaths", SARIF_REPORT_PATHS_PARAM);
125 failDeserializingReport(FILE_1);
126 ReportAndResults reportAndResults2 = mockReportAndResults(FILE_2);
128 SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
129 sensor.execute(sensorContext);
131 verify(sarifImporter).importSarif(reportAndResults2.getSarifReport());
132 assertThat(logTester.logs(Level.WARN)).contains("Failed to process SARIF report from file 'path/to/sarif/file.sarif', error: 'deserialization failed'");
133 assertSummaryIsCorrectlyDisplayed(FILE_2, reportAndResults2.getSarifImportResults());
136 private void failDeserializingReport(String path) {
137 Path reportFilePath = sensorContext.fileSystem().resolvePath(path).toPath();
138 when(sarifSerializer.deserialize(reportFilePath)).thenThrow(new NullPointerException("deserialization failed"));
141 private ReportAndResults mockReportAndResults(String path) {
142 Sarif210 report = mock(Sarif210.class);
143 Path reportFilePath = sensorContext.fileSystem().resolvePath(path).toPath();
144 when(sarifSerializer.deserialize(reportFilePath)).thenReturn(report);
146 SarifImportResults sarifImportResults = mock(SarifImportResults.class);
147 when(sarifImportResults.getSuccessFullyImportedIssues()).thenReturn(RandomUtils.nextInt());
148 when(sarifImportResults.getSuccessFullyImportedRuns()).thenReturn(RandomUtils.nextInt());
149 when(sarifImportResults.getFailedRuns()).thenReturn(RandomUtils.nextInt());
151 when(sarifImporter.importSarif(report)).thenReturn(sarifImportResults);
152 return new ReportAndResults(report, sarifImportResults);
155 private void assertSummaryIsCorrectlyDisplayed(String filePath, SarifImportResults sarifImportResults) {
156 LogAndArguments logAndArguments = findLogEntry(filePath);
157 assertThat(logAndArguments.getRawMsg()).isEqualTo("File {}: successfully imported {} vulnerabilities spread in {} runs. {} failed run(s).");
158 assertThat(logAndArguments.getArgs()).isPresent()
159 .contains(new Object[] {filePath, sarifImportResults.getSuccessFullyImportedIssues(), sarifImportResults.getSuccessFullyImportedRuns(), sarifImportResults.getFailedRuns()});
162 private LogAndArguments findLogEntry(String filePath) {
163 Optional<LogAndArguments> optLogAndArguments = logTester.getLogs(LoggerLevel.INFO).stream()
164 .filter(log -> log.getFormattedMsg().contains(filePath))
165 .collect(MoreCollectors.toOptional());
166 assertThat(optLogAndArguments).as("Log entry missing for file %s", filePath).isPresent();
167 return optLogAndArguments.get();
170 private static class ReportAndResults {
171 private final Sarif210 sarifReport;
172 private final SarifImportResults sarifImportResults;
174 private ReportAndResults(Sarif210 sarifReport, SarifImportResults sarifImportResults) {
175 this.sarifReport = sarifReport;
176 this.sarifImportResults = sarifImportResults;
179 private Sarif210 getSarifReport() {
183 private SarifImportResults getSarifImportResults() {
184 return sarifImportResults;