]> source.dussan.org Git - sonarqube.git/blob
08e74098b12aed6a3c5290e2b2c33fdca6ccd8a1
[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
60 public class QProfileBackuperImplTest {
61
62   private static final String EMPTY_BACKUP = "<?xml version='1.0' encoding='UTF-8'?>" +
63     "<profile><name>foo</name>" +
64     "<language>js</language>" +
65     "<rules/>" +
66     "</profile>";
67
68   private final System2 system2 = new AlwaysIncreasingSystem2();
69
70   @Rule
71   public DbTester db = DbTester.create(system2);
72
73   private final DummyReset reset = new DummyReset();
74   private final QProfileFactory profileFactory = new DummyProfileFactory();
75   private final RuleCreator ruleCreator = mock(RuleCreator.class);
76
77   private final QProfileBackuper underTest = new QProfileBackuperImpl(db.getDbClient(), reset, profileFactory, ruleCreator, new QProfileParser());
78
79   @Test
80   public void backup_generates_xml_file() {
81     RuleDefinitionDto rule = createRule();
82     QProfileDto profile = createProfile(rule.getLanguage());
83     ActiveRuleDto activeRule = activate(profile, rule);
84
85     StringWriter writer = new StringWriter();
86     underTest.backup(db.getSession(), profile, writer);
87
88     assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
89       "<profile><name>" + profile.getName() + "</name>" +
90       "<language>" + profile.getLanguage() + "</language>" +
91       "<rules>" +
92       "<rule>" +
93       "<repositoryKey>" + rule.getRepositoryKey() + "</repositoryKey>" +
94       "<key>" + rule.getRuleKey() + "</key>" +
95       "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" +
96       "<priority>" + activeRule.getSeverityString() + "</priority>" +
97       "<parameters></parameters>" +
98       "</rule>" +
99       "</rules>" +
100       "</profile>");
101   }
102
103   @Test
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);
109
110     StringWriter writer = new StringWriter();
111     underTest.backup(db.getSession(), profile, writer);
112
113     assertThat(writer.toString()).contains(
114       "<rule>" +
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>" +
123         "</rule>");
124   }
125
126   @Test
127   public void backup_empty_profile() {
128     RuleDefinitionDto rule = createRule();
129     QProfileDto profile = createProfile(rule.getLanguage());
130
131     StringWriter writer = new StringWriter();
132     underTest.backup(db.getSession(), profile, writer);
133
134     assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
135       "<profile><name>" + profile.getName() + "</name>" +
136       "<language>" + profile.getLanguage() + "</language>" +
137       "<rules></rules>" +
138       "</profile>");
139   }
140
141   @Test
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       .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(UuidFactoryFast.getInstance().create(), "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);
153
154     StringWriter writer = new StringWriter();
155     underTest.backup(db.getSession(), profile, writer);
156
157     assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
158       "<profile>" +
159       "<name>" + profile.getName() + "</name>" +
160       "<language>" + profile.getLanguage() + "</language>" +
161       "<rules><rule>" +
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.getDefaultRuleDescriptionSectionDto().getDescription() + "</description>" +
169       "<parameters><parameter>" +
170       "<key>" + param.getName() + "</key>" +
171       "<value>20</value>" +
172       "</parameter></parameters>" +
173       "</rule></rules></profile>");
174   }
175
176   @Test
177   public void restore_backup_on_the_profile_specified_in_backup() {
178     Reader backup = new StringReader(EMPTY_BACKUP);
179
180     QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, (String) null);
181
182     assertThat(summary.getProfile().getName()).isEqualTo("foo");
183     assertThat(summary.getProfile().getLanguage()).isEqualTo("js");
184
185     assertThat(reset.calledProfile.getKee()).isEqualTo(summary.getProfile().getKee());
186     assertThat(reset.calledActivations).isEmpty();
187   }
188
189   @Test
190   public void restore_detects_deprecated_rule_keys() {
191     String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
192     db.rules().insertDeprecatedKey(c -> c.setRuleUuid(ruleUuid).setOldRuleKey("oldkey").setOldRepositoryKey("oldrepo"));
193
194     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
195       "<profile><name>foo</name>" +
196       "<language>js</language>" +
197       "<rules>" +
198       "<rule>" +
199       "<repositoryKey>oldrepo</repositoryKey>" +
200       "<key>oldkey</key>" +
201       "<priority>BLOCKER</priority>" +
202       "<parameters>" +
203       "<parameter><key>bar</key><value>baz</value></parameter>" +
204       "</parameters>" +
205       "</rule>" +
206       "</rules>" +
207       "</profile>");
208
209     underTest.restore(db.getSession(), backup, (String) null);
210
211     assertThat(reset.calledActivations).hasSize(1);
212     RuleActivation activation = reset.calledActivations.get(0);
213     assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
214     assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
215     assertThat(activation.getParameter("bar")).isEqualTo("baz");
216   }
217
218   @Test
219   public void restore_ignores_deprecated_rule_keys_if_new_key_is_already_present() {
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>MAJOR</priority>" +
231       "<parameters>" +
232       "<parameter><key>bar</key><value>baz</value></parameter>" +
233       "</parameters>" +
234       "</rule>" +
235       "<rule>" +
236       "<repositoryKey>sonarjs</repositoryKey>" +
237       "<key>s001</key>" +
238       "<priority>BLOCKER</priority>" +
239       "<parameters>" +
240       "<parameter><key>bar2</key><value>baz2</value></parameter>" +
241       "</parameters>" +
242       "</rule>" +
243       "</rules>" +
244       "</profile>");
245
246     underTest.restore(db.getSession(), backup, (String) null);
247
248     assertThat(reset.calledActivations).hasSize(1);
249     RuleActivation activation = reset.calledActivations.get(0);
250     assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
251     assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
252     assertThat(activation.getParameter("bar2")).isEqualTo("baz2");
253   }
254
255   @Test
256   public void restore_backup_on_profile_having_different_name() {
257     Reader backup = new StringReader(EMPTY_BACKUP);
258
259     QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, "bar");
260
261     assertThat(summary.getProfile().getName()).isEqualTo("bar");
262     assertThat(summary.getProfile().getLanguage()).isEqualTo("js");
263
264     assertThat(reset.calledProfile.getKee()).isEqualTo(summary.getProfile().getKee());
265     assertThat(reset.calledActivations).isEmpty();
266   }
267
268   @Test
269   public void restore_resets_the_activated_rules() {
270     String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
271     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
272       "<profile><name>foo</name>" +
273       "<language>js</language>" +
274       "<rules>" +
275       "<rule>" +
276       "<repositoryKey>sonarjs</repositoryKey>" +
277       "<key>s001</key>" +
278       "<priority>BLOCKER</priority>" +
279       "<parameters>" +
280       "<parameter><key>bar</key><value>baz</value></parameter>" +
281       "</parameters>" +
282       "</rule>" +
283       "</rules>" +
284       "</profile>");
285
286     underTest.restore(db.getSession(), backup, (String) null);
287
288     assertThat(reset.calledActivations).hasSize(1);
289     RuleActivation activation = reset.calledActivations.get(0);
290     assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
291     assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
292     assertThat(activation.getParameter("bar")).isEqualTo("baz");
293   }
294
295   @Test
296   public void restore_custom_rule() {
297     when(ruleCreator.create(any(), anyList())).then(invocation -> Collections.singletonList(db.rules().insert(RuleKey.of("sonarjs", "s001")).getKey()));
298
299     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
300       "<profile>" +
301       "<name>custom rule</name>" +
302       "<language>js</language>" +
303       "<rules><rule>" +
304       "<repositoryKey>sonarjs</repositoryKey>" +
305       "<key>s001</key>" +
306       "<type>CODE_SMELL</type>" +
307       "<priority>CRITICAL</priority>" +
308       "<name>custom rule name</name>" +
309       "<templateKey>rule_mc8</templateKey>" +
310       "<description>custom rule description</description>" +
311       "<parameters><parameter>" +
312       "<key>bar</key>" +
313       "<value>baz</value>" +
314       "</parameter>" +
315       "</parameters>" +
316       "</rule></rules></profile>");
317
318     underTest.restore(db.getSession(), backup, (String) null);
319
320     assertThat(reset.calledActivations).hasSize(1);
321     RuleActivation activation = reset.calledActivations.get(0);
322     assertThat(activation.getSeverity()).isEqualTo("CRITICAL");
323     assertThat(activation.getParameter("bar")).isEqualTo("baz");
324   }
325
326   @Test
327   public void restore_skips_rule_without_template_key_and_db_definition() {
328     String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
329     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
330       "<profile><name>foo</name>" +
331       "<language>js</language>" +
332       "<rules>" +
333       "<rule>" +
334       "<repositoryKey>sonarjs</repositoryKey>" +
335       "<key>s001</key>" +
336       "<priority>BLOCKER</priority>" +
337       "<parameters>" +
338       "<parameter><key>bar</key><value>baz</value></parameter>" +
339       "</parameters>" +
340       "</rule>" +
341       "<rule>" +
342       "<repositoryKey>sonarjs</repositoryKey>" +
343       "<key>s002</key>" +
344       "<priority>MAJOR</priority>" +
345       "</rule>" +
346       "</rules>" +
347       "</profile>");
348
349     underTest.restore(db.getSession(), backup, (String) null);
350
351     assertThat(reset.calledActivations).hasSize(1);
352     RuleActivation activation = reset.calledActivations.get(0);
353     assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
354     assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
355     assertThat(activation.getParameter("bar")).isEqualTo("baz");
356   }
357
358   @Test
359   public void copy_profile() {
360     RuleDefinitionDto rule = createRule();
361     RuleParamDto param = db.rules().insertRuleParam(rule);
362     QProfileDto from = createProfile(rule.getLanguage());
363     ActiveRuleDto activeRule = activate(from, rule, param);
364
365     QProfileDto to = createProfile(rule.getLanguage());
366     underTest.copy(db.getSession(), from, to);
367
368     assertThat(reset.calledActivations).extracting(RuleActivation::getRuleUuid).containsOnly(activeRule.getRuleUuid());
369     assertThat(reset.calledActivations.get(0).getParameter(param.getName())).isEqualTo("20");
370     assertThat(reset.calledProfile).isEqualTo(to);
371   }
372
373   @Test
374   public void fail_to_restore_if_bad_xml_format() {
375     DbSession session = db.getSession();
376     StringReader backup = new StringReader("<rules><rule></rules>");
377     IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
378     assertThat(thrown).hasMessage("Backup XML is not valid. Root element must be <profile>.");
379     assertThat(reset.calledProfile).isNull();
380   }
381
382   @Test
383   public void fail_to_restore_if_not_xml_backup() {
384     DbSession session = db.getSession();
385     StringReader backup = new StringReader("foo");
386     assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
387     assertThat(reset.calledProfile).isNull();
388   }
389
390   @Test
391   public void fail_to_restore_if_xml_is_not_well_formed() {
392     assertThatThrownBy(() -> {
393       String notWellFormedXml = "<?xml version='1.0' encoding='UTF-8'?><profile><name>\"profil\"</name><language>\"language\"</language><rules/></profile";
394
395       underTest.restore(db.getSession(), new StringReader(notWellFormedXml), (String) null);
396     })
397       .isInstanceOf(IllegalArgumentException.class)
398       .hasMessage("Fail to restore Quality profile backup, XML document is not well formed");
399   }
400
401   @Test
402   public void fail_to_restore_if_duplicate_rule() throws Exception {
403     DbSession session = db.getSession();
404     String xml = Resources.toString(getClass().getResource("QProfileBackuperTest/duplicates-xml-backup.xml"), UTF_8);
405     StringReader backup = new StringReader(xml);
406     IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> underTest.restore(session, backup, (String) null));
407     assertThat(thrown).hasMessage("The quality profile cannot be restored as it contains duplicates for the following rules: xoo:x1, xoo:x2");
408     assertThat(reset.calledProfile).isNull();
409   }
410
411   @Test
412   public void fail_to_restore_external_rule() {
413     db.rules().insert(RuleKey.of("sonarjs", "s001"), r -> r.setIsExternal(true));
414     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
415       "<profile><name>foo</name>" +
416       "<language>js</language>" +
417       "<rules>" +
418       "<rule>" +
419       "<repositoryKey>sonarjs</repositoryKey>" +
420       "<key>s001</key>" +
421       "<priority>BLOCKER</priority>" +
422       "<parameters>" +
423       "<parameter><key>bar</key><value>baz</value></parameter>" +
424       "</parameters>" +
425       "</rule>" +
426       "</rules>" +
427       "</profile>");
428
429     assertThatThrownBy(() -> {
430       underTest.restore(db.getSession(), backup, (String) null);
431     })
432       .isInstanceOf(IllegalArgumentException.class)
433       .hasMessage("The quality profile cannot be restored as it contains rules from external rule engines: sonarjs:s001");
434   }
435
436   private RuleDefinitionDto createRule() {
437     return db.rules().insert();
438   }
439
440   private QProfileDto createProfile(String language) {
441     return db.qualityProfiles().insert(p -> p.setLanguage(language));
442   }
443
444   private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule) {
445     return db.qualityProfiles().activateRule(profile, rule);
446   }
447
448   private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule, RuleParamDto param) {
449     ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile, rule);
450     ActiveRuleParamDto dto = ActiveRuleParamDto.createFor(param)
451       .setValue("20")
452       .setActiveRuleUuid(activeRule.getUuid());
453     db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRule, dto);
454     return activeRule;
455   }
456
457   private static class DummyReset implements QProfileReset {
458     private QProfileDto calledProfile;
459     private List<RuleActivation> calledActivations;
460
461     @Override
462     public BulkChangeResult reset(DbSession dbSession, QProfileDto profile, Collection<RuleActivation> activations) {
463       this.calledProfile = profile;
464       this.calledActivations = new ArrayList<>(activations);
465       return new BulkChangeResult();
466     }
467   }
468
469   private static class DummyProfileFactory implements QProfileFactory {
470     @Override
471     public QProfileDto getOrCreateCustom(DbSession dbSession, QProfileName key) {
472       return QualityProfileTesting.newQualityProfileDto()
473         .setLanguage(key.getLanguage())
474         .setName(key.getName());
475     }
476
477     @Override
478     public QProfileDto checkAndCreateCustom(DbSession dbSession, QProfileName name) {
479       throw new UnsupportedOperationException();
480     }
481
482     @Override
483     public QProfileDto createCustom(DbSession dbSession, QProfileName name, @Nullable String parentKey) {
484       throw new UnsupportedOperationException();
485     }
486
487     @Override
488     public void delete(DbSession dbSession, Collection<QProfileDto> profiles) {
489       throw new UnsupportedOperationException();
490     }
491   }
492 }