]> source.dussan.org Git - sonarqube.git/blob
dcfa537892f6a506393b8a16dff2a7a51567326f
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.ce.task.projectanalysis.issue;
21
22 import java.util.Date;
23 import java.util.Set;
24 import org.junit.Rule;
25 import org.junit.Test;
26 import org.sonar.api.issue.impact.Severity;
27 import org.sonar.api.issue.impact.SoftwareQuality;
28 import org.sonar.api.rules.CleanCodeAttribute;
29 import org.sonar.api.rules.RuleType;
30 import org.sonar.api.utils.Duration;
31 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
32 import org.sonar.ce.task.projectanalysis.analysis.Branch;
33 import org.sonar.core.issue.DefaultIssue;
34 import org.sonar.core.issue.DefaultIssueComment;
35 import org.sonar.core.issue.FieldDiffs;
36 import org.sonar.core.issue.IssueChangeContext;
37 import org.sonar.db.component.BranchType;
38 import org.sonar.db.protobuf.DbCommons;
39 import org.sonar.db.protobuf.DbIssues;
40 import org.sonar.server.issue.IssueFieldsSetter;
41 import org.sonar.server.issue.workflow.IssueWorkflow;
42
43 import static com.google.common.collect.Lists.newArrayList;
44 import static org.assertj.core.api.Assertions.assertThat;
45 import static org.assertj.core.api.Assertions.assertThatThrownBy;
46 import static org.assertj.core.api.Assertions.entry;
47 import static org.assertj.core.groups.Tuple.tuple;
48 import static org.mockito.Mockito.mock;
49 import static org.mockito.Mockito.never;
50 import static org.mockito.Mockito.verify;
51 import static org.mockito.Mockito.verifyNoInteractions;
52 import static org.mockito.Mockito.when;
53 import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
54 import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
55 import static org.sonar.api.issue.Issue.STATUS_CLOSED;
56 import static org.sonar.api.issue.Issue.STATUS_OPEN;
57 import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
58 import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
59 import static org.sonar.api.rule.Severity.BLOCKER;
60 import static org.sonar.api.utils.DateUtils.parseDate;
61 import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
62 import static org.sonar.db.rule.RuleTesting.XOO_X1;
63
64 public class IssueLifecycleTest {
65   private static final Date DEFAULT_DATE = new Date();
66   private static final Duration DEFAULT_DURATION = Duration.create(10);
67   private static final String TEST_CONTEXT_KEY = "test_context_key";
68
69   private final DumbRule rule = new DumbRule(XOO_X1);
70
71   @Rule
72   public RuleRepositoryRule ruleRepository = new RuleRepositoryRule().add(rule);
73   @Rule
74   public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
75
76   private final IssueChangeContext issueChangeContext = issueChangeContextByUserBuilder(DEFAULT_DATE, "default_user_uuid").build();
77   private final IssueWorkflow workflow = mock(IssueWorkflow.class);
78   private final IssueFieldsSetter updater = mock(IssueFieldsSetter.class);
79   private final DebtCalculator debtCalculator = mock(DebtCalculator.class);
80   private final IssueLifecycle underTest = new IssueLifecycle(analysisMetadataHolder, issueChangeContext, workflow, updater, debtCalculator, ruleRepository);
81
82   @Test
83   public void initNewOpenIssue() {
84     DefaultIssue issue = new DefaultIssue()
85       .setRuleKey(XOO_X1);
86     when(debtCalculator.calculate(issue)).thenReturn(DEFAULT_DURATION);
87
88     underTest.initNewOpenIssue(issue);
89
90     assertThat(issue.key()).isNotNull();
91     assertThat(issue.creationDate()).isNotNull();
92     assertThat(issue.updateDate()).isNotNull();
93     assertThat(issue.status()).isEqualTo(STATUS_OPEN);
94     assertThat(issue.effort()).isEqualTo(DEFAULT_DURATION);
95     assertThat(issue.isNew()).isTrue();
96     assertThat(issue.isCopied()).isFalse();
97     assertThat(issue.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL);
98   }
99
100   @Test
101   public void initNewOpenHotspot() {
102     rule.setType(RuleType.SECURITY_HOTSPOT);
103     DefaultIssue issue = new DefaultIssue()
104       .setRuleKey(XOO_X1);
105     when(debtCalculator.calculate(issue)).thenReturn(DEFAULT_DURATION);
106
107     underTest.initNewOpenIssue(issue);
108
109     assertThat(issue.key()).isNotNull();
110     assertThat(issue.creationDate()).isNotNull();
111     assertThat(issue.updateDate()).isNotNull();
112     assertThat(issue.status()).isEqualTo(STATUS_TO_REVIEW);
113     assertThat(issue.resolution()).isNull();
114     assertThat(issue.effort()).isEqualTo(DEFAULT_DURATION);
115     assertThat(issue.isNew()).isTrue();
116     assertThat(issue.isCopied()).isFalse();
117   }
118
119   @Test
120   public void mergeIssueFromPRIntoBranch() {
121     DefaultIssue raw = new DefaultIssue()
122       .setKey("raw");
123     DefaultIssue fromShort = new DefaultIssue()
124       .setKey("short")
125       .setIsNewCodeReferenceIssue(true);
126     fromShort.setResolution("resolution");
127     fromShort.setStatus("status");
128     fromShort.setCleanCodeAttribute(CleanCodeAttribute.COMPLETE);
129
130     Date commentDate = new Date();
131     fromShort.addComment(new DefaultIssueComment()
132       .setIssueKey("short")
133       .setCreatedAt(commentDate)
134       .setUserUuid("user_uuid")
135       .setMarkdownText("A comment"));
136
137     Date diffDate = new Date();
138     // file diff alone
139     fromShort.addChange(new FieldDiffs()
140       .setCreationDate(diffDate)
141       .setIssueKey("short")
142       .setUserUuid("user_uuid")
143       .setDiff("file", "uuidA1", "uuidB1"));
144     // file diff with another field
145     fromShort.addChange(new FieldDiffs()
146       .setCreationDate(diffDate)
147       .setIssueKey("short")
148       .setUserUuid("user_uuid")
149       .setDiff("severity", "MINOR", "MAJOR")
150       .setDiff("file", "uuidA2", "uuidB2"));
151
152     Branch branch = mock(Branch.class);
153     when(branch.getName()).thenReturn("master");
154     analysisMetadataHolder.setBranch(branch);
155
156     underTest.mergeConfirmedOrResolvedFromPrOrBranch(raw, fromShort, BranchType.PULL_REQUEST, "2");
157
158     assertThat(raw.resolution()).isEqualTo("resolution");
159     assertThat(raw.status()).isEqualTo("status");
160     assertThat(raw.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.COMPLETE);
161     assertThat(raw.defaultIssueComments())
162       .extracting(DefaultIssueComment::issueKey, DefaultIssueComment::createdAt, DefaultIssueComment::userUuid, DefaultIssueComment::markdownText)
163       .containsOnly(tuple("raw", commentDate, "user_uuid", "A comment"));
164     assertThat(raw.changes()).hasSize(2);
165     assertThat(raw.changes().get(0).creationDate()).isEqualTo(diffDate);
166     assertThat(raw.changes().get(0).userUuid()).contains("user_uuid");
167     assertThat(raw.changes().get(0).issueKey()).contains("raw");
168     assertThat(raw.changes().get(0).diffs()).containsOnlyKeys("severity");
169     assertThat(raw.changes().get(1).userUuid()).contains("default_user_uuid");
170     assertThat(raw.changes().get(1).diffs()).containsOnlyKeys(IssueFieldsSetter.FROM_BRANCH);
171     assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_BRANCH).oldValue()).isEqualTo("#2");
172     assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_BRANCH).newValue()).isEqualTo("master");
173     assertThat(raw.isNewCodeReferenceIssue()).isTrue();
174   }
175
176   @Test
177   public void copyExistingIssuesFromSourceBranchOfPullRequest() {
178     String pullRequestKey = "1";
179     Branch branch = mock(Branch.class);
180     when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
181     when(branch.getName()).thenReturn("sourceBranch-1");
182     when(branch.getPullRequestKey()).thenReturn(pullRequestKey);
183     analysisMetadataHolder.setBranch(branch);
184     analysisMetadataHolder.setPullRequestKey(pullRequestKey);
185     DefaultIssue raw = new DefaultIssue()
186       .setKey("raw");
187     DefaultIssue fromShort = new DefaultIssue()
188       .setKey("short");
189     fromShort.setResolution("resolution");
190     fromShort.setStatus("status");
191     fromShort.setCleanCodeAttribute(CleanCodeAttribute.DISTINCT);
192
193     Date commentDate = new Date();
194     fromShort.addComment(new DefaultIssueComment()
195       .setIssueKey("short")
196       .setCreatedAt(commentDate)
197       .setUserUuid("user_uuid")
198       .setMarkdownText("A comment"));
199
200     Date diffDate = new Date();
201     // file diff alone
202     fromShort.addChange(new FieldDiffs()
203       .setCreationDate(diffDate)
204       .setIssueKey("short")
205       .setUserUuid("user_uuid")
206       .setDiff("file", "uuidA1", "uuidB1"));
207     // file diff with another field
208     fromShort.addChange(new FieldDiffs()
209       .setCreationDate(diffDate)
210       .setIssueKey("short")
211       .setUserUuid("user_uuid")
212       .setDiff("severity", "MINOR", "MAJOR")
213       .setDiff("file", "uuidA2", "uuidB2"));
214
215     underTest.copyExistingIssueFromSourceBranchToPullRequest(raw, fromShort);
216
217     assertThat(raw.resolution()).isEqualTo("resolution");
218     assertThat(raw.status()).isEqualTo("status");
219     assertThat(raw.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.DISTINCT);
220     assertThat(raw.defaultIssueComments())
221       .extracting(DefaultIssueComment::issueKey, DefaultIssueComment::createdAt, DefaultIssueComment::userUuid, DefaultIssueComment::markdownText)
222       .containsOnly(tuple("raw", commentDate, "user_uuid", "A comment"));
223     assertThat(raw.changes()).hasSize(2);
224     assertThat(raw.changes().get(0).creationDate()).isEqualTo(diffDate);
225     assertThat(raw.changes().get(0).userUuid()).contains("user_uuid");
226     assertThat(raw.changes().get(0).issueKey()).contains("raw");
227     assertThat(raw.changes().get(0).diffs()).containsOnlyKeys("severity");
228     assertThat(raw.changes().get(1).userUuid()).contains("default_user_uuid");
229     assertThat(raw.changes().get(1).diffs()).containsOnlyKeys(IssueFieldsSetter.FROM_BRANCH);
230     assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_BRANCH).oldValue()).isEqualTo("sourceBranch-1");
231     assertThat(raw.changes().get(1).get(IssueFieldsSetter.FROM_BRANCH).newValue()).isEqualTo("#1");
232   }
233
234   @Test
235   public void copyExistingIssuesFromSourceBranchOfPullRequest_copyFieldDiffsCorrectly() {
236     String pullRequestKey = "1";
237     Branch branch = mock(Branch.class);
238     when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
239     when(branch.getName()).thenReturn("sourceBranch-1");
240     when(branch.getPullRequestKey()).thenReturn(pullRequestKey);
241     analysisMetadataHolder.setBranch(branch);
242     analysisMetadataHolder.setPullRequestKey(pullRequestKey);
243     DefaultIssue destIssue = new DefaultIssue()
244       .setKey("raw");
245     DefaultIssue sourceIssue = new DefaultIssue()
246       .setKey("issue");
247     sourceIssue.setResolution("resolution");
248     sourceIssue.setStatus("status");
249
250     FieldDiffs sourceFieldDiffs = new FieldDiffs();
251     sourceIssue.addChange(sourceFieldDiffs
252       .setCreationDate(new Date())
253       .setIssueKey("short")
254       .setUserUuid("user_uuid")
255       .setExternalUser("toto")
256       .setWebhookSource("github")
257       .setDiff("severity", "MINOR", "MAJOR"));
258
259     underTest.copyExistingIssueFromSourceBranchToPullRequest(destIssue, sourceIssue);
260
261     FieldDiffs actualFieldDiffs = destIssue.changes().iterator().next();
262     assertThat(actualFieldDiffs.issueKey()).contains(destIssue.key());
263     assertThat(actualFieldDiffs).usingRecursiveComparison().ignoringFields("issueKey").isEqualTo(sourceFieldDiffs);
264   }
265
266   @Test
267   public void copyExistingIssuesFromSourceBranchOfPullRequest_only_works_for_pull_requests() {
268     DefaultIssue raw = new DefaultIssue()
269       .setKey("raw");
270     DefaultIssue from = new DefaultIssue()
271       .setKey("short");
272     from.setResolution("resolution");
273     from.setStatus("status");
274     Branch branch = mock(Branch.class);
275     when(branch.getType()).thenReturn(BranchType.BRANCH);
276     analysisMetadataHolder.setBranch(branch);
277
278     assertThatThrownBy(() -> underTest.copyExistingIssueFromSourceBranchToPullRequest(raw, from))
279       .isInstanceOf(IllegalStateException.class)
280       .hasMessage("This operation should be done only on pull request analysis");
281   }
282
283   @Test
284   public void copiedIssue() {
285     DefaultIssue raw = new DefaultIssue()
286       .setNew(true)
287       .setKey("RAW_KEY")
288       .setCreationDate(parseDate("2015-10-01"))
289       .setUpdateDate(parseDate("2015-10-02"))
290       .setCloseDate(parseDate("2015-10-03"))
291       .setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
292
293     DbIssues.Locations issueLocations = DbIssues.Locations.newBuilder()
294       .setTextRange(DbCommons.TextRange.newBuilder()
295         .setStartLine(10)
296         .setEndLine(12)
297         .build())
298       .build();
299     DefaultIssue base = new DefaultIssue()
300       .setKey("BASE_KEY")
301       .setCreationDate(parseDate("2015-01-01"))
302       .setUpdateDate(parseDate("2015-01-02"))
303       .setCloseDate(parseDate("2015-01-03"))
304       .setCleanCodeAttribute(CleanCodeAttribute.FOCUSED)
305       .setResolution(RESOLUTION_FIXED)
306       .setStatus(STATUS_CLOSED)
307       .setSeverity(BLOCKER)
308       .setAssigneeUuid("base assignee uuid")
309       .setAssigneeLogin("base assignee login")
310       .setAuthorLogin("base author")
311       .setTags(newArrayList("base tag"))
312       .setOnDisabledRule(true)
313       .setSelectedAt(1000L)
314       .setLine(10)
315       .setMessage("message")
316       .setGap(15d)
317       .setEffort(Duration.create(15L))
318       .setManualSeverity(false)
319       .setLocations(issueLocations);
320
321     when(debtCalculator.calculate(raw)).thenReturn(DEFAULT_DURATION);
322
323     Branch branch = mock(Branch.class);
324     when(branch.getName()).thenReturn("release-2.x");
325     analysisMetadataHolder.setBranch(branch);
326
327     underTest.copyExistingOpenIssueFromBranch(raw, base, "master");
328
329     assertThat(raw.isNew()).isFalse();
330     assertThat(raw.isCopied()).isTrue();
331     assertThat(raw.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.FOCUSED);
332     assertThat(raw.key()).isNotNull();
333     assertThat(raw.key()).isNotEqualTo(base.key());
334     assertThat(raw.creationDate()).isEqualTo(base.creationDate());
335     assertThat(raw.updateDate()).isEqualTo(base.updateDate());
336     assertThat(raw.closeDate()).isEqualTo(base.closeDate());
337     assertThat(raw.resolution()).isEqualTo(RESOLUTION_FIXED);
338     assertThat(raw.status()).isEqualTo(STATUS_CLOSED);
339     assertThat(raw.assignee()).isEqualTo("base assignee uuid");
340     assertThat(raw.assigneeLogin()).isEqualTo("base assignee login");
341     assertThat(raw.authorLogin()).isEqualTo("base author");
342     assertThat(raw.tags()).containsOnly("base tag");
343     assertThat(raw.effort()).isEqualTo(DEFAULT_DURATION);
344     assertThat(raw.isOnDisabledRule()).isTrue();
345     assertThat(raw.selectedAt()).isEqualTo(1000L);
346     assertThat(raw.changes().get(0).get(IssueFieldsSetter.FROM_BRANCH).oldValue()).isEqualTo("master");
347     assertThat(raw.changes().get(0).get(IssueFieldsSetter.FROM_BRANCH).newValue()).isEqualTo("release-2.x");
348     assertThat(raw.getRuleDescriptionContextKey()).contains(TEST_CONTEXT_KEY);
349
350     verifyNoInteractions(updater);
351   }
352
353   @Test
354   public void doAutomaticTransition() {
355     DefaultIssue issue = new DefaultIssue();
356
357     underTest.doAutomaticTransition(issue);
358
359     verify(workflow).doAutomaticTransition(issue, issueChangeContext);
360   }
361
362   @Test
363   public void doManualTransition() {
364     DefaultIssue issue = new DefaultIssue();
365     String transitionKey = "transitionKey";
366     String userUuid = "userUuid";
367
368     underTest.doManualTransition(issue, transitionKey, userUuid);
369
370     verify(workflow).doManualTransition(issue, transitionKey, getIssueChangeContextWithUser(userUuid));
371   }
372
373   @Test
374   public void addComment() {
375     DefaultIssue issue = new DefaultIssue();
376     String comment = "comment";
377     String userUuid = "userUuid";
378
379     underTest.addComment(issue, comment, userUuid);
380
381     verify(updater).addComment(issue, comment, getIssueChangeContextWithUser(userUuid));
382   }
383
384   private IssueChangeContext getIssueChangeContextWithUser(String userUuid) {
385     return IssueChangeContext.newBuilder()
386       .setDate(issueChangeContext.date())
387       .setWebhookSource(issueChangeContext.getWebhookSource())
388       .setUserUuid(userUuid).build();
389   }
390
391   @Test
392   public void mergeExistingOpenIssue() {
393     DefaultIssue raw = new DefaultIssue()
394       .setNew(true)
395       .setKey("RAW_KEY")
396       .setRuleKey(XOO_X1)
397       .setRuleDescriptionContextKey("spring")
398       .setCleanCodeAttribute(CleanCodeAttribute.IDENTIFIABLE)
399       .setCodeVariants(Set.of("foo", "bar"))
400       .addImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)
401       .setCreationDate(parseDate("2015-10-01"))
402       .setUpdateDate(parseDate("2015-10-02"))
403       .setCloseDate(parseDate("2015-10-03"));
404
405     DbIssues.Locations issueLocations = DbIssues.Locations.newBuilder()
406       .setTextRange(DbCommons.TextRange.newBuilder()
407         .setStartLine(10)
408         .setEndLine(12)
409         .build())
410       .build();
411
412     DbIssues.MessageFormattings messageFormattings = DbIssues.MessageFormattings.newBuilder()
413       .addMessageFormatting(DbIssues.MessageFormatting
414         .newBuilder()
415         .setStart(13)
416         .setEnd(17)
417         .setType(DbIssues.MessageFormattingType.CODE)
418         .build())
419       .build();
420
421     DefaultIssue base = new DefaultIssue()
422       .setKey("BASE_KEY")
423       .setCreationDate(parseDate("2015-01-01"))
424       .setUpdateDate(parseDate("2015-01-02"))
425       .setCleanCodeAttribute(CleanCodeAttribute.FOCUSED)
426       .setResolution(RESOLUTION_FALSE_POSITIVE)
427       .setStatus(STATUS_RESOLVED)
428       .setSeverity(BLOCKER)
429       .setAssigneeUuid("base assignee uuid")
430       .setAssigneeLogin("base assignee login")
431       .setAuthorLogin("base author")
432       .setTags(newArrayList("base tag"))
433       .setOnDisabledRule(true)
434       .setSelectedAt(1000L)
435       .setLine(10)
436       .setMessage("message with code")
437       .setMessageFormattings(messageFormattings)
438       .setGap(15d)
439       .setRuleDescriptionContextKey("hibernate")
440       .setCodeVariants(Set.of("donut"))
441       .addImpact(SoftwareQuality.RELIABILITY, Severity.LOW)
442       .setEffort(Duration.create(15L))
443       .setManualSeverity(false)
444       .setLocations(issueLocations)
445       .addChange(new FieldDiffs().setDiff("foo", "bar", "donut"))
446       .addChange(new FieldDiffs().setDiff("file", "A", "B"));
447
448     when(debtCalculator.calculate(raw)).thenReturn(DEFAULT_DURATION);
449
450     underTest.mergeExistingOpenIssue(raw, base);
451
452     assertThat(raw.isNew()).isFalse();
453     assertThat(raw.key()).isEqualTo("BASE_KEY");
454     assertThat(raw.creationDate()).isEqualTo(base.creationDate());
455     assertThat(raw.updateDate()).isEqualTo(base.updateDate());
456     assertThat(raw.resolution()).isEqualTo(RESOLUTION_FALSE_POSITIVE);
457     assertThat(raw.status()).isEqualTo(STATUS_RESOLVED);
458     assertThat(raw.assignee()).isEqualTo("base assignee uuid");
459     assertThat(raw.assigneeLogin()).isEqualTo("base assignee login");
460     assertThat(raw.authorLogin()).isEqualTo("base author");
461     assertThat(raw.tags()).containsOnly("base tag");
462     assertThat(raw.codeVariants()).containsOnly("foo", "bar");
463     assertThat(raw.effort()).isEqualTo(DEFAULT_DURATION);
464     assertThat(raw.isOnDisabledRule()).isTrue();
465     assertThat(raw.selectedAt()).isEqualTo(1000L);
466     assertThat(raw.isChanged()).isFalse();
467     assertThat(raw.changes()).hasSize(2);
468     assertThat(raw.changes().get(0).diffs())
469       .containsOnly(entry("foo", new FieldDiffs.Diff<>("bar", "donut")));
470     assertThat(raw.changes().get(1).diffs())
471       .containsOnly(entry("file", new FieldDiffs.Diff<>("A", "B")));
472     assertThat(raw.impacts())
473       .containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.HIGH);
474     verify(updater).setPastSeverity(raw, BLOCKER, issueChangeContext);
475     verify(updater).setPastLine(raw, 10);
476     verify(updater).setRuleDescriptionContextKey(raw, "hibernate");
477     verify(updater).setCodeVariants(raw, Set.of("donut"), issueChangeContext);
478     verify(updater).setPastMessage(raw, "message with code", messageFormattings, issueChangeContext);
479     verify(updater).setPastEffort(raw, Duration.create(15L), issueChangeContext);
480     verify(updater).setPastLocations(raw, issueLocations);
481     verify(updater).setCleanCodeAttribute(raw, CleanCodeAttribute.FOCUSED, issueChangeContext);
482   }
483
484   @Test
485   public void mergeExistingOpenIssue_with_manual_severity() {
486     DefaultIssue raw = new DefaultIssue()
487       .setNew(true)
488       .setKey("RAW_KEY")
489       .setRuleKey(XOO_X1);
490     DefaultIssue base = new DefaultIssue()
491       .setKey("BASE_KEY")
492       .setResolution(RESOLUTION_FIXED)
493       .setStatus(STATUS_CLOSED)
494       .setSeverity(BLOCKER)
495       .setManualSeverity(true);
496
497     underTest.mergeExistingOpenIssue(raw, base);
498
499     assertThat(raw.manualSeverity()).isTrue();
500     assertThat(raw.severity()).isEqualTo(BLOCKER);
501
502     verify(updater, never()).setPastSeverity(raw, BLOCKER, issueChangeContext);
503   }
504
505   @Test
506   public void mergeExistingOpenIssue_with_base_changed() {
507     DefaultIssue raw = new DefaultIssue()
508       .setNew(true)
509       .setKey("RAW_KEY")
510       .setRuleKey(XOO_X1);
511     DefaultIssue base = new DefaultIssue()
512       .setChanged(true)
513       .setKey("BASE_KEY")
514       .setResolution(RESOLUTION_FALSE_POSITIVE)
515       .setStatus(STATUS_RESOLVED);
516
517     underTest.mergeExistingOpenIssue(raw, base);
518
519     assertThat(raw.isChanged()).isTrue();
520   }
521
522   @Test
523   public void mergeExistingOpenIssue_with_rule_description_context_key_added() {
524     DefaultIssue raw = new DefaultIssue()
525       .setNew(true)
526       .setKey("RAW_KEY")
527       .setRuleKey(XOO_X1)
528       .setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
529     DefaultIssue base = new DefaultIssue()
530       .setChanged(true)
531       .setKey("RAW_KEY")
532       .setResolution(RESOLUTION_FALSE_POSITIVE)
533       .setStatus(STATUS_RESOLVED)
534       .setRuleDescriptionContextKey(null);
535
536     underTest.mergeExistingOpenIssue(raw, base);
537
538     assertThat(raw.isChanged()).isTrue();
539     assertThat(raw.getRuleDescriptionContextKey()).isEqualTo(raw.getRuleDescriptionContextKey());
540   }
541
542   @Test
543   public void mergeExistingOpenIssue_with_rule_description_context_key_removed() {
544     DefaultIssue raw = new DefaultIssue()
545       .setNew(true)
546       .setKey("RAW_KEY")
547       .setRuleKey(XOO_X1)
548       .setRuleDescriptionContextKey(null);
549     DefaultIssue base = new DefaultIssue()
550       .setChanged(true)
551       .setKey("RAW_KEY")
552       .setResolution(RESOLUTION_FALSE_POSITIVE)
553       .setStatus(STATUS_RESOLVED)
554       .setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
555
556     underTest.mergeExistingOpenIssue(raw, base);
557
558     assertThat(raw.isChanged()).isTrue();
559     assertThat(raw.getRuleDescriptionContextKey()).isEmpty();
560   }
561 }