diff options
author | Aurelien Poscia <aurelien.poscia@sonarsource.com> | 2022-11-09 14:35:07 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-11-15 20:02:59 +0000 |
commit | 53f94935f393750ba08a7e1fa00742acadbadafb (patch) | |
tree | 647e21c0a08c735234f87979c73f961493926037 /sonar-scanner-engine/src/test | |
parent | 59df4a4ad498fa1ce6df396c0b7a6afb70b7ec83 (diff) | |
download | sonarqube-53f94935f393750ba08a7e1fa00742acadbadafb.tar.gz sonarqube-53f94935f393750ba08a7e1fa00742acadbadafb.zip |
SONAR-17564 Import vulnerabilities from a SARIF report
Diffstat (limited to 'sonar-scanner-engine/src/test')
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")); + } +} |