aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-scanner-engine/src/test
diff options
context:
space:
mode:
authorAurelien Poscia <aurelien.poscia@sonarsource.com>2022-11-09 14:35:07 +0100
committersonartech <sonartech@sonarsource.com>2022-11-15 20:02:59 +0000
commit53f94935f393750ba08a7e1fa00742acadbadafb (patch)
tree647e21c0a08c735234f87979c73f961493926037 /sonar-scanner-engine/src/test
parent59df4a4ad498fa1ce6df396c0b7a6afb70b7ec83 (diff)
downloadsonarqube-53f94935f393750ba08a7e1fa00742acadbadafb.tar.gz
sonarqube-53f94935f393750ba08a7e1fa00742acadbadafb.zip
SONAR-17564 Import vulnerabilities from a SARIF report
Diffstat (limited to 'sonar-scanner-engine/src/test')
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/DefaultSarif210ImporterTest.java109
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/LocationMapperTest.java171
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/RegionMapperTest.java143
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/ResultMapperTest.java165
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/RunMapperTest.java129
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/SarifIssuesImportSensorTest.java135
6 files changed, 852 insertions, 0 deletions
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/DefaultSarif210ImporterTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/DefaultSarif210ImporterTest.java
new file mode 100644
index 00000000000..8493eb091d8
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/DefaultSarif210ImporterTest.java
@@ -0,0 +1,109 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.externalissue.sarif;
+
+import java.util.List;
+import java.util.Set;
+import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonar.api.batch.sensor.issue.NewExternalIssue;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.core.sarif.Run;
+import org.sonar.core.sarif.Sarif210;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DefaultSarif210ImporterTest extends TestCase {
+
+ @Mock
+ private RunMapper runMapper;
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @InjectMocks
+ DefaultSarif210Importer sarif210Importer;
+
+ @Test
+ public void importSarif_shouldDelegateRunMapping_toRunMapper() {
+ Sarif210 sarif210 = mock(Sarif210.class);
+
+ Run run1 = mock(Run.class);
+ Run run2 = mock(Run.class);
+ when(sarif210.getRuns()).thenReturn(Set.of(run1, run2));
+
+ NewExternalIssue issue1run1 = mock(NewExternalIssue.class);
+ NewExternalIssue issue2run1 = mock(NewExternalIssue.class);
+ NewExternalIssue issue1run2 = mock(NewExternalIssue.class);
+ when(runMapper.mapRun(run1)).thenReturn(List.of(issue1run1, issue2run1));
+ when(runMapper.mapRun(run2)).thenReturn(List.of(issue1run2));
+
+ sarif210Importer.importSarif(sarif210);
+
+ verify(issue1run1).save();
+ verify(issue2run1).save();
+ verify(issue1run2).save();
+ }
+
+ @Test
+ public void importSarif_whenExceptionThrownByRunMapper_shouldLogAndContinueProcessing() {
+ Sarif210 sarif210 = mock(Sarif210.class);
+
+ Run run1 = mock(Run.class);
+ Run run2 = mock(Run.class);
+ when(sarif210.getRuns()).thenReturn(Set.of(run1, run2));
+
+ Exception testException = new RuntimeException("test");
+ when(runMapper.mapRun(run1)).thenThrow(testException);
+ NewExternalIssue issue1run2 = mock(NewExternalIssue.class);
+ when(runMapper.mapRun(run2)).thenReturn(List.of(issue1run2));
+
+ sarif210Importer.importSarif(sarif210);
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly("Failed to import a sarif run, error: " + testException.getMessage());
+ verify(issue1run2).save();
+ }
+
+ @Test
+ public void importSarif_whenGetRunsReturnNull_shouldFailWithProperMessage() {
+ Sarif210 sarif210 = mock(Sarif210.class);
+
+ when(sarif210.getRuns()).thenReturn(null);
+
+ assertThatNullPointerException()
+ .isThrownBy(() -> sarif210Importer.importSarif(sarif210))
+ .withMessage("The runs section of the Sarif report is null");
+
+ verifyNoInteractions(runMapper);
+ }
+
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/LocationMapperTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/LocationMapperTest.java
new file mode 100644
index 00000000000..be6f703643a
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/LocationMapperTest.java
@@ -0,0 +1,171 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.externalissue.sarif;
+
+import java.net.URI;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonar.api.batch.fs.FilePredicate;
+import org.sonar.api.batch.fs.FilePredicates;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.TextRange;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.issue.NewIssueLocation;
+import org.sonar.api.scanner.fs.InputProject;
+import org.sonar.core.sarif.Location;
+import org.sonar.core.sarif.Result;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class LocationMapperTest {
+
+ private static final String TEST_MESSAGE = "test message";
+ private static final String URI_TEST = "URI_TEST";
+ private static final String EXPECTED_MESSAGE_URI_MISSING = "The field location.physicalLocation.artifactLocation.uri is not set.";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private SensorContext sensorContext;
+
+ @Mock
+ private RegionMapper regionMapper;
+
+ @InjectMocks
+ private LocationMapper locationMapper;
+
+ @Mock
+ private NewIssueLocation newIssueLocation;
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Result result;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Location location;
+
+ @Mock
+ private InputFile inputFile;
+
+ @Before
+ public void setup() {
+ when(newIssueLocation.message(any())).thenReturn(newIssueLocation);
+ when(newIssueLocation.on(any())).thenReturn(newIssueLocation);
+ when(newIssueLocation.at(any())).thenReturn(newIssueLocation);
+ when(sensorContext.project()).thenReturn(mock(InputProject.class));
+
+ when(result.getMessage().getText()).thenReturn(TEST_MESSAGE);
+
+ when(location.getPhysicalLocation().getArtifactLocation().getUri()).thenReturn(URI_TEST);
+
+ FilePredicate filePredicate = mock(FilePredicate.class);
+ FilePredicates predicates = sensorContext.fileSystem().predicates();
+ when(predicates.or(
+ predicates.hasURI(URI.create(URI_TEST)), predicates.hasPath(URI_TEST)
+ )).thenReturn(filePredicate);
+
+ when(sensorContext.fileSystem().inputFile(filePredicate)).thenReturn(inputFile);
+
+ }
+
+ @Test
+ public void fillIssueInProjectLocation_shouldFillRelevantFields() {
+ NewIssueLocation actualIssueLocation = locationMapper.fillIssueInProjectLocation(result, newIssueLocation);
+
+ assertThat(actualIssueLocation).isEqualTo(newIssueLocation);
+ verify(newIssueLocation).message(TEST_MESSAGE);
+ verify(newIssueLocation).on(sensorContext.project());
+ verifyNoMoreInteractions(newIssueLocation);
+ }
+
+ @Test
+ public void fillIssueInFileLocation_whenFileNotFound_returnsNull() {
+ when(sensorContext.fileSystem().inputFile(any())).thenReturn(null);
+
+ NewIssueLocation actualIssueLocation = locationMapper.fillIssueInFileLocation(result, newIssueLocation, location);
+
+ assertThat(actualIssueLocation).isNull();
+ }
+
+ @Test
+ public void fillIssueInFileLocation_whenMapRegionReturnsNull_onlyFillsSimpleFields() {
+ when(regionMapper.mapRegion(location.getPhysicalLocation().getRegion(), inputFile))
+ .thenReturn(Optional.empty());
+
+ NewIssueLocation actualIssueLocation = locationMapper.fillIssueInFileLocation(result, newIssueLocation, location);
+
+ assertThat(actualIssueLocation).isSameAs(newIssueLocation);
+ verify(newIssueLocation).message(TEST_MESSAGE);
+ verify(newIssueLocation).on(inputFile);
+ verifyNoMoreInteractions(newIssueLocation);
+ }
+
+ @Test
+ public void fillIssueInFileLocation_whenMapRegionReturnsRegion_callsAt() {
+ TextRange textRange = mock(TextRange.class);
+ when(regionMapper.mapRegion(location.getPhysicalLocation().getRegion(), inputFile))
+ .thenReturn(Optional.of(textRange));
+
+ NewIssueLocation actualIssueLocation = locationMapper.fillIssueInFileLocation(result, newIssueLocation, location);
+
+ assertThat(actualIssueLocation).isSameAs(newIssueLocation);
+ verify(newIssueLocation).message(TEST_MESSAGE);
+ verify(newIssueLocation).on(inputFile);
+ verify(newIssueLocation).at(textRange);
+ verifyNoMoreInteractions(newIssueLocation);
+ }
+
+ @Test
+ public void fillIssueInFileLocation_ifNullUri_throws() {
+ when(location.getPhysicalLocation().getArtifactLocation().getUri()).thenReturn(null);
+
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> locationMapper.fillIssueInFileLocation(result, newIssueLocation, location))
+ .withMessage(EXPECTED_MESSAGE_URI_MISSING);
+ }
+
+ @Test
+ public void fillIssueInFileLocation_ifNullArtifactLocation_throws() {
+ when(location.getPhysicalLocation().getArtifactLocation()).thenReturn(null);
+
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> locationMapper.fillIssueInFileLocation(result, newIssueLocation, location))
+ .withMessage(EXPECTED_MESSAGE_URI_MISSING);
+ }
+
+ @Test
+ public void fillIssueInFileLocation_ifNullPhysicalLocation_throws() {
+ when(location.getPhysicalLocation().getArtifactLocation()).thenReturn(null);
+
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> locationMapper.fillIssueInFileLocation(result, newIssueLocation, location))
+ .withMessage(EXPECTED_MESSAGE_URI_MISSING);
+ }
+
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/RegionMapperTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/RegionMapperTest.java
new file mode 100644
index 00000000000..27a973a15cb
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/RegionMapperTest.java
@@ -0,0 +1,143 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.externalissue.sarif;
+
+import java.nio.file.Paths;
+import java.util.Optional;
+import java.util.stream.IntStream;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonar.api.batch.fs.TextRange;
+import org.sonar.api.batch.fs.internal.DefaultIndexedFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.fs.internal.Metadata;
+import org.sonar.core.sarif.Region;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RegionMapperTest {
+ private static final int LINE_END_OFFSET = 10;
+ private static final DefaultInputFile INPUT_FILE = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "relative/path", null),
+ f -> f.setMetadata(generateMetadata()));
+
+
+ private static Metadata generateMetadata() {
+ Metadata metadata = mock(Metadata.class);
+ when(metadata.lines()).thenReturn(100);
+ when(metadata.originalLineStartOffsets()).thenReturn(IntStream.range(0, 100).toArray());
+ when(metadata.originalLineEndOffsets()).thenReturn(IntStream.range(0, 100).map(i -> i + LINE_END_OFFSET).toArray());
+ return metadata;
+ }
+
+ @Mock
+ private Region region;
+
+ @InjectMocks
+ private RegionMapper regionMapper;
+
+ @Test
+ public void mapRegion_whenNullRegion_returnsEmpty() {
+ assertThat(regionMapper.mapRegion(null, INPUT_FILE)).isEmpty();
+ }
+
+ @Test
+ public void mapRegion_whenStartLineIsNull_shouldThrow() {
+ when(region.getStartLine()).thenReturn(null);
+
+ assertThatNullPointerException()
+ .isThrownBy(() -> regionMapper.mapRegion(region, INPUT_FILE))
+ .withMessage("No start line defined for the region.");
+ }
+
+ @Test
+ public void mapRegion_whenAllCoordinatesDefined() {
+ Region fullRegion = mockRegion(1, 2, 3, 4);
+
+ Optional<TextRange> optTextRange = regionMapper.mapRegion(fullRegion, INPUT_FILE);
+
+ assertThat(optTextRange).isPresent();
+ TextRange textRange = optTextRange.get();
+ assertThat(textRange.start().line()).isEqualTo(fullRegion.getStartLine());
+ assertThat(textRange.start().lineOffset()).isEqualTo(fullRegion.getStartColumn());
+ assertThat(textRange.end().line()).isEqualTo(fullRegion.getEndLine());
+ assertThat(textRange.end().lineOffset()).isEqualTo(fullRegion.getEndColumn());
+ }
+
+ @Test
+ public void mapRegion_whenStartEndLinesDefined() {
+ Region fullRegion = mockRegion(null, null, 3, 8);
+
+ Optional<TextRange> optTextRange = regionMapper.mapRegion(fullRegion, INPUT_FILE);
+
+ assertThat(optTextRange).isPresent();
+ TextRange textRange = optTextRange.get();
+ assertThat(textRange.start().line()).isEqualTo(fullRegion.getStartLine());
+ assertThat(textRange.start().lineOffset()).isEqualTo(1);
+ assertThat(textRange.end().line()).isEqualTo(fullRegion.getEndLine());
+ assertThat(textRange.end().lineOffset()).isEqualTo(LINE_END_OFFSET);
+ }
+
+ @Test
+ public void mapRegion_whenStartEndLinesDefinedAndStartColumn() {
+ Region fullRegion = mockRegion(8, null, 3, 8);
+
+ Optional<TextRange> optTextRange = regionMapper.mapRegion(fullRegion, INPUT_FILE);
+
+ assertThat(optTextRange).isPresent();
+ TextRange textRange = optTextRange.get();
+ assertThat(textRange.start().line()).isEqualTo(fullRegion.getStartLine());
+ assertThat(textRange.start().lineOffset()).isEqualTo(fullRegion.getStartColumn());
+ assertThat(textRange.end().line()).isEqualTo(fullRegion.getEndLine());
+ assertThat(textRange.end().lineOffset()).isEqualTo(LINE_END_OFFSET);
+ }
+
+ @Test
+ public void mapRegion_whenStartEndLinesDefinedAndEndColumn() {
+ Region fullRegion = mockRegion(null, 8, 3, 8);
+
+ Optional<TextRange> optTextRange = regionMapper.mapRegion(fullRegion, INPUT_FILE);
+
+ assertThat(optTextRange).isPresent();
+ TextRange textRange = optTextRange.get();
+ assertThat(textRange.start().line()).isEqualTo(fullRegion.getStartLine());
+ assertThat(textRange.start().lineOffset()).isEqualTo(1);
+ assertThat(textRange.end().line()).isEqualTo(fullRegion.getEndLine());
+ assertThat(textRange.end().lineOffset()).isEqualTo(fullRegion.getEndLine());
+ }
+
+ private static Region mockRegion(@Nullable Integer startColumn, @Nullable Integer endColumn, @Nullable Integer startLine, @Nullable Integer endLine) {
+ Region region = mock(Region.class);
+ when(region.getStartColumn()).thenReturn(startColumn);
+ when(region.getEndColumn()).thenReturn(endColumn);
+ when(region.getStartLine()).thenReturn(startLine);
+ when(region.getEndLine()).thenReturn(endLine);
+ return region;
+ }
+
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/ResultMapperTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/ResultMapperTest.java
new file mode 100644
index 00000000000..3e1d9a4172f
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/ResultMapperTest.java
@@ -0,0 +1,165 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.externalissue.sarif;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.api.batch.rule.Severity;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.issue.NewExternalIssue;
+import org.sonar.api.batch.sensor.issue.NewIssueLocation;
+import org.sonar.api.rules.RuleType;
+import org.sonar.core.sarif.Location;
+import org.sonar.core.sarif.Result;
+
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+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.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(DataProviderRunner.class)
+public class ResultMapperTest {
+
+ private static final String RULE_ID = "test_rules_id";
+ private static final String DRIVER_NAME = "driverName";
+
+ @Mock
+ private LocationMapper locationMapper;
+
+ @Mock
+ private SensorContext sensorContext;
+
+ @Mock
+ private NewExternalIssue newExternalIssue;
+
+ @Mock
+ private NewIssueLocation newExternalIssueLocation;
+
+ @Mock
+ private Result result;
+
+ @InjectMocks
+ ResultMapper resultMapper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.openMocks(this);
+ when(result.getRuleId()).thenReturn(RULE_ID);
+ when(sensorContext.newExternalIssue()).thenReturn(newExternalIssue);
+ when(locationMapper.fillIssueInFileLocation(any(), any(), any())).thenReturn(newExternalIssueLocation);
+ when(locationMapper.fillIssueInProjectLocation(any(), any())).thenReturn(newExternalIssueLocation);
+ when(newExternalIssue.newLocation()).thenReturn(newExternalIssueLocation);
+ }
+
+ @Test
+ public void mapResult_mapsSimpleFieldsCorrectly() {
+ NewExternalIssue newExternalIssue = resultMapper.mapResult(DRIVER_NAME, result);
+
+ verify(newExternalIssue).type(RuleType.VULNERABILITY);
+ verify(newExternalIssue).engineId(DRIVER_NAME);
+ verify(newExternalIssue).severity(ResultMapper.DEFAULT_SEVERITY);
+ verify(newExternalIssue).ruleId(RULE_ID);
+ }
+
+ @Test
+ public void mapResult_ifRuleIdMissing_fails() {
+ when(result.getRuleId()).thenReturn(null);
+ assertThatNullPointerException()
+ .isThrownBy(() -> resultMapper.mapResult(DRIVER_NAME, result))
+ .withMessage("No ruleId found for issue thrown by driver driverName");
+ }
+
+ @Test
+ public void mapResult_whenLocationExists_createsFileLocation() {
+ Location location = mock(Location.class);
+ when(result.getLocations()).thenReturn(Set.of(location));
+
+ NewExternalIssue newExternalIssue = resultMapper.mapResult(DRIVER_NAME, result);
+
+ verify(locationMapper).fillIssueInFileLocation(result, newExternalIssueLocation, location);
+ verifyNoMoreInteractions(locationMapper);
+ verify(newExternalIssue).at(newExternalIssueLocation);
+ verify(newExternalIssue, never()).addLocation(any());
+ verify(newExternalIssue, never()).addFlow(any());
+ }
+
+ @Test
+ public void mapResult_whenLocationExistsButLocationMapperReturnsNull_createsProjectLocation() {
+ Location location = mock(Location.class);
+ when(result.getLocations()).thenReturn(Set.of(location));
+ when(locationMapper.fillIssueInFileLocation(any(), any(), any())).thenReturn(null);
+
+ NewExternalIssue newExternalIssue = resultMapper.mapResult(DRIVER_NAME, result);
+
+ verify(locationMapper).fillIssueInProjectLocation(result, newExternalIssueLocation);
+ verify(newExternalIssue).at(newExternalIssueLocation);
+ verify(newExternalIssue, never()).addLocation(any());
+ verify(newExternalIssue, never()).addFlow(any());
+ }
+
+ @Test
+ public void mapResult_whenLocationNotFound_createsProjectLocation() {
+ NewExternalIssue newExternalIssue = resultMapper.mapResult(DRIVER_NAME, result);
+
+ verify(locationMapper).fillIssueInProjectLocation(result, newExternalIssueLocation);
+ verifyNoMoreInteractions(locationMapper);
+ verify(newExternalIssue).at(newExternalIssueLocation);
+ verify(newExternalIssue, never()).addLocation(any());
+ verify(newExternalIssue, never()).addFlow(any());
+ }
+
+ @Test
+ public void mapResult_mapsErrorLevel_toCriticalSeverity() {
+ when(result.getLevel()).thenReturn("error");
+ NewExternalIssue newExternalIssue = resultMapper.mapResult(DRIVER_NAME, result);
+ verify(newExternalIssue).severity(Severity.CRITICAL);
+ }
+
+ @DataProvider
+ public static Object[][] level_severity_mapping() {
+ return new Object[][] {
+ {"error", Severity.CRITICAL},
+ {"warning", Severity.MAJOR},
+ {"note", Severity.MINOR},
+ {"none", Severity.INFO},
+ {"anything else", ResultMapper.DEFAULT_SEVERITY},
+ };
+ }
+
+ @Test
+ @UseDataProvider("level_severity_mapping")
+ public void mapResult_mapsCorrectlyLevelToSeverity(String level, Severity severity) {
+ when(result.getLevel()).thenReturn(level);
+ NewExternalIssue newExternalIssue = resultMapper.mapResult(DRIVER_NAME, result);
+ verify(newExternalIssue).severity(severity);
+ }
+
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/RunMapperTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/RunMapperTest.java
new file mode 100644
index 00000000000..c44b765d312
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/RunMapperTest.java
@@ -0,0 +1,129 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.externalissue.sarif;
+
+import java.util.List;
+import java.util.Set;
+import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonar.api.batch.sensor.issue.NewExternalIssue;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.core.sarif.Result;
+import org.sonar.core.sarif.Run;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RunMapperTest extends TestCase {
+
+ private static final String TEST_DRIVER = "Test driver";
+
+ @Mock
+ private ResultMapper resultMapper;
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Run run;
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @InjectMocks
+ private RunMapper runMapper;
+
+ @Test
+ public void mapRun_delegatesToMapResult() {
+ when(run.getTool().getDriver().getName()).thenReturn(TEST_DRIVER);
+ Result result1 = mock(Result.class);
+ Result result2 = mock(Result.class);
+ when(run.getResults()).thenReturn(Set.of(result1, result2));
+
+ NewExternalIssue externalIssue1 = mockMappedResult(result1);
+ NewExternalIssue externalIssue2 = mockMappedResult(result2);
+
+ List<NewExternalIssue> newExternalIssues = runMapper.mapRun(run);
+
+ assertThat(newExternalIssues)
+ .containsOnly(externalIssue1, externalIssue2);
+ }
+
+ @Test
+ public void mapRun_ifExceptionThrownByResultMapper_logsThemAndContinueProcessing() {
+ when(run.getTool().getDriver().getName()).thenReturn(TEST_DRIVER);
+
+ Result result1 = mock(Result.class);
+ Result result2 = mock(Result.class);
+ when(run.getResults()).thenReturn(Set.of(result1, result2));
+
+ NewExternalIssue externalIssue2 = mockMappedResult(result2);
+
+ when(resultMapper.mapResult(TEST_DRIVER, result1)).thenThrow(new IllegalArgumentException("test"));
+
+ List<NewExternalIssue> newExternalIssues = runMapper.mapRun(run);
+
+ assertThat(newExternalIssues)
+ .containsExactly(externalIssue2);
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly("Failed to import an issue raised by tool Test driver, error: test");
+ }
+
+ @Test
+ public void mapRun_failsIfToolNotSet() {
+ when(run.getTool()).thenReturn(null);
+
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> runMapper.mapRun(run))
+ .withMessage("The run does not have a tool driver name defined.");
+ }
+
+ @Test
+ public void mapRun_failsIfDriverNotSet() {
+ when(run.getTool().getDriver()).thenReturn(null);
+
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> runMapper.mapRun(run))
+ .withMessage("The run does not have a tool driver name defined.");
+ }
+
+ @Test
+ public void mapRun_failsIfDriverNameIsNotSet() {
+ when(run.getTool().getDriver().getName()).thenReturn(null);
+
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> runMapper.mapRun(run))
+ .withMessage("The run does not have a tool driver name defined.");
+ }
+
+ private NewExternalIssue mockMappedResult(Result result) {
+ NewExternalIssue externalIssue = mock(NewExternalIssue.class);
+ when(resultMapper.mapResult(TEST_DRIVER, result)).thenReturn(externalIssue);
+ return externalIssue;
+ }
+
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/SarifIssuesImportSensorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/SarifIssuesImportSensorTest.java
new file mode 100644
index 00000000000..8d6d10db945
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/externalissue/sarif/SarifIssuesImportSensorTest.java
@@ -0,0 +1,135 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.externalissue.sarif;
+
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonar.api.batch.sensor.internal.SensorContextTester;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.core.sarif.Sarif210;
+import org.sonar.core.sarif.SarifSerializer;
+
+import static org.mockito.Mockito.doThrow;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SarifIssuesImportSensorTest {
+
+ private static final String FILE_1 = "path/to/sarif/file.sarif";
+ private static final String FILE_2 = "path/to/sarif/file2.sarif";
+ private static final String SARIF_REPORT_PATHS_PARAM = FILE_1 + "," + FILE_2;
+
+ @Mock
+ private SarifSerializer sarifSerializer;
+ @Mock
+ private Sarif210Importer sarifImporter;
+
+ private MapSettings sensorSettings;
+
+ @Before
+ public void before() {
+ sensorSettings = new MapSettings();
+ }
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ private SensorContextTester sensorContext = SensorContextTester.create(Path.of("."));
+
+ @Test
+ public void execute_single_files() {
+ sensorSettings.setProperty("sonar.sarifReportPaths", FILE_1);
+
+ Sarif210 sarifReport = mockReport(FILE_1);
+
+ SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
+ sensor.execute(sensorContext);
+
+ verify(sarifImporter).importSarif(sarifReport);
+ }
+
+ @Test
+ public void execute_multiple_files() {
+
+ sensorSettings.setProperty("sonar.sarifReportPaths", SARIF_REPORT_PATHS_PARAM);
+
+ Sarif210 sarifReport1 = mockReport(FILE_1);
+ Sarif210 sarifReport2 = mockReport(FILE_2);
+
+ SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
+ sensor.execute(sensorContext);
+
+ verify(sarifImporter).importSarif(sarifReport1);
+ verify(sarifImporter).importSarif(sarifReport2);
+ }
+
+ @Test
+ public void skip_report_when_import_fails() {
+ sensorSettings.setProperty("sonar.sarifReportPaths", SARIF_REPORT_PATHS_PARAM);
+
+ Sarif210 sarifReport1 = mockReport(FILE_1);
+ Sarif210 sarifReport2 = mockReport(FILE_2);
+
+ doThrow(new NullPointerException("import failed")).when(sarifImporter).importSarif(sarifReport1);
+
+ SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
+ sensor.execute(sensorContext);
+
+ verify(sarifImporter).importSarif(sarifReport2);
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains("Failed to process SARIF report from file 'path/to/sarif/file.sarif', error 'import failed'");
+ }
+
+ @Test
+ public void skip_report_when_deserialization_fails() {
+ sensorSettings.setProperty("sonar.sarifReportPaths", SARIF_REPORT_PATHS_PARAM);
+
+ failDeserializingReport(FILE_1);
+ Sarif210 sarifReport2 = mockReport(FILE_2);
+
+ SarifIssuesImportSensor sensor = new SarifIssuesImportSensor(sarifSerializer, sarifImporter, sensorSettings.asConfig());
+ sensor.execute(sensorContext);
+
+ verify(sarifImporter).importSarif(sarifReport2);
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains("Failed to process SARIF report from file 'path/to/sarif/file.sarif', error 'deserialization failed'");
+
+ }
+
+ private Sarif210 mockReport(String path) {
+ Sarif210 report = mock(Sarif210.class);
+ Path reportFilePath = sensorContext.fileSystem().resolvePath(path).toPath();
+ when(sarifSerializer.deserialize(reportFilePath)).thenReturn(report);
+ return report;
+ }
+
+ private void failDeserializingReport(String path) {
+ Path reportFilePath = sensorContext.fileSystem().resolvePath(path).toPath();
+ when(sarifSerializer.deserialize(reportFilePath)).thenThrow(new NullPointerException("deserialization failed"));
+ }
+}