3 * Copyright (C) 2009-2019 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 org.junit.Rule;
31 import org.junit.Test;
32 import org.junit.rules.ExpectedException;
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.organization.OrganizationDto;
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.RuleMetadataDto;
47 import org.sonar.db.rule.RuleParamDto;
48 import org.sonar.server.rule.RuleCreator;
50 import static java.nio.charset.StandardCharsets.UTF_8;
51 import static org.assertj.core.api.Assertions.assertThat;
52 import static org.junit.Assert.fail;
53 import static org.junit.rules.ExpectedException.none;
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;
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 System2 system2 = new AlwaysIncreasingSystem2();
70 public ExpectedException expectedException = none();
72 public DbTester db = DbTester.create(system2);
74 private DummyReset reset = new DummyReset();
75 private QProfileFactory profileFactory = new DummyProfileFactory();
76 private RuleCreator ruleCreator = mock(RuleCreator.class);
78 private QProfileBackuper underTest = new QProfileBackuperImpl(db.getDbClient(), reset, profileFactory, ruleCreator, new QProfileParser());
81 public void backup_generates_xml_file() {
82 RuleDefinitionDto rule = createRule();
83 QProfileDto profile = createProfile(rule);
84 ActiveRuleDto activeRule = activate(profile, rule);
86 StringWriter writer = new StringWriter();
87 underTest.backup(db.getSession(), profile, writer);
89 assertThat(writer.toString()).isEqualTo("<?xml version='1.0' encoding='UTF-8'?>" +
90 "<profile><name>" + profile.getName() + "</name>" +
91 "<language>" + profile.getLanguage() + "</language>" +
94 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
95 "<key>" + rule.getRuleKey() + "</key>" +
96 "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
97 "<priority>" + activeRule.getSeverityString() + "</priority>" +
105 public void backup_rules_having_parameters() {
106 RuleDefinitionDto rule = createRule();
107 RuleParamDto param = db.rules().insertRuleParam(rule);
108 QProfileDto profile = createProfile(rule);
109 ActiveRuleDto activeRule = activate(profile, rule, param);
111 StringWriter writer = new StringWriter();
112 underTest.backup(db.getSession(), profile, writer);
114 assertThat(writer.toString()).contains(
116 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
117 "<key>" + rule.getRuleKey() + "</key>" +
118 "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
119 "<priority>" + activeRule.getSeverityString() + "</priority>" +
120 "<parameters><parameter>" +
121 "<key>" + param.getName() + "</key>" +
122 "<value>20</value>" +
123 "</parameter></parameters>" +
128 public void backup_empty_profile() {
129 RuleDefinitionDto rule = createRule();
130 QProfileDto profile = createProfile(rule);
132 StringWriter writer = new StringWriter();
133 underTest.backup(db.getSession(), profile, writer);
135 assertThat(writer.toString()).isEqualTo("<?xml version='1.0' encoding='UTF-8'?>" +
136 "<profile><name>" + profile.getName() + "</name>" +
137 "<language>" + profile.getLanguage() + "</language>" +
143 public void backup_custom_rules_with_params() {
144 RuleDefinitionDto templateRule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
145 .setIsTemplate(true));
146 RuleDefinitionDto rule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
147 .setDescription("custom rule description")
148 .setName("custom rule name")
149 .setStatus(RuleStatus.READY)
150 .setTemplateId(templateRule.getId()));
151 RuleParamDto param = db.rules().insertRuleParam(rule);
152 QProfileDto profile = createProfile(rule);
153 ActiveRuleDto activeRule = activate(profile, rule, param);
155 StringWriter writer = new StringWriter();
156 underTest.backup(db.getSession(), profile, writer);
158 assertThat(writer.toString()).isEqualTo("<?xml version='1.0' encoding='UTF-8'?>" +
160 "<name>" + profile.getName() + "</name>" +
161 "<language>" + profile.getLanguage() + "</language>" +
163 "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
164 "<key>" + rule.getKey().rule() + "</key>" +
165 "<type>" + RuleType.valueOf(rule.getType()) + "</type>" +
166 "<priority>" + activeRule.getSeverityString() + "</priority>" +
167 "<name>" + rule.getName() + "</name>" +
168 "<templateKey>" + templateRule.getKey().rule() + "</templateKey>" +
169 "<description>" + rule.getDescription() + "</description>" +
170 "<parameters><parameter>" +
171 "<key>" + param.getName() + "</key>" +
172 "<value>20</value>" +
173 "</parameter></parameters>" +
174 "</rule></rules></profile>");
178 public void restore_backup_on_the_profile_specified_in_backup() {
179 OrganizationDto organization = db.organizations().insert();
180 Reader backup = new StringReader(EMPTY_BACKUP);
182 QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, organization, null);
184 assertThat(summary.getProfile().getName()).isEqualTo("foo");
185 assertThat(summary.getProfile().getLanguage()).isEqualTo("js");
187 assertThat(reset.calledProfile.getKee()).isEqualTo(summary.getProfile().getKee());
188 assertThat(reset.calledActivations).isEmpty();
192 public void restore_backup_on_profile_having_different_name() {
193 OrganizationDto organization = db.organizations().insert();
194 Reader backup = new StringReader(EMPTY_BACKUP);
196 QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, organization, "bar");
198 assertThat(summary.getProfile().getName()).isEqualTo("bar");
199 assertThat(summary.getProfile().getLanguage()).isEqualTo("js");
201 assertThat(reset.calledProfile.getKee()).isEqualTo(summary.getProfile().getKee());
202 assertThat(reset.calledActivations).isEmpty();
206 public void restore_resets_the_activated_rules() {
207 Integer ruleId = db.rules().insert(RuleKey.of("sonarjs", "s001")).getId();
208 OrganizationDto organization = db.organizations().insert();
209 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
210 "<profile><name>foo</name>" +
211 "<language>js</language>" +
214 "<repositoryKey>sonarjs</repositoryKey>" +
216 "<priority>BLOCKER</priority>" +
218 "<parameter><key>bar</key><value>baz</value></parameter>" +
224 underTest.restore(db.getSession(), backup, organization, null);
226 assertThat(reset.calledActivations).hasSize(1);
227 RuleActivation activation = reset.calledActivations.get(0);
228 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
229 assertThat(activation.getRuleId()).isEqualTo(ruleId);
230 assertThat(activation.getParameter("bar")).isEqualTo("baz");
234 public void restore_custom_rule() {
235 when(ruleCreator.create(any(), anyList())).then(invocation ->
236 Collections.singletonList(db.rules().insert(RuleKey.of("sonarjs", "s001")).getKey()));
238 OrganizationDto organization = db.organizations().insert();
239 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
241 "<name>custom rule</name>" +
242 "<language>js</language>" +
244 "<repositoryKey>sonarjs</repositoryKey>" +
246 "<type>CODE_SMELL</type>" +
247 "<priority>CRITICAL</priority>" +
248 "<name>custom rule name</name>" +
249 "<templateKey>rule_mc8</templateKey>" +
250 "<description>custom rule description</description>" +
251 "<parameters><parameter>" +
253 "<value>baz</value>" +
256 "</rule></rules></profile>");
258 underTest.restore(db.getSession(), backup, organization, null);
260 assertThat(reset.calledActivations).hasSize(1);
261 RuleActivation activation = reset.calledActivations.get(0);
262 assertThat(activation.getSeverity()).isEqualTo("CRITICAL");
263 assertThat(activation.getParameter("bar")).isEqualTo("baz");
267 public void restore_skips_rule_without_template_key_and_db_definition() {
268 Integer ruleId = db.rules().insert(RuleKey.of("sonarjs", "s001")).getId();
269 OrganizationDto organization = db.organizations().insert();
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>" +
283 "<repositoryKey>sonarjs</repositoryKey>" +
285 "<priority>MAJOR</priority>" +
290 underTest.restore(db.getSession(), backup, organization, null);
292 assertThat(reset.calledActivations).hasSize(1);
293 RuleActivation activation = reset.calledActivations.get(0);
294 assertThat(activation.getRuleId()).isEqualTo(ruleId);
295 assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
296 assertThat(activation.getParameter("bar")).isEqualTo("baz");
300 public void fail_to_restore_if_bad_xml_format() {
301 OrganizationDto organization = db.organizations().insert();
303 underTest.restore(db.getSession(), new StringReader("<rules><rule></rules>"), organization, null);
305 } catch (IllegalArgumentException e) {
306 assertThat(e).hasMessage("Backup XML is not valid. Root element must be <profile>.");
307 assertThat(reset.calledProfile).isNull();
312 public void fail_to_restore_if_not_xml_backup() {
313 OrganizationDto organization = db.organizations().insert();
315 underTest.restore(db.getSession(), new StringReader("foo"), organization, null);
317 } catch (IllegalArgumentException e) {
318 assertThat(reset.calledProfile).isNull();
323 public void fail_to_restore_if_xml_is_not_well_formed() {
324 expectedException.expect(IllegalArgumentException.class);
325 expectedException.expectMessage("Fail to restore Quality profile backup, XML document is not well formed");
326 OrganizationDto organization = db.organizations().insert();
327 String notWellFormedXml = "<?xml version='1.0' encoding='UTF-8'?><profile><name>\"profil\"</name><language>\"language\"</language><rules/></profile";
329 underTest.restore(db.getSession(), new StringReader(notWellFormedXml), organization, null);
333 public void fail_to_restore_if_duplicate_rule() throws Exception {
334 OrganizationDto organization = db.organizations().insert();
336 String xml = Resources.toString(getClass().getResource("QProfileBackuperTest/duplicates-xml-backup.xml"), UTF_8);
337 underTest.restore(db.getSession(), new StringReader(xml), organization, null);
339 } catch (IllegalArgumentException e) {
340 assertThat(e).hasMessage("The quality profile cannot be restored as it contains duplicates for the following rules: xoo:x1, xoo:x2");
341 assertThat(reset.calledProfile).isNull();
346 public void fail_to_restore_external_rule() {
347 db.rules().insert(RuleKey.of("sonarjs", "s001"), r -> r.setIsExternal(true)).getId();
348 OrganizationDto organization = db.organizations().insert();
349 Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
350 "<profile><name>foo</name>" +
351 "<language>js</language>" +
354 "<repositoryKey>sonarjs</repositoryKey>" +
356 "<priority>BLOCKER</priority>" +
358 "<parameter><key>bar</key><value>baz</value></parameter>" +
364 expectedException.expect(IllegalArgumentException.class);
365 expectedException.expectMessage("The quality profile cannot be restored as it contains rules from external rule engines: sonarjs:s001");
366 underTest.restore(db.getSession(), backup, organization, null);
369 private RuleDefinitionDto createRule() {
370 return db.rules().insert();
373 private RuleMetadataDto createRuleMetadata(RuleMetadataDto metadataDto) {
374 return db.rules().insertOrUpdateMetadata(metadataDto);
377 private QProfileDto createProfile(RuleDefinitionDto rule) {
378 return db.qualityProfiles().insert(db.getDefaultOrganization(), p -> p.setLanguage(rule.getLanguage()));
381 private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule) {
382 return db.qualityProfiles().activateRule(profile, rule);
385 private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule, RuleParamDto param) {
386 ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile, rule);
387 ActiveRuleParamDto dto = ActiveRuleParamDto.createFor(param)
389 .setActiveRuleId(activeRule.getId());
390 db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRule, dto);
394 private static class DummyReset implements QProfileReset {
395 private QProfileDto calledProfile;
396 private List<RuleActivation> calledActivations;
399 public BulkChangeResult reset(DbSession dbSession, QProfileDto profile, Collection<RuleActivation> activations) {
400 this.calledProfile = profile;
401 this.calledActivations = new ArrayList<>(activations);
402 return new BulkChangeResult();
406 private static class DummyProfileFactory implements QProfileFactory {
408 public QProfileDto getOrCreateCustom(DbSession dbSession, OrganizationDto organization, QProfileName key) {
409 return QualityProfileTesting.newQualityProfileDto()
410 .setOrganizationUuid(organization.getUuid())
411 .setLanguage(key.getLanguage())
412 .setName(key.getName());
416 public QProfileDto checkAndCreateCustom(DbSession dbSession, OrganizationDto organization, QProfileName name) {
417 throw new UnsupportedOperationException();
421 public void delete(DbSession dbSession, Collection<QProfileDto> profiles) {
422 throw new UnsupportedOperationException();