]> source.dussan.org Git - sonarqube.git/blob
765429591cc67afffb3735b073983dd9c95a36e4
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2021 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.junit.rules.ExpectedException;
34 import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
35 import org.sonar.api.rule.RuleKey;
36 import org.sonar.api.rule.RuleStatus;
37 import org.sonar.api.rules.RuleType;
38 import org.sonar.api.utils.System2;
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.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.junit.Assert.fail;
52 import static org.junit.rules.ExpectedException.none;
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
58 public class QProfileBackuperImplTest {
59
60   private static final String EMPTY_BACKUP = "<?xml version='1.0' encoding='UTF-8'?>" +
61     "<profile><name>foo</name>" +
62     "<language>js</language>" +
63     "<rules/>" +
64     "</profile>";
65
66   private final System2 system2 = new AlwaysIncreasingSystem2();
67
68   @Rule
69   public ExpectedException expectedException = none();
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.toString()).isEqualTo("<?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/>" +
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.toString()).isEqualTo("<?xml version='1.0' encoding='UTF-8'?>" +
135       "<profile><name>" + profile.getName() + "</name>" +
136       "<language>" + profile.getLanguage() + "</language>" +
137       "<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       .setDescription("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.toString()).isEqualTo("<?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.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_backup_on_profile_having_different_name() {
191     Reader backup = new StringReader(EMPTY_BACKUP);
192
193     QProfileRestoreSummary summary = underTest.restore(db.getSession(), backup, "bar");
194
195     assertThat(summary.getProfile().getName()).isEqualTo("bar");
196     assertThat(summary.getProfile().getLanguage()).isEqualTo("js");
197
198     assertThat(reset.calledProfile.getKee()).isEqualTo(summary.getProfile().getKee());
199     assertThat(reset.calledActivations).isEmpty();
200   }
201
202   @Test
203   public void restore_resets_the_activated_rules() {
204     String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
205     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
206       "<profile><name>foo</name>" +
207       "<language>js</language>" +
208       "<rules>" +
209       "<rule>" +
210       "<repositoryKey>sonarjs</repositoryKey>" +
211       "<key>s001</key>" +
212       "<priority>BLOCKER</priority>" +
213       "<parameters>" +
214       "<parameter><key>bar</key><value>baz</value></parameter>" +
215       "</parameters>" +
216       "</rule>" +
217       "</rules>" +
218       "</profile>");
219
220     underTest.restore(db.getSession(), backup, (String) null);
221
222     assertThat(reset.calledActivations).hasSize(1);
223     RuleActivation activation = reset.calledActivations.get(0);
224     assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
225     assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
226     assertThat(activation.getParameter("bar")).isEqualTo("baz");
227   }
228
229   @Test
230   public void restore_custom_rule() {
231     when(ruleCreator.create(any(), anyList())).then(invocation -> Collections.singletonList(db.rules().insert(RuleKey.of("sonarjs", "s001")).getKey()));
232
233     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
234       "<profile>" +
235       "<name>custom rule</name>" +
236       "<language>js</language>" +
237       "<rules><rule>" +
238       "<repositoryKey>sonarjs</repositoryKey>" +
239       "<key>s001</key>" +
240       "<type>CODE_SMELL</type>" +
241       "<priority>CRITICAL</priority>" +
242       "<name>custom rule name</name>" +
243       "<templateKey>rule_mc8</templateKey>" +
244       "<description>custom rule description</description>" +
245       "<parameters><parameter>" +
246       "<key>bar</key>" +
247       "<value>baz</value>" +
248       "</parameter>" +
249       "</parameters>" +
250       "</rule></rules></profile>");
251
252     underTest.restore(db.getSession(), backup, (String) null);
253
254     assertThat(reset.calledActivations).hasSize(1);
255     RuleActivation activation = reset.calledActivations.get(0);
256     assertThat(activation.getSeverity()).isEqualTo("CRITICAL");
257     assertThat(activation.getParameter("bar")).isEqualTo("baz");
258   }
259
260   @Test
261   public void restore_skips_rule_without_template_key_and_db_definition() {
262     String ruleUuid = db.rules().insert(RuleKey.of("sonarjs", "s001")).getUuid();
263     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
264       "<profile><name>foo</name>" +
265       "<language>js</language>" +
266       "<rules>" +
267       "<rule>" +
268       "<repositoryKey>sonarjs</repositoryKey>" +
269       "<key>s001</key>" +
270       "<priority>BLOCKER</priority>" +
271       "<parameters>" +
272       "<parameter><key>bar</key><value>baz</value></parameter>" +
273       "</parameters>" +
274       "</rule>" +
275       "<rule>" +
276       "<repositoryKey>sonarjs</repositoryKey>" +
277       "<key>s002</key>" +
278       "<priority>MAJOR</priority>" +
279       "</rule>" +
280       "</rules>" +
281       "</profile>");
282
283     underTest.restore(db.getSession(), backup, (String) null);
284
285     assertThat(reset.calledActivations).hasSize(1);
286     RuleActivation activation = reset.calledActivations.get(0);
287     assertThat(activation.getRuleUuid()).isEqualTo(ruleUuid);
288     assertThat(activation.getSeverity()).isEqualTo("BLOCKER");
289     assertThat(activation.getParameter("bar")).isEqualTo("baz");
290   }
291
292   @Test
293   public void copy_profile() {
294     RuleDefinitionDto rule = createRule();
295     RuleParamDto param = db.rules().insertRuleParam(rule);
296     QProfileDto from = createProfile(rule.getLanguage());
297     ActiveRuleDto activeRule = activate(from, rule, param);
298
299     QProfileDto to = createProfile(rule.getLanguage());
300     underTest.copy(db.getSession(), from, to);
301
302     assertThat(reset.calledActivations).extracting(RuleActivation::getRuleUuid).containsOnly(activeRule.getRuleUuid());
303     assertThat(reset.calledActivations.get(0).getParameter(param.getName())).isEqualTo("20");
304     assertThat(reset.calledProfile).isEqualTo(to);
305   }
306
307   @Test
308   public void fail_to_restore_if_bad_xml_format() {
309     try {
310       underTest.restore(db.getSession(), new StringReader("<rules><rule></rules>"), (String) null);
311       fail();
312     } catch (IllegalArgumentException e) {
313       assertThat(e).hasMessage("Backup XML is not valid. Root element must be <profile>.");
314       assertThat(reset.calledProfile).isNull();
315     }
316   }
317
318   @Test
319   public void fail_to_restore_if_not_xml_backup() {
320     try {
321       underTest.restore(db.getSession(), new StringReader("foo"), (String) null);
322       fail();
323     } catch (IllegalArgumentException e) {
324       assertThat(reset.calledProfile).isNull();
325     }
326   }
327
328   @Test
329   public void fail_to_restore_if_xml_is_not_well_formed() {
330     expectedException.expect(IllegalArgumentException.class);
331     expectedException.expectMessage("Fail to restore Quality profile backup, XML document is not well formed");
332     String notWellFormedXml = "<?xml version='1.0' encoding='UTF-8'?><profile><name>\"profil\"</name><language>\"language\"</language><rules/></profile";
333
334     underTest.restore(db.getSession(), new StringReader(notWellFormedXml), (String) null);
335   }
336
337   @Test
338   public void fail_to_restore_if_duplicate_rule() throws Exception {
339     try {
340       String xml = Resources.toString(getClass().getResource("QProfileBackuperTest/duplicates-xml-backup.xml"), UTF_8);
341       underTest.restore(db.getSession(), new StringReader(xml), (String) null);
342       fail();
343     } catch (IllegalArgumentException e) {
344       assertThat(e).hasMessage("The quality profile cannot be restored as it contains duplicates for the following rules: xoo:x1, xoo:x2");
345       assertThat(reset.calledProfile).isNull();
346     }
347   }
348
349   @Test
350   public void fail_to_restore_external_rule() {
351     db.rules().insert(RuleKey.of("sonarjs", "s001"), r -> r.setIsExternal(true));
352     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
353       "<profile><name>foo</name>" +
354       "<language>js</language>" +
355       "<rules>" +
356       "<rule>" +
357       "<repositoryKey>sonarjs</repositoryKey>" +
358       "<key>s001</key>" +
359       "<priority>BLOCKER</priority>" +
360       "<parameters>" +
361       "<parameter><key>bar</key><value>baz</value></parameter>" +
362       "</parameters>" +
363       "</rule>" +
364       "</rules>" +
365       "</profile>");
366
367     expectedException.expect(IllegalArgumentException.class);
368     expectedException.expectMessage("The quality profile cannot be restored as it contains rules from external rule engines: sonarjs:s001");
369     underTest.restore(db.getSession(), backup, (String) null);
370   }
371
372   private RuleDefinitionDto createRule() {
373     return db.rules().insert();
374   }
375
376   private QProfileDto createProfile(String language) {
377     return db.qualityProfiles().insert(p -> p.setLanguage(language));
378   }
379
380   private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule) {
381     return db.qualityProfiles().activateRule(profile, rule);
382   }
383
384   private ActiveRuleDto activate(QProfileDto profile, RuleDefinitionDto rule, RuleParamDto param) {
385     ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile, rule);
386     ActiveRuleParamDto dto = ActiveRuleParamDto.createFor(param)
387       .setValue("20")
388       .setActiveRuleUuid(activeRule.getUuid());
389     db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRule, dto);
390     return activeRule;
391   }
392
393   private static class DummyReset implements QProfileReset {
394     private QProfileDto calledProfile;
395     private List<RuleActivation> calledActivations;
396
397     @Override
398     public BulkChangeResult reset(DbSession dbSession, QProfileDto profile, Collection<RuleActivation> activations) {
399       this.calledProfile = profile;
400       this.calledActivations = new ArrayList<>(activations);
401       return new BulkChangeResult();
402     }
403   }
404
405   private static class DummyProfileFactory implements QProfileFactory {
406     @Override
407     public QProfileDto getOrCreateCustom(DbSession dbSession, QProfileName key) {
408       return QualityProfileTesting.newQualityProfileDto()
409         .setLanguage(key.getLanguage())
410         .setName(key.getName());
411     }
412
413     @Override
414     public QProfileDto checkAndCreateCustom(DbSession dbSession, QProfileName name) {
415       throw new UnsupportedOperationException();
416     }
417
418     @Override
419     public QProfileDto createCustom(DbSession dbSession, QProfileName name, @Nullable String parentKey) {
420       throw new UnsupportedOperationException();
421     }
422
423     @Override
424     public void delete(DbSession dbSession, Collection<QProfileDto> profiles) {
425       throw new UnsupportedOperationException();
426     }
427   }
428 }