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