3 * Copyright (C) 2009-2022 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.server.qualityprofile;
22 import java.util.List;
24 import java.util.Optional;
25 import javax.annotation.Nullable;
26 import org.junit.Rule;
27 import org.junit.Test;
28 import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
29 import org.sonar.api.rule.RuleStatus;
30 import org.sonar.api.rule.Severity;
31 import org.sonar.api.utils.System2;
32 import org.sonar.db.DbTester;
33 import org.sonar.db.qualityprofile.ActiveRuleParamDto;
34 import org.sonar.db.qualityprofile.OrgActiveRuleDto;
35 import org.sonar.db.qualityprofile.QProfileDto;
36 import org.sonar.db.rule.RuleDto;
37 import org.sonar.server.es.EsTester;
38 import org.sonar.server.exceptions.BadRequestException;
39 import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
40 import org.sonar.server.qualityprofile.builtin.RuleActivator;
41 import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
42 import org.sonar.server.tester.UserSessionRule;
43 import org.sonar.server.util.IntegerTypeValidation;
44 import org.sonar.server.util.StringTypeValidation;
45 import org.sonar.server.util.TypeValidations;
47 import static java.util.Arrays.asList;
48 import static java.util.Collections.emptyMap;
49 import static java.util.Collections.singleton;
50 import static org.assertj.core.api.Assertions.assertThat;
51 import static org.assertj.core.api.Assertions.assertThatThrownBy;
52 import static org.mockito.ArgumentMatchers.any;
53 import static org.mockito.ArgumentMatchers.anyList;
54 import static org.mockito.ArgumentMatchers.eq;
55 import static org.mockito.Mockito.mock;
56 import static org.mockito.Mockito.times;
57 import static org.mockito.Mockito.verify;
58 import static org.sonar.api.rule.Severity.BLOCKER;
59 import static org.sonar.server.qualityprofile.ActiveRuleInheritance.INHERITED;
61 public class QProfileTreeImplTest {
63 private System2 system2 = new AlwaysIncreasingSystem2();
65 public DbTester db = DbTester.create(system2);
67 public EsTester es = EsTester.create();
69 public UserSessionRule userSession = UserSessionRule.standalone();
70 private ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(db.getDbClient(), es.client());
71 private TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation()));
72 private QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class);
73 private RuleActivator ruleActivator = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession);
74 private QProfileRules qProfileRules = new QProfileRulesImpl(db.getDbClient(), ruleActivator, null, activeRuleIndexer, qualityProfileChangeEventService);
75 private QProfileTree underTest = new QProfileTreeImpl(db.getDbClient(), ruleActivator, System2.INSTANCE, activeRuleIndexer, mock(QualityProfileChangeEventService.class));
78 public void set_itself_as_parent_fails() {
79 RuleDto rule = createRule();
80 QProfileDto profile = createProfile(rule);
82 assertThatThrownBy(() -> underTest.setParentAndCommit(db.getSession(), profile, profile))
83 .isInstanceOf(BadRequestException.class)
84 .hasMessageContaining(" can not be selected as parent of ");
88 public void set_child_as_parent_fails() {
89 RuleDto rule = createRule();
90 QProfileDto parentProfile = createProfile(rule);
91 QProfileDto childProfile = createChildProfile(parentProfile);
93 assertThatThrownBy(() -> underTest.setParentAndCommit(db.getSession(), parentProfile, childProfile))
94 .isInstanceOf(BadRequestException.class)
95 .hasMessageContaining(" can not be selected as parent of ");
99 public void set_grandchild_as_parent_fails() {
100 RuleDto rule = createRule();
101 QProfileDto parentProfile = createProfile(rule);
102 QProfileDto childProfile = createChildProfile(parentProfile);
103 QProfileDto grandchildProfile = createChildProfile(childProfile);
105 assertThatThrownBy(() -> underTest.setParentAndCommit(db.getSession(), parentProfile, grandchildProfile))
106 .isInstanceOf(BadRequestException.class)
107 .hasMessageContaining(" can not be selected as parent of ");
111 public void cannot_set_parent_if_language_is_different() {
112 RuleDto rule1 = db.rules().insert(r -> r.setLanguage("foo"));
113 RuleDto rule2 = db.rules().insert(r -> r.setLanguage("bar"));
115 QProfileDto parentProfile = createProfile(rule1);
116 List<ActiveRuleChange> changes = activate(parentProfile, RuleActivation.create(rule1.getUuid()));
117 assertThat(changes).hasSize(1);
119 QProfileDto childProfile = createProfile(rule2);
120 changes = activate(childProfile, RuleActivation.create(rule2.getUuid()));
121 assertThat(changes).hasSize(1);
123 assertThatThrownBy(() -> underTest.setParentAndCommit(db.getSession(), childProfile, parentProfile))
124 .isInstanceOf(BadRequestException.class)
125 .hasMessageContaining("Cannot set the profile");
129 public void set_then_unset_parent() {
130 RuleDto rule1 = createJavaRule();
131 RuleDto rule2 = createJavaRule();
133 QProfileDto profile1 = createProfile(rule1);
134 List<ActiveRuleChange> changes = activate(profile1, RuleActivation.create(rule1.getUuid()));
135 assertThat(changes).hasSize(1);
137 QProfileDto profile2 = createProfile(rule2);
138 changes = activate(profile2, RuleActivation.create(rule2.getUuid()));
139 assertThat(changes).hasSize(1);
141 changes = underTest.setParentAndCommit(db.getSession(), profile2, profile1);
142 assertThat(changes).hasSize(1);
143 assertThatRuleIsActivated(profile2, rule1, changes, rule1.getSeverityString(), INHERITED, emptyMap());
144 assertThatRuleIsActivated(profile2, rule2, null, rule2.getSeverityString(), null, emptyMap());
145 verify(qualityProfileChangeEventService, times(2)).distributeRuleChangeEvent(any(), any(), eq(profile2.getLanguage()));
147 changes = underTest.removeParentAndCommit(db.getSession(), profile2);
148 assertThat(changes).hasSize(1);
149 assertThatRuleIsActivated(profile2, rule2, null, rule2.getSeverityString(), null, emptyMap());
150 assertThatRuleIsNotPresent(profile2, rule1);
151 verify(qualityProfileChangeEventService, times(2)).distributeRuleChangeEvent(any(), any(), eq(profile2.getLanguage()));
155 public void set_then_unset_parent_keep_overridden_rules() {
156 RuleDto rule1 = createJavaRule();
157 RuleDto rule2 = createJavaRule();
158 QProfileDto profile1 = createProfile(rule1);
159 List<ActiveRuleChange> changes = activate(profile1, RuleActivation.create(rule1.getUuid()));
160 assertThat(changes).hasSize(1);
162 QProfileDto profile2 = createProfile(rule2);
163 changes = activate(profile2, RuleActivation.create(rule2.getUuid()));
164 assertThat(changes).hasSize(1);
166 changes = underTest.setParentAndCommit(db.getSession(), profile2, profile1);
167 assertThat(changes).hasSize(1);
168 assertThatRuleIsActivated(profile2, rule1, changes, rule1.getSeverityString(), INHERITED, emptyMap());
169 assertThatRuleIsActivated(profile2, rule2, null, rule2.getSeverityString(), null, emptyMap());
170 verify(qualityProfileChangeEventService, times(2)).distributeRuleChangeEvent(any(), any(), eq(profile2.getLanguage()));
172 RuleActivation activation = RuleActivation.create(rule1.getUuid(), BLOCKER, null);
173 changes = activate(profile2, activation);
174 assertThat(changes).hasSize(1);
175 assertThatRuleIsUpdated(profile2, rule1, BLOCKER, ActiveRuleInheritance.OVERRIDES, emptyMap());
176 assertThatRuleIsActivated(profile2, rule2, null, rule2.getSeverityString(), null, emptyMap());
178 changes = underTest.removeParentAndCommit(db.getSession(), profile2);
179 assertThat(changes).hasSize(1);
180 // Not testing changes here since severity is not set in changelog
181 assertThatRuleIsActivated(profile2, rule1, null, BLOCKER, null, emptyMap());
182 assertThatRuleIsActivated(profile2, rule2, null, rule2.getSeverityString(), null, emptyMap());
183 verify(qualityProfileChangeEventService, times(3)).distributeRuleChangeEvent(anyList(), any(), eq(profile2.getLanguage()));
187 public void activation_errors_are_ignored_when_setting_a_parent() {
188 RuleDto rule1 = createJavaRule();
189 RuleDto rule2 = createJavaRule();
190 QProfileDto parentProfile = createProfile(rule1);
191 activate(parentProfile, RuleActivation.create(rule1.getUuid()));
192 activate(parentProfile, RuleActivation.create(rule2.getUuid()));
194 rule1.setStatus(RuleStatus.REMOVED);
195 db.rules().update(rule1);
197 QProfileDto childProfile = createProfile(rule1);
198 List<ActiveRuleChange> changes = underTest.setParentAndCommit(db.getSession(), childProfile, parentProfile);
199 verify(qualityProfileChangeEventService, times(2)).distributeRuleChangeEvent(any(), any(), eq(childProfile.getLanguage()));
201 assertThatRuleIsNotPresent(childProfile, rule1);
202 assertThatRuleIsActivated(childProfile, rule2, changes, rule2.getSeverityString(), INHERITED, emptyMap());
205 private List<ActiveRuleChange> activate(QProfileDto profile, RuleActivation activation) {
206 return qProfileRules.activateAndCommit(db.getSession(), profile, singleton(activation));
209 private QProfileDto createProfile(RuleDto rule) {
210 return db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()));
213 private QProfileDto createChildProfile(QProfileDto parent) {
214 return db.qualityProfiles().insert(p -> p
215 .setLanguage(parent.getLanguage())
216 .setParentKee(parent.getKee())
217 .setName("Child of " + parent.getName()));
220 private void assertThatRuleIsActivated(QProfileDto profile, RuleDto rule, @Nullable List<ActiveRuleChange> changes,
221 String expectedSeverity, @Nullable ActiveRuleInheritance expectedInheritance, Map<String, String> expectedParams) {
222 OrgActiveRuleDto activeRule = db.getDbClient().activeRuleDao().selectByProfile(db.getSession(), profile)
224 .filter(ar -> ar.getRuleKey().equals(rule.getKey()))
226 .orElseThrow(IllegalStateException::new);
228 assertThat(activeRule.getSeverityString()).isEqualTo(expectedSeverity);
229 assertThat(activeRule.getInheritance()).isEqualTo(expectedInheritance != null ? expectedInheritance.name() : null);
231 List<ActiveRuleParamDto> params = db.getDbClient().activeRuleDao().selectParamsByActiveRuleUuid(db.getSession(), activeRule.getUuid());
232 assertThat(params).hasSize(expectedParams.size());
234 if (changes != null) {
235 ActiveRuleChange change = changes.stream()
236 .filter(c -> c.getActiveRule().getUuid().equals(activeRule.getUuid()))
237 .findFirst().orElseThrow(IllegalStateException::new);
238 assertThat(change.getInheritance()).isEqualTo(expectedInheritance);
239 assertThat(change.getSeverity()).isEqualTo(expectedSeverity);
240 assertThat(change.getType()).isEqualTo(ActiveRuleChange.Type.ACTIVATED);
244 private void assertThatRuleIsNotPresent(QProfileDto profile, RuleDto rule) {
245 Optional<OrgActiveRuleDto> activeRule = db.getDbClient().activeRuleDao().selectByProfile(db.getSession(), profile)
247 .filter(ar -> ar.getRuleKey().equals(rule.getKey()))
250 assertThat(activeRule).isEmpty();
253 private void assertThatRuleIsUpdated(QProfileDto profile, RuleDto rule,
254 String expectedSeverity, @Nullable ActiveRuleInheritance expectedInheritance, Map<String, String> expectedParams) {
255 OrgActiveRuleDto activeRule = db.getDbClient().activeRuleDao().selectByProfile(db.getSession(), profile)
257 .filter(ar -> ar.getRuleKey().equals(rule.getKey()))
259 .orElseThrow(IllegalStateException::new);
261 assertThat(activeRule.getSeverityString()).isEqualTo(expectedSeverity);
262 assertThat(activeRule.getInheritance()).isEqualTo(expectedInheritance != null ? expectedInheritance.name() : null);
264 List<ActiveRuleParamDto> params = db.getDbClient().activeRuleDao().selectParamsByActiveRuleUuid(db.getSession(), activeRule.getUuid());
265 assertThat(params).hasSize(expectedParams.size());
268 private RuleDto createRule() {
269 return db.rules().insert(r -> r.setSeverity(Severity.MAJOR));
272 private RuleDto createJavaRule() {
273 return db.rules().insert(r -> r.setSeverity(Severity.MAJOR).setLanguage("java"));