3 * Copyright (C) 2009-2024 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.issue;
22 import java.util.Date;
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;
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;
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";
69 private final DumbRule rule = new DumbRule(XOO_X1);
72 public RuleRepositoryRule ruleRepository = new RuleRepositoryRule().add(rule);
74 public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
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);
83 public void initNewOpenIssue() {
84 DefaultIssue issue = new DefaultIssue()
86 when(debtCalculator.calculate(issue)).thenReturn(DEFAULT_DURATION);
88 underTest.initNewOpenIssue(issue);
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);
101 public void initNewOpenHotspot() {
102 rule.setType(RuleType.SECURITY_HOTSPOT);
103 DefaultIssue issue = new DefaultIssue()
105 when(debtCalculator.calculate(issue)).thenReturn(DEFAULT_DURATION);
107 underTest.initNewOpenIssue(issue);
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();
120 public void mergeIssueFromPRIntoBranch() {
121 DefaultIssue raw = new DefaultIssue()
123 DefaultIssue fromShort = new DefaultIssue()
125 .setIsNewCodeReferenceIssue(true);
126 fromShort.setResolution("resolution");
127 fromShort.setStatus("status");
128 fromShort.setCleanCodeAttribute(CleanCodeAttribute.COMPLETE);
130 Date commentDate = new Date();
131 fromShort.addComment(new DefaultIssueComment()
132 .setIssueKey("short")
133 .setCreatedAt(commentDate)
134 .setUserUuid("user_uuid")
135 .setMarkdownText("A comment"));
137 Date diffDate = new Date();
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"));
152 Branch branch = mock(Branch.class);
153 when(branch.getName()).thenReturn("master");
154 analysisMetadataHolder.setBranch(branch);
156 underTest.mergeConfirmedOrResolvedFromPrOrBranch(raw, fromShort, BranchType.PULL_REQUEST, "2");
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();
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()
187 DefaultIssue fromShort = new DefaultIssue()
189 fromShort.setResolution("resolution");
190 fromShort.setStatus("status");
191 fromShort.setCleanCodeAttribute(CleanCodeAttribute.DISTINCT);
193 Date commentDate = new Date();
194 fromShort.addComment(new DefaultIssueComment()
195 .setIssueKey("short")
196 .setCreatedAt(commentDate)
197 .setUserUuid("user_uuid")
198 .setMarkdownText("A comment"));
200 Date diffDate = new Date();
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"));
215 underTest.copyExistingIssueFromSourceBranchToPullRequest(raw, fromShort);
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");
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()
245 DefaultIssue sourceIssue = new DefaultIssue()
247 sourceIssue.setResolution("resolution");
248 sourceIssue.setStatus("status");
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"));
259 underTest.copyExistingIssueFromSourceBranchToPullRequest(destIssue, sourceIssue);
261 FieldDiffs actualFieldDiffs = destIssue.changes().iterator().next();
262 assertThat(actualFieldDiffs.issueKey()).contains(destIssue.key());
263 assertThat(actualFieldDiffs).usingRecursiveComparison().ignoringFields("issueKey").isEqualTo(sourceFieldDiffs);
267 public void copyExistingIssuesFromSourceBranchOfPullRequest_only_works_for_pull_requests() {
268 DefaultIssue raw = new DefaultIssue()
270 DefaultIssue from = new DefaultIssue()
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);
278 assertThatThrownBy(() -> underTest.copyExistingIssueFromSourceBranchToPullRequest(raw, from))
279 .isInstanceOf(IllegalStateException.class)
280 .hasMessage("This operation should be done only on pull request analysis");
284 public void copiedIssue() {
285 DefaultIssue raw = new DefaultIssue()
288 .setCreationDate(parseDate("2015-10-01"))
289 .setUpdateDate(parseDate("2015-10-02"))
290 .setCloseDate(parseDate("2015-10-03"))
291 .setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
293 DbIssues.Locations issueLocations = DbIssues.Locations.newBuilder()
294 .setTextRange(DbCommons.TextRange.newBuilder()
299 DefaultIssue base = new DefaultIssue()
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)
315 .setMessage("message")
317 .setEffort(Duration.create(15L))
318 .setManualSeverity(false)
319 .setLocations(issueLocations);
321 when(debtCalculator.calculate(raw)).thenReturn(DEFAULT_DURATION);
323 Branch branch = mock(Branch.class);
324 when(branch.getName()).thenReturn("release-2.x");
325 analysisMetadataHolder.setBranch(branch);
327 underTest.copyExistingOpenIssueFromBranch(raw, base, "master");
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);
350 verifyNoInteractions(updater);
354 public void doAutomaticTransition() {
355 DefaultIssue issue = new DefaultIssue();
357 underTest.doAutomaticTransition(issue);
359 verify(workflow).doAutomaticTransition(issue, issueChangeContext);
363 public void doManualTransition() {
364 DefaultIssue issue = new DefaultIssue();
365 String transitionKey = "transitionKey";
366 String userUuid = "userUuid";
368 underTest.doManualTransition(issue, transitionKey, userUuid);
370 verify(workflow).doManualTransition(issue, transitionKey, getIssueChangeContextWithUser(userUuid));
374 public void addComment() {
375 DefaultIssue issue = new DefaultIssue();
376 String comment = "comment";
377 String userUuid = "userUuid";
379 underTest.addComment(issue, comment, userUuid);
381 verify(updater).addComment(issue, comment, getIssueChangeContextWithUser(userUuid));
384 private IssueChangeContext getIssueChangeContextWithUser(String userUuid) {
385 return IssueChangeContext.newBuilder()
386 .setDate(issueChangeContext.date())
387 .setWebhookSource(issueChangeContext.getWebhookSource())
388 .setUserUuid(userUuid).build();
392 public void mergeExistingOpenIssue() {
393 DefaultIssue raw = new DefaultIssue()
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"));
405 DbIssues.Locations issueLocations = DbIssues.Locations.newBuilder()
406 .setTextRange(DbCommons.TextRange.newBuilder()
412 DbIssues.MessageFormattings messageFormattings = DbIssues.MessageFormattings.newBuilder()
413 .addMessageFormatting(DbIssues.MessageFormatting
417 .setType(DbIssues.MessageFormattingType.CODE)
421 DefaultIssue base = new DefaultIssue()
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)
436 .setMessage("message with code")
437 .setMessageFormattings(messageFormattings)
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"));
448 when(debtCalculator.calculate(raw)).thenReturn(DEFAULT_DURATION);
450 underTest.mergeExistingOpenIssue(raw, base);
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);
485 public void mergeExistingOpenIssue_with_manual_severity() {
486 DefaultIssue raw = new DefaultIssue()
490 DefaultIssue base = new DefaultIssue()
492 .setResolution(RESOLUTION_FIXED)
493 .setStatus(STATUS_CLOSED)
494 .setSeverity(BLOCKER)
495 .setManualSeverity(true);
497 underTest.mergeExistingOpenIssue(raw, base);
499 assertThat(raw.manualSeverity()).isTrue();
500 assertThat(raw.severity()).isEqualTo(BLOCKER);
502 verify(updater, never()).setPastSeverity(raw, BLOCKER, issueChangeContext);
506 public void mergeExistingOpenIssue_with_base_changed() {
507 DefaultIssue raw = new DefaultIssue()
511 DefaultIssue base = new DefaultIssue()
514 .setResolution(RESOLUTION_FALSE_POSITIVE)
515 .setStatus(STATUS_RESOLVED);
517 underTest.mergeExistingOpenIssue(raw, base);
519 assertThat(raw.isChanged()).isTrue();
523 public void mergeExistingOpenIssue_with_rule_description_context_key_added() {
524 DefaultIssue raw = new DefaultIssue()
528 .setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
529 DefaultIssue base = new DefaultIssue()
532 .setResolution(RESOLUTION_FALSE_POSITIVE)
533 .setStatus(STATUS_RESOLVED)
534 .setRuleDescriptionContextKey(null);
536 underTest.mergeExistingOpenIssue(raw, base);
538 assertThat(raw.isChanged()).isTrue();
539 assertThat(raw.getRuleDescriptionContextKey()).isEqualTo(raw.getRuleDescriptionContextKey());
543 public void mergeExistingOpenIssue_with_rule_description_context_key_removed() {
544 DefaultIssue raw = new DefaultIssue()
548 .setRuleDescriptionContextKey(null);
549 DefaultIssue base = new DefaultIssue()
552 .setResolution(RESOLUTION_FALSE_POSITIVE)
553 .setStatus(STATUS_RESOLVED)
554 .setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
556 underTest.mergeExistingOpenIssue(raw, base);
558 assertThat(raw.isChanged()).isTrue();
559 assertThat(raw.getRuleDescriptionContextKey()).isEmpty();