3 * Copyright (C) 2009-2022 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.db.DbSession;
39 import org.sonar.db.DbTester;
40 import org.sonar.db.qualityprofile.ActiveRuleDto;
41 import org.sonar.db.qualityprofile.ActiveRuleParamDto;
42 import org.sonar.db.qualityprofile.QProfileDto;
43 import org.sonar.db.qualityprofile.QualityProfileTesting;
44 import org.sonar.db.rule.RuleDefinitionDto;
45 import org.sonar.db.rule.RuleParamDto;
46 import org.sonar.server.qualityprofile.builtin.QProfileName;
47 import org.sonar.server.rule.RuleCreator;
49 import static java.nio.charset.StandardCharsets.UTF_8;
50 import static org.assertj.core.api.Assertions.assertThat;
51 import static org.assertj.core.api.Assertions.assertThatThrownBy;
52 import static org.junit.Assert.assertThrows;
53 import static org.mockito.ArgumentMatchers.any;
54 import static org.mockito.ArgumentMatchers.anyList;
55 import static org.mockito.Mockito.mock;
56 import static org.mockito.Mockito.when;
57 import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
59 public class QProfileBackuperImplTest {
61 private static final String EMPTY_BACKUP = "<?xml version='1.0' encoding='UTF-8'?>" +
62 "<profile><name>foo</name>" +
63 "<language>js</language>" +
67 private final System2 system2 = new AlwaysIncreasingSystem2();
70 public DbTester db = DbTester.create(system2);
72 private final DummyReset reset = new DummyReset();
73 private final QProfileFactory profileFactory = new DummyProfileFactory();
74 private final RuleCreator ruleCreator = mock(RuleCreator.class);
76 private final QProfileBackuper underTest = new QProfileBackuperImpl(db.getDbClient(), reset, profileFactory, ruleCreator, new QProfileParser());
79 public void backup_generates_xml_file() {
80 RuleDefinitionDto rule = createRule();
81 QProfileDto profile = createProfile(rule.getLanguage());
82 ActiveRuleDto activeRule = activate(profile, rule);
84 StringWriter writer = new StringWriter();
85 underTest.backup(db.getSession(), profile, writer);
87 assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
88 "<profile><name>" + profile.getName() + "</name>" +
89 "<language>" + profile.getLanguage() + "</language>" +
92 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
93 "<key>" + rule.getRuleKey() + "</key>" +
94 "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
95 "<priority>" + activeRule.getSeverityString() + "</priority>" +
96 "<parameters></parameters>" +
103 public void backup_rules_having_parameters() {
104 RuleDefinitionDto rule = createRule();
105 RuleParamDto param = db.rules().insertRuleParam(rule);
106 QProfileDto profile = createProfile(rule.getLanguage());
107 ActiveRuleDto activeRule = activate(profile, rule, param);
109 StringWriter writer = new StringWriter();
110 underTest.backup(db.getSession(), profile, writer);
112 assertThat(writer.toString()).contains(
114 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
115 "<key>" + rule.getRuleKey() + "</key>" +
116 "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
117 "<priority>" + activeRule.getSeverityString() + "</priority>" +
118 "<parameters><parameter>" +
119 "<key>" + param.getName() + "</key>" +
120 "<value>20</value>" +
121 "</parameter></parameters>" +
126 public void backup_empty_profile() {
127 RuleDefinitionDto rule = createRule();
128 QProfileDto profile = createProfile(rule.getLanguage());
130 StringWriter writer = new StringWriter();
131 underTest.backup(db.getSession(), profile, writer);
133 assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
134 "<profile><name>" + profile.getName() + "</name>" +
135 "<language>" + profile.getLanguage() + "</language>" +
141 public void backup_custom_rules_with_params() {
142 RuleDefinitionDto templateRule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
143 .setIsTemplate(true));
144 RuleDefinitionDto rule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
145 .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("custom rule description"))
146 .setName("custom rule name")
147 .setStatus(RuleStatus.READY)
148 .setTemplateUuid(templateRule.getUuid()));
149 RuleParamDto param = db.rules().insertRuleParam(rule);
150 QProfileDto profile = createProfile(rule.getLanguage());
151 ActiveRuleDto activeRule = activate(profile, rule, param);
153 StringWriter writer = new StringWriter();
154 underTest.backup(db.getSession(), profile, writer);
156 assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
158 "<name>" + profile.getName() + "</name>" +
159 "<language>" + profile.getLanguage() + "</language>" +
161 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
162 "<key>" + rule.getKey().rule() + "</key>" +
163 "<type>" + RuleType.valueOf(rule.getType()) + "</type>" +
164 "<priority>" + activeRule.getSeverityString() + "</priority>" +
165 "<name>" + rule.getName() + "</name>" +
166 "<templateKey>" + templateRule.getKey().rule() + "</templateKey>" +
167 "<description>" + rule.getDefaultRuleDescriptionSectionDto().getDescription() + "</description>" +
168 "<parameters><parameter>" +
169 "<key>" + param.getName() + "</key>" +
170 "<value>20</value>" +
171 "</parameter></parameters>" +
172 "</rule></rules></profile>");
176 public void restore_backup_on_the_profile_specified_in_backup() {
177 Reader backup = new StringReader(EMPTY_BACKUP);
179 QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, (String) null);
181 assertThat(summary.getProfile().getName()).isEqualTo("foo");
182 assertThat(summary.getProfile().getLanguage()).isEqualTo("js");
184 assertThat(reset.calledProfile.getKee()).isEqualTo(summary.getProfile().getKee());
185 assertThat(reset.calledActivations).isEmpty();
189 public void restore_detects_deprecated_rule_keys() {
190 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
191 db.rules().insertDeprecatedKey(c -> c.setRuleUuid(ruleUuid).setOldRuleKey("oldkey").setOldRepositoryKey("oldrepo"));
193 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
194 "<profile><name>foo</name>" +
195 "<language>js</language>" +
198 "<repositoryKey>oldrepo</repositoryKey>" +
199 "<key>oldkey</key>" +
200 "<priority>BLOCKER</priority>" +
202 "<parameter><key>bar</key><value>baz</value></parameter>" +
208 underTest.restore(db.getSession(), backup, (String) null);
210 assertThat(reset.calledActivations).hasSize(1);
211 RuleActivation activation = reset.calledActivations.get(0);
212 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
213 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
214 assertThat(activation.getParameter("bar")).isEqualTo("baz");
218 public void restore_ignores_deprecated_rule_keys_if_new_key_is_already_present() {
219 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
220 db.rules().insertDeprecatedKey(c -> c.setRuleUuid(ruleUuid).setOldRuleKey("oldkey").setOldRepositoryKey("oldrepo"));
222 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
223 "<profile><name>foo</name>" +
224 "<language>js</language>" +
227 "<repositoryKey>oldrepo</repositoryKey>" +
228 "<key>oldkey</key>" +
229 "<priority>MAJOR</priority>" +
231 "<parameter><key>bar</key><value>baz</value></parameter>" +
235 "<repositoryKey>sonarjs</repositoryKey>" +
237 "<priority>BLOCKER</priority>" +
239 "<parameter><key>bar2</key><value>baz2</value></parameter>" +
245 underTest.restore(db.getSession(), backup, (String) null);
247 assertThat(reset.calledActivations).hasSize(1);
248 RuleActivation activation = reset.calledActivations.get(0);
249 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
250 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
251 assertThat(activation.getParameter("bar2")).isEqualTo("baz2");
255 public void restore_backup_on_profile_having_different_name() {
256 Reader backup = new StringReader(EMPTY_BACKUP);
258 QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, "bar");
260 assertThat(summary.getProfile().getName()).isEqualTo("bar");
261 assertThat(summary.getProfile().getLanguage()).isEqualTo("js");
263 assertThat(reset.calledProfile.getKee()).isEqualTo(summary.getProfile().getKee());
264 assertThat(reset.calledActivations).isEmpty();
268 public void restore_resets_the_activated_rules() {
269 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
270 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
271 "<profile><name>foo</name>" +
272 "<language>js</language>" +
275 "<repositoryKey>sonarjs</repositoryKey>" +
277 "<priority>BLOCKER</priority>" +
279 "<parameter><key>bar</key><value>baz</value></parameter>" +
285 underTest.restore(db.getSession(), backup, (String) null);
287 assertThat(reset.calledActivations).hasSize(1);
288 RuleActivation activation = reset.calledActivations.get(0);
289 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
290 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
291 assertThat(activation.getParameter("bar")).isEqualTo("baz");
295 public void restore_custom_rule() {
296 when(ruleCreator.create(any(), anyList())).then(invocation -> Collections.singletonList(db.rules().insert(RuleKey.of("sonarjs", "s001")).getKey()));
298 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
300 "<name>custom rule</name>" +
301 "<language>js</language>" +
303 "<repositoryKey>sonarjs</repositoryKey>" +
305 "<type>CODE_SMELL</type>" +
306 "<priority>CRITICAL</priority>" +
307 "<name>custom rule name</name>" +
308 "<templateKey>rule_mc8</templateKey>" +
309 "<description>custom rule description</description>" +
310 "<parameters><parameter>" +
312 "<value>baz</value>" +
315 "</rule></rules></profile>");
317 underTest.restore(db.getSession(), backup, (String) null);
319 assertThat(reset.calledActivations).hasSize(1);
320 RuleActivation activation = reset.calledActivations.get(0);
321 assertThat(activation.getSeverity()).isEqualTo("CRITICAL");
322 assertThat(activation.getParameter("bar")).isEqualTo("baz");
326 public void restore_skips_rule_without_template_key_and_db_definition() {
327 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
328 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
329 "<profile><name>foo</name>" +
330 "<language>js</language>" +
333 "<repositoryKey>sonarjs</repositoryKey>" +
335 "<priority>BLOCKER</priority>" +
337 "<parameter><key>bar</key><value>baz</value></parameter>" +
341 "<repositoryKey>sonarjs</repositoryKey>" +
343 "<priority>MAJOR</priority>" +
348 underTest.restore(db.getSession(), backup, (String) null);
350 assertThat(reset.calledActivations).hasSize(1);
351 RuleActivation activation = reset.calledActivations.get(0);
352 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
353 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
354 assertThat(activation.getParameter("bar")).isEqualTo("baz");
358 public void copy_profile() {
359 RuleDefinitionDto rule = createRule();
360 RuleParamDto param = db.rules().insertRuleParam(rule);
361 QProfileDto from = createProfile(rule.getLanguage());
362 ActiveRuleDto activeRule = activate(from, rule, param);
364 QProfileDto to = createProfile(rule.getLanguage());
365 underTest.copy(db.getSession(), from, to);
367 assertThat(reset.calledActivations).extracting(RuleActivation::getRuleUuid).containsOnly(activeRule.getRuleUuid());
368 assertThat(reset.calledActivations.get(0).getParameter(param.getName())).isEqualTo("20");
369 assertThat(reset.calledProfile).isEqualTo(to);
373 public void fail_to_restore_if_bad_xml_format() {
374 DbSession session = db.getSession();
375 StringReader backup = new StringReader("<rules><rule></rules>");
376 IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
377 assertThat(thrown).hasMessage("Backup XML is not valid. Root element must be <profile>.");
378 assertThat(reset.calledProfile).isNull();
382 public void fail_to_restore_if_not_xml_backup() {
383 DbSession session = db.getSession();
384 StringReader backup = new StringReader("foo");
385 assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
386 assertThat(reset.calledProfile).isNull();
390 public void fail_to_restore_if_xml_is_not_well_formed() {
391 assertThatThrownBy(() -> {
392 String notWellFormedXml = "<?xml version='1.0' encoding='UTF-8'?><profile><name>\"profil\"</name><language>\"language\"</language><rules/></profile";
394 underTest.restore(db.getSession(), new StringReader(notWellFormedXml), (String) null);
396 .isInstanceOf(IllegalArgumentException.class)
397 .hasMessage("Fail to restore Quality profile backup, XML document is not well formed");
401 public void fail_to_restore_if_duplicate_rule() throws Exception {
402 DbSession session = db.getSession();
403 String xml = Resources.toString(getClass().getResource("QProfileBackuperTest/duplicates-xml-backup.xml"), UTF_8);
404 StringReader backup = new StringReader(xml);
405 IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
406 assertThat(thrown).hasMessage("The quality profile cannot be restored as it contains duplicates for the following rules: xoo:x1, xoo:x2");
407 assertThat(reset.calledProfile).isNull();
411 public void fail_to_restore_external_rule() {
412 db.rules().insert(RuleKey.of("sonarjs", "s001"), r -> r.setIsExternal(true));
413 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
414 "<profile><name>foo</name>" +
415 "<language>js</language>" +
418 "<repositoryKey>sonarjs</repositoryKey>" +
420 "<priority>BLOCKER</priority>" +
422 "<parameter><key>bar</key><value>baz</value></parameter>" +
428 assertThatThrownBy(() -> {
429 underTest.restore(db.getSession(), backup, (String) null);
431 .isInstanceOf(IllegalArgumentException.class)
432 .hasMessage("The quality profile cannot be restored as it contains rules from external rule engines: sonarjs:s001");
435 private RuleDefinitionDto createRule() {
436 return db.rules().insert();
439 private QProfileDto createProfile(String language) {
440 return db.qualityProfiles().insert(p -> p.setLanguage(language));
443 private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule) {
444 return db.qualityProfiles().activateRule(profile, rule);
447 private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule, RuleParamDto param) {
448 ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile, rule);
449 ActiveRuleParamDto dto = ActiveRuleParamDto.createFor(param)
451 .setActiveRuleUuid(activeRule.getUuid());
452 db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRule, dto);
456 private static class DummyReset implements QProfileReset {
457 private QProfileDto calledProfile;
458 private List<RuleActivation> calledActivations;
461 public BulkChangeResult reset(DbSession dbSession, QProfileDto profile, Collection<RuleActivation> activations) {
462 this.calledProfile = profile;
463 this.calledActivations = new ArrayList<>(activations);
464 return new BulkChangeResult();
468 private static class DummyProfileFactory implements QProfileFactory {
470 public QProfileDto getOrCreateCustom(DbSession dbSession, QProfileName key) {
471 return QualityProfileTesting.newQualityProfileDto()
472 .setLanguage(key.getLanguage())
473 .setName(key.getName());
477 public QProfileDto checkAndCreateCustom(DbSession dbSession, QProfileName name) {
478 throw new UnsupportedOperationException();
482 public QProfileDto createCustom(DbSession dbSession, QProfileName name, @Nullable String parentKey) {
483 throw new UnsupportedOperationException();
487 public void delete(DbSession dbSession, Collection<QProfileDto> profiles) {
488 throw new UnsupportedOperationException();