3 * Copyright (C) 2009-2021 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.junit.rules.ExpectedException;
34 import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
35 import org.sonar.api.rule.RuleKey;
36 import org.sonar.api.rule.RuleStatus;
37 import org.sonar.api.rules.RuleType;
38 import org.sonar.api.utils.System2;
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.RuleDefinitionDto;
46 import org.sonar.db.rule.RuleParamDto;
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.junit.Assert.fail;
52 import static org.junit.rules.ExpectedException.none;
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;
58 public class QProfileBackuperImplTest {
60 private static final String EMPTY_BACKUP = "<?xml version='1.0' encoding='UTF-8'?>" +
61 "<profile><name>foo</name>" +
62 "<language>js</language>" +
66 private final System2 system2 = new AlwaysIncreasingSystem2();
69 public ExpectedException expectedException = none();
71 public DbTester db = DbTester.create(system2);
73 private final DummyReset reset = new DummyReset();
74 private final QProfileFactory profileFactory = new DummyProfileFactory();
75 private final RuleCreator ruleCreator = mock(RuleCreator.class);
77 private final QProfileBackuper underTest = new QProfileBackuperImpl(db.getDbClient(), reset, profileFactory, ruleCreator, new QProfileParser());
80 public void backup_generates_xml_file() {
81 RuleDefinitionDto rule = createRule();
82 QProfileDto profile = createProfile(rule.getLanguage());
83 ActiveRuleDto activeRule = activate(profile, rule);
85 StringWriter writer = new StringWriter();
86 underTest.backup(db.getSession(), profile, writer);
88 assertThat(writer.toString()).isEqualTo("<?xml version='1.0' encoding='UTF-8'?>" +
89 "<profile><name>" + profile.getName() + "</name>" +
90 "<language>" + profile.getLanguage() + "</language>" +
93 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
94 "<key>" + rule.getRuleKey() + "</key>" +
95 "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
96 "<priority>" + activeRule.getSeverityString() + "</priority>" +
104 public void backup_rules_having_parameters() {
105 RuleDefinitionDto rule = createRule();
106 RuleParamDto param = db.rules().insertRuleParam(rule);
107 QProfileDto profile = createProfile(rule.getLanguage());
108 ActiveRuleDto activeRule = activate(profile, rule, param);
110 StringWriter writer = new StringWriter();
111 underTest.backup(db.getSession(), profile, writer);
113 assertThat(writer.toString()).contains(
115 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
116 "<key>" + rule.getRuleKey() + "</key>" +
117 "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
118 "<priority>" + activeRule.getSeverityString() + "</priority>" +
119 "<parameters><parameter>" +
120 "<key>" + param.getName() + "</key>" +
121 "<value>20</value>" +
122 "</parameter></parameters>" +
127 public void backup_empty_profile() {
128 RuleDefinitionDto rule = createRule();
129 QProfileDto profile = createProfile(rule.getLanguage());
131 StringWriter writer = new StringWriter();
132 underTest.backup(db.getSession(), profile, writer);
134 assertThat(writer.toString()).isEqualTo("<?xml version='1.0' encoding='UTF-8'?>" +
135 "<profile><name>" + profile.getName() + "</name>" +
136 "<language>" + profile.getLanguage() + "</language>" +
142 public void backup_custom_rules_with_params() {
143 RuleDefinitionDto templateRule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
144 .setIsTemplate(true));
145 RuleDefinitionDto rule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
146 .setDescription("custom rule description")
147 .setName("custom rule name")
148 .setStatus(RuleStatus.READY)
149 .setTemplateUuid(templateRule.getUuid()));
150 RuleParamDto param = db.rules().insertRuleParam(rule);
151 QProfileDto profile = createProfile(rule.getLanguage());
152 ActiveRuleDto activeRule = activate(profile, rule, param);
154 StringWriter writer = new StringWriter();
155 underTest.backup(db.getSession(), profile, writer);
157 assertThat(writer.toString()).isEqualTo("<?xml version='1.0' encoding='UTF-8'?>" +
159 "<name>" + profile.getName() + "</name>" +
160 "<language>" + profile.getLanguage() + "</language>" +
162 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
163 "<key>" + rule.getKey().rule() + "</key>" +
164 "<type>" + RuleType.valueOf(rule.getType()) + "</type>" +
165 "<priority>" + activeRule.getSeverityString() + "</priority>" +
166 "<name>" + rule.getName() + "</name>" +
167 "<templateKey>" + templateRule.getKey().rule() + "</templateKey>" +
168 "<description>" + rule.getDescription() + "</description>" +
169 "<parameters><parameter>" +
170 "<key>" + param.getName() + "</key>" +
171 "<value>20</value>" +
172 "</parameter></parameters>" +
173 "</rule></rules></profile>");
177 public void restore_backup_on_the_profile_specified_in_backup() {
178 Reader backup = new StringReader(EMPTY_BACKUP);
180 QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, (String) null);
182 assertThat(summary.getProfile().getName()).isEqualTo("foo");
183 assertThat(summary.getProfile().getLanguage()).isEqualTo("js");
185 assertThat(reset.calledProfile.getKee()).isEqualTo(summary.getProfile().getKee());
186 assertThat(reset.calledActivations).isEmpty();
190 public void restore_backup_on_profile_having_different_name() {
191 Reader backup = new StringReader(EMPTY_BACKUP);
193 QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, "bar");
195 assertThat(summary.getProfile().getName()).isEqualTo("bar");
196 assertThat(summary.getProfile().getLanguage()).isEqualTo("js");
198 assertThat(reset.calledProfile.getKee()).isEqualTo(summary.getProfile().getKee());
199 assertThat(reset.calledActivations).isEmpty();
203 public void restore_resets_the_activated_rules() {
204 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
205 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
206 "<profile><name>foo</name>" +
207 "<language>js</language>" +
210 "<repositoryKey>sonarjs</repositoryKey>" +
212 "<priority>BLOCKER</priority>" +
214 "<parameter><key>bar</key><value>baz</value></parameter>" +
220 underTest.restore(db.getSession(), backup, (String) null);
222 assertThat(reset.calledActivations).hasSize(1);
223 RuleActivation activation = reset.calledActivations.get(0);
224 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
225 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
226 assertThat(activation.getParameter("bar")).isEqualTo("baz");
230 public void restore_custom_rule() {
231 when(ruleCreator.create(any(), anyList())).then(invocation -> Collections.singletonList(db.rules().insert(RuleKey.of("sonarjs", "s001")).getKey()));
233 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
235 "<name>custom rule</name>" +
236 "<language>js</language>" +
238 "<repositoryKey>sonarjs</repositoryKey>" +
240 "<type>CODE_SMELL</type>" +
241 "<priority>CRITICAL</priority>" +
242 "<name>custom rule name</name>" +
243 "<templateKey>rule_mc8</templateKey>" +
244 "<description>custom rule description</description>" +
245 "<parameters><parameter>" +
247 "<value>baz</value>" +
250 "</rule></rules></profile>");
252 underTest.restore(db.getSession(), backup, (String) null);
254 assertThat(reset.calledActivations).hasSize(1);
255 RuleActivation activation = reset.calledActivations.get(0);
256 assertThat(activation.getSeverity()).isEqualTo("CRITICAL");
257 assertThat(activation.getParameter("bar")).isEqualTo("baz");
261 public void restore_skips_rule_without_template_key_and_db_definition() {
262 String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
263 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
264 "<profile><name>foo</name>" +
265 "<language>js</language>" +
268 "<repositoryKey>sonarjs</repositoryKey>" +
270 "<priority>BLOCKER</priority>" +
272 "<parameter><key>bar</key><value>baz</value></parameter>" +
276 "<repositoryKey>sonarjs</repositoryKey>" +
278 "<priority>MAJOR</priority>" +
283 underTest.restore(db.getSession(), backup, (String) null);
285 assertThat(reset.calledActivations).hasSize(1);
286 RuleActivation activation = reset.calledActivations.get(0);
287 assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
288 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
289 assertThat(activation.getParameter("bar")).isEqualTo("baz");
293 public void copy_profile() {
294 RuleDefinitionDto rule = createRule();
295 RuleParamDto param = db.rules().insertRuleParam(rule);
296 QProfileDto from = createProfile(rule.getLanguage());
297 ActiveRuleDto activeRule = activate(from, rule, param);
299 QProfileDto to = createProfile(rule.getLanguage());
300 underTest.copy(db.getSession(), from, to);
302 assertThat(reset.calledActivations).extracting(RuleActivation::getRuleUuid).containsOnly(activeRule.getRuleUuid());
303 assertThat(reset.calledActivations.get(0).getParameter(param.getName())).isEqualTo("20");
304 assertThat(reset.calledProfile).isEqualTo(to);
308 public void fail_to_restore_if_bad_xml_format() {
310 underTest.restore(db.getSession(), new StringReader("<rules><rule></rules>"), (String) null);
312 } catch (IllegalArgumentException e) {
313 assertThat(e).hasMessage("Backup XML is not valid. Root element must be <profile>.");
314 assertThat(reset.calledProfile).isNull();
319 public void fail_to_restore_if_not_xml_backup() {
321 underTest.restore(db.getSession(), new StringReader("foo"), (String) null);
323 } catch (IllegalArgumentException e) {
324 assertThat(reset.calledProfile).isNull();
329 public void fail_to_restore_if_xml_is_not_well_formed() {
330 expectedException.expect(IllegalArgumentException.class);
331 expectedException.expectMessage("Fail to restore Quality profile backup, XML document is not well formed");
332 String notWellFormedXml = "<?xml version='1.0' encoding='UTF-8'?><profile><name>\"profil\"</name><language>\"language\"</language><rules/></profile";
334 underTest.restore(db.getSession(), new StringReader(notWellFormedXml), (String) null);
338 public void fail_to_restore_if_duplicate_rule() throws Exception {
340 String xml = Resources.toString(getClass().getResource("QProfileBackuperTest/duplicates-xml-backup.xml"), UTF_8);
341 underTest.restore(db.getSession(), new StringReader(xml), (String) null);
343 } catch (IllegalArgumentException e) {
344 assertThat(e).hasMessage("The quality profile cannot be restored as it contains duplicates for the following rules: xoo:x1, xoo:x2");
345 assertThat(reset.calledProfile).isNull();
350 public void fail_to_restore_external_rule() {
351 db.rules().insert(RuleKey.of("sonarjs", "s001"), r -> r.setIsExternal(true));
352 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
353 "<profile><name>foo</name>" +
354 "<language>js</language>" +
357 "<repositoryKey>sonarjs</repositoryKey>" +
359 "<priority>BLOCKER</priority>" +
361 "<parameter><key>bar</key><value>baz</value></parameter>" +
367 expectedException.expect(IllegalArgumentException.class);
368 expectedException.expectMessage("The quality profile cannot be restored as it contains rules from external rule engines: sonarjs:s001");
369 underTest.restore(db.getSession(), backup, (String) null);
372 private RuleDefinitionDto createRule() {
373 return db.rules().insert();
376 private QProfileDto createProfile(String language) {
377 return db.qualityProfiles().insert(p -> p.setLanguage(language));
380 private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule) {
381 return db.qualityProfiles().activateRule(profile, rule);
384 private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule, RuleParamDto param) {
385 ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile, rule);
386 ActiveRuleParamDto dto = ActiveRuleParamDto.createFor(param)
388 .setActiveRuleUuid(activeRule.getUuid());
389 db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRule, dto);
393 private static class DummyReset implements QProfileReset {
394 private QProfileDto calledProfile;
395 private List<RuleActivation> calledActivations;
398 public BulkChangeResult reset(DbSession dbSession, QProfileDto profile, Collection<RuleActivation> activations) {
399 this.calledProfile = profile;
400 this.calledActivations = new ArrayList<>(activations);
401 return new BulkChangeResult();
405 private static class DummyProfileFactory implements QProfileFactory {
407 public QProfileDto getOrCreateCustom(DbSession dbSession, QProfileName key) {
408 return QualityProfileTesting.newQualityProfileDto()
409 .setLanguage(key.getLanguage())
410 .setName(key.getName());
414 public QProfileDto checkAndCreateCustom(DbSession dbSession, QProfileName name) {
415 throw new UnsupportedOperationException();
419 public QProfileDto createCustom(DbSession dbSession, QProfileName name, @Nullable String parentKey) {
420 throw new UnsupportedOperationException();
424 public void delete(DbSession dbSession, Collection<QProfileDto> profiles) {
425 throw new UnsupportedOperationException();