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.server.qualityprofile;
22 import com.google.common.io.Resources;
23 import java.io.Reader;
24 import java.io.StringReader;
25 import java.io.StringWriter;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.List;
30 import javax.annotation.Nullable;
31 import org.junit.Rule;
32 import org.junit.Test;
33 import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
34 import org.sonar.api.rule.RuleKey;
35 import org.sonar.api.rule.RuleStatus;
36 import org.sonar.api.rules.RuleType;
37 import org.sonar.api.utils.System2;
38 import org.sonar.core.util.UuidFactoryFast;
39 import org.sonar.db.DbSession;
40 import org.sonar.db.DbTester;
41 import org.sonar.db.qualityprofile.ActiveRuleDto;
42 import org.sonar.db.qualityprofile.ActiveRuleParamDto;
43 import org.sonar.db.qualityprofile.QProfileDto;
44 import org.sonar.db.qualityprofile.QualityProfileTesting;
45 import org.sonar.db.rule.RuleDto;
46 import org.sonar.db.rule.RuleParamDto;
47 import org.sonar.server.qualityprofile.builtin.QProfileName;
48 import org.sonar.server.common.rule.RuleCreator;
50 import static java.nio.charset.StandardCharsets.UTF_8;
51 import static org.assertj.core.api.Assertions.assertThat;
52 import static org.assertj.core.api.Assertions.assertThatThrownBy;
53 import static org.junit.Assert.assertThrows;
54 import static org.mockito.ArgumentMatchers.any;
55 import static org.mockito.ArgumentMatchers.anyList;
56 import static org.mockito.Mockito.mock;
57 import static org.mockito.Mockito.when;
58 import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
59 import static org.sonar.db.rule.RuleTesting.newRule;
60 import static org.sonar.db.rule.RuleTesting.newRuleWithoutDescriptionSection;
62 public class QProfileBackuperImplIT {
64 private static final String EMPTY_BACKUP = "<?xml version='1.0' encoding='UTF-8'?>" +
65 "<profile><name>foo</name>" +
66 "<language>js</language>" +
70 private final System2 system2 = new AlwaysIncreasingSystem2();
73 public DbTester db = DbTester.create(system2);
75 private final DummyReset reset = new DummyReset();
76 private final QProfileFactory profileFactory = new DummyProfileFactory();
77 private final RuleCreator ruleCreator = mock(RuleCreator.class);
79 private final QProfileBackuper underTest = new QProfileBackuperImpl(db.getDbClient(), reset, profileFactory, ruleCreator, new QProfileParser());
82 public void backup_generates_xml_file() {
83 RuleDto rule = createRule();
84 QProfileDto profile = createProfile(rule.getLanguage());
85 ActiveRuleDto activeRule = activate(profile, rule);
87 StringWriter writer = new StringWriter();
88 underTest.backup(db.getSession(), profile, writer);
90 assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
91 "<profile><name>" + profile.getName() + "</name>" +
92 "<language>" + profile.getLanguage() + "</language>" +
95 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
96 "<key>" + rule.getRuleKey() + "</key>" +
97 "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
98 "<priority>" + activeRule.getSeverityString() + "</priority>" +
99 "<parameters></parameters>" +
106 public void backup_rules_having_parameters() {
107 RuleDto rule = createRule();
108 RuleParamDto param = db.rules().insertRuleParam(rule);
109 QProfileDto profile = createProfile(rule.getLanguage());
110 ActiveRuleDto activeRule = activate(profile, rule, param);
112 StringWriter writer = new StringWriter();
113 underTest.backup(db.getSession(), profile, writer);
115 assertThat(writer.toString()).contains(
117 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
118 "<key>" + rule.getRuleKey() + "</key>" +
119 "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
120 "<priority>" + activeRule.getSeverityString() + "</priority>" +
121 "<parameters><parameter>" +
122 "<key>" + param.getName() + "</key>" +
123 "<value>20</value>" +
124 "</parameter></parameters>" +
129 public void backup_empty_profile() {
130 RuleDto rule = createRule();
131 QProfileDto profile = createProfile(rule.getLanguage());
133 StringWriter writer = new StringWriter();
134 underTest.backup(db.getSession(), profile, writer);
136 assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
137 "<profile><name>" + profile.getName() + "</name>" +
138 "<language>" + profile.getLanguage() + "</language>" +
144 public void backup_custom_rules_with_params() {
145 RuleDto templateRule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
146 .setIsTemplate(true));
147 RuleDto rule = db.rules().insert(
148 newRule(createDefaultRuleDescriptionSection(UuidFactoryFast.getInstance().create(), "custom rule description"))
149 .setName("custom rule name")
150 .setStatus(RuleStatus.READY)
151 .setTemplateUuid(templateRule.getUuid()));
152 RuleParamDto param = db.rules().insertRuleParam(rule);
153 QProfileDto profile = createProfile(rule.getLanguage());
154 ActiveRuleDto activeRule = activate(profile, rule, param);
156 StringWriter writer = new StringWriter();
157 underTest.backup(db.getSession(), profile, writer);
159 assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
161 "<name>" + profile.getName() + "</name>" +
162 "<language>" + profile.getLanguage() + "</language>" +
164 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
165 "<key>" + rule.getKey().rule() + "</key>" +
166 "<type>" + RuleType.valueOf(rule.getType()) + "</type>" +
167 "<priority>" + activeRule.getSeverityString() + "</priority>" +
168 "<name>" + rule.getName() + "</name>" +
169 "<templateKey>" + templateRule.getKey().rule() + "</templateKey>" +
170 "<description>" + rule.getDefaultRuleDescriptionSection().getContent() + "</description>" +
171 "<parameters><parameter>" +
172 "<key>" + param.getName() + "</key>" +
173 "<value>20</value>" +
174 "</parameter></parameters>" +
175 "</rule></rules></profile>");
179 public void backup_custom_rules_without_description_section() {
180 var rule = newRuleWithoutDescriptionSection();
181 db.rules().insert(rule);
182 RuleParamDto param = db.rules().insertRuleParam(rule);
183 QProfileDto profile = createProfile(rule.getLanguage());
184 ActiveRuleDto activeRule = activate(profile, rule, param);
186 StringWriter writer = new StringWriter();
187 underTest.backup(db.getSession(), profile, writer);
189 assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
191 "<name>" + profile.getName() + "</name>" +
192 "<language>" + profile.getLanguage() + "</language>" +
194 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
195 "<key>" + rule.getKey().rule() + "</key>" +
196 "<type>" + RuleType.valueOf(rule.getType()) + "</type>" +
197 "<priority>" + activeRule.getSeverityString() + "</priority>" +
198 "<parameters><parameter>" +
199 "<key>" + param.getName() + "</key>" +
200 "<value>20</value>" +
201 "</parameter></parameters>" +
202 "</rule></rules></profile>");
206 public void restore_backup_on_the_profile_specified_in_backup() {
207 Reader backup = new StringReader(EMPTY_BACKUP);
209 QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, (String) null);
211 assertThat(summary.profile().getName()).isEqualTo("foo");
212 assertThat(summary.profile().getLanguage()).isEqualTo("js");
214 assertThat(reset.calledProfile.getKee()).isEqualTo(summary.profile().getKee());
215 assertThat(reset.calledActivations).isEmpty();
219 public void restore_detects_deprecated_rule_keys() {
220 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
221 db.rules().insertDeprecatedKey(c -> c.setRuleUuid(ruleUuid).setOldRuleKey("oldkey").setOldRepositoryKey("oldrepo"));
223 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
224 "<profile><name>foo</name>" +
225 "<language>js</language>" +
228 "<repositoryKey>oldrepo</repositoryKey>" +
229 "<key>oldkey</key>" +
230 "<priority>BLOCKER</priority>" +
232 "<parameter><key>bar</key><value>baz</value></parameter>" +
238 underTest.restore(db.getSession(), backup, (String) null);
240 assertThat(reset.calledActivations).hasSize(1);
241 RuleActivation activation = reset.calledActivations.get(0);
242 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
243 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
244 assertThat(activation.getParameter("bar")).isEqualTo("baz");
248 public void restore_ignores_deprecated_rule_keys_if_new_key_is_already_present() {
249 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
250 db.rules().insertDeprecatedKey(c -> c.setRuleUuid(ruleUuid).setOldRuleKey("oldkey").setOldRepositoryKey("oldrepo"));
252 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
253 "<profile><name>foo</name>" +
254 "<language>js</language>" +
257 "<repositoryKey>oldrepo</repositoryKey>" +
258 "<key>oldkey</key>" +
259 "<priority>MAJOR</priority>" +
261 "<parameter><key>bar</key><value>baz</value></parameter>" +
265 "<repositoryKey>sonarjs</repositoryKey>" +
267 "<priority>BLOCKER</priority>" +
269 "<parameter><key>bar2</key><value>baz2</value></parameter>" +
275 underTest.restore(db.getSession(), backup, (String) null);
277 assertThat(reset.calledActivations).hasSize(1);
278 RuleActivation activation = reset.calledActivations.get(0);
279 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
280 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
281 assertThat(activation.getParameter("bar2")).isEqualTo("baz2");
285 public void restore_backup_on_profile_having_different_name() {
286 Reader backup = new StringReader(EMPTY_BACKUP);
288 QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, "bar");
290 assertThat(summary.profile().getName()).isEqualTo("bar");
291 assertThat(summary.profile().getLanguage()).isEqualTo("js");
293 assertThat(reset.calledProfile.getKee()).isEqualTo(summary.profile().getKee());
294 assertThat(reset.calledActivations).isEmpty();
298 public void restore_resets_the_activated_rules() {
299 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
300 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
301 "<profile><name>foo</name>" +
302 "<language>js</language>" +
305 "<repositoryKey>sonarjs</repositoryKey>" +
307 "<priority>BLOCKER</priority>" +
309 "<parameter><key>bar</key><value>baz</value></parameter>" +
315 underTest.restore(db.getSession(), backup, (String) null);
317 assertThat(reset.calledActivations).hasSize(1);
318 RuleActivation activation = reset.calledActivations.get(0);
319 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
320 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
321 assertThat(activation.getParameter("bar")).isEqualTo("baz");
325 public void restore_custom_rule() {
326 when(ruleCreator.create(any(), anyList())).then(invocation -> Collections.singletonList(db.rules().insert(RuleKey.of("sonarjs", "s001"))));
328 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
330 "<name>custom rule</name>" +
331 "<language>js</language>" +
333 "<repositoryKey>sonarjs</repositoryKey>" +
335 "<type>CODE_SMELL</type>" +
336 "<priority>CRITICAL</priority>" +
337 "<name>custom rule name</name>" +
338 "<templateKey>rule_mc8</templateKey>" +
339 "<description>custom rule description</description>" +
340 "<parameters><parameter>" +
342 "<value>baz</value>" +
345 "</rule></rules></profile>");
347 underTest.restore(db.getSession(), backup, (String) null);
349 assertThat(reset.calledActivations).hasSize(1);
350 RuleActivation activation = reset.calledActivations.get(0);
351 assertThat(activation.getSeverity()).isEqualTo("CRITICAL");
352 assertThat(activation.getParameter("bar")).isEqualTo("baz");
356 public void restore_skips_rule_without_template_key_and_db_definition() {
357 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
358 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
359 "<profile><name>foo</name>" +
360 "<language>js</language>" +
363 "<repositoryKey>sonarjs</repositoryKey>" +
365 "<priority>BLOCKER</priority>" +
367 "<parameter><key>bar</key><value>baz</value></parameter>" +
371 "<repositoryKey>sonarjs</repositoryKey>" +
373 "<priority>MAJOR</priority>" +
378 underTest.restore(db.getSession(), backup, (String) null);
380 assertThat(reset.calledActivations).hasSize(1);
381 RuleActivation activation = reset.calledActivations.get(0);
382 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
383 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
384 assertThat(activation.getParameter("bar")).isEqualTo("baz");
388 public void copy_profile() {
389 RuleDto rule = createRule();
390 RuleParamDto param = db.rules().insertRuleParam(rule);
391 QProfileDto from = createProfile(rule.getLanguage());
392 ActiveRuleDto activeRule = activate(from, rule, param);
394 QProfileDto to = createProfile(rule.getLanguage());
395 underTest.copy(db.getSession(), from, to);
397 assertThat(reset.calledActivations).extracting(RuleActivation::getRuleUuid).containsOnly(activeRule.getRuleUuid());
398 assertThat(reset.calledActivations.get(0).getParameter(param.getName())).isEqualTo("20");
399 assertThat(reset.calledProfile).isEqualTo(to);
403 public void copy_profile_with_custom_rule() {
404 RuleDto templateRule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
405 .setIsTemplate(true));
406 RuleDto rule = db.rules().insert(
407 newRule(createDefaultRuleDescriptionSection(UuidFactoryFast.getInstance().create(), "custom rule description"))
408 .setName("custom rule name")
409 .setStatus(RuleStatus.READY)
410 .setTemplateUuid(templateRule.getUuid()));
412 RuleParamDto param = db.rules().insertRuleParam(rule);
413 QProfileDto from = createProfile(rule.getLanguage());
414 ActiveRuleDto activeRule = activate(from, rule, param);
416 QProfileDto to = createProfile(rule.getLanguage());
417 underTest.copy(db.getSession(), from, to);
419 assertThat(reset.calledActivations).extracting(RuleActivation::getRuleUuid).containsOnly(activeRule.getRuleUuid());
420 assertThat(reset.calledActivations.get(0).getParameter(param.getName())).isEqualTo("20");
421 assertThat(reset.calledProfile).isEqualTo(to);
425 public void fail_to_restore_if_bad_xml_format() {
426 DbSession session = db.getSession();
427 StringReader backup = new StringReader("<rules><rule></rules>");
428 IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
429 assertThat(thrown).hasMessage("Backup XML is not valid. Root element must be <profile>.");
430 assertThat(reset.calledProfile).isNull();
434 public void fail_to_restore_if_not_xml_backup() {
435 DbSession session = db.getSession();
436 StringReader backup = new StringReader("foo");
437 assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
438 assertThat(reset.calledProfile).isNull();
442 public void fail_to_restore_if_xml_is_not_well_formed() {
443 assertThatThrownBy(() -> {
444 String notWellFormedXml = "<?xml version='1.0' encoding='UTF-8'?><profile><name>\"profil\"</name><language>\"language\"</language><rules/></profile";
446 underTest.restore(db.getSession(), new StringReader(notWellFormedXml), (String) null);
448 .isInstanceOf(IllegalArgumentException.class)
449 .hasMessage("Fail to restore Quality profile backup, XML document is not well formed");
453 public void fail_to_restore_if_duplicate_rule() throws Exception {
454 DbSession session = db.getSession();
455 String xml = Resources.toString(getClass().getResource("QProfileBackuperIT/duplicates-xml-backup.xml"), UTF_8);
456 StringReader backup = new StringReader(xml);
457 IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
458 assertThat(thrown).hasMessage("The quality profile cannot be restored as it contains duplicates for the following rules: xoo:x1, xoo:x2");
459 assertThat(reset.calledProfile).isNull();
463 public void fail_to_restore_external_rule() {
464 db.rules().insert(RuleKey.of("sonarjs", "s001"), r -> r.setIsExternal(true));
465 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
466 "<profile><name>foo</name>" +
467 "<language>js</language>" +
470 "<repositoryKey>sonarjs</repositoryKey>" +
472 "<priority>BLOCKER</priority>" +
474 "<parameter><key>bar</key><value>baz</value></parameter>" +
480 assertThatThrownBy(() -> {
481 underTest.restore(db.getSession(), backup, (String) null);
483 .isInstanceOf(IllegalArgumentException.class)
484 .hasMessage("The quality profile cannot be restored as it contains rules from external rule engines: sonarjs:s001");
487 private RuleDto createRule() {
488 return db.rules().insert();
491 private QProfileDto createProfile(String language) {
492 return db.qualityProfiles().insert(p -> p.setLanguage(language));
495 private ActiveRuleDto activate(QProfileDto profile, RuleDto rule) {
496 return db.qualityProfiles().activateRule(profile, rule);
499 private ActiveRuleDto activate(QProfileDto profile, RuleDto rule, RuleParamDto param) {
500 ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile, rule);
501 ActiveRuleParamDto dto = ActiveRuleParamDto.createFor(param)
503 .setActiveRuleUuid(activeRule.getUuid());
504 db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRule, dto);
508 private static class DummyReset implements QProfileReset {
509 private QProfileDto calledProfile;
510 private List<RuleActivation> calledActivations;
513 public BulkChangeResult reset(DbSession dbSession, QProfileDto profile, Collection<RuleActivation> activations) {
514 this.calledProfile = profile;
515 this.calledActivations = new ArrayList<>(activations);
516 return new BulkChangeResult();
520 private static class DummyProfileFactory implements QProfileFactory {
522 public QProfileDto getOrCreateCustom(DbSession dbSession, QProfileName key) {
523 return QualityProfileTesting.newQualityProfileDto()
524 .setLanguage(key.getLanguage())
525 .setName(key.getName());
529 public QProfileDto checkAndCreateCustom(DbSession dbSession, QProfileName name) {
530 throw new UnsupportedOperationException();
534 public QProfileDto createCustom(DbSession dbSession, QProfileName name, @Nullable String parentKey) {
535 throw new UnsupportedOperationException();
539 public void delete(DbSession dbSession, Collection<QProfileDto> profiles) {
540 throw new UnsupportedOperationException();