]> source.dussan.org Git - sonarqube.git/blob
7beb4f45b5de349c4826624dd054649b2dd08fb3
[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.collect.Multimap;
23 import java.security.SecureRandom;
24 import java.util.Arrays;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Random;
28 import java.util.function.Consumer;
29 import org.junit.Rule;
30 import org.junit.Test;
31 import org.mockito.ArgumentCaptor;
32 import org.sonar.api.rule.RuleKey;
33 import org.sonar.api.rule.Severity;
34 import org.sonar.api.rules.RulePriority;
35 import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
36 import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInActiveRule;
37 import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
38 import org.sonar.api.utils.System2;
39 import org.sonar.api.utils.log.LogTester;
40 import org.sonar.core.util.UuidFactoryFast;
41 import org.sonar.core.util.stream.MoreCollectors;
42 import org.sonar.db.DbClient;
43 import org.sonar.db.DbTester;
44 import org.sonar.db.qualityprofile.ActiveRuleDto;
45 import org.sonar.db.qualityprofile.QProfileDto;
46 import org.sonar.db.qualityprofile.RulesProfileDto;
47 import org.sonar.db.rule.RuleDefinitionDto;
48 import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
49 import org.sonar.server.qualityprofile.builtin.BuiltInQProfile;
50 import org.sonar.server.qualityprofile.builtin.BuiltInQProfileInsert;
51 import org.sonar.server.qualityprofile.builtin.BuiltInQProfileInsertImpl;
52 import org.sonar.server.qualityprofile.builtin.BuiltInQProfileRepositoryRule;
53 import org.sonar.server.qualityprofile.builtin.BuiltInQProfileUpdate;
54 import org.sonar.server.qualityprofile.builtin.BuiltInQProfileUpdateImpl;
55 import org.sonar.server.qualityprofile.builtin.BuiltInQualityProfilesUpdateListener;
56 import org.sonar.server.qualityprofile.builtin.QProfileName;
57 import org.sonar.server.qualityprofile.builtin.RuleActivator;
58 import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
59 import org.sonar.server.rule.DefaultRuleFinder;
60 import org.sonar.server.rule.ServerRuleFinder;
61 import org.sonar.server.rule.index.RuleIndex;
62 import org.sonar.server.tester.UserSessionRule;
63 import org.sonar.server.util.TypeValidations;
64
65 import static com.google.common.base.Preconditions.checkState;
66 import static java.util.Collections.singleton;
67 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
68 import static org.apache.commons.lang.math.RandomUtils.nextLong;
69 import static org.assertj.core.api.Assertions.assertThat;
70 import static org.assertj.core.api.Assertions.tuple;
71 import static org.mockito.ArgumentMatchers.any;
72 import static org.mockito.ArgumentMatchers.anyLong;
73 import static org.mockito.ArgumentMatchers.eq;
74 import static org.mockito.Mockito.mock;
75 import static org.mockito.Mockito.verify;
76 import static org.mockito.Mockito.verifyNoInteractions;
77 import static org.mockito.Mockito.verifyZeroInteractions;
78 import static org.mockito.Mockito.when;
79 import static org.sonar.api.rules.RulePriority.MAJOR;
80 import static org.sonar.db.qualityprofile.QualityProfileTesting.newRuleProfileDto;
81 import static org.sonar.server.language.LanguageTesting.newLanguage;
82 import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED;
83 import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
84 import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.UPDATED;
85
86 public class RegisterQualityProfilesNotificationTest {
87
88   private static final Random RANDOM = new SecureRandom();
89
90   private System2 system2 = mock(System2.class);
91   @Rule
92   public DbTester db = DbTester.create(system2);
93   @Rule
94   public UserSessionRule userSessionRule = UserSessionRule.standalone();
95   @Rule
96   public BuiltInQProfileRepositoryRule builtInQProfileRepositoryRule = new BuiltInQProfileRepositoryRule();
97   @Rule
98   public LogTester logTester = new LogTester();
99
100   private DbClient dbClient = db.getDbClient();
101   private TypeValidations typeValidations = mock(TypeValidations.class);
102   private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class);
103   private QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class);
104   private ServerRuleFinder ruleFinder = new DefaultRuleFinder(dbClient);
105   private BuiltInQProfileInsert builtInQProfileInsert = new BuiltInQProfileInsertImpl(dbClient, ruleFinder, system2, UuidFactoryFast.getInstance(),
106     typeValidations, activeRuleIndexer);
107   private RuleActivator ruleActivator = new RuleActivator(system2, dbClient, typeValidations, userSessionRule);
108   private QProfileRules qProfileRules = new QProfileRulesImpl(dbClient, ruleActivator, mock(RuleIndex.class), activeRuleIndexer, qualityProfileChangeEventService);
109   private BuiltInQProfileUpdate builtInQProfileUpdate = new BuiltInQProfileUpdateImpl(dbClient, ruleActivator, activeRuleIndexer, qualityProfileChangeEventService);
110   private BuiltInQualityProfilesUpdateListener builtInQualityProfilesNotification = mock(BuiltInQualityProfilesUpdateListener.class);
111   private RegisterQualityProfiles underTest = new RegisterQualityProfiles(builtInQProfileRepositoryRule, dbClient,
112     builtInQProfileInsert, builtInQProfileUpdate, builtInQualityProfilesNotification, system2);
113
114   @Test
115   public void do_not_send_notification_on_new_profile() {
116     String language = newLanguageKey();
117     builtInQProfileRepositoryRule.add(newLanguage(language), "Sonar way");
118     builtInQProfileRepositoryRule.initialize();
119
120     underTest.start();
121
122     verifyZeroInteractions(builtInQualityProfilesNotification);
123   }
124
125   @Test
126   public void do_not_send_notification_when_profile_is_not_updated() {
127     String language = newLanguageKey();
128     RuleDefinitionDto dbRule = db.rules().insert(r -> r.setLanguage(language));
129     RulesProfileDto dbProfile = insertBuiltInProfile(language);
130     activateRuleInDb(dbProfile, dbRule, MAJOR);
131     addPluginProfile(dbProfile, dbRule);
132     builtInQProfileRepositoryRule.initialize();
133
134     underTest.start();
135
136     verifyNoInteractions(builtInQualityProfilesNotification);
137   }
138
139   @Test
140   public void send_notification_when_a_new_rule_is_activated() {
141     String language = newLanguageKey();
142     RuleDefinitionDto existingRule = db.rules().insert(r -> r.setLanguage(language));
143     RulesProfileDto dbProfile = insertBuiltInProfile(language);
144     activateRuleInDb(dbProfile, existingRule, MAJOR);
145     RuleDefinitionDto newRule = db.rules().insert(r -> r.setLanguage(language));
146     addPluginProfile(dbProfile, existingRule, newRule);
147     builtInQProfileRepositoryRule.initialize();
148
149     underTest.start();
150
151     ArgumentCaptor<Multimap> captor = ArgumentCaptor.forClass(Multimap.class);
152     verify(builtInQualityProfilesNotification).onChange(captor.capture(), anyLong(), anyLong());
153     Multimap<QProfileName, ActiveRuleChange> updatedProfiles = captor.getValue();
154     assertThat(updatedProfiles.keySet())
155       .extracting(QProfileName::getName, QProfileName::getLanguage)
156       .containsExactlyInAnyOrder(tuple(dbProfile.getName(), dbProfile.getLanguage()));
157     assertThat(updatedProfiles.values())
158       .extracting(value -> value.getActiveRule().getRuleUuid(), ActiveRuleChange::getType)
159       .containsExactlyInAnyOrder(tuple(newRule.getUuid(), ACTIVATED));
160   }
161
162   @Test
163   public void send_notification_when_a_rule_is_deactivated() {
164     String language = newLanguageKey();
165     RuleDefinitionDto existingRule = db.rules().insert(r -> r.setLanguage(language));
166     RulesProfileDto dbProfile = insertBuiltInProfile(language);
167     activateRuleInDb(dbProfile, existingRule, MAJOR);
168     addPluginProfile(dbProfile);
169     builtInQProfileRepositoryRule.initialize();
170
171     underTest.start();
172
173     ArgumentCaptor<Multimap> captor = ArgumentCaptor.forClass(Multimap.class);
174     verify(builtInQualityProfilesNotification).onChange(captor.capture(), anyLong(), anyLong());
175     Multimap<QProfileName, ActiveRuleChange> updatedProfiles = captor.getValue();
176     assertThat(updatedProfiles.keySet())
177       .extracting(QProfileName::getName, QProfileName::getLanguage)
178       .containsExactlyInAnyOrder(tuple(dbProfile.getName(), dbProfile.getLanguage()));
179     assertThat(updatedProfiles.values())
180       .extracting(value -> value.getActiveRule().getRuleUuid(), ActiveRuleChange::getType)
181       .containsExactlyInAnyOrder(tuple(existingRule.getUuid(), DEACTIVATED));
182   }
183
184   @Test
185   public void send_a_single_notification_when_multiple_rules_are_activated() {
186     String language = newLanguageKey();
187
188     RuleDefinitionDto existingRule1 = db.rules().insert(r -> r.setLanguage(language));
189     RuleDefinitionDto newRule1 = db.rules().insert(r -> r.setLanguage(language));
190     RulesProfileDto dbProfile1 = insertBuiltInProfile(language);
191     activateRuleInDb(dbProfile1, existingRule1, MAJOR);
192     addPluginProfile(dbProfile1, existingRule1, newRule1);
193
194     RuleDefinitionDto existingRule2 = db.rules().insert(r -> r.setLanguage(language));
195     RuleDefinitionDto newRule2 = db.rules().insert(r -> r.setLanguage(language));
196     RulesProfileDto dbProfile2 = insertBuiltInProfile(language);
197     activateRuleInDb(dbProfile2, existingRule2, MAJOR);
198     addPluginProfile(dbProfile2, existingRule2, newRule2);
199     builtInQProfileRepositoryRule.initialize();
200
201     underTest.start();
202
203     ArgumentCaptor<Multimap> captor = ArgumentCaptor.forClass(Multimap.class);
204     verify(builtInQualityProfilesNotification).onChange(captor.capture(), anyLong(), anyLong());
205     Multimap<QProfileName, ActiveRuleChange> updatedProfiles = captor.getValue();
206     assertThat(updatedProfiles.keySet())
207       .extracting(QProfileName::getName, QProfileName::getLanguage)
208       .containsExactlyInAnyOrder(
209         tuple(dbProfile1.getName(), dbProfile1.getLanguage()),
210         tuple(dbProfile2.getName(), dbProfile2.getLanguage()));
211     assertThat(updatedProfiles.values())
212       .extracting(value -> value.getActiveRule().getRuleUuid(), ActiveRuleChange::getType)
213       .containsExactlyInAnyOrder(
214         tuple(newRule1.getUuid(), ACTIVATED),
215         tuple(newRule2.getUuid(), ACTIVATED));
216   }
217
218   @Test
219   public void notification_does_not_include_inherited_profiles_when_rule_is_added() {
220     String language = newLanguageKey();
221     RuleDefinitionDto newRule = db.rules().insert(r -> r.setLanguage(language));
222
223     QProfileDto builtInQProfileDto = insertProfile(orgQProfile -> orgQProfile.setIsBuiltIn(true).setLanguage(language));
224     QProfileDto childQProfileDto = insertProfile(orgQProfile -> orgQProfile.setIsBuiltIn(false).setLanguage(language).setParentKee(builtInQProfileDto.getKee()));
225     addPluginProfile(builtInQProfileDto, newRule);
226     builtInQProfileRepositoryRule.initialize();
227
228     underTest.start();
229
230     ArgumentCaptor<Multimap> captor = ArgumentCaptor.forClass(Multimap.class);
231     verify(builtInQualityProfilesNotification).onChange(captor.capture(), anyLong(), anyLong());
232     Multimap<QProfileName, ActiveRuleChange> updatedProfiles = captor.getValue();
233     assertThat(updatedProfiles.keySet())
234       .extracting(QProfileName::getName, QProfileName::getLanguage)
235       .containsExactlyInAnyOrder(tuple(builtInQProfileDto.getName(), builtInQProfileDto.getLanguage()));
236     assertThat(updatedProfiles.values())
237       .extracting(value -> value.getActiveRule().getRuleUuid(), ActiveRuleChange::getType)
238       .containsExactlyInAnyOrder(tuple(newRule.getUuid(), ACTIVATED));
239   }
240
241   @Test
242   public void notification_does_not_include_inherited_profiled_when_rule_is_changed() {
243     String language = newLanguageKey();
244     RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage(language).setSeverity(Severity.MINOR));
245
246     QProfileDto builtInProfile = insertProfile(orgQProfile -> orgQProfile.setIsBuiltIn(true).setLanguage(language));
247     db.qualityProfiles().activateRule(builtInProfile, rule, ar -> ar.setSeverity(Severity.MINOR));
248     QProfileDto childProfile = insertProfile(orgQProfile -> orgQProfile.setIsBuiltIn(false).setLanguage(language).setParentKee(builtInProfile.getKee()));
249     db.qualityProfiles().activateRule(childProfile, rule, ar -> ar.setInheritance(ActiveRuleDto.INHERITED).setSeverity(Severity.MINOR));
250     addPluginProfile(builtInProfile, rule);
251     builtInQProfileRepositoryRule.initialize();
252     db.commit();
253
254     underTest.start();
255
256     ArgumentCaptor<Multimap> captor = ArgumentCaptor.forClass(Multimap.class);
257     verify(builtInQualityProfilesNotification).onChange(captor.capture(), anyLong(), anyLong());
258     Multimap<QProfileName, ActiveRuleChange> updatedProfiles = captor.getValue();
259     assertThat(updatedProfiles.keySet())
260       .extracting(QProfileName::getName, QProfileName::getLanguage)
261       .containsExactlyInAnyOrder(tuple(builtInProfile.getName(), builtInProfile.getLanguage()));
262     assertThat(updatedProfiles.values())
263       .extracting(value -> value.getActiveRule().getRuleUuid(), ActiveRuleChange::getType)
264       .containsExactlyInAnyOrder(tuple(rule.getUuid(), UPDATED));
265   }
266
267   @Test
268   public void notification_does_not_include_inherited_profiles_when_rule_is_deactivated() {
269     String language = newLanguageKey();
270     RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage(language).setSeverity(Severity.MINOR));
271
272     QProfileDto builtInQProfileDto = insertProfile(orgQProfile -> orgQProfile.setIsBuiltIn(true).setLanguage(language));
273     db.qualityProfiles().activateRule(builtInQProfileDto, rule);
274     QProfileDto childQProfileDto = insertProfile(orgQProfile -> orgQProfile.setIsBuiltIn(false).setLanguage(language).setParentKee(builtInQProfileDto.getKee()));
275     qProfileRules.activateAndCommit(db.getSession(), childQProfileDto, singleton(RuleActivation.create(rule.getUuid())));
276     db.commit();
277
278     addPluginProfile(builtInQProfileDto);
279     builtInQProfileRepositoryRule.initialize();
280
281     underTest.start();
282
283     ArgumentCaptor<Multimap> captor = ArgumentCaptor.forClass(Multimap.class);
284     verify(builtInQualityProfilesNotification).onChange(captor.capture(), anyLong(), anyLong());
285     Multimap<QProfileName, ActiveRuleChange> updatedProfiles = captor.getValue();
286     assertThat(updatedProfiles.keySet())
287       .extracting(QProfileName::getName, QProfileName::getLanguage)
288       .containsExactlyInAnyOrder(tuple(builtInQProfileDto.getName(), builtInQProfileDto.getLanguage()));
289     assertThat(updatedProfiles.values())
290       .extracting(value -> value.getActiveRule().getRuleUuid(), ActiveRuleChange::getType)
291       .containsExactlyInAnyOrder(tuple(rule.getUuid(), DEACTIVATED));
292   }
293
294   @Test
295   public void notification_contains_send_start_and_end_date() {
296     String language = newLanguageKey();
297     RuleDefinitionDto existingRule = db.rules().insert(r -> r.setLanguage(language));
298     RulesProfileDto dbProfile = insertBuiltInProfile(language);
299     activateRuleInDb(dbProfile, existingRule, MAJOR);
300     RuleDefinitionDto newRule = db.rules().insert(r -> r.setLanguage(language));
301     addPluginProfile(dbProfile, existingRule, newRule);
302     builtInQProfileRepositoryRule.initialize();
303     long startDate = RANDOM.nextInt(5000);
304     long endDate = startDate + RANDOM.nextInt(5000);
305     when(system2.now()).thenReturn(startDate, endDate);
306
307     underTest.start();
308
309     verify(builtInQualityProfilesNotification).onChange(any(), eq(startDate), eq(endDate));
310   }
311
312   private void addPluginProfile(RulesProfileDto dbProfile, RuleDefinitionDto... dbRules) {
313     BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
314     NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(dbProfile.getName(), dbProfile.getLanguage());
315
316     Arrays.stream(dbRules).forEach(dbRule -> newQp.activateRule(dbRule.getRepositoryKey(), dbRule.getRuleKey()).overrideSeverity(Severity.MAJOR));
317     newQp.done();
318     List<BuiltInActiveRule> rules = context.profile(dbProfile.getLanguage(), dbProfile.getName()).rules();
319     BuiltInQProfile.ActiveRule[] activeRules = toActiveRules(rules, dbRules);
320     builtInQProfileRepositoryRule.add(newLanguage(dbProfile.getLanguage()), dbProfile.getName(), false, activeRules);
321   }
322
323   private static BuiltInQProfile.ActiveRule[] toActiveRules(List<BuiltInActiveRule> rules, RuleDefinitionDto[] dbRules) {
324     Map<RuleKey, RuleDefinitionDto> dbRulesByRuleKey = Arrays.stream(dbRules)
325       .collect(MoreCollectors.uniqueIndex(RuleDefinitionDto::getKey));
326     return rules.stream()
327       .map(r -> {
328         RuleKey ruleKey = RuleKey.of(r.repoKey(), r.ruleKey());
329         RuleDefinitionDto ruleDefinitionDto = dbRulesByRuleKey.get(ruleKey);
330         checkState(ruleDefinitionDto != null, "Rule '%s' not found", ruleKey);
331         return new BuiltInQProfile.ActiveRule(ruleDefinitionDto.getUuid(), r);
332       }).toArray(BuiltInQProfile.ActiveRule[]::new);
333   }
334
335   private void addPluginProfile(QProfileDto profile, RuleDefinitionDto... dbRules) {
336     BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
337     NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage());
338
339     Arrays.stream(dbRules).forEach(dbRule -> newQp.activateRule(dbRule.getRepositoryKey(), dbRule.getRuleKey()).overrideSeverity(Severity.MAJOR));
340     newQp.done();
341     BuiltInQProfile.ActiveRule[] activeRules = toActiveRules(context.profile(profile.getLanguage(), profile.getName()).rules(), dbRules);
342     builtInQProfileRepositoryRule.add(newLanguage(profile.getLanguage()), profile.getName(), false, activeRules);
343   }
344
345   private RulesProfileDto insertBuiltInProfile(String language) {
346     RulesProfileDto ruleProfileDto = newRuleProfileDto(rp -> rp.setIsBuiltIn(true).setLanguage(language));
347     db.getDbClient().qualityProfileDao().insert(db.getSession(), ruleProfileDto);
348     db.commit();
349     return ruleProfileDto;
350   }
351
352   private void activateRuleInDb(RulesProfileDto profile, RuleDefinitionDto rule, RulePriority severity) {
353     ActiveRuleDto dto = new ActiveRuleDto()
354       .setProfileUuid(profile.getUuid())
355       .setSeverity(severity.name())
356       .setRuleUuid(rule.getUuid())
357       .setCreatedAt(nextLong())
358       .setUpdatedAt(nextLong());
359     db.getDbClient().activeRuleDao().insert(db.getSession(), dto);
360     db.commit();
361   }
362
363   private QProfileDto insertProfile(Consumer<QProfileDto> consumer) {
364     QProfileDto builtInQProfileDto = db.qualityProfiles().insert(consumer);
365     db.commit();
366     return builtInQProfileDto;
367   }
368
369   private static String newLanguageKey() {
370     return randomAlphanumeric(20).toLowerCase();
371   }
372 }