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