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.

RulesRegistrantIT.java 57KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363
  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.registration;
  21. import com.tngtech.java.junit.dataprovider.DataProvider;
  22. import com.tngtech.java.junit.dataprovider.DataProviderRunner;
  23. import com.tngtech.java.junit.dataprovider.UseDataProvider;
  24. import java.util.Arrays;
  25. import java.util.Date;
  26. import java.util.List;
  27. import java.util.Objects;
  28. import java.util.Optional;
  29. import java.util.Set;
  30. import java.util.function.Consumer;
  31. import java.util.stream.Collectors;
  32. import java.util.stream.IntStream;
  33. import org.elasticsearch.common.util.set.Sets;
  34. import org.jetbrains.annotations.Nullable;
  35. import org.junit.Before;
  36. import org.junit.Test;
  37. import org.junit.runner.RunWith;
  38. import org.sonar.api.impl.utils.TestSystem2;
  39. import org.sonar.api.issue.impact.Severity;
  40. import org.sonar.api.issue.impact.SoftwareQuality;
  41. import org.sonar.api.resources.Language;
  42. import org.sonar.api.resources.Languages;
  43. import org.sonar.api.rule.RuleKey;
  44. import org.sonar.api.rule.RuleScope;
  45. import org.sonar.api.rule.RuleStatus;
  46. import org.sonar.api.rules.CleanCodeAttribute;
  47. import org.sonar.api.rules.RuleType;
  48. import org.sonar.api.server.debt.DebtRemediationFunction;
  49. import org.sonar.api.server.rule.Context;
  50. import org.sonar.api.server.rule.RuleDescriptionSection;
  51. import org.sonar.api.server.rule.RulesDefinition;
  52. import org.sonar.api.utils.DateUtils;
  53. import org.sonar.api.testfixtures.log.LogTester;
  54. import org.sonar.core.util.UuidFactory;
  55. import org.sonar.core.util.UuidFactoryFast;
  56. import org.sonar.db.DbClient;
  57. import org.sonar.db.DbSession;
  58. import org.sonar.db.DbTester;
  59. import org.sonar.db.issue.ImpactDto;
  60. import org.sonar.db.qualityprofile.ActiveRuleDto;
  61. import org.sonar.db.qualityprofile.QProfileChangeDto;
  62. import org.sonar.db.qualityprofile.QProfileChangeQuery;
  63. import org.sonar.db.qualityprofile.QProfileDto;
  64. import org.sonar.db.rule.DeprecatedRuleKeyDto;
  65. import org.sonar.db.rule.RuleDescriptionSectionContextDto;
  66. import org.sonar.db.rule.RuleDescriptionSectionDto;
  67. import org.sonar.db.rule.RuleDto;
  68. import org.sonar.db.rule.RuleDto.Scope;
  69. import org.sonar.db.rule.RuleParamDto;
  70. import org.sonar.db.rule.RuleRepositoryDto;
  71. import org.sonar.server.es.EsTester;
  72. import org.sonar.server.es.SearchIdResult;
  73. import org.sonar.server.es.SearchOptions;
  74. import org.sonar.server.es.metadata.MetadataIndex;
  75. import org.sonar.server.plugins.ServerPluginRepository;
  76. import org.sonar.server.qualityprofile.ActiveRuleChange;
  77. import org.sonar.server.qualityprofile.QProfileRules;
  78. import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
  79. import org.sonar.server.rule.RuleDefinitionsLoader;
  80. import org.sonar.server.rule.RuleDescriptionSectionsGenerator;
  81. import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver;
  82. import org.sonar.server.rule.WebServerRuleFinder;
  83. import org.sonar.server.rule.index.RuleIndex;
  84. import org.sonar.server.rule.index.RuleIndexDefinition;
  85. import org.sonar.server.rule.index.RuleIndexer;
  86. import org.sonar.server.rule.index.RuleQuery;
  87. import static com.google.common.collect.Sets.newHashSet;
  88. import static java.lang.String.format;
  89. import static java.lang.String.valueOf;
  90. import static java.util.Collections.emptySet;
  91. import static java.util.Collections.singletonList;
  92. import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
  93. import static org.assertj.core.api.Assertions.assertThat;
  94. import static org.assertj.core.api.Assertions.assertThatThrownBy;
  95. import static org.assertj.core.api.Assertions.tuple;
  96. import static org.mockito.ArgumentMatchers.any;
  97. import static org.mockito.ArgumentMatchers.eq;
  98. import static org.mockito.Mockito.mock;
  99. import static org.mockito.Mockito.reset;
  100. import static org.mockito.Mockito.verify;
  101. import static org.mockito.Mockito.verifyNoInteractions;
  102. import static org.mockito.Mockito.when;
  103. import static org.sonar.api.rule.RuleStatus.READY;
  104. import static org.sonar.api.rule.RuleStatus.REMOVED;
  105. import static org.sonar.api.rule.Severity.BLOCKER;
  106. import static org.sonar.api.rule.Severity.CRITICAL;
  107. import static org.sonar.api.rule.Severity.INFO;
  108. import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY;
  109. import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY;
  110. import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY;
  111. import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY;
  112. import static org.sonar.api.server.rule.RulesDefinition.NewRepository;
  113. import static org.sonar.api.server.rule.RulesDefinition.NewRule;
  114. import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10;
  115. import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
  116. import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY;
  117. import static org.sonar.db.rule.RuleDescriptionSectionDto.builder;
  118. import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
  119. import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
  120. @RunWith(DataProviderRunner.class)
  121. public class RulesRegistrantIT {
  122. private static final String FAKE_PLUGIN_KEY = "unittest";
  123. private static final Date DATE1 = DateUtils.parseDateTime("2014-01-01T19:10:03+0100");
  124. private static final Date DATE2 = DateUtils.parseDateTime("2014-02-01T12:10:03+0100");
  125. private static final Date DATE3 = DateUtils.parseDateTime("2014-03-01T12:10:03+0100");
  126. private static final RuleKey EXTERNAL_RULE_KEY1 = RuleKey.of("external_eslint", "rule1");
  127. private static final RuleKey EXTERNAL_HOTSPOT_RULE_KEY = RuleKey.of("external_eslint", "hotspot");
  128. private static final RuleKey RULE_KEY1 = RuleKey.of("fake", "rule1");
  129. private static final RuleKey RULE_KEY2 = RuleKey.of("fake", "rule2");
  130. private static final RuleKey RULE_KEY3 = RuleKey.of("fake", "rule3");
  131. private static final RuleKey HOTSPOT_RULE_KEY = RuleKey.of("fake", "hotspot");
  132. private final TestSystem2 system = new TestSystem2().setNow(DATE1.getTime());
  133. @org.junit.Rule
  134. public DbTester db = DbTester.create(system);
  135. @org.junit.Rule
  136. public EsTester es = EsTester.create();
  137. @org.junit.Rule
  138. public LogTester logTester = new LogTester();
  139. private final QProfileRules qProfileRules = mock(QProfileRules.class);
  140. private final WebServerRuleFinder webServerRuleFinder = mock(WebServerRuleFinder.class);
  141. private final DbClient dbClient = db.getDbClient();
  142. private final MetadataIndex metadataIndex = mock(MetadataIndex.class);
  143. private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
  144. private RuleIndexer ruleIndexer;
  145. private ActiveRuleIndexer activeRuleIndexer;
  146. private RuleIndex ruleIndex;
  147. private final RuleDescriptionSectionsGenerator ruleDescriptionSectionsGenerator = mock(RuleDescriptionSectionsGenerator.class);
  148. private final RuleDescriptionSectionsGeneratorResolver resolver = mock(RuleDescriptionSectionsGeneratorResolver.class);
  149. private final RulesKeyVerifier rulesKeyVerifier = new RulesKeyVerifier();
  150. private final StartupRuleUpdater startupRuleUpdater = new StartupRuleUpdater(dbClient, system, uuidFactory, resolver);
  151. private final NewRuleCreator newRuleCreator = new NewRuleCreator(dbClient, resolver, uuidFactory, system);
  152. @Before
  153. public void before() {
  154. ruleIndexer = new RuleIndexer(es.client(), dbClient);
  155. ruleIndex = new RuleIndex(es.client(), system);
  156. activeRuleIndexer = new ActiveRuleIndexer(dbClient, es.client());
  157. when(resolver.generateFor(any())).thenAnswer(answer -> {
  158. RulesDefinition.Rule rule = answer.getArgument(0, RulesDefinition.Rule.class);
  159. String description = rule.htmlDescription() == null ? rule.markdownDescription() : rule.htmlDescription();
  160. Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = rule.ruleDescriptionSections().stream()
  161. .map(s -> builder()
  162. .uuid(UuidFactoryFast.getInstance().create())
  163. .key(s.getKey())
  164. .content(s.getHtmlContent())
  165. .context(s.getContext().map(c -> RuleDescriptionSectionContextDto.of(c.getKey(), c.getDisplayName())).orElse(null))
  166. .build()
  167. )
  168. .collect(Collectors.toSet());
  169. return Sets.union(ruleDescriptionSectionDtos, Set.of(builder().uuid(UuidFactoryFast.getInstance().create()).key("default").content(description).build()));
  170. });
  171. when(ruleDescriptionSectionsGenerator.isGeneratorForRule(any())).thenReturn(true);
  172. }
  173. @Test
  174. public void insert_new_rules() {
  175. execute(new FakeRepositoryV1());
  176. // verify db
  177. assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
  178. RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
  179. verifyRule(rule1, RuleType.CODE_SMELL, BLOCKER);
  180. assertThat(rule1.isExternal()).isFalse();
  181. assertThat(rule1.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL);
  182. assertThat(rule1.getDefaultImpacts()).extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity).containsOnly(tuple(SoftwareQuality.RELIABILITY, Severity.HIGH));
  183. assertThat(rule1.getDefRemediationFunction()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name());
  184. assertThat(rule1.getDefRemediationGapMultiplier()).isEqualTo("5d");
  185. assertThat(rule1.getDefRemediationBaseEffort()).isEqualTo("10h");
  186. RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
  187. verifyHotspot(hotspotRule);
  188. List<RuleParamDto> params = dbClient.ruleDao().selectRuleParamsByRuleKey(db.getSession(), RULE_KEY1);
  189. assertThat(params).hasSize(2);
  190. RuleParamDto param = getParam(params, "param1");
  191. assertThat(param.getDescription()).isEqualTo("parameter one");
  192. assertThat(param.getDefaultValue()).isEqualTo("default1");
  193. // verify index
  194. RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
  195. assertThat(rule2.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.EFFICIENT);
  196. assertThat(rule2.getDefaultImpacts()).extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity).containsOnly(tuple(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM));
  197. assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid(), hotspotRule.getUuid());
  198. verifyIndicesMarkedAsInitialized();
  199. // verify repositories
  200. assertThat(dbClient.ruleRepositoryDao().selectAll(db.getSession())).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
  201. }
  202. private void verifyHotspot(RuleDto hotspotRule) {
  203. assertThat(hotspotRule.getName()).isEqualTo("Hotspot");
  204. assertThat(hotspotRule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Minimal hotspot");
  205. assertThat(hotspotRule.getCreatedAt()).isEqualTo(RulesRegistrantIT.DATE1.getTime());
  206. assertThat(hotspotRule.getUpdatedAt()).isEqualTo(RulesRegistrantIT.DATE1.getTime());
  207. assertThat(hotspotRule.getType()).isEqualTo(RuleType.SECURITY_HOTSPOT.getDbConstant());
  208. assertThat(hotspotRule.getSecurityStandards()).containsExactly("cwe:1", "cwe:123", "cwe:863", "owaspTop10-2021:a1", "owaspTop10-2021:a3");
  209. }
  210. @Test
  211. public void insert_new_external_rule() {
  212. execute(new ExternalRuleRepository());
  213. // verify db
  214. assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(2);
  215. RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_RULE_KEY1);
  216. verifyRule(rule1, RuleType.CODE_SMELL, BLOCKER);
  217. assertThat(rule1.isExternal()).isTrue();
  218. assertThat(rule1.getDefRemediationFunction()).isNull();
  219. assertThat(rule1.getDefRemediationGapMultiplier()).isNull();
  220. assertThat(rule1.getDefRemediationBaseEffort()).isNull();
  221. RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_HOTSPOT_RULE_KEY);
  222. verifyHotspot(hotspotRule);
  223. }
  224. private void verifyRule(RuleDto rule, RuleType type, String expectedSeverity) {
  225. assertThat(rule.getName()).isEqualTo("One");
  226. assertThat(rule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Description of One");
  227. assertThat(rule.getSeverityString()).isEqualTo(expectedSeverity);
  228. assertThat(rule.getTags()).isEmpty();
  229. assertThat(rule.getSystemTags()).containsOnly("tag1", "tag2", "tag3");
  230. assertThat(rule.getConfigKey()).isEqualTo("config1");
  231. assertThat(rule.getStatus()).isEqualTo(RuleStatus.BETA);
  232. assertThat(rule.getCreatedAt()).isEqualTo(DATE1.getTime());
  233. assertThat(rule.getScope()).isEqualTo(Scope.ALL);
  234. assertThat(rule.getUpdatedAt()).isEqualTo(DATE1.getTime());
  235. assertThat(rule.getType()).isEqualTo(type.getDbConstant());
  236. assertThat(rule.getPluginKey()).isEqualTo(FAKE_PLUGIN_KEY);
  237. assertThat(rule.isAdHoc()).isFalse();
  238. assertThat(rule.getEducationPrinciples()).containsOnly("concept1", "concept2", "concept3");
  239. }
  240. @Test
  241. public void insert_then_remove_rule() {
  242. String ruleKey = randomAlphanumeric(5);
  243. // register one rule
  244. execute(context -> {
  245. NewRepository repo = context.createRepository("fake", "java");
  246. repo.createRule(ruleKey)
  247. .setName(randomAlphanumeric(5))
  248. .setHtmlDescription(randomAlphanumeric(20));
  249. repo.done();
  250. });
  251. // verify db
  252. List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
  253. assertThat(rules)
  254. .extracting(RuleDto::getKey)
  255. .extracting(RuleKey::rule)
  256. .containsExactly(ruleKey);
  257. RuleDto rule = rules.iterator().next();
  258. // verify index
  259. assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
  260. .containsExactly(rule.getUuid());
  261. verifyIndicesMarkedAsInitialized();
  262. // register no rule
  263. execute(context -> context.createRepository("fake", "java").done());
  264. // verify db
  265. assertThat(dbClient.ruleDao().selectAll(db.getSession()))
  266. .extracting(RuleDto::getKey)
  267. .extracting(RuleKey::rule)
  268. .containsExactly(ruleKey);
  269. assertThat(dbClient.ruleDao().selectAll(db.getSession()))
  270. .extracting(RuleDto::getStatus)
  271. .containsExactly(REMOVED);
  272. // verify index
  273. assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
  274. .isEmpty();
  275. verifyIndicesNotMarkedAsInitialized();
  276. }
  277. @Test
  278. public void mass_insert_then_remove_rule() {
  279. int numberOfRules = 5000;
  280. // register many rules
  281. execute(context -> {
  282. NewRepository repo = context.createRepository("fake", "java");
  283. IntStream.range(0, numberOfRules)
  284. .mapToObj(i -> "rule-" + i)
  285. .forEach(ruleKey -> repo.createRule(ruleKey)
  286. .setName(randomAlphanumeric(20))
  287. .setHtmlDescription(randomAlphanumeric(20)));
  288. repo.done();
  289. });
  290. // verify db
  291. assertThat(dbClient.ruleDao().selectAll(db.getSession()))
  292. .hasSize(numberOfRules)
  293. .extracting(RuleDto::getStatus)
  294. .containsOnly(READY);
  295. // verify index
  296. assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(numberOfRules);
  297. assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
  298. .isNotEmpty();
  299. // register no rule
  300. execute(context -> context.createRepository("fake", "java").done());
  301. // verify db
  302. assertThat(dbClient.ruleDao().selectAll(db.getSession()))
  303. .hasSize(numberOfRules)
  304. .extracting(RuleDto::getStatus)
  305. .containsOnly(REMOVED);
  306. // verify index (documents are still in the index, but all are removed)
  307. assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isEqualTo(numberOfRules);
  308. assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids())
  309. .isEmpty();
  310. }
  311. @Test
  312. public void delete_repositories_that_have_been_uninstalled() {
  313. RuleRepositoryDto repository = new RuleRepositoryDto("findbugs", "java", "Findbugs");
  314. DbSession dbSession = db.getSession();
  315. db.getDbClient().ruleRepositoryDao().insert(dbSession, singletonList(repository));
  316. dbSession.commit();
  317. execute(new FakeRepositoryV1());
  318. assertThat(db.getDbClient().ruleRepositoryDao().selectAll(dbSession)).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
  319. }
  320. @Test
  321. public void update_and_remove_rules_on_changes() {
  322. execute(new FakeRepositoryV1());
  323. assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
  324. RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
  325. RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
  326. RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
  327. assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).containsOnly(valueOf(rule1.getUuid()), valueOf(rule2.getUuid()), valueOf(hotspotRule.getUuid()));
  328. verifyIndicesMarkedAsInitialized();
  329. // user adds tags and sets markdown note
  330. rule1.setTags(newHashSet("usertag1", "usertag2"));
  331. rule1.setNoteData("user *note*");
  332. rule1.setNoteUserUuid("marius");
  333. dbClient.ruleDao().update(db.getSession(), rule1);
  334. db.getSession().commit();
  335. system.setNow(DATE2.getTime());
  336. execute(new FakeRepositoryV2());
  337. verifyIndicesNotMarkedAsInitialized();
  338. // rule1 has been updated
  339. rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
  340. assertThatRule1IsV2(rule1);
  341. List<RuleParamDto> params = dbClient.ruleDao().selectRuleParamsByRuleKey(db.getSession(), RULE_KEY1);
  342. assertThat(params).hasSize(2);
  343. RuleParamDto param = getParam(params, "param1");
  344. assertThat(param.getDescription()).isEqualTo("parameter one v2");
  345. assertThat(param.getDefaultValue()).isEqualTo("default1 v2");
  346. // rule2 has been removed -> status set to REMOVED but db row is not deleted
  347. rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
  348. assertThat(rule2.getStatus()).isEqualTo(REMOVED);
  349. assertThat(rule2.getUpdatedAt()).isEqualTo(DATE2.getTime());
  350. // rule3 has been created
  351. RuleDto rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
  352. assertThat(rule3).isNotNull();
  353. assertThat(rule3.getStatus()).isEqualTo(READY);
  354. // verify index
  355. assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
  356. // verify repositories
  357. assertThat(dbClient.ruleRepositoryDao().selectAll(db.getSession())).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
  358. system.setNow(DATE3.getTime());
  359. execute(new FakeRepositoryV3());
  360. rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
  361. assertThat(rule3.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Rule Three V2");
  362. assertThat(rule3.getDescriptionFormat()).isEqualTo(RuleDto.Format.MARKDOWN);
  363. }
  364. private void assertThatRule1IsV2(RuleDto rule1) {
  365. assertThat(rule1.getName()).isEqualTo("One v2");
  366. RuleDescriptionSectionDto defaultRuleDescriptionSection = rule1.getDefaultRuleDescriptionSection();
  367. assertThat(defaultRuleDescriptionSection.getContent()).isEqualTo("Description of One v2");
  368. assertThat(defaultRuleDescriptionSection.getKey()).isEqualTo(DEFAULT_KEY);
  369. assertThat(rule1.getDescriptionFormat()).isEqualTo(RuleDto.Format.HTML);
  370. assertThat(rule1.getSeverityString()).isEqualTo(INFO);
  371. assertThat(rule1.getTags()).containsOnly("usertag1", "usertag2");
  372. assertThat(rule1.getSystemTags()).containsOnly("tag1", "tag4");
  373. assertThat(rule1.getConfigKey()).isEqualTo("config1 v2");
  374. assertThat(rule1.getNoteData()).isEqualTo("user *note*");
  375. assertThat(rule1.getNoteUserUuid()).isEqualTo("marius");
  376. assertThat(rule1.getStatus()).isEqualTo(READY);
  377. assertThat(rule1.getType()).isEqualTo(RuleType.BUG.getDbConstant());
  378. assertThat(rule1.getCreatedAt()).isEqualTo(DATE1.getTime());
  379. assertThat(rule1.getUpdatedAt()).isEqualTo(DATE2.getTime());
  380. assertThat(rule1.getEducationPrinciples()).containsOnly("concept1","concept4");
  381. }
  382. @Test
  383. public void add_new_tag() {
  384. execute(context -> {
  385. NewRepository repo = context.createRepository("fake", "java");
  386. repo.createRule("rule1")
  387. .setName("Rule One")
  388. .setHtmlDescription("Description of Rule One")
  389. .setTags("tag1");
  390. repo.done();
  391. });
  392. RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
  393. assertThat(rule.getSystemTags()).containsOnly("tag1");
  394. execute(context -> {
  395. NewRepository repo = context.createRepository("fake", "java");
  396. repo.createRule("rule1")
  397. .setName("Rule One")
  398. .setHtmlDescription("Description of Rule One")
  399. .setTags("tag1", "tag2");
  400. repo.done();
  401. });
  402. rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
  403. assertThat(rule.getSystemTags()).containsOnly("tag1", "tag2");
  404. }
  405. @Test
  406. public void add_new_security_standards() {
  407. execute(context -> {
  408. NewRepository repo = context.createRepository("fake", "java");
  409. repo.createRule("rule1")
  410. .setName("Rule One")
  411. .setHtmlDescription("Description of Rule One")
  412. .addOwaspTop10(Y2021, OwaspTop10.A1)
  413. .addCwe(123);
  414. repo.done();
  415. });
  416. RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
  417. assertThat(rule.getSecurityStandards()).containsOnly("cwe:123", "owaspTop10-2021:a1");
  418. execute(context -> {
  419. NewRepository repo = context.createRepository("fake", "java");
  420. repo.createRule("rule1")
  421. .setName("Rule One")
  422. .setHtmlDescription("Description of Rule One")
  423. .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
  424. .addCwe(1, 123, 863);
  425. repo.done();
  426. });
  427. rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
  428. assertThat(rule.getSecurityStandards()).containsOnly("cwe:1", "cwe:123", "cwe:863", "owaspTop10-2021:a1", "owaspTop10-2021:a3");
  429. }
  430. @Test
  431. public void update_only_rule_name() {
  432. system.setNow(DATE1.getTime());
  433. execute(context -> {
  434. NewRepository repo = context.createRepository("fake", "java");
  435. repo.createRule("rule")
  436. .setName("Name1")
  437. .setHtmlDescription("Description");
  438. repo.done();
  439. });
  440. system.setNow(DATE2.getTime());
  441. execute(context -> {
  442. NewRepository repo = context.createRepository("fake", "java");
  443. repo.createRule("rule")
  444. .setName("Name2")
  445. .setHtmlDescription("Description");
  446. repo.done();
  447. });
  448. // rule1 has been updated
  449. RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
  450. assertThat(rule1.getName()).isEqualTo("Name2");
  451. assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Description");
  452. assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions()).getTotal()).isOne();
  453. assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
  454. }
  455. @Test
  456. public void update_template_rule_key_should_also_update_custom_rules() {
  457. system.setNow(DATE1.getTime());
  458. execute(context -> {
  459. NewRepository repo = context.createRepository("squid", "java");
  460. repo.createRule("rule")
  461. .setName("Name1")
  462. .setHtmlDescription("Description")
  463. .setTemplate(true);
  464. repo.done();
  465. });
  466. RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("squid", "rule"));
  467. // insert custom rule
  468. db.rules().insert(new RuleDto()
  469. .setRuleKey(RuleKey.of("squid", "custom"))
  470. .setLanguage("java")
  471. .setScope(Scope.ALL)
  472. .setTemplateUuid(rule1.getUuid())
  473. .setName("custom1"));
  474. db.commit();
  475. // re-key rule
  476. execute(context -> {
  477. NewRepository repo = context.createRepository("java", "java");
  478. repo.createRule("rule")
  479. .setName("Name1")
  480. .setHtmlDescription("Description")
  481. .addDeprecatedRuleKey("squid", "rule")
  482. .setTemplate(true);
  483. repo.done();
  484. });
  485. // template rule and custom rule have been updated
  486. rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("java", "rule"));
  487. RuleDto custom = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("java", "custom"));
  488. }
  489. @Test
  490. public void update_if_rule_key_renamed_and_deprecated_key_declared() {
  491. String ruleKey1 = "rule1";
  492. String ruleKey2 = "rule2";
  493. String repository = "fake";
  494. system.setNow(DATE1.getTime());
  495. execute(context -> {
  496. NewRepository repo = context.createRepository(repository, "java");
  497. repo.createRule(ruleKey1)
  498. .setName("Name1")
  499. .setHtmlDescription("Description");
  500. repo.done();
  501. });
  502. RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository, ruleKey1));
  503. SearchIdResult<String> searchRule1 = ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions());
  504. assertThat(searchRule1.getUuids()).containsOnly(rule1.getUuid());
  505. assertThat(searchRule1.getTotal()).isOne();
  506. system.setNow(DATE2.getTime());
  507. execute(context -> {
  508. NewRepository repo = context.createRepository(repository, "java");
  509. repo.createRule(ruleKey2)
  510. .setName("Name2")
  511. .setHtmlDescription("Description")
  512. .addDeprecatedRuleKey(repository, ruleKey1);
  513. repo.done();
  514. });
  515. // rule2 is actually rule1
  516. RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository, ruleKey2));
  517. assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
  518. assertThat(rule2.getName()).isEqualTo("Name2");
  519. assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
  520. SearchIdResult<String> searchRule2 = ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions());
  521. assertThat(searchRule2.getUuids()).containsOnly(rule2.getUuid());
  522. assertThat(searchRule2.getTotal()).isOne();
  523. assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
  524. }
  525. @Test
  526. public void update_if_repository_changed_and_deprecated_key_declared() {
  527. String ruleKey = "rule";
  528. String repository1 = "fake1";
  529. String repository2 = "fake2";
  530. system.setNow(DATE1.getTime());
  531. execute(context -> {
  532. NewRepository repo = context.createRepository(repository1, "java");
  533. repo.createRule(ruleKey)
  534. .setName("Name1")
  535. .setHtmlDescription("Description");
  536. repo.done();
  537. });
  538. RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository1, ruleKey));
  539. SearchIdResult<String> searchRule1 = ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions());
  540. assertThat(searchRule1.getUuids()).containsOnly(rule1.getUuid());
  541. assertThat(searchRule1.getTotal()).isOne();
  542. system.setNow(DATE2.getTime());
  543. execute(context -> {
  544. NewRepository repo = context.createRepository(repository2, "java");
  545. repo.createRule(ruleKey)
  546. .setName("Name2")
  547. .setHtmlDescription("Description")
  548. .addDeprecatedRuleKey(repository1, ruleKey);
  549. repo.done();
  550. });
  551. // rule2 is actually rule1
  552. RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository2, ruleKey));
  553. assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
  554. assertThat(rule2.getName()).isEqualTo("Name2");
  555. assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
  556. SearchIdResult<String> searchRule2 = ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions());
  557. assertThat(searchRule2.getUuids()).containsOnly(rule2.getUuid());
  558. assertThat(searchRule2.getTotal()).isOne();
  559. assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
  560. }
  561. @Test
  562. @UseDataProvider("allRenamingCases")
  563. public void update_if_only_renamed_and_deprecated_key_declared(String ruleKey1, String repo1, String ruleKey2, String repo2) {
  564. String name = "Name1";
  565. String description = "Description";
  566. system.setNow(DATE1.getTime());
  567. execute(context -> {
  568. NewRepository repo = context.createRepository(repo1, "java");
  569. repo.createRule(ruleKey1)
  570. .setName(name)
  571. .setHtmlDescription(description);
  572. repo.done();
  573. });
  574. RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repo1, ruleKey1));
  575. assertThat(ruleIndex.search(new RuleQuery().setQueryText(name), new SearchOptions()).getUuids())
  576. .containsOnly(rule1.getUuid());
  577. system.setNow(DATE2.getTime());
  578. execute(context -> {
  579. NewRepository repo = context.createRepository(repo2, "java");
  580. repo.createRule(ruleKey2)
  581. .setName(name)
  582. .setHtmlDescription(description)
  583. .addDeprecatedRuleKey(repo1, ruleKey1);
  584. repo.done();
  585. });
  586. // rule2 is actually rule1
  587. RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repo2, ruleKey2));
  588. assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
  589. assertThat(rule2.getName()).isEqualTo(rule1.getName());
  590. assertThat(rule2.getDefaultRuleDescriptionSection().getContent()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getContent());
  591. assertThat(ruleIndex.search(new RuleQuery().setQueryText(name), new SearchOptions()).getUuids())
  592. .containsOnly(rule2.getUuid());
  593. }
  594. @DataProvider
  595. public static Object[][] allRenamingCases() {
  596. return new Object[][]{
  597. {"repo1", "rule1", "repo1", "rule2"},
  598. {"repo1", "rule1", "repo2", "rule1"},
  599. {"repo1", "rule1", "repo2", "rule2"},
  600. };
  601. }
  602. @Test
  603. public void update_if_repository_and_key_changed_and_deprecated_key_declared_among_others() {
  604. String ruleKey1 = "rule1";
  605. String ruleKey2 = "rule2";
  606. String repository1 = "fake1";
  607. String repository2 = "fake2";
  608. system.setNow(DATE1.getTime());
  609. execute(context -> {
  610. NewRepository repo = context.createRepository(repository1, "java");
  611. repo.createRule(ruleKey1)
  612. .setName("Name1")
  613. .setHtmlDescription("Description");
  614. repo.done();
  615. });
  616. RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository1, ruleKey1));
  617. assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getUuids())
  618. .containsOnly(rule1.getUuid());
  619. system.setNow(DATE2.getTime());
  620. execute(context -> {
  621. NewRepository repo = context.createRepository(repository2, "java");
  622. repo.createRule(ruleKey2)
  623. .setName("Name2")
  624. .setHtmlDescription("Description")
  625. .addDeprecatedRuleKey("foo", "bar")
  626. .addDeprecatedRuleKey(repository1, ruleKey1)
  627. .addDeprecatedRuleKey("some", "noise");
  628. repo.done();
  629. });
  630. // rule2 is actually rule1
  631. RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository2, ruleKey2));
  632. assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
  633. assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions()).getUuids())
  634. .containsOnly(rule1.getUuid());
  635. }
  636. @Test
  637. public void update_only_rule_description() {
  638. system.setNow(DATE1.getTime());
  639. execute(context -> {
  640. NewRepository repo = context.createRepository("fake", "java");
  641. repo.createRule("rule")
  642. .setName("Name")
  643. .setHtmlDescription("Desc1");
  644. repo.done();
  645. });
  646. system.setNow(DATE2.getTime());
  647. execute(context -> {
  648. NewRepository repo = context.createRepository("fake", "java");
  649. repo.createRule("rule")
  650. .setName("Name")
  651. .setHtmlDescription("Desc2");
  652. repo.done();
  653. });
  654. // rule1 has been updated
  655. RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
  656. assertThat(rule1.getName()).isEqualTo("Name");
  657. assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Desc2");
  658. assertThat(ruleIndex.search(new RuleQuery().setQueryText("Desc2"), new SearchOptions()).getTotal()).isOne();
  659. assertThat(ruleIndex.search(new RuleQuery().setQueryText("Desc1"), new SearchOptions()).getTotal()).isZero();
  660. }
  661. @Test
  662. public void update_several_rule_descriptions() {
  663. system.setNow(DATE1.getTime());
  664. RuleDescriptionSection section1context1 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx1 content", "ctx_1");
  665. RuleDescriptionSection section1context2 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx2 content", "ctx_2");
  666. RuleDescriptionSection section2context1 = createRuleDescriptionSection(RESOURCES_SECTION_KEY, "section2 content", "ctx_1");
  667. RuleDescriptionSection section2context2 = createRuleDescriptionSection(RESOURCES_SECTION_KEY,"section2 ctx2 content", "ctx_2");
  668. RuleDescriptionSection section3noContext = createRuleDescriptionSection(ASSESS_THE_PROBLEM_SECTION_KEY, "section3 content", null);
  669. RuleDescriptionSection section4noContext = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, "section4 content", null);
  670. execute(context -> {
  671. NewRepository repo = context.createRepository("fake", "java");
  672. repo.createRule("rule")
  673. .setName("Name")
  674. .addDescriptionSection(section1context1)
  675. .addDescriptionSection(section1context2)
  676. .addDescriptionSection(section2context1)
  677. .addDescriptionSection(section2context2)
  678. .addDescriptionSection(section3noContext)
  679. .addDescriptionSection(section4noContext)
  680. .setHtmlDescription("Desc1");
  681. repo.done();
  682. });
  683. RuleDescriptionSection section1context2updated = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "section1 ctx2 updated content", "ctx_2");
  684. RuleDescriptionSection section2updatedWithoutContext = createRuleDescriptionSection(RESOURCES_SECTION_KEY, section2context1.getHtmlContent(), null);
  685. RuleDescriptionSection section4updatedWithContext1 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, section4noContext.getHtmlContent(), "ctx_1");
  686. RuleDescriptionSection section4updatedWithContext2 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, section4noContext.getHtmlContent(), "ctx_2");
  687. system.setNow(DATE2.getTime());
  688. execute(context -> {
  689. NewRepository repo = context.createRepository("fake", "java");
  690. repo.createRule("rule")
  691. .setName("Name")
  692. .addDescriptionSection(section1context1)
  693. .addDescriptionSection(section1context2updated)
  694. .addDescriptionSection(section2updatedWithoutContext)
  695. .addDescriptionSection(section3noContext)
  696. .addDescriptionSection(section4updatedWithContext1)
  697. .addDescriptionSection(section4updatedWithContext2)
  698. .setHtmlDescription("Desc2");
  699. repo.done();
  700. });
  701. RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
  702. assertThat(rule1.getName()).isEqualTo("Name");
  703. assertThat(rule1.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Desc2");
  704. Set<RuleDescriptionSection> expectedSections = Set.of(section1context1, section1context2updated,
  705. section2updatedWithoutContext, section3noContext, section4updatedWithContext1, section4updatedWithContext2);
  706. assertThat(rule1.getRuleDescriptionSectionDtos()).hasSize(expectedSections.size() + 1);
  707. expectedSections.forEach(apiSection -> assertSectionExists(apiSection, rule1.getRuleDescriptionSectionDtos()));
  708. }
  709. private static RuleDescriptionSection createRuleDescriptionSection(String sectionKey, String description, @Nullable String contextKey) {
  710. Context context = Optional.ofNullable(contextKey).map(key -> new Context(contextKey, contextKey + randomAlphanumeric(10))).orElse(null);
  711. return RuleDescriptionSection.builder().sectionKey(sectionKey)
  712. .htmlContent(description)
  713. .context(context)
  714. .build();
  715. }
  716. private static void assertSectionExists(RuleDescriptionSection apiSection, Set<RuleDescriptionSectionDto> sectionDtos) {
  717. sectionDtos.stream()
  718. .filter(sectionDto -> sectionDto.getKey().equals(apiSection.getKey()) && sectionDto.getContent().equals(apiSection.getHtmlContent()))
  719. .filter(sectionDto -> isSameContext(apiSection.getContext(), sectionDto.getContext()))
  720. .findAny()
  721. .orElseThrow(() -> new AssertionError(format("Impossible to find a section dto matching the API section %s", apiSection.getKey())));
  722. }
  723. private static boolean isSameContext(Optional<Context> apiContext, @Nullable RuleDescriptionSectionContextDto contextDto) {
  724. if (apiContext.isEmpty() && contextDto == null) {
  725. return true;
  726. }
  727. return apiContext.filter(context -> isSameContext(context, contextDto)).isPresent();
  728. }
  729. private static boolean isSameContext(Context apiContext, @Nullable RuleDescriptionSectionContextDto contextDto) {
  730. if (contextDto == null) {
  731. return false;
  732. }
  733. return Objects.equals(apiContext.getKey(), contextDto.getKey()) && Objects.equals(apiContext.getDisplayName(), contextDto.getDisplayName());
  734. }
  735. @Test
  736. public void rule_previously_created_as_adhoc_becomes_none_adhoc() {
  737. RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake").setIsExternal(true).setIsAdHoc(true));
  738. system.setNow(DATE2.getTime());
  739. execute(context -> {
  740. NewRepository repo = context.createExternalRepository("fake", rule.getLanguage());
  741. repo.createRule(rule.getRuleKey())
  742. .setName(rule.getName())
  743. .setHtmlDescription(rule.getDefaultRuleDescriptionSection().getContent());
  744. repo.done();
  745. });
  746. RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
  747. assertThat(reloaded.isAdHoc()).isFalse();
  748. }
  749. @Test
  750. public void remove_no_more_defined_external_rule() {
  751. RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake")
  752. .setStatus(READY)
  753. .setIsExternal(true)
  754. .setIsAdHoc(false));
  755. execute();
  756. RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
  757. assertThat(reloaded.getStatus()).isEqualTo(REMOVED);
  758. }
  759. @Test
  760. public void do_not_remove_no_more_defined_ad_hoc_rule() {
  761. RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_fake")
  762. .setStatus(READY)
  763. .setIsExternal(true)
  764. .setIsAdHoc(true));
  765. execute();
  766. RuleDto reloaded = dbClient.ruleDao().selectByKey(db.getSession(), rule.getKey()).get();
  767. assertThat(reloaded.getStatus()).isEqualTo(READY);
  768. }
  769. @Test
  770. public void disable_then_enable_rule() {
  771. // Install rule
  772. system.setNow(DATE1.getTime());
  773. execute(new FakeRepositoryV1());
  774. // Uninstall rule
  775. system.setNow(DATE2.getTime());
  776. execute();
  777. RuleDto rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
  778. assertThat(rule.getStatus()).isEqualTo(REMOVED);
  779. assertThat(ruleIndex.search(new RuleQuery().setKey(RULE_KEY1.toString()), new SearchOptions()).getTotal()).isZero();
  780. // Re-install rule
  781. system.setNow(DATE3.getTime());
  782. execute(new FakeRepositoryV1());
  783. rule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
  784. assertThat(rule.getStatus()).isEqualTo(RuleStatus.BETA);
  785. assertThat(ruleIndex.search(new RuleQuery().setKey(RULE_KEY1.toString()), new SearchOptions()).getTotal()).isOne();
  786. }
  787. @Test
  788. public void do_not_update_rules_when_no_changes() {
  789. execute(new FakeRepositoryV1());
  790. assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
  791. system.setNow(DATE2.getTime());
  792. execute(new FakeRepositoryV1());
  793. RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
  794. assertThat(rule1.getCreatedAt()).isEqualTo(DATE1.getTime());
  795. assertThat(rule1.getUpdatedAt()).isEqualTo(DATE1.getTime());
  796. }
  797. @Test
  798. public void do_not_update_already_removed_rules() {
  799. execute(new FakeRepositoryV1());
  800. assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3);
  801. RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
  802. RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
  803. RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
  804. assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).containsOnly(valueOf(rule1.getUuid()), valueOf(rule2.getUuid()), valueOf(hotspotRule.getUuid()));
  805. assertThat(rule2.getStatus()).isEqualTo(READY);
  806. system.setNow(DATE2.getTime());
  807. execute(new FakeRepositoryV2());
  808. // On MySQL, need to update a rule otherwise rule2 will be seen as READY, but why ???
  809. dbClient.ruleDao().update(db.getSession(), rule1);
  810. db.getSession().commit();
  811. // rule2 is removed
  812. rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
  813. RuleDto rule3 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY3);
  814. assertThat(rule2.getStatus()).isEqualTo(REMOVED);
  815. assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
  816. system.setNow(DATE3.getTime());
  817. execute(new FakeRepositoryV2());
  818. db.getSession().commit();
  819. // -> rule2 is still removed, but not update at DATE3
  820. rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2);
  821. assertThat(rule2.getStatus()).isEqualTo(REMOVED);
  822. assertThat(rule2.getUpdatedAt()).isEqualTo(DATE2.getTime());
  823. assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
  824. }
  825. @Test
  826. public void mass_insert() {
  827. execute(new BigRepository());
  828. assertThat(db.countRowsOfTable("rules")).isEqualTo(BigRepository.SIZE);
  829. assertThat(db.countRowsOfTable("rules_parameters")).isEqualTo(BigRepository.SIZE * 20);
  830. assertThat(es.getIds(RuleIndexDefinition.TYPE_RULE)).hasSize(BigRepository.SIZE);
  831. }
  832. @Test
  833. public void manage_repository_extensions() {
  834. execute(new FindbugsRepository(), new FbContribRepository());
  835. List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
  836. assertThat(rules).hasSize(2);
  837. for (RuleDto rule : rules) {
  838. assertThat(rule.getRepositoryKey()).isEqualTo("findbugs");
  839. }
  840. }
  841. @Test
  842. public void remove_system_tags_when_plugin_does_not_provide_any() {
  843. // Rule already exists in DB, with some system tags
  844. db.rules().insert(new RuleDto()
  845. .setRuleKey("rule1")
  846. .setRepositoryKey("findbugs")
  847. .setName("Rule One")
  848. .setType(RuleType.CODE_SMELL)
  849. .setScope(Scope.ALL)
  850. .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule one description"))
  851. .setDescriptionFormat(RuleDto.Format.HTML)
  852. .setSystemTags(newHashSet("tag1", "tag2")));
  853. db.getSession().commit();
  854. // Synchronize rule without tag
  855. execute(new FindbugsRepository());
  856. List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
  857. assertThat(rules).hasSize(1).extracting(RuleDto::getKey, RuleDto::getSystemTags)
  858. .containsOnly(tuple(RuleKey.of("findbugs", "rule1"), emptySet()));
  859. }
  860. @Test
  861. public void rules_that_deprecate_previous_rule_must_be_recorded() {
  862. execute(context -> {
  863. NewRepository repo = context.createRepository("fake", "java");
  864. createRule(repo, "rule1");
  865. repo.done();
  866. });
  867. execute(context -> {
  868. NewRepository repo = context.createRepository("fake", "java");
  869. createRule(repo, "newKey")
  870. .addDeprecatedRuleKey("fake", "rule1")
  871. .addDeprecatedRuleKey("fake", "rule2");
  872. repo.done();
  873. });
  874. List<RuleDto> rules = dbClient.ruleDao().selectAll(db.getSession());
  875. Set<DeprecatedRuleKeyDto> deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
  876. assertThat(rules).hasSize(1);
  877. assertThat(deprecatedRuleKeys).hasSize(2);
  878. }
  879. @Test
  880. public void rules_that_remove_deprecated_key_must_remove_records() {
  881. execute(context -> {
  882. NewRepository repo = context.createRepository("fake", "java");
  883. createRule(repo, "rule1");
  884. repo.done();
  885. });
  886. execute(context -> {
  887. NewRepository repo = context.createRepository("fake", "java");
  888. createRule(repo, "newKey")
  889. .addDeprecatedRuleKey("fake", "rule1")
  890. .addDeprecatedRuleKey("fake", "rule2");
  891. repo.done();
  892. });
  893. assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(1);
  894. Set<DeprecatedRuleKeyDto> deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
  895. assertThat(deprecatedRuleKeys).hasSize(2);
  896. execute(context -> {
  897. NewRepository repo = context.createRepository("fake", "java");
  898. createRule(repo, "newKey");
  899. repo.done();
  900. });
  901. assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(1);
  902. deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(db.getSession());
  903. assertThat(deprecatedRuleKeys).isEmpty();
  904. }
  905. @Test
  906. public void declaring_two_rules_with_same_deprecated_RuleKey_should_throw_ISE() {
  907. assertThatThrownBy(() -> {
  908. execute(context -> {
  909. NewRepository repo = context.createRepository("fake", "java");
  910. createRule(repo, "newKey1")
  911. .addDeprecatedRuleKey("fake", "old");
  912. createRule(repo, "newKey2")
  913. .addDeprecatedRuleKey("fake", "old");
  914. repo.done();
  915. });
  916. })
  917. .isInstanceOf(IllegalStateException.class)
  918. .hasMessage("The following deprecated rule keys are declared at least twice [fake:old]");
  919. }
  920. @Test
  921. public void declaring_a_rule_with_a_deprecated_RuleKey_still_used_should_throw_ISE() {
  922. assertThatThrownBy(() -> {
  923. execute(context -> {
  924. NewRepository repo = context.createRepository("fake", "java");
  925. createRule(repo, "newKey1");
  926. createRule(repo, "newKey2")
  927. .addDeprecatedRuleKey("fake", "newKey1");
  928. repo.done();
  929. });
  930. })
  931. .isInstanceOf(IllegalStateException.class)
  932. .hasMessage("The following rule keys are declared both as deprecated and used key [fake:newKey1]");
  933. }
  934. @Test
  935. public void updating_the_deprecated_to_a_new_ruleKey_should_throw_an_ISE() {
  936. // On this new rule add a deprecated key
  937. execute(context -> createRule(context, "javascript", "javascript", "s103",
  938. r -> r.addDeprecatedRuleKey("javascript", "linelength")));
  939. assertThatThrownBy(() -> {
  940. // This rule should have been moved to another repository
  941. execute(context -> createRule(context, "javascript", "sonarjs", "s103",
  942. r -> r.addDeprecatedRuleKey("javascript", "linelength")));
  943. })
  944. .isInstanceOf(IllegalStateException.class)
  945. .hasMessage("An incorrect state of deprecated rule keys has been detected.\n " +
  946. "The deprecated rule key [javascript:linelength] was previously deprecated by [javascript:s103]. [javascript:s103] should be a deprecated key of [sonarjs:s103],");
  947. }
  948. @Test
  949. public void deprecate_rule_that_deprecated_another_rule() {
  950. execute(context -> createRule(context, "javascript", "javascript", "s103"));
  951. execute(context -> createRule(context, "javascript", "javascript", "s104",
  952. r -> r.addDeprecatedRuleKey("javascript", "s103")));
  953. // This rule should have been moved to another repository
  954. execute(context -> createRule(context, "javascript", "sonarjs", "s105",
  955. r -> r.addDeprecatedRuleKey("javascript", "s103")
  956. .addDeprecatedRuleKey("javascript", "s104")));
  957. }
  958. @Test
  959. public void declaring_a_rule_with_an_existing_RuleKey_still_used_should_throw_IAE() {
  960. assertThatThrownBy(() -> {
  961. execute(context -> {
  962. NewRepository repo = context.createRepository("fake", "java");
  963. createRule(repo, "newKey1");
  964. createRule(repo, "newKey1");
  965. repo.done();
  966. });
  967. })
  968. .isInstanceOf(IllegalArgumentException.class)
  969. .hasMessage("The rule 'newKey1' of repository 'fake' is declared several times");
  970. }
  971. @Test
  972. public void removed_rule_should_appear_in_changelog() {
  973. //GIVEN
  974. QProfileDto qProfileDto = db.qualityProfiles().insert();
  975. RuleDto ruleDto = db.rules().insert(RULE_KEY1);
  976. db.qualityProfiles().activateRule(qProfileDto, ruleDto);
  977. ActiveRuleChange arChange = new ActiveRuleChange(DEACTIVATED, ActiveRuleDto.createFor(qProfileDto, ruleDto), ruleDto);
  978. when(qProfileRules.deleteRule(any(DbSession.class), eq(ruleDto))).thenReturn(List.of(arChange));
  979. //WHEN
  980. execute(context -> context.createRepository("fake", "java").done());
  981. //THEN
  982. List<QProfileChangeDto> qProfileChangeDtos = dbClient.qProfileChangeDao().selectByQuery(db.getSession(), new QProfileChangeQuery(qProfileDto.getKee()));
  983. assertThat(qProfileChangeDtos).extracting(QProfileChangeDto::getRulesProfileUuid, QProfileChangeDto::getChangeType)
  984. .contains(tuple(qProfileDto.getRulesProfileUuid(), "DEACTIVATED"));
  985. }
  986. @Test
  987. public void removed_rule_should_be_deleted_when_renamed_repository() {
  988. //GIVEN
  989. RuleDto removedRuleDto = db.rules().insert(RuleKey.of("old_repo", "removed_rule"));
  990. RuleDto renamedRuleDto = db.rules().insert(RuleKey.of("old_repo", "renamed_rule"));
  991. //WHEN
  992. execute(context -> createRule(context, "java", "new_repo", renamedRuleDto.getRuleKey(),
  993. rule -> rule.addDeprecatedRuleKey(renamedRuleDto.getRepositoryKey(), renamedRuleDto.getRuleKey())));
  994. //THEN
  995. verify(qProfileRules).deleteRule(any(DbSession.class), eq(removedRuleDto));
  996. }
  997. private void execute(RulesDefinition... defs) {
  998. ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
  999. when(pluginRepository.getPluginKey(any(RulesDefinition.class))).thenReturn(FAKE_PLUGIN_KEY);
  1000. RuleDefinitionsLoader loader = new RuleDefinitionsLoader(pluginRepository, defs);
  1001. Languages languages = mock(Languages.class);
  1002. when(languages.get(any())).thenReturn(mock(Language.class));
  1003. reset(webServerRuleFinder);
  1004. RulesRegistrant task = new RulesRegistrant(loader, qProfileRules, dbClient, ruleIndexer, activeRuleIndexer, languages, system, webServerRuleFinder, metadataIndex,
  1005. rulesKeyVerifier, startupRuleUpdater, newRuleCreator);
  1006. task.start();
  1007. // Execute a commit to refresh session state as the task is using its own session
  1008. db.getSession().commit();
  1009. verify(webServerRuleFinder).startCaching();
  1010. }
  1011. private NewRule createRule(NewRepository repo, String key) {
  1012. return repo.createRule(key)
  1013. .setName(key + " name")
  1014. .setHtmlDescription("Description of " + key)
  1015. .setSeverity(BLOCKER)
  1016. .setInternalKey("config1")
  1017. .setTags("tag1", "tag2", "tag3")
  1018. .setType(RuleType.CODE_SMELL)
  1019. .setStatus(RuleStatus.BETA);
  1020. }
  1021. @SafeVarargs
  1022. private void createRule(RulesDefinition.Context context, String language, String repositoryKey, String ruleKey, Consumer<NewRule>... consumers) {
  1023. NewRepository repo = context.createRepository(repositoryKey, language);
  1024. NewRule newRule = repo.createRule(ruleKey)
  1025. .setName(ruleKey)
  1026. .setHtmlDescription("Description of One")
  1027. .setSeverity(BLOCKER)
  1028. .setType(RuleType.CODE_SMELL)
  1029. .setStatus(RuleStatus.BETA);
  1030. Arrays.stream(consumers).forEach(c -> c.accept(newRule));
  1031. repo.done();
  1032. }
  1033. private void verifyIndicesMarkedAsInitialized() {
  1034. verify(metadataIndex).setInitialized(RuleIndexDefinition.TYPE_RULE, true);
  1035. verify(metadataIndex).setInitialized(RuleIndexDefinition.TYPE_ACTIVE_RULE, true);
  1036. reset(metadataIndex);
  1037. }
  1038. private void verifyIndicesNotMarkedAsInitialized() {
  1039. verifyNoInteractions(metadataIndex);
  1040. }
  1041. private RuleParamDto getParam(List<RuleParamDto> params, String key) {
  1042. for (RuleParamDto param : params) {
  1043. if (param.getName().equals(key)) {
  1044. return param;
  1045. }
  1046. }
  1047. return null;
  1048. }
  1049. static class FakeRepositoryV1 implements RulesDefinition {
  1050. @Override
  1051. public void define(Context context) {
  1052. NewRepository repo = context.createRepository("fake", "java");
  1053. NewRule rule1 = repo.createRule(RULE_KEY1.rule())
  1054. .setName("One")
  1055. .setHtmlDescription("Description of One")
  1056. .setSeverity(BLOCKER)
  1057. .setInternalKey("config1")
  1058. .setTags("tag1", "tag2", "tag3")
  1059. .setScope(RuleScope.ALL)
  1060. .setType(RuleType.CODE_SMELL)
  1061. .setStatus(RuleStatus.BETA)
  1062. .setGapDescription("java.S115.effortToFix")
  1063. .addEducationPrincipleKeys("concept1", "concept2", "concept3")
  1064. .addDefaultImpact(SoftwareQuality.RELIABILITY, Severity.HIGH);
  1065. rule1.setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("5d", "10h"));
  1066. rule1.createParam("param1").setDescription("parameter one").setDefaultValue("default1");
  1067. rule1.createParam("param2").setDescription("parameter two").setDefaultValue("default2");
  1068. repo.createRule(HOTSPOT_RULE_KEY.rule())
  1069. .setName("Hotspot")
  1070. .setHtmlDescription("Minimal hotspot")
  1071. .setType(RuleType.SECURITY_HOTSPOT)
  1072. .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
  1073. .addCwe(1, 123, 863);
  1074. repo.createRule(RULE_KEY2.rule())
  1075. .setName("Two")
  1076. .setHtmlDescription("Minimal rule")
  1077. .setCleanCodeAttribute(CleanCodeAttribute.EFFICIENT);
  1078. repo.done();
  1079. }
  1080. }
  1081. /**
  1082. * FakeRepositoryV1 with some changes
  1083. */
  1084. static class FakeRepositoryV2 implements RulesDefinition {
  1085. @Override
  1086. public void define(Context context) {
  1087. NewRepository repo = context.createRepository("fake", "java");
  1088. // almost all the attributes of rule1 are changed
  1089. NewRule rule1 = repo.createRule(RULE_KEY1.rule())
  1090. .setName("One v2")
  1091. .setHtmlDescription("Description of One v2")
  1092. .setSeverity(INFO)
  1093. .setInternalKey("config1 v2")
  1094. // tag2 and tag3 removed, tag4 added
  1095. .setTags("tag1", "tag4")
  1096. .setType(RuleType.BUG)
  1097. .setStatus(READY)
  1098. .setGapDescription("java.S115.effortToFix.v2")
  1099. .addEducationPrincipleKeys("concept1", "concept4");
  1100. rule1.setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("6d", "2h"));
  1101. rule1.createParam("param1").setDescription("parameter one v2").setDefaultValue("default1 v2");
  1102. rule1.createParam("param2").setDescription("parameter two v2").setDefaultValue("default2 v2");
  1103. // rule2 is dropped, rule3 is new
  1104. repo.createRule(RULE_KEY3.rule())
  1105. .setName("Three")
  1106. .setHtmlDescription("Rule Three");
  1107. repo.done();
  1108. }
  1109. }
  1110. static class FakeRepositoryV3 implements RulesDefinition {
  1111. @Override
  1112. public void define(Context context) {
  1113. NewRepository repo = context.createRepository("fake", "java");
  1114. // rule 3 is dropped
  1115. repo.createRule(RULE_KEY3.rule())
  1116. .setName("Three")
  1117. .setMarkdownDescription("Rule Three V2");
  1118. repo.done();
  1119. }
  1120. }
  1121. static class ExternalRuleRepository implements RulesDefinition {
  1122. @Override
  1123. public void define(Context context) {
  1124. NewRepository repo = context.createExternalRepository("eslint", "js");
  1125. repo.createRule(RULE_KEY1.rule())
  1126. .setName("One")
  1127. .setHtmlDescription("Description of One")
  1128. .setSeverity(BLOCKER)
  1129. .setInternalKey("config1")
  1130. .setTags("tag1", "tag2", "tag3")
  1131. .setScope(RuleScope.ALL)
  1132. .setType(RuleType.CODE_SMELL)
  1133. .setStatus(RuleStatus.BETA)
  1134. .addEducationPrincipleKeys("concept1", "concept2", "concept3");
  1135. repo.createRule(EXTERNAL_HOTSPOT_RULE_KEY.rule())
  1136. .setName("Hotspot")
  1137. .setHtmlDescription("Minimal hotspot")
  1138. .setType(RuleType.SECURITY_HOTSPOT)
  1139. .addOwaspTop10(Y2021, OwaspTop10.A1, OwaspTop10.A3)
  1140. .addCwe(1, 123, 863);
  1141. repo.done();
  1142. }
  1143. }
  1144. static class BigRepository implements RulesDefinition {
  1145. static final int SIZE = 500;
  1146. @Override
  1147. public void define(Context context) {
  1148. NewRepository repo = context.createRepository("big", "java");
  1149. for (int i = 0; i < SIZE; i++) {
  1150. NewRule rule = repo.createRule("rule" + i)
  1151. .setName("name of " + i)
  1152. .setHtmlDescription("description of " + i);
  1153. for (int j = 0; j < 20; j++) {
  1154. rule.createParam("param" + j);
  1155. }
  1156. }
  1157. repo.done();
  1158. }
  1159. }
  1160. static class FindbugsRepository implements RulesDefinition {
  1161. @Override
  1162. public void define(Context context) {
  1163. NewRepository repo = context.createRepository("findbugs", "java");
  1164. repo.createRule("rule1")
  1165. .setName("Rule One")
  1166. .setHtmlDescription("Description of Rule One");
  1167. repo.done();
  1168. }
  1169. }
  1170. static class FbContribRepository implements RulesDefinition {
  1171. @Override
  1172. public void define(Context context) {
  1173. NewExtendedRepository repo = context.createRepository("findbugs", "java");
  1174. repo.createRule("rule2")
  1175. .setName("Rule Two")
  1176. .setHtmlDescription("Description of Rule Two");
  1177. repo.done();
  1178. }
  1179. }
  1180. }