]> source.dussan.org Git - sonarqube.git/blob
a1b2120ef7ce0d7bc3b5743e2aa94849f1393b28
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2022 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.server.qualityprofile;
21
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.RuleDefinitionDto;
46 import org.sonar.db.rule.RuleParamDto;
47 import org.sonar.server.qualityprofile.builtin.QProfileName;
48 import org.sonar.server.rule.RuleCreator;
49
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.newRuleWithoutDescriptionSection;
60
61 public class QProfileBackuperImplTest {
62
63   private static final String EMPTY_BACKUP = "<?xml version='1.0' encoding='UTF-8'?>" +
64     "<profile><name>foo</name>" +
65     "<language>js</language>" +
66     "<rules/>" +
67     "</profile>";
68
69   private final System2 system2 = new AlwaysIncreasingSystem2();
70
71   @Rule
72   public DbTester db = DbTester.create(system2);
73
74   private final DummyReset reset = new DummyReset();
75   private final QProfileFactory profileFactory = new DummyProfileFactory();
76   private final RuleCreator ruleCreator = mock(RuleCreator.class);
77
78   private final QProfileBackuper underTest = new QProfileBackuperImpl(db.getDbClient(), reset, profileFactory, ruleCreator, new QProfileParser());
79
80   @Test
81   public void backup_generates_xml_file() {
82     RuleDefinitionDto rule = createRule();
83     QProfileDto profile = createProfile(rule.getLanguage());
84     ActiveRuleDto activeRule = activate(profile, rule);
85
86     StringWriter writer = new StringWriter();
87     underTest.backup(db.getSession(), profile, writer);
88
89     assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
90       "<profile><name>" + profile.getName() + "</name>" +
91       "<language>" + profile.getLanguage() + "</language>" +
92       "<rules>" +
93       "<rule>" +
94       "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
95       "<key>" + rule.getRuleKey() + "</key>" +
96       "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
97       "<priority>" + activeRule.getSeverityString() + "</priority>" +
98       "<parameters></parameters>" +
99       "</rule>" +
100       "</rules>" +
101       "</profile>");
102   }
103
104   @Test
105   public void backup_rules_having_parameters() {
106     RuleDefinitionDto rule = createRule();
107     RuleParamDto param = db.rules().insertRuleParam(rule);
108     QProfileDto profile = createProfile(rule.getLanguage());
109     ActiveRuleDto activeRule = activate(profile, rule, param);
110
111     StringWriter writer = new StringWriter();
112     underTest.backup(db.getSession(), profile, writer);
113
114     assertThat(writer.toString()).contains(
115       "<rule>" +
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>" +
124         "</rule>");
125   }
126
127   @Test
128   public void backup_empty_profile() {
129     RuleDefinitionDto rule = createRule();
130     QProfileDto profile = createProfile(rule.getLanguage());
131
132     StringWriter writer = new StringWriter();
133     underTest.backup(db.getSession(), profile, writer);
134
135     assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
136       "<profile><name>" + profile.getName() + "</name>" +
137       "<language>" + profile.getLanguage() + "</language>" +
138       "<rules></rules>" +
139       "</profile>");
140   }
141
142   @Test
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       .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(UuidFactoryFast.getInstance().create(), "custom rule description"))
148       .setName("custom rule name")
149       .setStatus(RuleStatus.READY)
150       .setTemplateUuid(templateRule.getUuid()));
151     RuleParamDto param = db.rules().insertRuleParam(rule);
152     QProfileDto profile = createProfile(rule.getLanguage());
153     ActiveRuleDto activeRule = activate(profile, rule, param);
154
155     StringWriter writer = new StringWriter();
156     underTest.backup(db.getSession(), profile, writer);
157
158     assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
159       "<profile>" +
160       "<name>" + profile.getName() + "</name>" +
161       "<language>" + profile.getLanguage() + "</language>" +
162       "<rules><rule>" +
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.getDefaultRuleDescriptionSectionDto().getContent() + "</description>" +
170       "<descriptionSections><descriptionSection><key>default</key><content>" + rule.getDefaultRuleDescriptionSectionDto().getContent() + "</content></descriptionSection></descriptionSections>" +
171       "<parameters><parameter>" +
172       "<key>" + param.getName() + "</key>" +
173       "<value>20</value>" +
174       "</parameter></parameters>" +
175       "</rule></rules></profile>");
176   }
177
178   @Test
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);
185
186     StringWriter writer = new StringWriter();
187     underTest.backup(db.getSession(), profile, writer);
188
189     assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
190       "<profile>" +
191       "<name>" + profile.getName() + "</name>" +
192       "<language>" + profile.getLanguage() + "</language>" +
193       "<rules><rule>" +
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>");
203   }
204
205   @Test
206   public void restore_backup_on_the_profile_specified_in_backup() {
207     Reader backup = new StringReader(EMPTY_BACKUP);
208
209     QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, (String) null);
210
211     assertThat(summary.getProfile().getName()).isEqualTo("foo");
212     assertThat(summary.getProfile().getLanguage()).isEqualTo("js");
213
214     assertThat(reset.calledProfile.getKee()).isEqualTo(summary.getProfile().getKee());
215     assertThat(reset.calledActivations).isEmpty();
216   }
217
218   @Test
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"));
222
223     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
224       "<profile><name>foo</name>" +
225       "<language>js</language>" +
226       "<rules>" +
227       "<rule>" +
228       "<repositoryKey>oldrepo</repositoryKey>" +
229       "<key>oldkey</key>" +
230       "<priority>BLOCKER</priority>" +
231       "<parameters>" +
232       "<parameter><key>bar</key><value>baz</value></parameter>" +
233       "</parameters>" +
234       "</rule>" +
235       "</rules>" +
236       "</profile>");
237
238     underTest.restore(db.getSession(), backup, (String) null);
239
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");
245   }
246
247   @Test
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"));
251
252     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
253       "<profile><name>foo</name>" +
254       "<language>js</language>" +
255       "<rules>" +
256       "<rule>" +
257       "<repositoryKey>oldrepo</repositoryKey>" +
258       "<key>oldkey</key>" +
259       "<priority>MAJOR</priority>" +
260       "<parameters>" +
261       "<parameter><key>bar</key><value>baz</value></parameter>" +
262       "</parameters>" +
263       "</rule>" +
264       "<rule>" +
265       "<repositoryKey>sonarjs</repositoryKey>" +
266       "<key>s001</key>" +
267       "<priority>BLOCKER</priority>" +
268       "<parameters>" +
269       "<parameter><key>bar2</key><value>baz2</value></parameter>" +
270       "</parameters>" +
271       "</rule>" +
272       "</rules>" +
273       "</profile>");
274
275     underTest.restore(db.getSession(), backup, (String) null);
276
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");
282   }
283
284   @Test
285   public void restore_backup_on_profile_having_different_name() {
286     Reader backup = new StringReader(EMPTY_BACKUP);
287
288     QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, "bar");
289
290     assertThat(summary.getProfile().getName()).isEqualTo("bar");
291     assertThat(summary.getProfile().getLanguage()).isEqualTo("js");
292
293     assertThat(reset.calledProfile.getKee()).isEqualTo(summary.getProfile().getKee());
294     assertThat(reset.calledActivations).isEmpty();
295   }
296
297   @Test
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>" +
303       "<rules>" +
304       "<rule>" +
305       "<repositoryKey>sonarjs</repositoryKey>" +
306       "<key>s001</key>" +
307       "<priority>BLOCKER</priority>" +
308       "<parameters>" +
309       "<parameter><key>bar</key><value>baz</value></parameter>" +
310       "</parameters>" +
311       "</rule>" +
312       "</rules>" +
313       "</profile>");
314
315     underTest.restore(db.getSession(), backup, (String) null);
316
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");
322   }
323
324   @Test
325   public void restore_custom_rule() {
326     when(ruleCreator.create(any(), anyList())).then(invocation -> Collections.singletonList(db.rules().insert(RuleKey.of("sonarjs", "s001")).getKey()));
327
328     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
329       "<profile>" +
330       "<name>custom rule</name>" +
331       "<language>js</language>" +
332       "<rules><rule>" +
333       "<repositoryKey>sonarjs</repositoryKey>" +
334       "<key>s001</key>" +
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>" +
341       "<key>bar</key>" +
342       "<value>baz</value>" +
343       "</parameter>" +
344       "</parameters>" +
345       "</rule></rules></profile>");
346
347     underTest.restore(db.getSession(), backup, (String) null);
348
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");
353   }
354
355   @Test
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>" +
361       "<rules>" +
362       "<rule>" +
363       "<repositoryKey>sonarjs</repositoryKey>" +
364       "<key>s001</key>" +
365       "<priority>BLOCKER</priority>" +
366       "<parameters>" +
367       "<parameter><key>bar</key><value>baz</value></parameter>" +
368       "</parameters>" +
369       "</rule>" +
370       "<rule>" +
371       "<repositoryKey>sonarjs</repositoryKey>" +
372       "<key>s002</key>" +
373       "<priority>MAJOR</priority>" +
374       "</rule>" +
375       "</rules>" +
376       "</profile>");
377
378     underTest.restore(db.getSession(), backup, (String) null);
379
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");
385   }
386
387   @Test
388   public void copy_profile() {
389     RuleDefinitionDto rule = createRule();
390     RuleParamDto param = db.rules().insertRuleParam(rule);
391     QProfileDto from = createProfile(rule.getLanguage());
392     ActiveRuleDto activeRule = activate(from, rule, param);
393
394     QProfileDto to = createProfile(rule.getLanguage());
395     underTest.copy(db.getSession(), from, to);
396
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);
400   }
401
402   @Test
403   public void fail_to_restore_if_bad_xml_format() {
404     DbSession session = db.getSession();
405     StringReader backup = new StringReader("<rules><rule></rules>");
406     IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
407     assertThat(thrown).hasMessage("Backup XML is not valid. Root element must be <profile>.");
408     assertThat(reset.calledProfile).isNull();
409   }
410
411   @Test
412   public void fail_to_restore_if_not_xml_backup() {
413     DbSession session = db.getSession();
414     StringReader backup = new StringReader("foo");
415     assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
416     assertThat(reset.calledProfile).isNull();
417   }
418
419   @Test
420   public void fail_to_restore_if_xml_is_not_well_formed() {
421     assertThatThrownBy(() -> {
422       String notWellFormedXml = "<?xml version='1.0' encoding='UTF-8'?><profile><name>\"profil\"</name><language>\"language\"</language><rules/></profile";
423
424       underTest.restore(db.getSession(), new StringReader(notWellFormedXml), (String) null);
425     })
426       .isInstanceOf(IllegalArgumentException.class)
427       .hasMessage("Fail to restore Quality profile backup, XML document is not well formed");
428   }
429
430   @Test
431   public void fail_to_restore_if_duplicate_rule() throws Exception {
432     DbSession session = db.getSession();
433     String xml = Resources.toString(getClass().getResource("QProfileBackuperTest/duplicates-xml-backup.xml"), UTF_8);
434     StringReader backup = new StringReader(xml);
435     IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
436     assertThat(thrown).hasMessage("The quality profile cannot be restored as it contains duplicates for the following rules: xoo:x1, xoo:x2");
437     assertThat(reset.calledProfile).isNull();
438   }
439
440   @Test
441   public void fail_to_restore_external_rule() {
442     db.rules().insert(RuleKey.of("sonarjs", "s001"), r -> r.setIsExternal(true));
443     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
444       "<profile><name>foo</name>" +
445       "<language>js</language>" +
446       "<rules>" +
447       "<rule>" +
448       "<repositoryKey>sonarjs</repositoryKey>" +
449       "<key>s001</key>" +
450       "<priority>BLOCKER</priority>" +
451       "<parameters>" +
452       "<parameter><key>bar</key><value>baz</value></parameter>" +
453       "</parameters>" +
454       "</rule>" +
455       "</rules>" +
456       "</profile>");
457
458     assertThatThrownBy(() -> {
459       underTest.restore(db.getSession(), backup, (String) null);
460     })
461       .isInstanceOf(IllegalArgumentException.class)
462       .hasMessage("The quality profile cannot be restored as it contains rules from external rule engines: sonarjs:s001");
463   }
464
465   private RuleDefinitionDto createRule() {
466     return db.rules().insert();
467   }
468
469   private QProfileDto createProfile(String language) {
470     return db.qualityProfiles().insert(p -> p.setLanguage(language));
471   }
472
473   private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule) {
474     return db.qualityProfiles().activateRule(profile, rule);
475   }
476
477   private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule, RuleParamDto param) {
478     ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile, rule);
479     ActiveRuleParamDto dto = ActiveRuleParamDto.createFor(param)
480       .setValue("20")
481       .setActiveRuleUuid(activeRule.getUuid());
482     db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRule, dto);
483     return activeRule;
484   }
485
486   private static class DummyReset implements QProfileReset {
487     private QProfileDto calledProfile;
488     private List<RuleActivation> calledActivations;
489
490     @Override
491     public BulkChangeResult reset(DbSession dbSession, QProfileDto profile, Collection<RuleActivation> activations) {
492       this.calledProfile = profile;
493       this.calledActivations = new ArrayList<>(activations);
494       return new BulkChangeResult();
495     }
496   }
497
498   private static class DummyProfileFactory implements QProfileFactory {
499     @Override
500     public QProfileDto getOrCreateCustom(DbSession dbSession, QProfileName key) {
501       return QualityProfileTesting.newQualityProfileDto()
502         .setLanguage(key.getLanguage())
503         .setName(key.getName());
504     }
505
506     @Override
507     public QProfileDto checkAndCreateCustom(DbSession dbSession, QProfileName name) {
508       throw new UnsupportedOperationException();
509     }
510
511     @Override
512     public QProfileDto createCustom(DbSession dbSession, QProfileName name, @Nullable String parentKey) {
513       throw new UnsupportedOperationException();
514     }
515
516     @Override
517     public void delete(DbSession dbSession, Collection<QProfileDto> profiles) {
518       throw new UnsupportedOperationException();
519     }
520   }
521 }