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.junit.Before;
26 import org.junit.Rule;
27 import org.junit.Test;
28 import org.junit.runner.RunWith;
29 import org.mockito.Mock;
30 import org.mockito.junit.MockitoJUnitRunner;
31 import org.slf4j.event.Level;
32 import org.sonar.api.batch.sensor.internal.SensorContextTester;
33 import org.sonar.api.config.internal.MapSettings;
34 import org.sonar.api.testfixtures.log.LogAndArguments;
35 import org.sonar.api.testfixtures.log.LogTester;
36 import org.sonar.api.utils.log.LoggerLevel;
37 import org.sonar.core.sarif.Sarif210;
38 import org.sonar.core.sarif.SarifSerializer;
40 import static org.assertj.core.api.Assertions.assertThat;
41 import static org.mockito.Mockito.doThrow;
42 import static org.mockito.Mockito.mock;
43 import static org.mockito.Mockito.verify;
44 import static org.mockito.Mockito.when;
46 @RunWith(MockitoJUnitRunner.class)
47 public class SarifIssuesImportSensorTest {
49 private static final String FILE_1 = "path/to/sarif/file.sarif";
50 private static final String FILE_2 = "path/to/sarif/file2.sarif";
51 private static final String SARIF_REPORT_PATHS_PARAM = FILE_1 + "," + FILE_2;
54 private SarifSerializer sarifSerializer;
56 private Sarif210Importer sarifImporter;
58 private MapSettings sensorSettings;
61 public void before() {
62 sensorSettings = new MapSettings();
66 public final LogTester logTester = new LogTester();
68 private final SensorContextTester sensorContext = SensorContextTester.create(Path.of("."));
71 public void execute_whenSingleFileIsSpecified_shouldImportResults() {
72 sensorSettings.setProperty("sonar.sarifReportPaths", FILE_1);
74 ReportAndResults reportAndResults = mockSuccessfulReportAndResults(FILE_1);
76 SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
77 sensor.execute(sensorContext);
79 verify(sarifImporter).importSarif(reportAndResults.getSarifReport());
81 assertThat(logTester.logs(Level.INFO)).hasSize(1);
82 assertSummaryIsCorrectlyDisplayedForSuccessfulFile(FILE_1, reportAndResults.getSarifImportResults());
86 public void execute_whenMultipleFilesAreSpecified_shouldImportResults() {
87 sensorSettings.setProperty("sonar.sarifReportPaths", SARIF_REPORT_PATHS_PARAM);
88 ReportAndResults reportAndResults1 = mockSuccessfulReportAndResults(FILE_1);
89 ReportAndResults reportAndResults2 = mockSuccessfulReportAndResults(FILE_2);
91 SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
92 sensor.execute(sensorContext);
94 verify(sarifImporter).importSarif(reportAndResults1.getSarifReport());
95 verify(sarifImporter).importSarif(reportAndResults2.getSarifReport());
97 assertSummaryIsCorrectlyDisplayedForSuccessfulFile(FILE_1, reportAndResults1.getSarifImportResults());
98 assertSummaryIsCorrectlyDisplayedForSuccessfulFile(FILE_2, reportAndResults2.getSarifImportResults());
102 public void execute_whenFileContainsOnlySuccessfulRuns_shouldLogCorrectMessage() {
103 sensorSettings.setProperty("sonar.sarifReportPaths", FILE_1);
104 ReportAndResults reportAndResults = mockSuccessfulReportAndResults(FILE_1);
106 SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
107 sensor.execute(sensorContext);
109 assertSummaryIsCorrectlyDisplayedForSuccessfulFile(FILE_1, reportAndResults.getSarifImportResults());
113 public void execute_whenFileContainsOnlyFailedRuns_shouldLogCorrectMessage() {
115 sensorSettings.setProperty("sonar.sarifReportPaths", FILE_1);
116 ReportAndResults reportAndResults = mockFailedReportAndResults(FILE_1);
118 SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
119 sensor.execute(sensorContext);
121 assertSummaryIsCorrectlyDisplayedForFailedFile(FILE_1, reportAndResults.getSarifImportResults());
125 public void execute_whenFileContainsFailedAndSuccessfulRuns_shouldLogCorrectMessage() {
127 sensorSettings.setProperty("sonar.sarifReportPaths", FILE_1);
129 ReportAndResults reportAndResults = mockMixedReportAndResults(FILE_1);
131 SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
132 sensor.execute(sensorContext);
134 verify(sarifImporter).importSarif(reportAndResults.getSarifReport());
136 assertSummaryIsCorrectlyDisplayedForMixedFile(FILE_1, reportAndResults.getSarifImportResults());
140 public void execute_whenImportFails_shouldSkipReport() {
141 sensorSettings.setProperty("sonar.sarifReportPaths", SARIF_REPORT_PATHS_PARAM);
143 ReportAndResults reportAndResults1 = mockFailedReportAndResults(FILE_1);
144 ReportAndResults reportAndResults2 = mockSuccessfulReportAndResults(FILE_2);
146 doThrow(new NullPointerException("import failed")).when(sarifImporter).importSarif(reportAndResults1.getSarifReport());
148 SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
149 sensor.execute(sensorContext);
151 verify(sarifImporter).importSarif(reportAndResults2.getSarifReport());
152 assertThat(logTester.logs(Level.WARN)).contains("Failed to process SARIF report from file 'path/to/sarif/file.sarif', error: 'import failed'");
153 assertSummaryIsCorrectlyDisplayedForSuccessfulFile(FILE_2, reportAndResults2.getSarifImportResults());
157 public void execute_whenDeserializationFails_shouldSkipReport() {
158 sensorSettings.setProperty("sonar.sarifReportPaths", SARIF_REPORT_PATHS_PARAM);
160 failDeserializingReport(FILE_1);
161 ReportAndResults reportAndResults2 = mockSuccessfulReportAndResults(FILE_2);
163 SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
164 sensor.execute(sensorContext);
166 verify(sarifImporter).importSarif(reportAndResults2.getSarifReport());
167 assertThat(logTester.logs(Level.WARN)).contains("Failed to process SARIF report from file 'path/to/sarif/file.sarif', error: 'deserialization failed'");
168 assertSummaryIsCorrectlyDisplayedForSuccessfulFile(FILE_2, reportAndResults2.getSarifImportResults());
171 private void failDeserializingReport(String path) {
172 Path reportFilePath = sensorContext.fileSystem().resolvePath(path).toPath();
173 when(sarifSerializer.deserialize(reportFilePath)).thenThrow(new NullPointerException("deserialization failed"));
176 private ReportAndResults mockSuccessfulReportAndResults(String path) {
177 Sarif210 report = mockSarifReport(path);
179 SarifImportResults sarifImportResults = mock(SarifImportResults.class);
180 when(sarifImportResults.getSuccessFullyImportedIssues()).thenReturn(10);
181 when(sarifImportResults.getSuccessFullyImportedRuns()).thenReturn(3);
182 when(sarifImportResults.getFailedRuns()).thenReturn(0);
184 when(sarifImporter.importSarif(report)).thenReturn(sarifImportResults);
185 return new ReportAndResults(report, sarifImportResults);
188 private Sarif210 mockSarifReport(String path) {
189 Sarif210 report = mock(Sarif210.class);
190 Path reportFilePath = sensorContext.fileSystem().resolvePath(path).toPath();
191 when(sarifSerializer.deserialize(reportFilePath)).thenReturn(report);
195 private ReportAndResults mockFailedReportAndResults(String path) {
196 Sarif210 report = mockSarifReport(path);
198 SarifImportResults sarifImportResults = mock(SarifImportResults.class);
199 when(sarifImportResults.getSuccessFullyImportedRuns()).thenReturn(0);
200 when(sarifImportResults.getFailedRuns()).thenReturn(1);
202 when(sarifImporter.importSarif(report)).thenReturn(sarifImportResults);
203 return new ReportAndResults(report, sarifImportResults);
206 private ReportAndResults mockMixedReportAndResults(String path) {
207 Sarif210 report = mockSarifReport(path);
209 SarifImportResults sarifImportResults = mock(SarifImportResults.class);
210 when(sarifImportResults.getSuccessFullyImportedIssues()).thenReturn(10);
211 when(sarifImportResults.getSuccessFullyImportedRuns()).thenReturn(3);
212 when(sarifImportResults.getFailedRuns()).thenReturn(1);
214 when(sarifImporter.importSarif(report)).thenReturn(sarifImportResults);
215 return new ReportAndResults(report, sarifImportResults);
218 private void assertSummaryIsCorrectlyDisplayedForSuccessfulFile(String filePath, SarifImportResults sarifImportResults) {
219 verifyLogContainsLine(LoggerLevel.INFO, filePath, "File {}: {} run(s) successfully imported ({} vulnerabilities in total).",
220 filePath, sarifImportResults.getSuccessFullyImportedRuns(), sarifImportResults.getSuccessFullyImportedIssues());
223 private void assertSummaryIsCorrectlyDisplayedForFailedFile(String filePath, SarifImportResults sarifImportResults) {
224 verifyLogContainsLine(LoggerLevel.WARN, filePath, "File {}: {} run(s) could not be imported (see warning above).",
225 filePath, sarifImportResults.getFailedRuns());
228 private void assertSummaryIsCorrectlyDisplayedForMixedFile(String filePath, SarifImportResults sarifImportResults) {
229 verifyLogContainsLine(LoggerLevel.WARN, filePath,
230 "File {}: {} run(s) could not be imported (see warning above) and {} run(s) successfully imported ({} vulnerabilities in total).",
231 filePath, sarifImportResults.getFailedRuns(), sarifImportResults.getSuccessFullyImportedRuns(), sarifImportResults.getSuccessFullyImportedIssues());
234 private void verifyLogContainsLine(LoggerLevel level, String filePath, String rawMsg, Object... arguments) {
235 LogAndArguments logAndArguments = findLogEntry(level, filePath);
236 assertThat(logAndArguments.getRawMsg())
238 assertThat(logAndArguments.getArgs()).isPresent()
239 .contains(arguments);
242 private LogAndArguments findLogEntry(LoggerLevel level, String filePath) {
243 Optional<LogAndArguments> optLogAndArguments = logTester.getLogs(level).stream()
244 .filter(log -> log.getFormattedMsg().contains(filePath))
245 .collect(MoreCollectors.toOptional());
246 assertThat(optLogAndArguments).as("Log entry missing for file %s", filePath).isPresent();
247 return optLogAndArguments.get();
250 private static class ReportAndResults {
251 private final Sarif210 sarifReport;
252 private final SarifImportResults sarifImportResults;
254 private ReportAndResults(Sarif210 sarifReport, SarifImportResults sarifImportResults) {
255 this.sarifReport = sarifReport;
256 this.sarifImportResults = sarifImportResults;
259 private Sarif210 getSarifReport() {
263 private SarifImportResults getSarifImportResults() {
264 return sarifImportResults;