You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SearchActionIT.java 52KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 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.rule.ws;
  21. import java.time.Clock;
  22. import java.time.Instant;
  23. import java.time.OffsetDateTime;
  24. import java.time.ZoneId;
  25. import java.time.format.DateTimeFormatter;
  26. import java.util.HashSet;
  27. import java.util.List;
  28. import java.util.Optional;
  29. import java.util.Set;
  30. import java.util.function.Consumer;
  31. import java.util.function.Function;
  32. import java.util.stream.Collectors;
  33. import javax.annotation.Nullable;
  34. import org.junit.Before;
  35. import org.junit.Test;
  36. import org.sonar.api.config.Configuration;
  37. import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
  38. import org.sonar.api.issue.impact.Severity;
  39. import org.sonar.api.issue.impact.SoftwareQuality;
  40. import org.sonar.api.resources.Languages;
  41. import org.sonar.api.rule.RuleKey;
  42. import org.sonar.api.rule.RuleStatus;
  43. import org.sonar.api.rules.CleanCodeAttribute;
  44. import org.sonar.api.rules.RuleType;
  45. import org.sonar.api.server.debt.DebtRemediationFunction;
  46. import org.sonar.api.server.ws.WebService;
  47. import org.sonar.api.utils.System2;
  48. import org.sonar.api.utils.Version;
  49. import org.sonar.core.platform.SonarQubeVersion;
  50. import org.sonar.core.util.UuidFactory;
  51. import org.sonar.core.util.UuidFactoryFast;
  52. import org.sonar.core.util.Uuids;
  53. import org.sonar.db.DbTester;
  54. import org.sonar.db.issue.ImpactDto;
  55. import org.sonar.db.qualityprofile.ActiveRuleParamDto;
  56. import org.sonar.db.qualityprofile.QProfileDto;
  57. import org.sonar.db.rule.RuleDescriptionSectionContextDto;
  58. import org.sonar.db.rule.RuleDescriptionSectionDto;
  59. import org.sonar.db.rule.RuleDto;
  60. import org.sonar.db.rule.RuleParamDto;
  61. import org.sonar.db.user.UserDto;
  62. import org.sonar.server.es.EsTester;
  63. import org.sonar.server.exceptions.NotFoundException;
  64. import org.sonar.server.language.LanguageTesting;
  65. import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
  66. import org.sonar.server.qualityprofile.ActiveRuleChange;
  67. import org.sonar.server.qualityprofile.QProfileRules;
  68. import org.sonar.server.qualityprofile.QProfileRulesImpl;
  69. import org.sonar.server.qualityprofile.RuleActivation;
  70. import org.sonar.server.qualityprofile.builtin.RuleActivator;
  71. import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
  72. import org.sonar.server.rule.RuleDescriptionFormatter;
  73. import org.sonar.server.rule.index.RuleIndex;
  74. import org.sonar.server.rule.index.RuleIndexer;
  75. import org.sonar.server.tester.UserSessionRule;
  76. import org.sonar.server.text.MacroInterpreter;
  77. import org.sonar.server.util.IntegerTypeValidation;
  78. import org.sonar.server.util.StringTypeValidation;
  79. import org.sonar.server.util.TypeValidations;
  80. import org.sonar.server.ws.TestRequest;
  81. import org.sonar.server.ws.WsActionTester;
  82. import org.sonarqube.ws.Common;
  83. import org.sonarqube.ws.Rules;
  84. import org.sonarqube.ws.Rules.Rule;
  85. import org.sonarqube.ws.Rules.SearchResponse;
  86. import static java.util.Arrays.asList;
  87. import static java.util.Arrays.stream;
  88. import static java.util.Collections.emptySet;
  89. import static java.util.Collections.singleton;
  90. import static java.util.Collections.singletonList;
  91. import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
  92. import static org.assertj.core.api.Assertions.assertThat;
  93. import static org.assertj.core.api.Assertions.assertThatThrownBy;
  94. import static org.assertj.core.api.Assertions.tuple;
  95. import static org.assertj.guava.api.Assertions.entry;
  96. import static org.mockito.ArgumentMatchers.anyString;
  97. import static org.mockito.Mockito.doReturn;
  98. import static org.mockito.Mockito.mock;
  99. import static org.mockito.Mockito.when;
  100. import static org.sonar.api.rule.Severity.BLOCKER;
  101. import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY;
  102. import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
  103. import static org.sonar.db.rule.RuleTesting.newRule;
  104. import static org.sonar.db.rule.RuleTesting.newRuleWithoutDescriptionSection;
  105. import static org.sonar.db.rule.RuleTesting.setSystemTags;
  106. import static org.sonar.db.rule.RuleTesting.setTags;
  107. import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_CLEAN_CODE_ATTRIBUTE;
  108. import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
  109. import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
  110. import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_QPROFILE;
  111. import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_RULE_KEY;
  112. import static org.sonarqube.ws.Rules.Rule.DescriptionSection.Context.newBuilder;
  113. public class SearchActionIT {
  114. private static final String JAVA = "java";
  115. @org.junit.Rule
  116. public UserSessionRule userSession = UserSessionRule.standalone();
  117. private final System2 system2 = new AlwaysIncreasingSystem2();
  118. @org.junit.Rule
  119. public DbTester db = DbTester.create(system2);
  120. @org.junit.Rule
  121. public EsTester es = EsTester.create();
  122. private final RuleIndex ruleIndex = new RuleIndex(es.client(), system2);
  123. private final RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient());
  124. private final ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(db.getDbClient(), es.client());
  125. private final Languages languages = LanguageTesting.newLanguages(JAVA, "js");
  126. private final RuleQueryFactory ruleQueryFactory = new RuleQueryFactory(db.getDbClient());
  127. private final MacroInterpreter macroInterpreter = mock(MacroInterpreter.class);
  128. private final QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class);
  129. private final RuleMapper ruleMapper = new RuleMapper(languages, macroInterpreter, new RuleDescriptionFormatter());
  130. private final SearchAction underTest = new SearchAction(ruleIndex, ruleQueryFactory, db.getDbClient(),
  131. new RulesResponseFormatter(db.getDbClient(), new RuleWsSupport(db.getDbClient(), userSession), ruleMapper, languages));
  132. private final TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation()));
  133. private final SonarQubeVersion sonarQubeVersion = new SonarQubeVersion(Version.create(10, 3));
  134. private final RuleActivator ruleActivator = new RuleActivator(System2.INSTANCE, db.getDbClient(), typeValidations, userSession,
  135. mock(Configuration.class), sonarQubeVersion);
  136. private final QProfileRules qProfileRules = new QProfileRulesImpl(db.getDbClient(), ruleActivator, ruleIndex, activeRuleIndexer,
  137. qualityProfileChangeEventService);
  138. private final WsActionTester ws = new WsActionTester(underTest);
  139. private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
  140. @Before
  141. public void before() {
  142. doReturn("interpreted").when(macroInterpreter).interpret(anyString());
  143. }
  144. @Test
  145. public void test_definition() {
  146. WebService.Action def = ws.getDef();
  147. assertThat(def.isPost()).isFalse();
  148. assertThat(def.since()).isEqualTo("4.4");
  149. assertThat(def.isInternal()).isFalse();
  150. assertThat(def.responseExampleAsString()).isNotEmpty();
  151. assertThat(def.params()).hasSize(31);
  152. WebService.Param compareToProfile = def.param("compareToProfile");
  153. assertThat(compareToProfile.since()).isEqualTo("6.5");
  154. assertThat(compareToProfile.isRequired()).isFalse();
  155. assertThat(compareToProfile.isInternal()).isTrue();
  156. assertThat(compareToProfile.description()).isEqualTo("Quality profile key to filter rules that are activated. Meant to compare easily to profile set in 'qprofile'");
  157. }
  158. @Test
  159. public void return_empty_result() {
  160. Rules.SearchResponse response = ws.newRequest()
  161. .setParam(WebService.Param.FIELDS, "actives")
  162. .executeProtobuf(Rules.SearchResponse.class);
  163. assertThat(response.getTotal()).isZero();
  164. assertThat(response.getP()).isOne();
  165. assertThat(response.getPaging().getTotal()).isZero();
  166. assertThat(response.getPaging().getPageIndex()).isOne();
  167. assertThat(response.getPaging().getPageSize()).isNotZero();
  168. assertThat(response.getRulesCount()).isZero();
  169. }
  170. @Test
  171. public void return_all_rules() {
  172. RuleDto rule1 = db.rules().insert(r1 -> r1.setLanguage("java").setNoteUserUuid(null));
  173. RuleDto rule2 = db.rules().insert(r1 -> r1.setLanguage("java").setNoteUserUuid(null));
  174. indexRules();
  175. verify(r -> {
  176. }, rule1, rule2);
  177. }
  178. @Test
  179. public void return_note_login() {
  180. UserDto user1 = db.users().insertUser();
  181. RuleDto rule1 = db.rules().insert(r -> r.setNoteUserUuid(user1.getUuid()));
  182. UserDto disableUser = db.users().insertDisabledUser();
  183. RuleDto rule2 = db.rules().insert(r -> r.setNoteUserUuid(disableUser.getUuid()));
  184. indexRules();
  185. SearchResponse result = ws.newRequest()
  186. .setParam("f", "noteLogin")
  187. .executeProtobuf(SearchResponse.class);
  188. assertThat(result.getRulesList())
  189. .extracting(Rule::getKey, Rule::getNoteLogin)
  190. .containsExactlyInAnyOrder(
  191. tuple(rule1.getKey().toString(), user1.getLogin()),
  192. tuple(rule2.getKey().toString(), disableUser.getLogin()));
  193. }
  194. @Test
  195. public void dont_fail_if_note_author_no_longer_exists() {
  196. // note: this can only happen due to DB corruption (user deleted)
  197. RuleDto rule1 = db.rules().insert(r -> r.setNoteUserUuid("non-existent"));
  198. indexRules();
  199. SearchResponse result = ws.newRequest()
  200. .setParam("f", "noteLogin")
  201. .executeProtobuf(SearchResponse.class);
  202. assertThat(result.getRulesList())
  203. .extracting(Rule::getKey, Rule::getNoteLogin)
  204. .containsExactlyInAnyOrder(
  205. tuple(rule1.getKey().toString(), ""));
  206. }
  207. @Test
  208. public void filter_by_rule_key() {
  209. RuleDto rule1 = db.rules().insert(r1 -> r1.setLanguage("java").setNoteUserUuid(null));
  210. db.rules().insert(r1 -> r1.setLanguage("java").setNoteUserUuid(null));
  211. indexRules();
  212. verify(r -> r.setParam(PARAM_RULE_KEY, rule1.getKey().toString()), rule1);
  213. verifyNoResults(r -> r.setParam(PARAM_RULE_KEY, "missing"));
  214. }
  215. @Test
  216. public void filter_by_rule_name() {
  217. RuleDto rule1 = db.rules().insert(r1 -> r1.setName("Best rule ever").setNoteUserUuid(null));
  218. RuleDto rule2 = db.rules().insert(r1 -> r1.setName("Some other stuff").setNoteUserUuid(null));
  219. indexRules();
  220. verify(r -> r.setParam("q", "Be"), rule1);
  221. verify(r -> r.setParam("q", "Bes"), rule1);
  222. verify(r -> r.setParam("q", "Best"), rule1);
  223. verify(r -> r.setParam("q", "Best "), rule1);
  224. verify(r -> r.setParam("q", "Best rule"), rule1);
  225. verify(r -> r.setParam("q", "Best rule eve"), rule1);
  226. verify(r -> r.setParam("q", "Best rule ever"), rule1);
  227. verify(r -> r.setParam("q", "ru ev"), rule1);
  228. verify(r -> r.setParam("q", "ru ever"), rule1);
  229. verify(r -> r.setParam("q", "ev ve ver ru le"), rule1);
  230. verify(r -> r.setParam("q", "other"), rule2);
  231. }
  232. @Test
  233. public void filter_by_rule_name_requires_all_words_to_match() {
  234. RuleDto rule1 = db.rules().insert(r1 -> r1.setName("Best rule ever").setNoteUserUuid(null));
  235. RuleDto rule2 = db.rules().insert(r1 -> r1.setName("Some other stuff").setNoteUserUuid(null));
  236. indexRules();
  237. verify(r -> r.setParam("q", "Best other"));
  238. verify(r -> r.setParam("q", "Best rule"), rule1);
  239. verify(r -> r.setParam("q", "rule ever"), rule1);
  240. }
  241. @Test
  242. public void filter_by_rule_name_does_not_interpret_query() {
  243. RuleDto rule1 = db.rules().insert(r1 -> r1.setName("Best rule for-ever").setNoteUserUuid(null));
  244. RuleDto rule2 = db.rules().insert(r1 -> r1.setName("Some other stuff").setNoteUserUuid(null));
  245. indexRules();
  246. // do not interpret "-" as a "not"
  247. verify(r -> r.setParam("q", "-ever"), rule1);
  248. }
  249. @Test
  250. public void filter_by_rule_description() {
  251. RuleDto rule1 = db.rules().insert(
  252. newRule(createDefaultRuleDescriptionSection(uuidFactory.create(), "This is the <bold>best</bold> rule now&amp;for<b>ever</b>"))
  253. .setNoteUserUuid(null));
  254. db.rules().insert(r1 -> r1.setName("Some other stuff").setNoteUserUuid(null));
  255. indexRules();
  256. verify(r -> r.setParam("q", "Best "), rule1);
  257. verify(r -> r.setParam("q", "bold"));
  258. verify(r -> r.setParam("q", "now&forever"), rule1);
  259. }
  260. @Test
  261. public void filter_with_context_specific_rule_description() {
  262. RuleDescriptionSectionDto section1context1 = createRuleDescriptionSectionWithContext(RESOURCES_SECTION_KEY, "<div>I want to fix with Spring</div>", "ctx1");
  263. RuleDescriptionSectionDto section1context2 = createRuleDescriptionSectionWithContext(RESOURCES_SECTION_KEY, "<div>Another context</div>", "ctx2");
  264. RuleDto ruleDto = newRuleWithoutDescriptionSection()
  265. .setNoteUserUuid(null)
  266. .addRuleDescriptionSectionDto(section1context1)
  267. .addRuleDescriptionSectionDto(section1context2);
  268. db.rules().insert(ruleDto);
  269. indexRules();
  270. verify(r -> r.setParam("q", "Spring "), ruleDto);
  271. verify(r -> r.setParam("q", "bold"));
  272. verify(r -> r.setParam("q", "context"), ruleDto);
  273. }
  274. @Test
  275. public void filter_by_rule_name_or_descriptions_requires_all_words_to_match_anywhere() {
  276. RuleDto rule1 = db.rules().insert(newRuleWithoutDescriptionSection().setName("Best rule ever").setNoteUserUuid(null)
  277. .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "This is a good rule")));
  278. db.rules().insert(newRuleWithoutDescriptionSection().setName("Another thing").setNoteUserUuid(null)
  279. .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "Another thing")));
  280. indexRules();
  281. verify(r -> r.setParam("q", "Best good"), rule1);
  282. verify(r -> r.setParam("q", "Best Another"));
  283. }
  284. @Test
  285. public void return_all_rule_fields_by_default() {
  286. OffsetDateTime dateTime = OffsetDateTime.now(Clock.fixed(Instant.ofEpochMilli(1687816800000L), ZoneId.systemDefault()));
  287. RuleDto rule = db.rules().insert(
  288. r -> r.setCreatedAt(dateTime.toInstant().toEpochMilli()),
  289. r -> r.setUpdatedAt(dateTime.toInstant().toEpochMilli()),
  290. r -> r.setGapDescription("Gap Description"),
  291. r -> r.setIsTemplate(true),
  292. r -> r.setName("Name"),
  293. r -> r.setRepositoryKey("repo_key"),
  294. r -> r.setSeverity("MINOR"),
  295. r -> r.setLanguage("java"));
  296. indexRules();
  297. Rules.SearchResponse response = ws.newRequest().executeProtobuf(Rules.SearchResponse.class);
  298. Rules.Rule result = response.getRules(0);
  299. assertThat(result.getCreatedAt()).isEqualTo(dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ")));
  300. assertThat(result.getUpdatedAt()).isEqualTo(dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ")));
  301. assertThat(result.getGapDescription()).isEqualTo("Gap Description");
  302. assertThat(result.hasIsTemplate()).isTrue();
  303. assertThat(result.getName()).isEqualTo("Name");
  304. assertThat(result.getRepo()).isEqualTo("repo_key");
  305. assertThat(result.getSeverity()).isEqualTo("MINOR");
  306. assertThat(result.getType().name()).isEqualTo(RuleType.valueOf(rule.getType()).name());
  307. assertThat(result.getLang()).isEqualTo("java");
  308. }
  309. @Test
  310. public void return_subset_of_fields() {
  311. RuleDto rule = db.rules().insert(r -> r.setLanguage("java"));
  312. indexRules();
  313. Rules.SearchResponse response = ws.newRequest()
  314. .setParam(WebService.Param.FIELDS, "createdAt,langName,educationPrinciples")
  315. .executeProtobuf(Rules.SearchResponse.class);
  316. Rules.Rule result = response.getRules(0);
  317. // mandatory fields
  318. assertThat(result.getKey()).isEqualTo(rule.getKey().toString());
  319. assertThat(result.getType().getNumber()).isEqualTo(rule.getType());
  320. // selected fields
  321. assertThat(result.getCreatedAt()).isNotEmpty();
  322. assertThat(result.getLangName()).isNotEmpty();
  323. assertThat(result.getEducationPrinciples().getEducationPrinciplesList()).containsExactlyElementsOf(rule.getEducationPrinciples());
  324. // not returned fields
  325. assertThat(result.hasGapDescription()).isFalse();
  326. assertThat(result.hasHtmlDesc()).isFalse();
  327. assertThat(result.hasIsTemplate()).isFalse();
  328. assertThat(result.hasLang()).isFalse();
  329. assertThat(result.hasName()).isFalse();
  330. assertThat(result.hasSeverity()).isFalse();
  331. assertThat(result.hasRepo()).isFalse();
  332. assertThat(result.hasUpdatedAt()).isFalse();
  333. }
  334. @Test
  335. public void return_deprecatedKeys_in_response_on_demand() {
  336. RuleDto rule1 = db.rules().insert(r -> r.setLanguage("java"));
  337. db.rules().insertDeprecatedKey(r -> r.setRuleUuid(rule1.getUuid()).setOldRuleKey("oldrulekey").setOldRepositoryKey("oldrepositorykey"));
  338. db.rules().insertDeprecatedKey(r -> r.setRuleUuid(rule1.getUuid()).setOldRuleKey("oldrulekey2").setOldRepositoryKey("oldrepositorykey2"));
  339. RuleDto rule2 = db.rules().insert(r -> r.setLanguage("javascript"));
  340. indexRules();
  341. Rules.SearchResponse response = ws.newRequest()
  342. .setParam(WebService.Param.FIELDS, "deprecatedKeys")
  343. .executeProtobuf(Rules.SearchResponse.class);
  344. System.err.println(response.getRulesList());
  345. assertThat(response.getRulesList()).satisfies(l -> {
  346. assertThat(l).hasSize(2);
  347. assertThat(l).anySatisfy(e -> {
  348. assertThat(e.getKey()).isEqualTo(rule1.getKey().toString());
  349. assertThat(e.getType().getNumber()).isEqualTo(rule1.getType());
  350. assertThat(e.getDeprecatedKeys()).isNotNull();
  351. assertThat(e.getDeprecatedKeys().getDeprecatedKeyList()).contains("oldrepositorykey:oldrulekey", "oldrepositorykey2:oldrulekey2");
  352. });
  353. assertThat(l).anySatisfy(e -> {
  354. assertThat(e.getKey()).isEqualTo(rule2.getKey().toString());
  355. assertThat(e.getType().getNumber()).isEqualTo(rule2.getType());
  356. assertThat(e.getDeprecatedKeys()).isNotNull();
  357. assertThat(e.getDeprecatedKeys().getDeprecatedKeyList()).isEmpty();
  358. });
  359. });
  360. }
  361. @Test
  362. public void should_filter_on_specific_tags() {
  363. RuleDto rule1 = db.rules().insert(r -> r.setLanguage("java").setTags(Set.of("tag1", "tag2")));
  364. db.rules().insert(r -> r.setLanguage("java"));
  365. indexRules();
  366. Consumer<TestRequest> request = r -> r
  367. .setParam("f", "repo,name")
  368. .setParam("tags", rule1.getTags().stream().collect(Collectors.joining(",")));
  369. verify(request, rule1);
  370. }
  371. @Test
  372. public void when_searching_for_several_tags_combine_them_with_OR() {
  373. RuleDto bothTagsRule = db.rules().insert(r -> r.setLanguage("java"), setTags("tag1", "tag2"));
  374. RuleDto oneTagRule = db.rules().insert(r -> r.setLanguage("java"), setTags("tag1"));
  375. RuleDto otherTagRule = db.rules().insert(r -> r.setLanguage("java"), setTags("tag2"));
  376. db.rules().insert(r -> r.setLanguage("java"), setTags());
  377. indexRules();
  378. Consumer<TestRequest> request = r -> r
  379. .setParam("f", "repo,name")
  380. .setParam("tags", "tag1,tag2");
  381. verify(request, bothTagsRule, oneTagRule, otherTagRule);
  382. }
  383. @Test
  384. public void should_list_tags_in_tags_facet() {
  385. String[] tags = get101Tags();
  386. db.rules().insert(setSystemTags("x"), setTags(tags));
  387. indexRules();
  388. SearchResponse result = ws.newRequest()
  389. .setParam("f", "repo,name")
  390. .setParam("facets", "tags")
  391. .executeProtobuf(SearchResponse.class);
  392. assertThat(result.getFacets().getFacets(0).getValuesList()).extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
  393. .contains(tuple("tag0", 1L), tuple("tag25", 1L), tuple("tag99", 1L))
  394. .doesNotContain(tuple("x", 1L));
  395. }
  396. @Test
  397. public void should_list_tags_ordered_by_count_then_by_name_in_tags_facet() {
  398. db.rules().insert(setSystemTags("tag7", "tag5", "tag3", "tag1", "tag9"), setTags("tag2", "tag4", "tag6", "tag8", "tagA"));
  399. db.rules().insert(setSystemTags("tag2"), setTags());
  400. indexRules();
  401. SearchResponse result = ws.newRequest()
  402. .setParam("f", "repo,name")
  403. .setParam("facets", "tags")
  404. .executeProtobuf(SearchResponse.class);
  405. assertThat(result.getFacets().getFacets(0).getValuesList()).extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
  406. .containsExactly(tuple("tag2", 2L), tuple("tag1", 1L), tuple("tag3", 1L), tuple("tag4", 1L), tuple("tag5", 1L), tuple("tag6", 1L), tuple("tag7", 1L), tuple("tag8", 1L),
  407. tuple("tag9", 1L), tuple("tagA", 1L));
  408. }
  409. @Test
  410. public void should_include_selected_matching_tag_in_facet() {
  411. db.rules().insert(
  412. setSystemTags("tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9", "tagA", "x"),
  413. r -> r.setNoteUserUuid(null));
  414. indexRules();
  415. SearchResponse result = ws.newRequest()
  416. .setParam("facets", "tags")
  417. .setParam("tags", "x")
  418. .executeProtobuf(SearchResponse.class);
  419. assertThat(result.getFacets().getFacets(0).getValuesList()).extracting(v -> entry(v.getVal(), v.getCount())).contains(entry("x", 1L));
  420. }
  421. @Test
  422. public void execute_whenFacetIsSoftwareQuality_shouldReturnCorrectMatch() {
  423. db.rules().insert(
  424. r -> r.replaceAllDefaultImpacts(List.of(new ImpactDto().setUuid(Uuids.createFast()).setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.HIGH))));
  425. db.rules().insert(
  426. r -> r.replaceAllDefaultImpacts(List.of(new ImpactDto().setUuid(Uuids.createFast()).setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.MEDIUM))));
  427. indexRules();
  428. SearchResponse result = ws.newRequest()
  429. .setParam("facets", "impactSoftwareQualities")
  430. .setParam("impactSeverities", Severity.HIGH.name())
  431. .executeProtobuf(SearchResponse.class);
  432. assertThat(result.getFacets().getFacets(0).getValuesList()).extracting(v -> entry(v.getVal(), v.getCount()))
  433. .contains(entry(SoftwareQuality.MAINTAINABILITY.name(), 1L), entry(SoftwareQuality.RELIABILITY.name(), 0L), entry(SoftwareQuality.SECURITY.name(), 0L));
  434. }
  435. @Test
  436. public void execute_whenFacetIsImpactSeverity_shouldReturnCorrectMatch() {
  437. db.rules().insert(
  438. r -> r.replaceAllDefaultImpacts(List.of(new ImpactDto().setUuid(Uuids.createFast()).setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.HIGH))));
  439. db.rules().insert(
  440. r -> r.replaceAllDefaultImpacts(List.of(new ImpactDto().setUuid(Uuids.createFast()).setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.MEDIUM))));
  441. indexRules();
  442. SearchResponse result = ws.newRequest()
  443. .setParam("facets", "impactSeverities")
  444. .setParam("impactSoftwareQualities", SoftwareQuality.MAINTAINABILITY.name())
  445. .executeProtobuf(SearchResponse.class);
  446. assertThat(result.getFacets().getFacets(0).getValuesList()).extracting(v -> entry(v.getVal(), v.getCount()))
  447. .contains(entry(Severity.HIGH.name(), 1L), entry(Severity.MEDIUM.name(), 0L), entry(Severity.LOW.name(), 0L));
  448. }
  449. @Test
  450. public void execute_whenFacetIsCleanCodeAttributeCategories_shouldReturnCorrectMatch() {
  451. db.rules().insert(
  452. r -> r.setCleanCodeAttribute(CleanCodeAttribute.COMPLETE)
  453. .replaceAllDefaultImpacts(List.of(new ImpactDto().setUuid(Uuids.createFast()).setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.HIGH))));
  454. db.rules().insert(
  455. r -> r.setCleanCodeAttribute(CleanCodeAttribute.CONVENTIONAL));
  456. indexRules();
  457. SearchResponse result = ws.newRequest()
  458. .setParam("facets", "cleanCodeAttributeCategories")
  459. .setParam("impactSoftwareQualities", SoftwareQuality.RELIABILITY.name())
  460. .executeProtobuf(SearchResponse.class);
  461. assertThat(result.getFacets().getFacets(0).getValuesList())
  462. .extracting(v -> entry(v.getVal(), v.getCount())).contains(
  463. entry(CleanCodeAttribute.COMPLETE.getAttributeCategory().name(), 1L),
  464. entry(CleanCodeAttribute.CONVENTIONAL.getAttributeCategory().name(), 0L));
  465. }
  466. @Test
  467. public void should_included_selected_non_matching_tag_in_facet() {
  468. RuleDto rule = db.rules().insert(setSystemTags("tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9", "tagA"));
  469. indexRules();
  470. SearchResponse result = ws.newRequest()
  471. .setParam("facets", "tags")
  472. .setParam("tags", "x")
  473. .executeProtobuf(SearchResponse.class);
  474. assertThat(result.getFacets().getFacets(0).getValuesList()).extracting(v -> entry(v.getVal(), v.getCount())).contains(entry("x", 0L));
  475. }
  476. @Test
  477. public void should_return_specific_tags() {
  478. RuleDto rule = db.rules().insert(r -> r.setLanguage("java"), setTags("tag1", "tag2"));
  479. indexRules();
  480. SearchResponse result = ws.newRequest()
  481. .setParam("f", "tags")
  482. .executeProtobuf(SearchResponse.class);
  483. assertThat(result.getRulesList()).extracting(Rule::getKey).containsExactly(rule.getKey().toString());
  484. assertThat(result.getRulesList())
  485. .extracting(Rule::getTags).flatExtracting(Rules.Tags::getTagsList)
  486. .containsExactly(rule.getTags().toArray(new String[0]));
  487. }
  488. @Test
  489. public void returnRuleCleanCodeFields_whenEndpointIsCalled() {
  490. RuleDto rule = db.rules()
  491. .insert();
  492. indexRules();
  493. SearchResponse result = ws.newRequest()
  494. .setParam(WebService.Param.FIELDS, FIELD_CLEAN_CODE_ATTRIBUTE)
  495. .executeProtobuf(SearchResponse.class);
  496. // mandatory fields
  497. assertThat(result.getRulesList())
  498. .extracting(r -> r.getImpacts().getImpactsList().stream().findFirst().orElseThrow(() -> new IllegalStateException("Impact is a mandatory field in the response.")))
  499. .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
  500. .containsExactly(tuple(Common.SoftwareQuality.MAINTAINABILITY, Common.ImpactSeverity.HIGH));
  501. // selected fields
  502. assertThat(result.getRulesList()).extracting(Rule::getCleanCodeAttribute).containsExactly(Common.CleanCodeAttribute.CLEAR);
  503. assertThat(result.getRulesList()).extracting(Rule::getCleanCodeAttributeCategory).containsExactly(Common.CleanCodeAttributeCategory.INTENTIONAL);
  504. }
  505. @Test
  506. public void should_return_specified_fields() {
  507. when(macroInterpreter.interpret(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
  508. RuleDescriptionSectionDto section1context1 = createRuleDescriptionSectionWithContext(RESOURCES_SECTION_KEY, "<div>I want to fix with Spring</div>", "ctx1");
  509. RuleDescriptionSectionDto section1context2 = createRuleDescriptionSectionWithContext(RESOURCES_SECTION_KEY, "<div>Another context</div>", "ctx2");
  510. RuleDto rule = newRuleWithoutDescriptionSection()
  511. .setLanguage("java")
  512. .addRuleDescriptionSectionDto(section1context1)
  513. .addRuleDescriptionSectionDto(section1context2);
  514. db.rules().insert(rule);
  515. indexRules();
  516. checkField(rule, "repo", Rule::getRepo, rule.getRepositoryKey());
  517. checkField(rule, "name", Rule::getName, rule.getName());
  518. checkField(rule, "severity", Rule::getSeverity, rule.getSeverityString());
  519. checkField(rule, "status", r -> r.getStatus().toString(), rule.getStatus().toString());
  520. checkField(rule, "internalKey", Rule::getInternalKey, rule.getConfigKey());
  521. checkField(rule, "isTemplate", Rule::getIsTemplate, rule.isTemplate());
  522. checkField(rule, "lang", Rule::getLang, rule.getLanguage());
  523. checkField(rule, "langName", Rule::getLangName, languages.get(rule.getLanguage()).getName());
  524. checkField(rule, "gapDescription", Rule::getGapDescription, rule.getGapDescription());
  525. checkTags(rule, rule.getSystemTags());
  526. checkDescriptionSections(rule, rule.getRuleDescriptionSectionDtos().stream()
  527. .map(SearchActionIT::toProtobufDto)
  528. .collect(Collectors.toSet()));
  529. }
  530. private RuleDescriptionSectionDto createRuleDescriptionSectionWithContext(String key, String content, @Nullable String contextKey) {
  531. RuleDescriptionSectionContextDto contextDto = Optional.ofNullable(contextKey)
  532. .map(c -> RuleDescriptionSectionContextDto.of(contextKey, contextKey + " display name"))
  533. .orElse(null);
  534. return RuleDescriptionSectionDto.builder()
  535. .uuid(uuidFactory.create())
  536. .key(key)
  537. .content(content)
  538. .context(contextDto)
  539. .build();
  540. }
  541. private static Rule.DescriptionSection toProtobufDto(RuleDescriptionSectionDto section) {
  542. Rule.DescriptionSection.Builder builder = Rule.DescriptionSection.newBuilder().setKey(section.getKey()).setContent(section.getContent());
  543. if (section.getContext() != null) {
  544. RuleDescriptionSectionContextDto context = section.getContext();
  545. builder.setContext(newBuilder().setKey(context.getKey()).setDisplayName(context.getDisplayName()).build());
  546. }
  547. return builder.build();
  548. }
  549. @Test
  550. public void return_lang_key_field_when_language_name_is_not_available() {
  551. String unknownLanguage = "unknown_" + randomAlphanumeric(5);
  552. RuleDto rule = db.rules().insert(r -> r.setLanguage(unknownLanguage));
  553. indexRules();
  554. SearchResponse result = ws.newRequest()
  555. .setParam("f", "langName")
  556. .executeProtobuf(SearchResponse.class);
  557. assertThat(result.getTotal()).isOne();
  558. assertThat(result.getPaging().getTotal()).isOne();
  559. assertThat(result.getPaging().getPageIndex()).isOne();
  560. assertThat(result.getRulesCount()).isOne();
  561. Rule searchedRule = result.getRules(0);
  562. assertThat(searchedRule).isNotNull();
  563. assertThat(searchedRule.getLangName()).isEqualTo(unknownLanguage);
  564. }
  565. @Test
  566. public void search_debt_rules_with_default_and_overridden_debt_values() {
  567. db.rules().insert(r -> r.setLanguage("java")
  568. .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
  569. .setDefRemediationGapMultiplier("1h")
  570. .setDefRemediationBaseEffort("15min")
  571. .setRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
  572. .setRemediationGapMultiplier("2h")
  573. .setRemediationBaseEffort("25min"));
  574. indexRules();
  575. SearchResponse result = ws.newRequest()
  576. .setParam("f", "debtRemFn,remFnOverloaded,defaultDebtRemFn")
  577. .executeProtobuf(SearchResponse.class);
  578. assertThat(result.getTotal()).isOne();
  579. assertThat(result.getPaging().getTotal()).isOne();
  580. assertThat(result.getPaging().getPageIndex()).isOne();
  581. assertThat(result.getRulesCount()).isOne();
  582. Rule searchedRule = result.getRules(0);
  583. assertThat(searchedRule).isNotNull();
  584. assertThat(searchedRule.getDefaultRemFnGapMultiplier()).isEqualTo("1h");
  585. assertThat(searchedRule.getDefaultRemFnBaseEffort()).isEqualTo("15min");
  586. assertThat(searchedRule.getDefaultDebtRemFnType()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name());
  587. assertThat(searchedRule.getDefaultRemFnBaseEffort()).isEqualTo("15min");
  588. assertThat(searchedRule.getDefaultRemFnGapMultiplier()).isEqualTo("1h");
  589. assertThat(searchedRule.getDefaultRemFnType()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name());
  590. assertThat(searchedRule.getRemFnOverloaded()).isTrue();
  591. assertThat(searchedRule.getRemFnGapMultiplier()).isEqualTo("2h");
  592. assertThat(searchedRule.getRemFnBaseEffort()).isEqualTo("25min");
  593. assertThat(searchedRule.getDebtRemFnType()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name());
  594. }
  595. @Test
  596. public void search_debt_rules_with_default_linear_offset_and_overridden_constant_debt() {
  597. db.rules().insert(r -> r.setLanguage("java")
  598. .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
  599. .setDefRemediationGapMultiplier("1h")
  600. .setDefRemediationBaseEffort("15min")
  601. .setRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE.name())
  602. .setRemediationGapMultiplier(null)
  603. .setRemediationBaseEffort("5min"));
  604. indexRules();
  605. SearchResponse result = ws.newRequest()
  606. .setParam("f", "debtRemFn,remFnOverloaded,defaultDebtRemFn")
  607. .executeProtobuf(SearchResponse.class);
  608. assertThat(result.getTotal()).isOne();
  609. assertThat(result.getPaging().getTotal()).isOne();
  610. assertThat(result.getPaging().getPageIndex()).isOne();
  611. assertThat(result.getRulesCount()).isOne();
  612. Rule searchedRule = result.getRules(0);
  613. assertThat(searchedRule).isNotNull();
  614. assertThat(searchedRule.getDefaultRemFnGapMultiplier()).isEqualTo("1h");
  615. assertThat(searchedRule.getDefaultRemFnBaseEffort()).isEqualTo("15min");
  616. assertThat(searchedRule.getDefaultDebtRemFnType()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name());
  617. assertThat(searchedRule.getDefaultRemFnBaseEffort()).isEqualTo("15min");
  618. assertThat(searchedRule.getDefaultRemFnGapMultiplier()).isEqualTo("1h");
  619. assertThat(searchedRule.getDefaultRemFnType()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name());
  620. assertThat(searchedRule.getRemFnOverloaded()).isTrue();
  621. assertThat(searchedRule.getRemFnGapMultiplier()).isEmpty();
  622. assertThat(searchedRule.getRemFnBaseEffort()).isEqualTo("5min");
  623. assertThat(searchedRule.getDebtRemFnType()).isEqualTo(DebtRemediationFunction.Type.CONSTANT_ISSUE.name());
  624. }
  625. @Test
  626. public void search_debt_rules_with_default_linear_offset_and_overridden_linear_debt() {
  627. db.rules().insert(r -> r.setLanguage("java")
  628. .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
  629. .setDefRemediationGapMultiplier("1h")
  630. .setDefRemediationBaseEffort("15min")
  631. .setRemediationFunction(DebtRemediationFunction.Type.LINEAR.name())
  632. .setRemediationGapMultiplier("1h")
  633. .setRemediationBaseEffort(null));
  634. indexRules();
  635. SearchResponse result = ws.newRequest()
  636. .setParam("f", "debtRemFn,remFnOverloaded,defaultDebtRemFn")
  637. .executeProtobuf(SearchResponse.class);
  638. assertThat(result.getTotal()).isOne();
  639. assertThat(result.getPaging().getTotal()).isOne();
  640. assertThat(result.getPaging().getPageIndex()).isOne();
  641. assertThat(result.getRulesCount()).isOne();
  642. Rule searchedRule = result.getRules(0);
  643. assertThat(searchedRule).isNotNull();
  644. assertThat(searchedRule.getDefaultRemFnGapMultiplier()).isEqualTo("1h");
  645. assertThat(searchedRule.getDefaultRemFnBaseEffort()).isEqualTo("15min");
  646. assertThat(searchedRule.getDefaultDebtRemFnType()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name());
  647. assertThat(searchedRule.getDefaultRemFnBaseEffort()).isEqualTo("15min");
  648. assertThat(searchedRule.getDefaultRemFnGapMultiplier()).isEqualTo("1h");
  649. assertThat(searchedRule.getDefaultRemFnType()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name());
  650. assertThat(searchedRule.getRemFnOverloaded()).isTrue();
  651. assertThat(searchedRule.getRemFnGapMultiplier()).isEqualTo("1h");
  652. assertThat(searchedRule.getRemFnBaseEffort()).isEmpty();
  653. assertThat(searchedRule.getDebtRemFnType()).isEqualTo(DebtRemediationFunction.Type.LINEAR.name());
  654. }
  655. @Test
  656. public void search_template_rules() {
  657. RuleDto templateRule = db.rules().insert(r -> r.setLanguage("java")
  658. .setIsTemplate(true));
  659. RuleDto rule = db.rules().insert(r -> r.setLanguage("java")
  660. .setTemplateUuid(templateRule.getUuid()));
  661. indexRules();
  662. SearchResponse result = ws.newRequest()
  663. .setParam("f", "isTemplate")
  664. .setParam("is_template", "true")
  665. .executeProtobuf(SearchResponse.class);
  666. assertThat(result.getTotal()).isOne();
  667. assertThat(result.getPaging().getTotal()).isOne();
  668. assertThat(result.getPaging().getPageIndex()).isOne();
  669. assertThat(result.getRulesCount()).isOne();
  670. Rule searchedRule = result.getRules(0);
  671. assertThat(searchedRule).isNotNull();
  672. assertThat(searchedRule.getIsTemplate()).isTrue();
  673. assertThat(searchedRule.getKey()).isEqualTo(templateRule.getRepositoryKey() + ":" + templateRule.getRuleKey());
  674. }
  675. @Test
  676. public void search_custom_rules_from_template_key() {
  677. RuleDto templateRule = db.rules().insert(r -> r.setLanguage("java")
  678. .setIsTemplate(true));
  679. RuleDto rule = db.rules().insert(r -> r.setLanguage("java")
  680. .setTemplateUuid(templateRule.getUuid()));
  681. indexRules();
  682. SearchResponse result = ws.newRequest()
  683. .setParam("f", "templateKey")
  684. .setParam("template_key", templateRule.getRepositoryKey() + ":" + templateRule.getRuleKey())
  685. .executeProtobuf(SearchResponse.class);
  686. assertThat(result.getTotal()).isOne();
  687. assertThat(result.getPaging().getTotal()).isOne();
  688. assertThat(result.getPaging().getPageIndex()).isOne();
  689. assertThat(result.getRulesCount()).isOne();
  690. Rule searchedRule = result.getRules(0);
  691. assertThat(searchedRule).isNotNull();
  692. assertThat(searchedRule.getKey()).isEqualTo(rule.getRepositoryKey() + ":" + rule.getRuleKey());
  693. assertThat(searchedRule.getTemplateKey()).isEqualTo(templateRule.getRepositoryKey() + ":" + templateRule.getRuleKey());
  694. }
  695. @Test
  696. public void do_not_return_external_rule() {
  697. db.rules().insert(r -> r.setIsExternal(true));
  698. indexRules();
  699. SearchResponse result = ws.newRequest().executeProtobuf(SearchResponse.class);
  700. assertThat(result.getTotal()).isZero();
  701. assertThat(result.getPaging().getTotal()).isZero();
  702. assertThat(result.getPaging().getPageIndex()).isOne();
  703. assertThat(result.getRulesCount()).isZero();
  704. }
  705. @Test
  706. public void search_all_active_rules() {
  707. QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage("java"));
  708. RuleDto rule = db.rules().insert(r -> r.setLanguage("java").setNoteUserUuid(null));
  709. RuleActivation activation = RuleActivation.create(rule.getUuid(), BLOCKER, null);
  710. qProfileRules.activateAndCommit(db.getSession(), profile, singleton(activation));
  711. indexRules();
  712. SearchResponse result = ws.newRequest()
  713. .setParam("q", rule.getName())
  714. .setParam("activation", "true")
  715. .executeProtobuf(SearchResponse.class);
  716. assertThat(result.getTotal()).isOne();
  717. assertThat(result.getPaging().getTotal()).isOne();
  718. assertThat(result.getPaging().getPageIndex()).isOne();
  719. assertThat(result.getRulesCount()).isOne();
  720. Rule searchedRule = result.getRules(0);
  721. assertThat(searchedRule).isNotNull();
  722. assertThat(searchedRule.getKey()).isEqualTo(rule.getRepositoryKey() + ":" + rule.getRuleKey());
  723. assertThat(searchedRule.getName()).isEqualTo(rule.getName());
  724. }
  725. @Test
  726. public void search_profile_active_rules() {
  727. QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage("java"));
  728. QProfileDto waterproofProfile = db.qualityProfiles().insert(p -> p.setLanguage("java"));
  729. RuleDto rule = db.rules().insert(r -> r.setLanguage("java"));
  730. RuleParamDto ruleParam1 = db.rules().insertRuleParam(rule, p -> p.setDefaultValue("some value")
  731. .setType("STRING")
  732. .setDescription("My small description")
  733. .setName("my_var"));
  734. RuleParamDto ruleParam2 = db.rules().insertRuleParam(rule, p -> p.setDefaultValue("1")
  735. .setType("INTEGER")
  736. .setDescription("My small description")
  737. .setName("the_var"));
  738. // SONAR-7083
  739. RuleParamDto ruleParam3 = db.rules().insertRuleParam(rule, p -> p.setDefaultValue(null)
  740. .setType("STRING")
  741. .setDescription("Empty Param")
  742. .setName("empty_var"));
  743. RuleActivation activation = RuleActivation.create(rule.getUuid());
  744. List<ActiveRuleChange> activeRuleChanges1 = qProfileRules.activateAndCommit(db.getSession(), profile, singleton(activation));
  745. qProfileRules.activateAndCommit(db.getSession(), waterproofProfile, singleton(activation));
  746. assertThat(activeRuleChanges1).hasSize(1);
  747. indexRules();
  748. indexActiveRules();
  749. SearchResponse result = ws.newRequest()
  750. .setParam("f", "actives")
  751. .setParam("q", rule.getName())
  752. .setParam("activation", "true")
  753. .setParam("qprofile", profile.getKee())
  754. .executeProtobuf(SearchResponse.class);
  755. assertThat(result.getTotal()).isOne();
  756. assertThat(result.getPaging().getTotal()).isOne();
  757. assertThat(result.getPaging().getPageIndex()).isOne();
  758. assertThat(result.getRulesCount()).isOne();
  759. assertThat(result.getActives()).isNotNull();
  760. assertThat(result.getActives().getActives().get(rule.getKey().toString())).isNotNull();
  761. assertThat(result.getActives().getActives().get(rule.getKey().toString()).getActiveListList()).hasSize(1);
  762. // The rule without value is not inserted
  763. Rules.Active activeList = result.getActives().getActives().get(rule.getKey().toString()).getActiveList(0);
  764. assertThat(activeList.getParamsCount()).isEqualTo(2);
  765. assertThat(activeList.getParamsList()).extracting("key", "value").containsExactlyInAnyOrder(
  766. tuple(ruleParam1.getName(), ruleParam1.getDefaultValue()),
  767. tuple(ruleParam2.getName(), ruleParam2.getDefaultValue()));
  768. String unknownProfile = "unknown_profile" + randomAlphanumeric(5);
  769. assertThatThrownBy(() -> {
  770. ws.newRequest()
  771. .setParam("activation", "true")
  772. .setParam("qprofile", unknownProfile)
  773. .executeProtobuf(SearchResponse.class);
  774. })
  775. .isInstanceOf(NotFoundException.class)
  776. .hasMessage("The specified qualityProfile '" + unknownProfile + "' does not exist");
  777. }
  778. @Test
  779. public void search_for_active_rules_when_parameter_value_is_null() {
  780. QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage("java"));
  781. RuleDto rule = db.rules().insert(r -> r.setLanguage("java"));
  782. RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setDefaultValue("some value")
  783. .setType("STRING")
  784. .setDescription("My small description")
  785. .setName("my_var"));
  786. RuleActivation activation = RuleActivation.create(rule.getUuid());
  787. List<ActiveRuleChange> activeRuleChanges = qProfileRules.activateAndCommit(db.getSession(), profile, singleton(activation));
  788. // Insert directly in database a rule parameter with a null value
  789. ActiveRuleParamDto activeRuleParam = ActiveRuleParamDto.createFor(ruleParam).setValue(null);
  790. db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRuleChanges.get(0).getActiveRule(), activeRuleParam);
  791. db.commit();
  792. indexRules();
  793. indexActiveRules();
  794. SearchResponse result = ws.newRequest()
  795. .setParam("f", "actives")
  796. .setParam("q", rule.getName())
  797. .setParam("activation", "true")
  798. .setParam("qprofile", profile.getKee())
  799. .executeProtobuf(SearchResponse.class);
  800. assertThat(result.getTotal()).isOne();
  801. assertThat(result.getPaging().getTotal()).isOne();
  802. assertThat(result.getPaging().getPageIndex()).isOne();
  803. assertThat(result.getRulesCount()).isOne();
  804. assertThat(result.getActives()).isNotNull();
  805. assertThat(result.getActives().getActives().get(rule.getKey().toString())).isNotNull();
  806. assertThat(result.getActives().getActives().get(rule.getKey().toString()).getActiveListList()).hasSize(1);
  807. Rules.Active activeList = result.getActives().getActives().get(rule.getKey().toString()).getActiveList(0);
  808. assertThat(activeList.getParamsCount()).isEqualTo(2);
  809. assertThat(activeList.getParamsList()).extracting("key", "value").containsExactlyInAnyOrder(
  810. tuple(ruleParam.getName(), ruleParam.getDefaultValue()),
  811. tuple(activeRuleParam.getKey(), ""));
  812. }
  813. /**
  814. * When the user searches for inactive rules (for example for to "activate more"), then
  815. * only rules of the quality profiles' language are relevant
  816. */
  817. @Test
  818. public void facet_filtering_when_searching_for_inactive_rules() {
  819. QProfileDto profile = db.qualityProfiles().insert(q -> q.setLanguage("language1"));
  820. // on same language, not activated => match
  821. RuleDto rule1 = db.rules().insert(r -> r
  822. .setLanguage(profile.getLanguage())
  823. .setRepositoryKey("repositoryKey1")
  824. .setSystemTags(new HashSet<>(singletonList("tag1")))
  825. .setTags(emptySet())
  826. .setSeverity("CRITICAL")
  827. .setNoteUserUuid(null)
  828. .setStatus(RuleStatus.BETA)
  829. .setType(RuleType.CODE_SMELL));
  830. // on same language, activated => no match
  831. RuleDto rule2 = db.rules().insert(r -> r
  832. .setLanguage(profile.getLanguage())
  833. .setRepositoryKey("repositoryKey2")
  834. .setSystemTags(new HashSet<>(singletonList("tag2")))
  835. .setTags(emptySet())
  836. .setSeverity("MAJOR")
  837. .setNoteUserUuid(null)
  838. .setStatus(RuleStatus.DEPRECATED)
  839. .setType(RuleType.VULNERABILITY));
  840. RuleActivation activation = RuleActivation.create(rule2.getUuid(), null, null);
  841. qProfileRules.activateAndCommit(db.getSession(), profile, singleton(activation));
  842. // on other language, not activated => no match
  843. RuleDto rule3 = db.rules().insert(r -> r
  844. .setLanguage("language3")
  845. .setRepositoryKey("repositoryKey3")
  846. .setSystemTags(new HashSet<>(singletonList("tag3")))
  847. .setTags(emptySet())
  848. .setNoteUserUuid(null)
  849. .setSeverity("BLOCKER")
  850. .setStatus(RuleStatus.READY)
  851. .setType(RuleType.BUG));
  852. indexRules();
  853. indexActiveRules();
  854. SearchResponse result = ws.newRequest()
  855. .setParam("facets", "languages,repositories,tags,severities,statuses,types")
  856. .setParam("activation", "false")
  857. .setParam("qprofile", profile.getKee())
  858. .executeProtobuf(SearchResponse.class);
  859. assertThat(result.getRulesList())
  860. .extracting(Rule::getKey)
  861. .containsExactlyInAnyOrder(
  862. rule1.getKey().toString());
  863. assertThat(result.getFacets().getFacetsList().stream().filter(f -> "languages".equals(f.getProperty())).findAny().get().getValuesList())
  864. .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
  865. .as("Facet languages")
  866. .containsExactlyInAnyOrder(
  867. tuple(rule1.getLanguage(), 1L),
  868. // known limitation: irrelevant languages are shown in this case (SONAR-9683)
  869. tuple(rule3.getLanguage(), 1L));
  870. assertThat(result.getFacets().getFacetsList().stream().filter(f -> "tags".equals(f.getProperty())).findAny().get().getValuesList())
  871. .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
  872. .as("Facet tags")
  873. .containsExactlyInAnyOrder(
  874. tuple(rule1.getSystemTags().iterator().next(), 1L));
  875. assertThat(result.getFacets().getFacetsList().stream().filter(f -> "repositories".equals(f.getProperty())).findAny().get().getValuesList())
  876. .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
  877. .as("Facet repositories")
  878. .containsExactlyInAnyOrder(
  879. tuple(rule1.getRepositoryKey(), 1L));
  880. assertThat(result.getFacets().getFacetsList().stream().filter(f -> "severities".equals(f.getProperty())).findAny().get().getValuesList())
  881. .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
  882. .as("Facet severities")
  883. .containsExactlyInAnyOrder(
  884. tuple("BLOCKER" /* rule2 */, 0L),
  885. tuple("CRITICAL"/* rule1 */, 1L),
  886. tuple("MAJOR", 0L),
  887. tuple("MINOR", 0L),
  888. tuple("INFO", 0L));
  889. assertThat(result.getFacets().getFacetsList().stream().filter(f -> "statuses".equals(f.getProperty())).findAny().get().getValuesList())
  890. .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
  891. .as("Facet statuses")
  892. .containsExactlyInAnyOrder(
  893. tuple("READY"/* rule2 */, 0L),
  894. tuple("BETA" /* rule1 */, 1L),
  895. tuple("DEPRECATED", 0L));
  896. assertThat(result.getFacets().getFacetsList().stream().filter(f -> "types".equals(f.getProperty())).findAny().get().getValuesList())
  897. .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
  898. .as("Facet types")
  899. .containsExactlyInAnyOrder(
  900. tuple("BUG" /* rule2 */, 0L),
  901. tuple("CODE_SMELL"/* rule1 */, 1L),
  902. tuple("VULNERABILITY", 0L),
  903. tuple("SECURITY_HOTSPOT", 0L));
  904. }
  905. @Test
  906. public void statuses_facet_should_be_sticky() {
  907. RuleDto rule1 = db.rules().insert(r -> r.setLanguage("java"));
  908. RuleDto rule2 = db.rules().insert(r -> r.setLanguage("java").setStatus(RuleStatus.BETA));
  909. RuleDto rule3 = db.rules().insert(r -> r.setLanguage("java").setStatus(RuleStatus.DEPRECATED));
  910. indexRules();
  911. SearchResponse result = ws.newRequest()
  912. .setParam("f", "status")
  913. .setParam("status", "DEPRECATED")
  914. .executeProtobuf(SearchResponse.class);
  915. assertThat(result.getRulesCount()).isEqualTo(3);
  916. assertThat(result.getRulesList()).extracting(Rule::getKey, r -> r.getStatus().name()).containsExactlyInAnyOrder(
  917. tuple(rule1.getKey().toString(), rule1.getStatus().name()),
  918. tuple(rule2.getKey().toString(), rule2.getStatus().name()),
  919. tuple(rule3.getKey().toString(), rule3.getStatus().name()));
  920. }
  921. @Test
  922. public void paging() {
  923. for (int i = 0; i < 12; i++) {
  924. db.rules().insert(r -> r.setLanguage("java"));
  925. }
  926. indexRules();
  927. ws.newRequest()
  928. .setParam(WebService.Param.PAGE, "2")
  929. .setParam(WebService.Param.PAGE_SIZE, "9")
  930. .execute()
  931. .assertJson(this.getClass(), "paging.json");
  932. }
  933. @Test
  934. public void compare_to_another_profile() {
  935. QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage(JAVA));
  936. QProfileDto anotherProfile = db.qualityProfiles().insert(p -> p.setLanguage(JAVA));
  937. RuleDto commonRule = db.rules().insertRule(r -> r.setLanguage(JAVA));
  938. RuleDto profileRule1 = db.rules().insertRule(r -> r.setLanguage(JAVA));
  939. RuleDto profileRule2 = db.rules().insertRule(r -> r.setLanguage(JAVA));
  940. RuleDto profileRule3 = db.rules().insertRule(r -> r.setLanguage(JAVA));
  941. RuleDto anotherProfileRule1 = db.rules().insertRule(r -> r.setLanguage(JAVA));
  942. RuleDto anotherProfileRule2 = db.rules().insertRule(r -> r.setLanguage(JAVA));
  943. db.qualityProfiles().activateRule(profile, commonRule);
  944. db.qualityProfiles().activateRule(profile, profileRule1);
  945. db.qualityProfiles().activateRule(profile, profileRule2);
  946. db.qualityProfiles().activateRule(profile, profileRule3);
  947. db.qualityProfiles().activateRule(anotherProfile, commonRule);
  948. db.qualityProfiles().activateRule(anotherProfile, anotherProfileRule1);
  949. db.qualityProfiles().activateRule(anotherProfile, anotherProfileRule2);
  950. indexRules();
  951. indexActiveRules();
  952. SearchResponse result = ws.newRequest()
  953. .setParam(PARAM_QPROFILE, profile.getKee())
  954. .setParam(PARAM_ACTIVATION, "false")
  955. .setParam(PARAM_COMPARE_TO_PROFILE, anotherProfile.getKee())
  956. .executeProtobuf(SearchResponse.class);
  957. assertThat(result.getRulesList())
  958. .extracting(Rule::getKey)
  959. .containsExactlyInAnyOrder(anotherProfileRule1.getKey().toString(), anotherProfileRule2.getKey().toString());
  960. }
  961. @SafeVarargs
  962. private <T> void checkField(RuleDto rule, String fieldName, Function<Rule, T> responseExtractor, T... expected) {
  963. SearchResponse result = ws.newRequest()
  964. .setParam("f", fieldName)
  965. .executeProtobuf(SearchResponse.class);
  966. assertThat(result.getRulesList()).extracting(Rule::getKey).containsExactly(rule.getKey().toString());
  967. assertThat(result.getRulesList()).extracting(responseExtractor).containsExactlyInAnyOrder(expected);
  968. }
  969. private void checkTags(RuleDto rule, Set<String> expected) {
  970. SearchResponse result = ws.newRequest()
  971. .setParam("f", "sysTags")
  972. .executeProtobuf(SearchResponse.class);
  973. assertThat(result.getRulesList()).extracting(Rule::getKey).containsExactly(rule.getKey().toString());
  974. Set<String> actualTags = new HashSet<>(result.getRules(0).getSysTags().getSysTagsList());
  975. assertThat(actualTags).hasSameElementsAs(expected);
  976. }
  977. private void checkDescriptionSections(RuleDto rule, Set<Rule.DescriptionSection> expected) {
  978. SearchResponse result = ws.newRequest()
  979. .setParam("f", "descriptionSections")
  980. .executeProtobuf(SearchResponse.class);
  981. assertThat(result.getRulesList()).extracting(Rule::getKey).containsExactly(rule.getKey().toString());
  982. List<Rule.DescriptionSection> actualSections = result.getRules(0).getDescriptionSections().getDescriptionSectionsList();
  983. assertThat(actualSections).hasSameElementsAs(expected);
  984. }
  985. private void verifyNoResults(Consumer<TestRequest> requestPopulator) {
  986. verify(requestPopulator);
  987. }
  988. private void verify(Consumer<TestRequest> requestPopulator, RuleDto... expectedRules) {
  989. TestRequest request = ws.newRequest();
  990. requestPopulator.accept(request);
  991. Rules.SearchResponse response = request.executeProtobuf(Rules.SearchResponse.class);
  992. assertThat(response.getP()).isOne();
  993. assertThat(response.getPaging().getPageIndex()).isOne();
  994. assertThat(response.getPaging().getPageSize()).isNotZero();
  995. RuleKey[] expectedRuleKeys = stream(expectedRules).map(RuleDto::getKey).toList().toArray(new RuleKey[0]);
  996. assertThat(response.getRulesList())
  997. .extracting(r -> RuleKey.parse(r.getKey()))
  998. .containsExactlyInAnyOrder(expectedRuleKeys);
  999. assertThat(response.getTotal()).isEqualTo(expectedRules.length);
  1000. assertThat(response.getPaging().getTotal()).isEqualTo(expectedRules.length);
  1001. assertThat(response.getRulesCount()).isEqualTo(expectedRules.length);
  1002. }
  1003. private void indexRules() {
  1004. ruleIndexer.indexAll();
  1005. }
  1006. private void indexActiveRules() {
  1007. activeRuleIndexer.indexAll();
  1008. }
  1009. private String[] get101Tags() {
  1010. String[] tags = new String[101];
  1011. for (int i = 0; i < 100; i++) {
  1012. tags[i] = "tag" + i;
  1013. }
  1014. tags[100] = "tagA";
  1015. return tags;
  1016. }
  1017. }