]> source.dussan.org Git - sonarqube.git/blob
a4faa701fa1e680d2b38dcec36cab1faae2a1ce6
[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.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;
48
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;
58
59 public class QProfileBackuperImplTest {
60
61   private static final String EMPTY_BACKUP = "<?xml version='1.0' encoding='UTF-8'?>" +
62     "<profile><name>foo</name>" +
63     "<language>js</language>" +
64     "<rules/>" +
65     "</profile>";
66
67   private final System2 system2 = new AlwaysIncreasingSystem2();
68
69   @Rule
70   public DbTester db = DbTester.create(system2);
71
72   private final DummyReset reset = new DummyReset();
73   private final QProfileFactory profileFactory = new DummyProfileFactory();
74   private final RuleCreator ruleCreator = mock(RuleCreator.class);
75
76   private final QProfileBackuper underTest = new QProfileBackuperImpl(db.getDbClient(), reset, profileFactory, ruleCreator, new QProfileParser());
77
78   @Test
79   public void backup_generates_xml_file() {
80     RuleDefinitionDto rule = createRule();
81     QProfileDto profile = createProfile(rule.getLanguage());
82     ActiveRuleDto activeRule = activate(profile, rule);
83
84     StringWriter writer = new StringWriter();
85     underTest.backup(db.getSession(), profile, writer);
86
87     assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
88       "<profile><name>" + profile.getName() + "</name>" +
89       "<language>" + profile.getLanguage() + "</language>" +
90       "<rules>" +
91       "<rule>" +
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>" +
97       "</rule>" +
98       "</rules>" +
99       "</profile>");
100   }
101
102   @Test
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);
108
109     StringWriter writer = new StringWriter();
110     underTest.backup(db.getSession(), profile, writer);
111
112     assertThat(writer.toString()).contains(
113       "<rule>" +
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>" +
122         "</rule>");
123   }
124
125   @Test
126   public void backup_empty_profile() {
127     RuleDefinitionDto rule = createRule();
128     QProfileDto profile = createProfile(rule.getLanguage());
129
130     StringWriter writer = new StringWriter();
131     underTest.backup(db.getSession(), profile, writer);
132
133     assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
134       "<profile><name>" + profile.getName() + "</name>" +
135       "<language>" + profile.getLanguage() + "</language>" +
136       "<rules></rules>" +
137       "</profile>");
138   }
139
140   @Test
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);
152
153     StringWriter writer = new StringWriter();
154     underTest.backup(db.getSession(), profile, writer);
155
156     assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
157       "<profile>" +
158       "<name>" + profile.getName() + "</name>" +
159       "<language>" + profile.getLanguage() + "</language>" +
160       "<rules><rule>" +
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>");
173   }
174
175   @Test
176   public void restore_backup_on_the_profile_specified_in_backup() {
177     Reader backup = new StringReader(EMPTY_BACKUP);
178
179     QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, (String) null);
180
181     assertThat(summary.getProfile().getName()).isEqualTo("foo");
182     assertThat(summary.getProfile().getLanguage()).isEqualTo("js");
183
184     assertThat(reset.calledProfile.getKee()).isEqualTo(summary.getProfile().getKee());
185     assertThat(reset.calledActivations).isEmpty();
186   }
187
188   @Test
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"));
192
193     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
194       "<profile><name>foo</name>" +
195       "<language>js</language>" +
196       "<rules>" +
197       "<rule>" +
198       "<repositoryKey>oldrepo</repositoryKey>" +
199       "<key>oldkey</key>" +
200       "<priority>BLOCKER</priority>" +
201       "<parameters>" +
202       "<parameter><key>bar</key><value>baz</value></parameter>" +
203       "</parameters>" +
204       "</rule>" +
205       "</rules>" +
206       "</profile>");
207
208     underTest.restore(db.getSession(), backup, (String) null);
209
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");
215   }
216
217   @Test
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"));
221
222     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
223       "<profile><name>foo</name>" +
224       "<language>js</language>" +
225       "<rules>" +
226       "<rule>" +
227       "<repositoryKey>oldrepo</repositoryKey>" +
228       "<key>oldkey</key>" +
229       "<priority>MAJOR</priority>" +
230       "<parameters>" +
231       "<parameter><key>bar</key><value>baz</value></parameter>" +
232       "</parameters>" +
233       "</rule>" +
234       "<rule>" +
235       "<repositoryKey>sonarjs</repositoryKey>" +
236       "<key>s001</key>" +
237       "<priority>BLOCKER</priority>" +
238       "<parameters>" +
239       "<parameter><key>bar2</key><value>baz2</value></parameter>" +
240       "</parameters>" +
241       "</rule>" +
242       "</rules>" +
243       "</profile>");
244
245     underTest.restore(db.getSession(), backup, (String) null);
246
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");
252   }
253
254   @Test
255   public void restore_backup_on_profile_having_different_name() {
256     Reader backup = new StringReader(EMPTY_BACKUP);
257
258     QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, "bar");
259
260     assertThat(summary.getProfile().getName()).isEqualTo("bar");
261     assertThat(summary.getProfile().getLanguage()).isEqualTo("js");
262
263     assertThat(reset.calledProfile.getKee()).isEqualTo(summary.getProfile().getKee());
264     assertThat(reset.calledActivations).isEmpty();
265   }
266
267   @Test
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>" +
273       "<rules>" +
274       "<rule>" +
275       "<repositoryKey>sonarjs</repositoryKey>" +
276       "<key>s001</key>" +
277       "<priority>BLOCKER</priority>" +
278       "<parameters>" +
279       "<parameter><key>bar</key><value>baz</value></parameter>" +
280       "</parameters>" +
281       "</rule>" +
282       "</rules>" +
283       "</profile>");
284
285     underTest.restore(db.getSession(), backup, (String) null);
286
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");
292   }
293
294   @Test
295   public void restore_custom_rule() {
296     when(ruleCreator.create(any(), anyList())).then(invocation -> Collections.singletonList(db.rules().insert(RuleKey.of("sonarjs", "s001")).getKey()));
297
298     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
299       "<profile>" +
300       "<name>custom rule</name>" +
301       "<language>js</language>" +
302       "<rules><rule>" +
303       "<repositoryKey>sonarjs</repositoryKey>" +
304       "<key>s001</key>" +
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>" +
311       "<key>bar</key>" +
312       "<value>baz</value>" +
313       "</parameter>" +
314       "</parameters>" +
315       "</rule></rules></profile>");
316
317     underTest.restore(db.getSession(), backup, (String) null);
318
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");
323   }
324
325   @Test
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>" +
331       "<rules>" +
332       "<rule>" +
333       "<repositoryKey>sonarjs</repositoryKey>" +
334       "<key>s001</key>" +
335       "<priority>BLOCKER</priority>" +
336       "<parameters>" +
337       "<parameter><key>bar</key><value>baz</value></parameter>" +
338       "</parameters>" +
339       "</rule>" +
340       "<rule>" +
341       "<repositoryKey>sonarjs</repositoryKey>" +
342       "<key>s002</key>" +
343       "<priority>MAJOR</priority>" +
344       "</rule>" +
345       "</rules>" +
346       "</profile>");
347
348     underTest.restore(db.getSession(), backup, (String) null);
349
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");
355   }
356
357   @Test
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);
363
364     QProfileDto to = createProfile(rule.getLanguage());
365     underTest.copy(db.getSession(), from, to);
366
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);
370   }
371
372   @Test
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();
379   }
380
381   @Test
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();
387   }
388
389   @Test
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";
393
394       underTest.restore(db.getSession(), new StringReader(notWellFormedXml), (String) null);
395     })
396       .isInstanceOf(IllegalArgumentException.class)
397       .hasMessage("Fail to restore Quality profile backup, XML document is not well formed");
398   }
399
400   @Test
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();
408   }
409
410   @Test
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>" +
416       "<rules>" +
417       "<rule>" +
418       "<repositoryKey>sonarjs</repositoryKey>" +
419       "<key>s001</key>" +
420       "<priority>BLOCKER</priority>" +
421       "<parameters>" +
422       "<parameter><key>bar</key><value>baz</value></parameter>" +
423       "</parameters>" +
424       "</rule>" +
425       "</rules>" +
426       "</profile>");
427
428     assertThatThrownBy(() -> {
429       underTest.restore(db.getSession(), backup, (String) null);
430     })
431       .isInstanceOf(IllegalArgumentException.class)
432       .hasMessage("The quality profile cannot be restored as it contains rules from external rule engines: sonarjs:s001");
433   }
434
435   private RuleDefinitionDto createRule() {
436     return db.rules().insert();
437   }
438
439   private QProfileDto createProfile(String language) {
440     return db.qualityProfiles().insert(p -> p.setLanguage(language));
441   }
442
443   private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule) {
444     return db.qualityProfiles().activateRule(profile, rule);
445   }
446
447   private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule, RuleParamDto param) {
448     ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile, rule);
449     ActiveRuleParamDto dto = ActiveRuleParamDto.createFor(param)
450       .setValue("20")
451       .setActiveRuleUuid(activeRule.getUuid());
452     db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRule, dto);
453     return activeRule;
454   }
455
456   private static class DummyReset implements QProfileReset {
457     private QProfileDto calledProfile;
458     private List<RuleActivation> calledActivations;
459
460     @Override
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();
465     }
466   }
467
468   private static class DummyProfileFactory implements QProfileFactory {
469     @Override
470     public QProfileDto getOrCreateCustom(DbSession dbSession, QProfileName key) {
471       return QualityProfileTesting.newQualityProfileDto()
472         .setLanguage(key.getLanguage())
473         .setName(key.getName());
474     }
475
476     @Override
477     public QProfileDto checkAndCreateCustom(DbSession dbSession, QProfileName name) {
478       throw new UnsupportedOperationException();
479     }
480
481     @Override
482     public QProfileDto createCustom(DbSession dbSession, QProfileName name, @Nullable String parentKey) {
483       throw new UnsupportedOperationException();
484     }
485
486     @Override
487     public void delete(DbSession dbSession, Collection<QProfileDto> profiles) {
488       throw new UnsupportedOperationException();
489     }
490   }
491 }