]> source.dussan.org Git - sonarqube.git/blob
04e750e75014439d1297dd709d2cf38cd78167ac
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 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.RuleDto;
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.newRule;
60 import static org.sonar.db.rule.RuleTesting.newRuleWithoutDescriptionSection;
61
62 public class QProfileBackuperImplIT {
63
64   private static final String EMPTY_BACKUP = "<?xml version='1.0' encoding='UTF-8'?>" +
65     "<profile><name>foo</name>" +
66     "<language>js</language>" +
67     "<rules/>" +
68     "</profile>";
69
70   private final System2 system2 = new AlwaysIncreasingSystem2();
71
72   @Rule
73   public DbTester db = DbTester.create(system2);
74
75   private final DummyReset reset = new DummyReset();
76   private final QProfileFactory profileFactory = new DummyProfileFactory();
77   private final RuleCreator ruleCreator = mock(RuleCreator.class);
78
79   private final QProfileBackuper underTest = new QProfileBackuperImpl(db.getDbClient(), reset, profileFactory, ruleCreator, new QProfileParser());
80
81   @Test
82   public void backup_generates_xml_file() {
83     RuleDto rule = createRule();
84     QProfileDto profile = createProfile(rule.getLanguage());
85     ActiveRuleDto activeRule = activate(profile, rule);
86
87     StringWriter writer = new StringWriter();
88     underTest.backup(db.getSession(), profile, writer);
89
90     assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
91       "<profile><name>" + profile.getName() + "</name>" +
92       "<language>" + profile.getLanguage() + "</language>" +
93       "<rules>" +
94       "<rule>" +
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>" +
100       "</rule>" +
101       "</rules>" +
102       "</profile>");
103   }
104
105   @Test
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);
111
112     StringWriter writer = new StringWriter();
113     underTest.backup(db.getSession(), profile, writer);
114
115     assertThat(writer.toString()).contains(
116       "<rule>" +
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>" +
125         "</rule>");
126   }
127
128   @Test
129   public void backup_empty_profile() {
130     RuleDto rule = createRule();
131     QProfileDto profile = createProfile(rule.getLanguage());
132
133     StringWriter writer = new StringWriter();
134     underTest.backup(db.getSession(), profile, writer);
135
136     assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
137       "<profile><name>" + profile.getName() + "</name>" +
138       "<language>" + profile.getLanguage() + "</language>" +
139       "<rules></rules>" +
140       "</profile>");
141   }
142
143   @Test
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);
155
156     StringWriter writer = new StringWriter();
157     underTest.backup(db.getSession(), profile, writer);
158
159     assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
160       "<profile>" +
161       "<name>" + profile.getName() + "</name>" +
162       "<language>" + profile.getLanguage() + "</language>" +
163       "<rules><rule>" +
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>");
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.profile().getName()).isEqualTo("foo");
212     assertThat(summary.profile().getLanguage()).isEqualTo("js");
213
214     assertThat(reset.calledProfile.getKee()).isEqualTo(summary.profile().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.profile().getName()).isEqualTo("bar");
291     assertThat(summary.profile().getLanguage()).isEqualTo("js");
292
293     assertThat(reset.calledProfile.getKee()).isEqualTo(summary.profile().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     RuleDto 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 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()));
411
412     RuleParamDto param = db.rules().insertRuleParam(rule);
413     QProfileDto from = createProfile(rule.getLanguage());
414     ActiveRuleDto activeRule = activate(from, rule, param);
415
416     QProfileDto to = createProfile(rule.getLanguage());
417     underTest.copy(db.getSession(), from, to);
418
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);
422   }
423
424   @Test
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();
431   }
432
433   @Test
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();
439   }
440
441   @Test
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";
445
446       underTest.restore(db.getSession(), new StringReader(notWellFormedXml), (String) null);
447     })
448       .isInstanceOf(IllegalArgumentException.class)
449       .hasMessage("Fail to restore Quality profile backup, XML document is not well formed");
450   }
451
452   @Test
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();
460   }
461
462   @Test
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>" +
468       "<rules>" +
469       "<rule>" +
470       "<repositoryKey>sonarjs</repositoryKey>" +
471       "<key>s001</key>" +
472       "<priority>BLOCKER</priority>" +
473       "<parameters>" +
474       "<parameter><key>bar</key><value>baz</value></parameter>" +
475       "</parameters>" +
476       "</rule>" +
477       "</rules>" +
478       "</profile>");
479
480     assertThatThrownBy(() -> {
481       underTest.restore(db.getSession(), backup, (String) null);
482     })
483       .isInstanceOf(IllegalArgumentException.class)
484       .hasMessage("The quality profile cannot be restored as it contains rules from external rule engines: sonarjs:s001");
485   }
486
487   private RuleDto createRule() {
488     return db.rules().insert();
489   }
490
491   private QProfileDto createProfile(String language) {
492     return db.qualityProfiles().insert(p -> p.setLanguage(language));
493   }
494
495   private ActiveRuleDto activate(QProfileDto profile, RuleDto rule) {
496     return db.qualityProfiles().activateRule(profile, rule);
497   }
498
499   private ActiveRuleDto activate(QProfileDto profile, RuleDto rule, RuleParamDto param) {
500     ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile, rule);
501     ActiveRuleParamDto dto = ActiveRuleParamDto.createFor(param)
502       .setValue("20")
503       .setActiveRuleUuid(activeRule.getUuid());
504     db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRule, dto);
505     return activeRule;
506   }
507
508   private static class DummyReset implements QProfileReset {
509     private QProfileDto calledProfile;
510     private List<RuleActivation> calledActivations;
511
512     @Override
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();
517     }
518   }
519
520   private static class DummyProfileFactory implements QProfileFactory {
521     @Override
522     public QProfileDto getOrCreateCustom(DbSession dbSession, QProfileName key) {
523       return QualityProfileTesting.newQualityProfileDto()
524         .setLanguage(key.getLanguage())
525         .setName(key.getName());
526     }
527
528     @Override
529     public QProfileDto checkAndCreateCustom(DbSession dbSession, QProfileName name) {
530       throw new UnsupportedOperationException();
531     }
532
533     @Override
534     public QProfileDto createCustom(DbSession dbSession, QProfileName name, @Nullable String parentKey) {
535       throw new UnsupportedOperationException();
536     }
537
538     @Override
539     public void delete(DbSession dbSession, Collection<QProfileDto> profiles) {
540       throw new UnsupportedOperationException();
541     }
542   }
543 }