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.ce.task.projectanalysis.pushevent;
22 import com.google.gson.Gson;
23 import java.nio.charset.StandardCharsets;
24 import java.util.Date;
26 import org.junit.Before;
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.sonar.api.issue.Issue;
30 import org.sonar.api.rule.RuleKey;
31 import org.sonar.api.rules.RuleType;
32 import org.sonar.api.utils.DateUtils;
33 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
34 import org.sonar.ce.task.projectanalysis.analysis.TestBranch;
35 import org.sonar.ce.task.projectanalysis.component.Component.Type;
36 import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolderRule;
37 import org.sonar.ce.task.projectanalysis.component.ReportComponent;
38 import org.sonar.ce.task.projectanalysis.issue.RuleRepository;
39 import org.sonar.ce.task.projectanalysis.locations.flow.FlowGenerator;
40 import org.sonar.core.issue.DefaultIssue;
41 import org.sonar.core.issue.FieldDiffs;
42 import org.sonar.db.protobuf.DbCommons;
43 import org.sonar.db.protobuf.DbIssues;
44 import org.sonar.db.rule.RuleDto;
45 import org.sonar.server.issue.TaintChecker;
47 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
48 import static org.assertj.core.api.Assertions.assertThat;
49 import static org.assertj.core.api.Assertions.fail;
50 import static org.mockito.ArgumentMatchers.any;
51 import static org.mockito.Mockito.mock;
52 import static org.mockito.Mockito.when;
54 public class PushEventFactoryTest {
56 private static final Gson gson = new Gson();
57 private static final String BRANCH_NAME = "develop";
59 private final TaintChecker taintChecker = mock(TaintChecker.class);
61 public MutableTreeRootHolderRule treeRootHolder = new MutableTreeRootHolderRule();
63 public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
64 .setBranch(new TestBranch(BRANCH_NAME));
65 private final FlowGenerator flowGenerator = new FlowGenerator(treeRootHolder);
66 private final RuleRepository ruleRepository = mock(RuleRepository.class);
67 private final PushEventFactory underTest = new PushEventFactory(treeRootHolder, analysisMetadataHolder, taintChecker, flowGenerator,
72 when(ruleRepository.getByKey(RuleKey.of("javasecurity", "S123"))).thenReturn(buildRule());
77 public void raiseEventOnIssue_whenNewTaintVulnerability_shouldCreateRaisedEvent() {
78 DefaultIssue defaultIssue = createDefaultIssue()
80 .setRuleDescriptionContextKey(randomAlphabetic(6));
82 when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
84 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
86 .hasValueSatisfying(pushEventDto -> {
87 assertThat(pushEventDto.getName()).isEqualTo("TaintVulnerabilityRaised");
88 verifyPayload(pushEventDto.getPayload(), defaultIssue);
89 assertThat(pushEventDto.getLanguage()).isEqualTo("java");
90 assertThat(pushEventDto.getProjectUuid()).isEqualTo("some-project-uuid");
95 private static void verifyPayload(byte[] payload, DefaultIssue defaultIssue) {
96 assertThat(payload).isNotNull();
98 TaintVulnerabilityRaised taintVulnerabilityRaised = gson.fromJson(new String(payload, StandardCharsets.UTF_8),
99 TaintVulnerabilityRaised.class);
100 assertThat(taintVulnerabilityRaised.getProjectKey()).isEqualTo(defaultIssue.projectKey());
101 assertThat(taintVulnerabilityRaised.getCreationDate()).isEqualTo(defaultIssue.creationDate().getTime());
102 assertThat(taintVulnerabilityRaised.getKey()).isEqualTo(defaultIssue.key());
103 assertThat(taintVulnerabilityRaised.getSeverity()).isEqualTo(defaultIssue.severity());
104 assertThat(taintVulnerabilityRaised.getRuleKey()).isEqualTo(defaultIssue.ruleKey().toString());
105 assertThat(taintVulnerabilityRaised.getType()).isEqualTo(defaultIssue.type().name());
106 assertThat(taintVulnerabilityRaised.getBranch()).isEqualTo(BRANCH_NAME);
107 String ruleDescriptionContextKey = taintVulnerabilityRaised.getRuleDescriptionContextKey().orElseGet(() -> fail("No rule description " +
109 assertThat(ruleDescriptionContextKey).isEqualTo(defaultIssue.getRuleDescriptionContextKey().orElse(null));
113 public void raiseEventOnIssue_whenReopenedTaintVulnerability_shouldCreateRaisedEvent() {
114 DefaultIssue defaultIssue = createDefaultIssue()
118 .setCurrentChange(new FieldDiffs().setDiff("status", "CLOSED", "OPEN"));
120 when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
122 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
124 .hasValueSatisfying(pushEventDto -> {
125 assertThat(pushEventDto.getName()).isEqualTo("TaintVulnerabilityRaised");
126 assertThat(pushEventDto.getPayload()).isNotNull();
131 public void raiseEventOnIssue_whenTaintVulnerabilityStatusChange_shouldSkipEvent() {
132 DefaultIssue defaultIssue = createDefaultIssue()
136 .setCurrentChange(new FieldDiffs().setDiff("status", "OPEN", "FIXED"));
138 when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
140 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
144 public void raiseEventOnIssue_whenCopiedTaintVulnerability_shouldCreateRaisedEvent() {
145 DefaultIssue defaultIssue = createDefaultIssue()
148 when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
150 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
152 .hasValueSatisfying(pushEventDto -> {
153 assertThat(pushEventDto.getName()).isEqualTo("TaintVulnerabilityRaised");
154 assertThat(pushEventDto.getPayload()).isNotNull();
159 public void raiseEventOnIssue_whenClosedTaintVulnerability_shouldCreateClosedEvent() {
160 DefaultIssue defaultIssue = createDefaultIssue()
163 .setBeingClosed(true);
165 when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
167 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
169 .hasValueSatisfying(pushEventDto -> {
170 assertThat(pushEventDto.getName()).isEqualTo("TaintVulnerabilityClosed");
171 assertThat(pushEventDto.getPayload()).isNotNull();
176 public void raiseEventOnIssue_whenChangedTaintVulnerability_shouldSkipEvent() {
177 DefaultIssue defaultIssue = new DefaultIssue()
178 .setComponentUuid("issue-component-uuid")
182 .setType(RuleType.VULNERABILITY)
183 .setCreationDate(DateUtils.parseDate("2022-01-01"))
184 .setRuleKey(RuleKey.of("javasecurity", "S123"));
186 when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
188 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
192 public void raiseEventOnIssue_whenIssueNotFromTaintVulnerabilityRepository_shouldSkipEvent() {
193 DefaultIssue defaultIssue = new DefaultIssue()
194 .setComponentUuid("issue-component-uuid")
196 .setType(RuleType.VULNERABILITY)
197 .setRuleKey(RuleKey.of("weirdrepo", "S123"));
199 when(taintChecker.isTaintVulnerability(any())).thenReturn(false);
201 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
203 defaultIssue = new DefaultIssue()
204 .setComponentUuid("issue-component-uuid")
207 .setBeingClosed(true)
208 .setType(RuleType.VULNERABILITY)
209 .setRuleKey(RuleKey.of("weirdrepo", "S123"));
211 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
215 public void raiseEventOnIssue_whenIssueDoesNotHaveLocations_shouldSkipEvent() {
216 DefaultIssue defaultIssue = new DefaultIssue()
217 .setComponentUuid("issue-component-uuid")
219 .setType(RuleType.VULNERABILITY)
220 .setRuleKey(RuleKey.of("javasecurity", "S123"));
222 when(taintChecker.isTaintVulnerability(any())).thenReturn(false);
224 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
228 public void raiseEventOnIssue_whenNewHotspot_shouldCreateRaisedEvent() {
229 DefaultIssue defaultIssue = createDefaultIssue()
230 .setType(RuleType.SECURITY_HOTSPOT)
231 .setStatus(Issue.STATUS_TO_REVIEW)
233 .setRuleDescriptionContextKey(randomAlphabetic(6));
235 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
237 .hasValueSatisfying(pushEventDto -> {
238 assertThat(pushEventDto.getName()).isEqualTo(SecurityHotspotRaised.EVENT_NAME);
239 verifyHotspotRaisedEventPayload(pushEventDto.getPayload(), defaultIssue);
240 assertThat(pushEventDto.getLanguage()).isEqualTo("java");
241 assertThat(pushEventDto.getProjectUuid()).isEqualTo("some-project-uuid");
245 private static void verifyHotspotRaisedEventPayload(byte[] payload, DefaultIssue defaultIssue) {
246 assertThat(payload).isNotNull();
248 SecurityHotspotRaised event = gson.fromJson(new String(payload, StandardCharsets.UTF_8), SecurityHotspotRaised.class);
249 assertThat(event.getProjectKey()).isEqualTo(defaultIssue.projectKey());
250 assertThat(event.getCreationDate()).isEqualTo(defaultIssue.creationDate().getTime());
251 assertThat(event.getKey()).isEqualTo(defaultIssue.key());
252 assertThat(event.getRuleKey()).isEqualTo(defaultIssue.ruleKey().toString());
253 assertThat(event.getStatus()).isEqualTo(Issue.STATUS_TO_REVIEW);
254 assertThat(event.getVulnerabilityProbability()).isEqualTo("LOW");
255 assertThat(event.getMainLocation()).isNotNull();
256 assertThat(event.getBranch()).isEqualTo(BRANCH_NAME);
257 assertThat(event.getAssignee()).isEqualTo("some-user-login");
261 public void raiseEventOnIssue_whenReopenedHotspot_shouldCreateRaisedEvent() {
262 DefaultIssue defaultIssue = createDefaultIssue()
263 .setType(RuleType.SECURITY_HOTSPOT)
267 .setCurrentChange(new FieldDiffs().setDiff("status", "CLOSED", "TO_REVIEW"));
269 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
271 .hasValueSatisfying(pushEventDto -> {
272 assertThat(pushEventDto.getName()).isEqualTo(SecurityHotspotRaised.EVENT_NAME);
273 assertThat(pushEventDto.getPayload()).isNotNull();
278 public void raiseEventOnIssue_whenCopiedHotspot_shouldCreateRaisedEvent() {
279 DefaultIssue defaultIssue = createDefaultIssue()
280 .setType(RuleType.SECURITY_HOTSPOT)
283 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
285 .hasValueSatisfying(pushEventDto -> {
286 assertThat(pushEventDto.getName()).isEqualTo(SecurityHotspotRaised.EVENT_NAME);
287 assertThat(pushEventDto.getPayload()).isNotNull();
292 public void raiseEventOnIssue_whenClosedHotspot_shouldCreateClosedEvent() {
293 DefaultIssue defaultIssue = createDefaultIssue()
294 .setType(RuleType.SECURITY_HOTSPOT)
297 .setBeingClosed(true)
298 .setStatus(Issue.STATUS_CLOSED)
299 .setResolution(Issue.RESOLUTION_FIXED);
301 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
303 .hasValueSatisfying(pushEventDto -> {
304 assertThat(pushEventDto.getName()).isEqualTo(SecurityHotspotClosed.EVENT_NAME);
305 verifyHotspotClosedEventPayload(pushEventDto.getPayload(), defaultIssue);
306 assertThat(pushEventDto.getLanguage()).isEqualTo("java");
307 assertThat(pushEventDto.getProjectUuid()).isEqualTo("some-project-uuid");
311 private static void verifyHotspotClosedEventPayload(byte[] payload, DefaultIssue defaultIssue) {
312 assertThat(payload).isNotNull();
314 SecurityHotspotClosed event = gson.fromJson(new String(payload, StandardCharsets.UTF_8), SecurityHotspotClosed.class);
315 assertThat(event.getProjectKey()).isEqualTo(defaultIssue.projectKey());
316 assertThat(event.getKey()).isEqualTo(defaultIssue.key());
317 assertThat(event.getStatus()).isEqualTo(Issue.STATUS_CLOSED);
318 assertThat(event.getResolution()).isEqualTo(Issue.RESOLUTION_FIXED);
319 assertThat(event.getFilePath()).isEqualTo("component-name");
323 public void raiseEventOnIssue_whenChangedHotspot_shouldSkipEvent() {
324 DefaultIssue defaultIssue = createDefaultIssue()
325 .setType(RuleType.SECURITY_HOTSPOT)
330 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
334 public void raiseEventOnIssue_whenComponentUuidNull_shouldSkipEvent() {
335 DefaultIssue defaultIssue = createDefaultIssue()
336 .setComponentUuid(null);
338 assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
341 private void buildComponentTree() {
342 treeRootHolder.setRoot(ReportComponent.builder(Type.PROJECT, 1)
344 .addChildren(ReportComponent.builder(Type.FILE, 2)
345 .setName("component-name")
346 .setUuid("issue-component-uuid")
348 .addChildren(ReportComponent.builder(Type.FILE, 3)
349 .setUuid("location-component-uuid")
354 private DefaultIssue createDefaultIssue() {
355 return new DefaultIssue()
357 .setProjectKey("project-key")
358 .setComponentUuid("issue-component-uuid")
359 .setAssigneeUuid("some-user-uuid")
360 .setAssigneeLogin("some-user-login")
361 .setType(RuleType.VULNERABILITY)
363 .setCreationDate(new Date())
364 .setLocations(DbIssues.Locations.newBuilder()
365 .addFlow(DbIssues.Flow.newBuilder()
366 .addLocation(DbIssues.Location.newBuilder()
367 .setChecksum("checksum")
368 .setComponentId("location-component-uuid")
371 .setTextRange(DbCommons.TextRange.newBuilder()
375 .setRuleKey(RuleKey.of("javasecurity", "S123"));
378 private org.sonar.ce.task.projectanalysis.issue.Rule buildRule() {
379 RuleDto ruleDto = new RuleDto();
380 ruleDto.setRuleKey(RuleKey.of("javasecurity", "S123"));
381 ruleDto.setSecurityStandards(Set.of("owasp-a1"));
382 return new org.sonar.ce.task.projectanalysis.issue.RuleImpl(ruleDto);