]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16303 QP import/export should use a flat description, and not sections, as...
authorPierre <pierre.guillot@sonarsource.com>
Tue, 10 May 2022 09:05:35 +0000 (11:05 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 16 May 2022 20:03:56 +0000 (20:03 +0000)
13 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ExportRuleDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileExportMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileExportDaoTest.java
server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexerTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ImportedRule.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileParser.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/NewCustomRule.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/NewRuleDescriptionSection.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleCreator.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileParserTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/RuleCreatorTest.java

index 3795c115eb70278c48a2144ceb3c6bf4d5c190cc..a823e1889612ec8372be868597779dde268aff97 100644 (file)
@@ -21,18 +21,14 @@ package org.sonar.db.qualityprofile;
 
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Optional;
-import java.util.Set;
+import java.util.Objects;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.RuleType;
-import org.sonar.db.rule.RuleDescriptionSectionDto;
 import org.sonar.db.rule.SeverityUtil;
 
-import static java.util.Optional.ofNullable;
-import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY;
-
 public class ExportRuleDto {
   private String activeRuleUuid = null;
+  private String description = null;
   private String repository = null;
   private String rule = null;
   private String name = null;
@@ -44,8 +40,6 @@ public class ExportRuleDto {
 
   private List<ExportRuleParamDto> params = null;
 
-  private Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = null;
-
   public boolean isCustomRule() {
     return template != null;
   }
@@ -82,6 +76,10 @@ public class ExportRuleDto {
     return name;
   }
 
+  public String getDescriptionOrThrow() {
+    return Objects.requireNonNull(description, "description is expected to be set but it is null");
+  }
+
   public List<ExportRuleParamDto> getParams() {
     if (params == null) {
       params = new LinkedList<>();
@@ -93,16 +91,4 @@ public class ExportRuleDto {
     this.params = params;
   }
 
-  public Set<RuleDescriptionSectionDto> getRuleDescriptionSections() {
-    return ruleDescriptionSectionDtos;
-  }
-
-  public Optional<RuleDescriptionSectionDto> getDefaultRuleDescriptionSectionDto() {
-    return findExistingSectionWithSameKey(DEFAULT_KEY);
-  }
-
-  private Optional<RuleDescriptionSectionDto> findExistingSectionWithSameKey(String ruleDescriptionSectionKey) {
-    return ofNullable(ruleDescriptionSectionDtos).flatMap(sections ->
-      sections.stream().filter(section -> section.getKey().equals(ruleDescriptionSectionKey)).findAny());
-  }
 }
index d323a166cf00c025486e9bb709f1772c23e91c6a..57d80943f239fa06a20a29d042335678893ad0ff 100644 (file)
@@ -9,9 +9,10 @@
     rds.content as "rds_content",
   </sql>
 
-  <sql id="leftOuterJoinRulesDescriptionSections">
+  <sql id="leftOuterJoinRulesDefaultDescriptionSection">
     left outer join rule_desc_sections rds on
     rds.rule_uuid = r.uuid
+    and rds.kee = 'default'
   </sql>
 
   <sql id="exportRuleColumns">
@@ -26,7 +27,8 @@
     r.rule_type as "type",
     rt.plugin_rule_key as "template",
     rm.note_data as "extendedDescription",
-    rm.tags
+    rm.tags,
+    rds.content as "description"
   </sql>
 
   <sql id="exportRuleParamColumns">
     <result property="template" column="template"/>
     <result property="extendedDescription" column="extendedDescription"/>
     <result property="tags" column="tags"/>
-
-    <collection property="ruleDescriptionSectionDtos" ofType="org.sonar.db.rule.RuleDescriptionSectionDto">
-      <id property="uuid" column="rds_uuid"/>
-      <result property="key" column="rds_kee"/>
-      <result property="content" column="rds_content" typeHandler="org.apache.ibatis.type.StringTypeHandler"/>
-    </collection>
+    <result property="description" column="description"/>
 
   </resultMap>
 
@@ -64,7 +61,7 @@
     inner join rules r on r.uuid = a.rule_uuid and r.status != 'REMOVED'
     left join rules rt on rt.uuid = r.template_uuid
     left join rules_metadata rm on rm.rule_uuid = r.uuid
-    <include refid="leftOuterJoinRulesDescriptionSections"/>
+    <include refid="leftOuterJoinRulesDefaultDescriptionSection"/>
     where oqp.uuid = #{id, jdbcType=VARCHAR}
   </select>
 
index 9c1f9c09261e1df3561a52e79e324b51033afa28..0c446f4f76db947d57cd51494b6b8ef338884703 100644 (file)
@@ -46,8 +46,8 @@ public class QualityProfileExportDaoTest {
   @Rule
   public DbTester db = DbTester.create(new AlwaysIncreasingSystem2());
 
-  private DbSession dbSession = db.getSession();
-  private QualityProfileExportDao underTest = db.getDbClient().qualityProfileExportDao();
+  private final DbSession dbSession = db.getSession();
+  private final QualityProfileExportDao underTest = db.getDbClient().qualityProfileExportDao();
 
   @Test
   public void selectRulesByProfile_ready_rules_only() {
@@ -96,7 +96,7 @@ public class QualityProfileExportDaoTest {
     assertThat(exportCustomRuleDto).isNotNull();
     assertThat(exportCustomRuleDto.isCustomRule()).isTrue();
     assertThat(exportCustomRuleDto.getParams()).isEmpty();
-    assertThat(exportCustomRuleDto.getRuleDescriptionSections().iterator().next().getContent()).isEqualTo(customRuleContent);
+    assertThat(exportCustomRuleDto.getDescriptionOrThrow()).isEqualTo(customRuleContent);
     assertThat(exportCustomRuleDto.getExtendedDescription()).isEqualTo(customRuleMetadata.getNoteData());
     assertThat(exportCustomRuleDto.getName()).isEqualTo(customRule.getName());
     assertThat(exportCustomRuleDto.getRuleKey()).isEqualTo(customRule.getKey());
@@ -112,7 +112,7 @@ public class QualityProfileExportDaoTest {
     assertThat(exportRuleDto).isNotNull();
     assertThat(exportRuleDto.isCustomRule()).isFalse();
     assertThat(exportRuleDto.getParams()).isEmpty();
-    assertThat(exportRuleDto.getRuleDescriptionSections().iterator().next().getContent()).isEqualTo(ruleContent);
+    assertThat(exportRuleDto.getDescriptionOrThrow()).isEqualTo(ruleContent);
     assertThat(exportRuleDto.getExtendedDescription()).isEqualTo(ruleMetadata.getNoteData());
     assertThat(exportRuleDto.getName()).isEqualTo(rule.getName());
     assertThat(exportRuleDto.getRuleKey()).isEqualTo(rule.getKey());
index fbe76eebf3166aaf4eed4b278354d17c3b5c1ea2..c47c8ab560cfeec1118545b4c83694af59e0642b 100644 (file)
@@ -23,12 +23,6 @@ import com.google.common.collect.ImmutableSet;
 import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,6 +43,13 @@ import org.sonar.server.es.EsTester;
 import org.sonar.server.security.SecurityStandards;
 import org.sonar.server.security.SecurityStandards.SQCategory;
 
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
 import static com.google.common.collect.Sets.newHashSet;
 import static java.lang.String.format;
 import static java.util.Collections.emptyList;
index a9d2e60c01e3ff933731618f4134ab667f9244d9..ee5d2d0b0818820b08bcabe6616c4220af0e79e4 100644 (file)
  */
 package org.sonar.server.qualityprofile;
 
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
+
 import org.sonar.api.rule.RuleKey;
-import org.sonar.server.rule.NewRuleDescriptionSection;
 
 class ImportedRule {
   private String key = null;
@@ -36,7 +34,6 @@ class ImportedRule {
   private String severity = null;
   private String description = null;
   private Map<String, String> parameters = null;
-  private Set<NewRuleDescriptionSection> ruleDescriptionSections = new HashSet<>();
   public Map<String, String> getParameters() {
     return parameters;
   }
@@ -94,14 +91,6 @@ class ImportedRule {
     return template != null;
   }
 
-  public Set<NewRuleDescriptionSection> getRuleDescriptionSections() {
-    return ruleDescriptionSections;
-  }
-
-  public void addRuleDescriptionSection(NewRuleDescriptionSection ruleDescriptionSection) {
-    this.ruleDescriptionSections.add(ruleDescriptionSection);
-  }
-
   public void setRepository(String repository) {
     this.repository = repository;
   }
index 742e09c4d226935a83405716adec443e87876d89..99f173b86258d2fb3277e835e0fd1cc1acd484c9 100644 (file)
@@ -45,7 +45,6 @@ import org.sonar.db.rule.DeprecatedRuleKeyDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.server.qualityprofile.builtin.QProfileName;
 import org.sonar.server.rule.NewCustomRule;
-import org.sonar.server.rule.NewRuleDescriptionSection;
 import org.sonar.server.rule.RuleCreator;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -98,12 +97,9 @@ public class QProfileBackuperImpl implements QProfileBackuper {
       importedRule.setSeverity(exportRuleDto.getSeverityString());
       if (importedRule.isCustomRule()) {
         importedRule.setTemplate(exportRuleDto.getTemplateRuleKey().rule());
+        importedRule.setDescription(exportRuleDto.getDescriptionOrThrow());
       }
       importedRule.setType(exportRuleDto.getRuleType().name());
-      exportRuleDto.getRuleDescriptionSections()
-        .stream()
-        .map(r -> new NewRuleDescriptionSection(r.getKey(), r.getContent()))
-        .forEach(importedRule::addRuleDescriptionSection);
       importedRule.setParameters(exportRuleDto.getParams().stream().collect(Collectors.toMap(ExportRuleParamDto::getKey, ExportRuleParamDto::getValue)));
       importedRules.add(importedRule);
     }
@@ -221,7 +217,6 @@ public class QProfileBackuperImpl implements QProfileBackuper {
       .setPreventReactivation(true)
       .setType(RuleType.valueOf(r.getType()))
       .setMarkdownDescription(r.getDescription())
-      .setRuleDescriptionSections(r.getRuleDescriptionSections())
       .setParameters(r.getParameters());
   }
 
index ec4db716e3caae6b137cacdf4ba0d6138804dd43..171368bfc82bbcb275a21f9e4be2e1b1cc575558 100644 (file)
@@ -42,8 +42,6 @@ import org.sonar.api.utils.text.XmlWriter;
 import org.sonar.db.qualityprofile.ExportRuleDto;
 import org.sonar.db.qualityprofile.ExportRuleParamDto;
 import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.rule.RuleDescriptionSectionDto;
-import org.sonar.server.rule.NewRuleDescriptionSection;
 
 @ServerSide
 public class QProfileParser {
@@ -52,10 +50,6 @@ public class QProfileParser {
   private static final String ATTRIBUTE_LANGUAGE = "language";
   private static final String ATTRIBUTE_RULES = "rules";
   private static final String ATTRIBUTE_RULE = "rule";
-  private static final String ATTRIBUTE_DESCRIPTION_SECTIONS = "descriptionSections";
-  private static final String ATTRIBUTE_DESCRIPTION_SECTION = "descriptionSection";
-  private static final String ATTRIBUTE_DESCRIPTION_SECTION_KEY = "key";
-  private static final String ATTRIBUTE_DESCRIPTION_SECTION_CONTENT = "content";
   private static final String ATTRIBUTE_REPOSITORY_KEY = "repositoryKey";
   private static final String ATTRIBUTE_KEY = "key";
   private static final String ATTRIBUTE_PRIORITY = "priority";
@@ -85,19 +79,7 @@ public class QProfileParser {
       if (ruleToExport.isCustomRule()) {
         xml.prop(ATTRIBUTE_NAME, ruleToExport.getName());
         xml.prop(ATTRIBUTE_TEMPLATE_KEY, ruleToExport.getTemplateRuleKey().rule());
-        if (!ruleToExport.getRuleDescriptionSections().isEmpty()) {
-          ruleToExport.getDefaultRuleDescriptionSectionDto()
-            .map(RuleDescriptionSectionDto::getContent)
-            .ifPresent(desc -> xml.prop(ATTRIBUTE_DESCRIPTION, desc));
-        }
-        xml.begin(ATTRIBUTE_DESCRIPTION_SECTIONS);
-        for (RuleDescriptionSectionDto ruleDescriptionSection : ruleToExport.getRuleDescriptionSections()) {
-          xml.begin(ATTRIBUTE_DESCRIPTION_SECTION)
-            .prop(ATTRIBUTE_DESCRIPTION_SECTION_KEY, ruleDescriptionSection.getKey())
-            .prop(ATTRIBUTE_DESCRIPTION_SECTION_CONTENT, ruleDescriptionSection.getContent())
-            .end();
-        }
-        xml.end(ATTRIBUTE_DESCRIPTION_SECTIONS);
+        xml.prop(ATTRIBUTE_DESCRIPTION, ruleToExport.getDescriptionOrThrow());
       }
 
       xml.begin(ATTRIBUTE_PARAMETERS);
@@ -199,9 +181,6 @@ public class QProfileParser {
         SMInputCursor propsCursor = ruleCursor.childElementCursor(ATTRIBUTE_PARAMETER);
         readParameters(propsCursor, parameters);
         rule.setParameters(parameters);
-      } else if (StringUtils.equals(ATTRIBUTE_DESCRIPTION_SECTIONS, nodeName)) {
-        SMInputCursor propsCursor = ruleCursor.childElementCursor(ATTRIBUTE_DESCRIPTION_SECTION);
-        readDescriptionSections(propsCursor, rule);
       }
     }
   }
@@ -224,23 +203,4 @@ public class QProfileParser {
       }
     }
   }
-
-  private static void readDescriptionSections(SMInputCursor propsCursor, ImportedRule importedRule) throws XMLStreamException {
-    while (propsCursor.getNext() != null) {
-      SMInputCursor propCursor = propsCursor.childElementCursor();
-      String key = null;
-      String description = null;
-      while (propCursor.getNext() != null) {
-        String nodeName = propCursor.getLocalName();
-        if (StringUtils.equals(ATTRIBUTE_DESCRIPTION_SECTION_KEY, nodeName)) {
-          key = StringUtils.trim(propCursor.collectDescendantText(false));
-        } else if (StringUtils.equals(ATTRIBUTE_DESCRIPTION_SECTION_CONTENT, nodeName)) {
-          description = StringUtils.trim(propCursor.collectDescendantText(false));
-        }
-      }
-      if (key != null && description != null) {
-        importedRule.addRuleDescriptionSection(new NewRuleDescriptionSection(key, description));
-      }
-    }
-  }
 }
index d824eb927690570eb8286a63fcdc5e5c941f5281..baa19e4f31de9f056453fcd045a9e9115cc9d799 100644 (file)
@@ -21,11 +21,8 @@ package org.sonar.server.rule;
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.api.rule.RuleKey;
@@ -43,8 +40,6 @@ public class NewCustomRule {
   private RuleType type;
   private Map<String, String> parameters = new HashMap<>();
 
-  private Set<NewRuleDescriptionSection> ruleDescriptionSections = new HashSet<>();
-
   private boolean preventReactivation = false;
 
   private NewCustomRule() {
@@ -55,7 +50,6 @@ public class NewCustomRule {
     return ruleKey;
   }
 
-  @CheckForNull
   public RuleKey templateKey() {
     return templateKey;
   }
@@ -120,15 +114,6 @@ public class NewCustomRule {
     return this;
   }
 
-  public Set<NewRuleDescriptionSection> getRuleDescriptionSections() {
-    return Collections.unmodifiableSet(ruleDescriptionSections);
-  }
-
-  public NewCustomRule setRuleDescriptionSections(Set<NewRuleDescriptionSection> sections) {
-    this.ruleDescriptionSections = sections;
-    return this;
-  }
-
   public boolean isPreventReactivation() {
     return preventReactivation;
   }
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/NewRuleDescriptionSection.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/NewRuleDescriptionSection.java
deleted file mode 100644 (file)
index 7ab1ed7..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.rule;
-
-public class NewRuleDescriptionSection {
-  private final String key;
-
-  private final String content;
-
-  public NewRuleDescriptionSection(String key, String content) {
-    this.key = key;
-    this.content = content;
-  }
-
-  public String getKey() {
-    return key;
-  }
-
-  public String getContent() {
-    return content;
-  }
-}
index c3d82ccdd91edba92e59bd6dcd4c5174a54a8550..0a90a400c0301566fc2056ed87875eb25e11bec1 100644 (file)
@@ -49,6 +49,7 @@ import org.sonar.server.util.TypeValidations;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.collect.Lists.newArrayList;
 import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
 import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.server.exceptions.BadRequestException.checkRequest;
 
@@ -72,7 +73,6 @@ public class RuleCreator {
 
   public RuleKey create(DbSession dbSession, NewCustomRule newRule) {
     RuleKey templateKey = newRule.templateKey();
-    checkArgument(templateKey != null, "Rule template key should not be null");
     RuleDto templateRule = dbClient.ruleDao().selectByKey(dbSession, templateKey)
       .orElseThrow(() -> new IllegalArgumentException(format(TEMPLATE_KEY_NOT_EXIST_FORMAT, templateKey)));
     checkArgument(templateRule.isTemplate(), "This rule is not a template rule: %s", templateKey.toString());
@@ -166,18 +166,11 @@ public class RuleCreator {
   }
 
   private static void validateDescription(List<String> errors, NewCustomRule newRule) {
-    boolean missingDescription = newRule.getRuleDescriptionSections().isEmpty() ?
-      Strings.isNullOrEmpty(newRule.markdownDescription()) :
-      noDescriptionSectionHasContent(newRule);
-    if (missingDescription) {
+    if (Strings.isNullOrEmpty(newRule.markdownDescription())) {
       errors.add("The description is missing");
     }
   }
 
-  private static boolean noDescriptionSectionHasContent(NewCustomRule newRule) {
-    return newRule.getRuleDescriptionSections().stream().map(NewRuleDescriptionSection::getContent).allMatch(Strings::isNullOrEmpty);
-  }
-
   private static void validateRuleKey(List<String> errors, String ruleKey) {
     if (!ruleKey.matches("^[\\w]+$")) {
       errors.add(format("The rule key \"%s\" is invalid, it should only contain: a-z, 0-9, \"_\"", ruleKey));
@@ -189,6 +182,7 @@ public class RuleCreator {
   }
 
   private String createCustomRule(RuleKey ruleKey, NewCustomRule newRule, RuleDto templateRuleDto, DbSession dbSession) {
+    RuleDescriptionSectionDto ruleDescriptionSectionDto = createDefaultRuleDescriptionSection(uuidFactory.create(), requireNonNull(newRule.markdownDescription()));
     RuleDto ruleDto = new RuleDto()
       .setUuid(uuidFactory.create())
       .setRuleKey(ruleKey)
@@ -210,23 +204,9 @@ public class RuleCreator {
       .setIsExternal(false)
       .setIsAdHoc(false)
       .setCreatedAt(system2.now())
-      .setUpdatedAt(system2.now());
-
-    ruleDto.setDescriptionFormat(Format.MARKDOWN);
-
-    if (newRule.getRuleDescriptionSections().isEmpty() && newRule.markdownDescription() != null) {
-      RuleDescriptionSectionDto ruleDescriptionSectionDto = createDefaultRuleDescriptionSection(uuidFactory.create(), newRule.markdownDescription());
-      ruleDto.addRuleDescriptionSectionDto(ruleDescriptionSectionDto);
-    } else {
-      for (NewRuleDescriptionSection ruleDescriptionSection : newRule.getRuleDescriptionSections()) {
-        RuleDescriptionSectionDto ruleDescriptionSectionDto = RuleDescriptionSectionDto.builder()
-          .uuid(uuidFactory.create())
-          .key(ruleDescriptionSection.getKey())
-          .content(ruleDescriptionSection.getContent())
-          .build();
-        ruleDto.addRuleDescriptionSectionDto(ruleDescriptionSectionDto);
-      }
-    }
+      .setUpdatedAt(system2.now())
+      .setDescriptionFormat(Format.MARKDOWN)
+      .addRuleDescriptionSectionDto(ruleDescriptionSectionDto);
 
     Set<String> tags = templateRuleDto.getTags();
     if (!tags.isEmpty()) {
index 522938b226110dd3ee53da0de19dfa26739a9ed3..52f573821d48c5cbb57cd045879fcc4bb6effbcd 100644 (file)
 package org.sonar.server.qualityprofile;
 
 import com.google.common.io.Resources;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import javax.annotation.Nullable;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
@@ -47,6 +39,15 @@ import org.sonar.db.rule.RuleParamDto;
 import org.sonar.server.qualityprofile.builtin.QProfileName;
 import org.sonar.server.rule.RuleCreator;
 
+import javax.annotation.Nullable;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -167,7 +168,6 @@ public class QProfileBackuperImplTest {
       "<name>" + rule.getName() + "</name>" +
       "<templateKey>" + templateRule.getKey().rule() + "</templateKey>" +
       "<description>" + rule.getDefaultRuleDescriptionSection().getContent() + "</description>" +
-      "<descriptionSections><descriptionSection><key>default</key><content>" + rule.getDefaultRuleDescriptionSection().getContent() + "</content></descriptionSection></descriptionSections>" +
       "<parameters><parameter>" +
       "<key>" + param.getName() + "</key>" +
       "<value>20</value>" +
@@ -399,6 +399,28 @@ public class QProfileBackuperImplTest {
     assertThat(reset.calledProfile).isEqualTo(to);
   }
 
+  @Test
+  public void copy_profile_with_custom_rule() {
+    RuleDto templateRule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
+      .setIsTemplate(true));
+    RuleDto rule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
+      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(UuidFactoryFast.getInstance().create(), "custom rule description"))
+      .setName("custom rule name")
+      .setStatus(RuleStatus.READY)
+      .setTemplateUuid(templateRule.getUuid()));
+
+    RuleParamDto param = db.rules().insertRuleParam(rule);
+    QProfileDto from = createProfile(rule.getLanguage());
+    ActiveRuleDto activeRule = activate(from, rule, param);
+
+    QProfileDto to = createProfile(rule.getLanguage());
+    underTest.copy(db.getSession(), from, to);
+
+    assertThat(reset.calledActivations).extracting(RuleActivation::getRuleUuid).containsOnly(activeRule.getRuleUuid());
+    assertThat(reset.calledActivations.get(0).getParameter(param.getName())).isEqualTo("20");
+    assertThat(reset.calledProfile).isEqualTo(to);
+  }
+
   @Test
   public void fail_to_restore_if_bad_xml_format() {
     DbSession session = db.getSession();
index f832b5fdce4b414e36a5982ed6f2297105e88bbb..263ae3d23947c8fdec075a4c682c60a55210b9f5 100644 (file)
@@ -28,7 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 public class QProfileParserTest {
 
   @Test
-  public void readOlderVersionXml() {
+  public void readXml() {
     Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
       "<profile>" +
       "<name>custom rule</name>" +
@@ -53,34 +53,4 @@ public class QProfileParserTest {
     var importedRule = importedQProfile.getRules().get(0);
     assertThat(importedRule.getDescription()).isEqualTo("custom rule description");
   }
-
-  @Test
-  public void readNewVersionXml() {
-    Reader backup = new StringReader("<?xml version='1.0' encoding='UTF-8'?>" +
-      "<profile>" +
-      "<name>custom rule</name>" +
-      "<language>js</language>" +
-      "<rules><rule>" +
-      "<repositoryKey>sonarjs</repositoryKey>" +
-      "<key>s001</key>" +
-      "<type>CODE_SMELL</type>" +
-      "<priority>CRITICAL</priority>" +
-      "<name>custom rule name</name>" +
-      "<templateKey>rule_mc8</templateKey>" +
-      "<descriptionSections><descriptionSection><key>default</key><content>custom rule section content</content></descriptionSection></descriptionSections>" +
-      "<parameters><parameter>" +
-      "<key>bar</key>" +
-      "<value>baz</value>" +
-      "</parameter>" +
-      "</parameters>" +
-      "</rule></rules></profile>");
-    var parser = new QProfileParser();
-    var importedQProfile = parser.readXml(backup);
-    assertThat(importedQProfile.getRules()).hasSize(1);
-    var importedRule = importedQProfile.getRules().get(0);
-    assertThat(importedRule.getRuleDescriptionSections()).hasSize(1);
-    var section = importedRule.getRuleDescriptionSections().iterator().next();
-    assertThat(section.getKey()).isEqualTo("default");
-    assertThat(section.getContent()).isEqualTo("custom rule section content");
-  }
 }
index 7516b80edd5d9366fbd61e11913676e13ce7e377..46d27094d6d580873a7fdf1f0953be5c78743e6a 100644 (file)
@@ -23,10 +23,8 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import java.time.Instant;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Date;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 import org.assertj.core.api.Fail;
 import org.junit.Rule;
@@ -52,7 +50,9 @@ import org.sonar.server.rule.index.RuleIndex;
 import org.sonar.server.rule.index.RuleIndexer;
 import org.sonar.server.rule.index.RuleQuery;
 
+import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.fail;
 import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
@@ -61,547 +61,563 @@ import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidation
 
 public class RuleCreatorTest {
 
-  private System2 system2 = new TestSystem2().setNow(Instant.now().toEpochMilli());
-
-  @Rule
-  public DbTester dbTester = DbTester.create(system2);
-
-  @Rule
-  public EsTester es = EsTester.create();
-
-  private RuleIndex ruleIndex = new RuleIndex(es.client(), system2);
-  private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), dbTester.getDbClient());
-  private DbSession dbSession = dbTester.getSession();
-  private UuidFactory uuidFactory = new SequenceUuidFactory();
-
-  private RuleCreator underTest = new RuleCreator(system2, new RuleIndexer(es.client(), dbTester.getDbClient()), dbTester.getDbClient(), newFullTypeValidations(), uuidFactory);
-
-  @Test
-  public void create_custom_rule() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRule();
-    // Create custom rule
-    NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-      .setName("My custom")
-      .setMarkdownDescription("Some description")
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("regex", "a.*"));
-    RuleKey customRuleKey = underTest.create(dbSession, newRule);
-
-    RuleDto rule = dbTester.getDbClient().ruleDao().selectOrFailByKey(dbSession, customRuleKey);
-    assertThat(rule).isNotNull();
-    assertThat(rule.getKey()).isEqualTo(RuleKey.of("java", "CUSTOM_RULE"));
-    assertThat(rule.getPluginKey()).isEqualTo("sonarjava");
-    assertThat(rule.getTemplateUuid()).isEqualTo(templateRule.getUuid());
-    assertThat(rule.getName()).isEqualTo("My custom");
-    assertThat(rule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Some description");
-    assertThat(rule.getSeverityString()).isEqualTo("MAJOR");
-    assertThat(rule.getStatus()).isEqualTo(RuleStatus.READY);
-    assertThat(rule.getLanguage()).isEqualTo("java");
-    assertThat(rule.getConfigKey()).isEqualTo("S001");
-    assertThat(rule.getDefRemediationFunction()).isEqualTo("LINEAR_OFFSET");
-    assertThat(rule.getDefRemediationGapMultiplier()).isEqualTo("1h");
-    assertThat(rule.getDefRemediationBaseEffort()).isEqualTo("5min");
-    assertThat(rule.getGapDescription()).isEqualTo("desc");
-    assertThat(rule.getTags()).containsOnly("usertag1", "usertag2");
-    assertThat(rule.getSystemTags()).containsOnly("tag1", "tag4");
-    assertThat(rule.getSecurityStandards()).containsOnly("owaspTop10:a1", "cwe:123");
-    assertThat(rule.isExternal()).isFalse();
-    assertThat(rule.isAdHoc()).isFalse();
-
-    List<RuleParamDto> params = dbTester.getDbClient().ruleDao().selectRuleParamsByRuleKey(dbSession, customRuleKey);
-    assertThat(params).hasSize(1);
-
-    RuleParamDto param = params.get(0);
-    // From template rule
-    assertThat(param.getName()).isEqualTo("regex");
-    assertThat(param.getDescription()).isEqualTo("Reg ex");
-    assertThat(param.getType()).isEqualTo("STRING");
-    // From user
-    assertThat(param.getDefaultValue()).isEqualTo("a.*");
-
-    assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule.getUuid(), templateRule.getUuid());
-  }
-
-  @Test
-  public void create_custom_rule_with_both_markdown_description_and_description_sections() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRule();
-    // Create custom rule
-    NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-      .setName("My custom")
-      .setMarkdownDescription("Markdown description")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "new description section")))
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY);
-    RuleKey customRuleKey = underTest.create(dbSession, newRule);
-
-    RuleDto rule = dbTester.getDbClient().ruleDao().selectOrFailByKey(dbSession, customRuleKey);
-    assertThat(rule).isNotNull();
-    assertThat(rule.getKey()).isEqualTo(RuleKey.of("java", "CUSTOM_RULE"));
-    assertThat(rule.getPluginKey()).isEqualTo("sonarjava");
-    assertThat(rule.getTemplateUuid()).isEqualTo(templateRule.getUuid());
-    assertThat(rule.getName()).isEqualTo("My custom");
-    assertThat(rule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("new description section");
-    assertThat(rule.getSeverityString()).isEqualTo("MAJOR");
-    assertThat(rule.getStatus()).isEqualTo(RuleStatus.READY);
-    assertThat(rule.getLanguage()).isEqualTo("java");
-    assertThat(rule.getConfigKey()).isEqualTo("S001");
-    assertThat(rule.getDefRemediationFunction()).isEqualTo("LINEAR_OFFSET");
-    assertThat(rule.getDefRemediationGapMultiplier()).isEqualTo("1h");
-    assertThat(rule.getDefRemediationBaseEffort()).isEqualTo("5min");
-    assertThat(rule.getGapDescription()).isEqualTo("desc");
-    assertThat(rule.getTags()).containsOnly("usertag1", "usertag2");
-    assertThat(rule.getSystemTags()).containsOnly("tag1", "tag4");
-    assertThat(rule.getSecurityStandards()).containsOnly("owaspTop10:a1", "cwe:123");
-    assertThat(rule.isExternal()).isFalse();
-    assertThat(rule.isAdHoc()).isFalse();
-  }
-
-  @Test
-  public void create_custom_rule_with_empty_parameter_value() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRule();
-    NewRuleDescriptionSection defaultSection = new NewRuleDescriptionSection("default", "some description");
-    NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-      .setName("My custom")
-      .setRuleDescriptionSections(Set.of(defaultSection))
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("regex", ""));
-
-    RuleKey customRuleKey = underTest.create(dbSession, newRule);
-
-    List<RuleParamDto> params = dbTester.getDbClient().ruleDao().selectRuleParamsByRuleKey(dbSession, customRuleKey);
-    assertThat(params).hasSize(1);
-    RuleParamDto param = params.get(0);
-    assertThat(param.getName()).isEqualTo("regex");
-    assertThat(param.getDescription()).isEqualTo("Reg ex");
-    assertThat(param.getType()).isEqualTo("STRING");
-    assertThat(param.getDefaultValue()).isNull();
-  }
-
-  @Test
-  public void create_custom_rule_with_no_parameter_value() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRuleWithIntArrayParam();
-    NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-      .setName("My custom")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY);
-
-    RuleKey customRuleKey = underTest.create(dbSession, newRule);
-
-    List<RuleParamDto> params = dbTester.getDbClient().ruleDao().selectRuleParamsByRuleKey(dbSession, customRuleKey);
-    assertThat(params).hasSize(1);
-    RuleParamDto param = params.get(0);
-    assertThat(param.getName()).isEqualTo("myIntegers");
-    assertThat(param.getDescription()).isEqualTo("My Integers");
-    assertThat(param.getType()).isEqualTo("INTEGER,multiple=true,values=1;2;3");
-    assertThat(param.getDefaultValue()).isNull();
-  }
-
-  @Test
-  public void create_custom_rule_with_multiple_parameter_values() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRuleWithIntArrayParam();
-    NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-      .setName("My custom")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("myIntegers", "1,3"));
-
-    RuleKey customRuleKey = underTest.create(dbSession, newRule);
-
-    List<RuleParamDto> params = dbTester.getDbClient().ruleDao().selectRuleParamsByRuleKey(dbSession, customRuleKey);
-    assertThat(params).hasSize(1);
-    RuleParamDto param = params.get(0);
-    assertThat(param.getName()).isEqualTo("myIntegers");
-    assertThat(param.getDescription()).isEqualTo("My Integers");
-    assertThat(param.getType()).isEqualTo("INTEGER,multiple=true,values=1;2;3");
-    assertThat(param.getDefaultValue()).isEqualTo("1,3");
-  }
-
-  @Test
-  public void batch_create_custom_rules() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRuleWithIntArrayParam();
-
-    NewCustomRule firstRule = NewCustomRule.createForCustomRule("CUSTOM_RULE_1", templateRule.getKey())
-      .setName("My custom")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY);
-
-    NewCustomRule secondRule = NewCustomRule.createForCustomRule("CUSTOM_RULE_2", templateRule.getKey())
-      .setName("My custom")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY);
-
-    List<RuleKey> customRuleKeys = underTest.create(dbSession, Arrays.asList(firstRule, secondRule));
-
-    List<RuleDto> rules = dbTester.getDbClient().ruleDao().selectByKeys(dbSession, customRuleKeys);
-
-    assertThat(rules).hasSize(2);
-    assertThat(rules).asList()
-      .extracting("ruleKey")
-      .containsOnly("CUSTOM_RULE_1", "CUSTOM_RULE_2");
-  }
-
-  @Test
-  public void fail_to_create_custom_rules_when_wrong_rule_template() {
-    // insert rule
-    RuleDto rule = newRule(RuleKey.of("java", "S001")).setIsTemplate(false);
-    dbTester.rules().insert(rule);
-    dbSession.commit();
-
-    NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", rule.getKey())
-      .setName("My custom")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("regex", "a.*"));
-
-    assertThatThrownBy(() -> underTest.create(dbSession, Collections.singletonList(newRule)))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("This rule is not a template rule: java:S001");
-  }
-
-  @Test
-  public void fail_to_create_custom_rule_with_invalid_parameter() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRuleWithIntArrayParam();
-
-    assertThatThrownBy(() -> {
-      // Create custom rule
-      NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-        .setName("My custom")
-        .setMarkdownDescription("Some description")
-        .setSeverity(Severity.MAJOR)
-        .setStatus(RuleStatus.READY)
-        .setParameters(ImmutableMap.of("myIntegers", "1,polop,2"));
-      underTest.create(dbSession, newRule);
-    })
-      .isInstanceOf(BadRequestException.class)
-      .hasMessage("Value 'polop' must be an integer.");
-  }
-
-  @Test
-  public void fail_to_create_custom_rule_with_invalid_parameters() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRuleWithTwoIntParams();
-
-    // Create custom rule
-    NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-      .setName("My custom")
-      .setMarkdownDescription("Some description")
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("first", "polop", "second", "palap"));
-    try {
-      underTest.create(dbSession, newRule);
-      Fail.failBecauseExceptionWasNotThrown(BadRequestException.class);
-    } catch (BadRequestException badRequest) {
-      assertThat(badRequest.errors().toString()).contains("palap").contains("polop");
+    private final System2 system2 = new TestSystem2().setNow(Instant.now().toEpochMilli());
+
+    @Rule
+    public DbTester dbTester = DbTester.create(system2);
+
+    @Rule
+    public EsTester es = EsTester.create();
+
+    private final RuleIndex ruleIndex = new RuleIndex(es.client(), system2);
+    private final RuleIndexer ruleIndexer = new RuleIndexer(es.client(), dbTester.getDbClient());
+    private final DbSession dbSession = dbTester.getSession();
+    private final UuidFactory uuidFactory = new SequenceUuidFactory();
+
+    private final RuleCreator underTest = new RuleCreator(system2, new RuleIndexer(es.client(), dbTester.getDbClient()), dbTester.getDbClient(), newFullTypeValidations(), uuidFactory);
+
+    @Test
+    public void create_custom_rule() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRule();
+        // Create custom rule
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("Some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("regex", "a.*"));
+        RuleKey customRuleKey = underTest.create(dbSession, newRule);
+
+        RuleDto rule = dbTester.getDbClient().ruleDao().selectOrFailByKey(dbSession, customRuleKey);
+        assertThat(rule).isNotNull();
+        assertThat(rule.getKey()).isEqualTo(RuleKey.of("java", "CUSTOM_RULE"));
+        assertThat(rule.getPluginKey()).isEqualTo("sonarjava");
+        assertThat(rule.getTemplateUuid()).isEqualTo(templateRule.getUuid());
+        assertThat(rule.getName()).isEqualTo("My custom");
+        assertThat(rule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Some description");
+        assertThat(rule.getSeverityString()).isEqualTo("MAJOR");
+        assertThat(rule.getStatus()).isEqualTo(RuleStatus.READY);
+        assertThat(rule.getLanguage()).isEqualTo("java");
+        assertThat(rule.getConfigKey()).isEqualTo("S001");
+        assertThat(rule.getDefRemediationFunction()).isEqualTo("LINEAR_OFFSET");
+        assertThat(rule.getDefRemediationGapMultiplier()).isEqualTo("1h");
+        assertThat(rule.getDefRemediationBaseEffort()).isEqualTo("5min");
+        assertThat(rule.getGapDescription()).isEqualTo("desc");
+        assertThat(rule.getTags()).containsOnly("usertag1", "usertag2");
+        assertThat(rule.getSystemTags()).containsOnly("tag1", "tag4");
+        assertThat(rule.getSecurityStandards()).containsOnly("owaspTop10:a1", "cwe:123");
+        assertThat(rule.isExternal()).isFalse();
+        assertThat(rule.isAdHoc()).isFalse();
+
+        List<RuleParamDto> params = dbTester.getDbClient().ruleDao().selectRuleParamsByRuleKey(dbSession, customRuleKey);
+        assertThat(params).hasSize(1);
+
+        RuleParamDto param = params.get(0);
+        // From template rule
+        assertThat(param.getName()).isEqualTo("regex");
+        assertThat(param.getDescription()).isEqualTo("Reg ex");
+        assertThat(param.getType()).isEqualTo("STRING");
+        // From user
+        assertThat(param.getDefaultValue()).isEqualTo("a.*");
+
+        assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule.getUuid(), templateRule.getUuid());
     }
-  }
-
-  @Test
-  public void reactivate_custom_rule_if_already_exists_in_removed_status() {
-    String key = "CUSTOM_RULE";
-
-    // insert template rule
-    RuleDto templateRule = createTemplateRule();
-
-    // insert a removed rule
-    RuleDto rule = RuleTesting.newCustomRule(templateRule)
-      .setRuleKey(key)
-      .setStatus(RuleStatus.REMOVED)
-      .setName("Old name")
-      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "Old description"))
-      .setDescriptionFormat(Format.MARKDOWN)
-      .setSeverity(Severity.INFO);
-    dbTester.rules().insert(rule);
-    dbTester.rules().insertRuleParam(rule, param -> param.setDefaultValue("a.*"));
-    dbSession.commit();
-
-    // Create custom rule with same key, but with different values
-    NewCustomRule newRule = NewCustomRule.createForCustomRule(key, templateRule.getKey())
-      .setName("New name")
-      .setMarkdownDescription("New description")
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("regex", "c.*"));
-    RuleKey customRuleKey = underTest.create(dbSession, newRule);
-
-    RuleDto result = dbTester.getDbClient().ruleDao().selectOrFailByKey(dbSession, customRuleKey);
-    assertThat(result.getKey()).isEqualTo(RuleKey.of("java", key));
-    assertThat(result.getStatus()).isEqualTo(RuleStatus.READY);
-
-    // These values should be the same than before
-    assertThat(result.getName()).isEqualTo("Old name");
-    assertThat(result.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Old description");
-    assertThat(result.getSeverityString()).isEqualTo(Severity.INFO);
-
-    List<RuleParamDto> params = dbTester.getDbClient().ruleDao().selectRuleParamsByRuleKey(dbSession, customRuleKey);
-    assertThat(params).hasSize(1);
-    assertThat(params.get(0).getDefaultValue()).isEqualTo("a.*");
-  }
-
-  @Test
-  public void generate_reactivation_exception_when_rule_exists_in_removed_status_and_prevent_reactivation_parameter_is_true() {
-    String key = "CUSTOM_RULE";
-    // insert template rule
-    RuleDto templateRule = createTemplateRule();
-    // insert a removed rule
-    RuleDto rule = RuleTesting.newCustomRule(templateRule)
-      .setRuleKey(key)
-      .setStatus(RuleStatus.REMOVED)
-      .setName("Old name")
-      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "Old description"))
-      .setSeverity(Severity.INFO);
-    dbTester.rules().insert(rule);
-    dbTester.rules().insertRuleParam(rule, param -> param.setDefaultValue("a.*"));
-    dbSession.commit();
-
-    // Create custom rule with same key, but with different values
-    NewCustomRule newRule = NewCustomRule.createForCustomRule(key, templateRule.getKey())
-      .setName("New name")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("regex", "c.*"))
-      .setPreventReactivation(true);
-
-    try {
-      underTest.create(dbSession, newRule);
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(ReactivationException.class);
-      ReactivationException reactivationException = (ReactivationException) e;
-      assertThat(reactivationException.ruleKey()).isEqualTo(rule.getKey());
+
+    @Test
+    public void create_custom_rule_with_empty_parameter_value() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRule();
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("regex", ""));
+
+        RuleKey customRuleKey = underTest.create(dbSession, newRule);
+
+        List<RuleParamDto> params = dbTester.getDbClient().ruleDao().selectRuleParamsByRuleKey(dbSession, customRuleKey);
+        assertThat(params).hasSize(1);
+        RuleParamDto param = params.get(0);
+        assertThat(param.getName()).isEqualTo("regex");
+        assertThat(param.getDescription()).isEqualTo("Reg ex");
+        assertThat(param.getType()).isEqualTo("STRING");
+        assertThat(param.getDefaultValue()).isNull();
+    }
+
+    @Test
+    public void create_custom_rule_with_no_parameter_value() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRuleWithIntArrayParam();
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY);
+
+        RuleKey customRuleKey = underTest.create(dbSession, newRule);
+
+        List<RuleParamDto> params = dbTester.getDbClient().ruleDao().selectRuleParamsByRuleKey(dbSession, customRuleKey);
+        assertThat(params).hasSize(1);
+        RuleParamDto param = params.get(0);
+        assertThat(param.getName()).isEqualTo("myIntegers");
+        assertThat(param.getDescription()).isEqualTo("My Integers");
+        assertThat(param.getType()).isEqualTo("INTEGER,multiple=true,values=1;2;3");
+        assertThat(param.getDefaultValue()).isNull();
+    }
+
+    @Test
+    public void create_custom_rule_with_multiple_parameter_values() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRuleWithIntArrayParam();
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("myIntegers", "1,3"));
+
+        RuleKey customRuleKey = underTest.create(dbSession, newRule);
+
+        List<RuleParamDto> params = dbTester.getDbClient().ruleDao().selectRuleParamsByRuleKey(dbSession, customRuleKey);
+        assertThat(params).hasSize(1);
+        RuleParamDto param = params.get(0);
+        assertThat(param.getName()).isEqualTo("myIntegers");
+        assertThat(param.getDescription()).isEqualTo("My Integers");
+        assertThat(param.getType()).isEqualTo("INTEGER,multiple=true,values=1;2;3");
+        assertThat(param.getDefaultValue()).isEqualTo("1,3");
+    }
+
+    @Test
+    public void batch_create_custom_rules() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRuleWithIntArrayParam();
+
+        NewCustomRule firstRule = NewCustomRule.createForCustomRule("CUSTOM_RULE_1", templateRule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY);
+
+        NewCustomRule secondRule = NewCustomRule.createForCustomRule("CUSTOM_RULE_2", templateRule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY);
+
+        List<RuleKey> customRuleKeys = underTest.create(dbSession, Arrays.asList(firstRule, secondRule));
+
+        List<RuleDto> rules = dbTester.getDbClient().ruleDao().selectByKeys(dbSession, customRuleKeys);
+
+        assertThat(rules).hasSize(2);
+        assertThat(rules).asList()
+                .extracting("ruleKey")
+                .containsOnly("CUSTOM_RULE_1", "CUSTOM_RULE_2");
+    }
+
+    @Test
+    public void fail_to_create_custom_rules_when_wrong_rule_template() {
+        // insert rule
+        RuleDto rule = newRule(RuleKey.of("java", "S001")).setIsTemplate(false);
+        dbTester.rules().insert(rule);
+        dbSession.commit();
+
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", rule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("regex", "a.*"));
+
+        assertThatThrownBy(() -> underTest.create(dbSession, singletonList(newRule)))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessage("This rule is not a template rule: java:S001");
+    }
+
+    @Test
+    public void fail_to_create_custom_rules_when_removed_rule_template() {
+        // insert rule
+        RuleDto rule = createTemplateRule(); newRule(RuleKey.of("java", "S001")).setIsTemplate(false);
+        rule.setStatus(RuleStatus.REMOVED);
+        dbTester.rules().update(rule);
+        dbSession.commit();
+
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", rule.getKey())
+          .setName("My custom")
+          .setMarkdownDescription("Some description")
+          .setSeverity(Severity.MAJOR)
+          .setStatus(RuleStatus.READY);
+
+        List<NewCustomRule> newRules = singletonList(newRule);
+        assertThatThrownBy(() -> underTest.create(dbSession, newRules))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessage("The template key doesn't exist: java:S001");
+    }
+
+    @Test
+    public void fail_to_create_custom_rule_with_invalid_parameter() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRuleWithIntArrayParam();
+
+        assertThatThrownBy(() -> {
+            // Create custom rule
+            NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                    .setName("My custom")
+                    .setMarkdownDescription("Some description")
+                    .setSeverity(Severity.MAJOR)
+                    .setStatus(RuleStatus.READY)
+                    .setParameters(ImmutableMap.of("myIntegers", "1,polop,2"));
+            underTest.create(dbSession, newRule);
+        })
+                .isInstanceOf(BadRequestException.class)
+                .hasMessage("Value 'polop' must be an integer.");
+    }
+
+    @Test
+    public void fail_to_create_custom_rule_with_invalid_parameters() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRuleWithTwoIntParams();
+
+        // Create custom rule
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("Some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("first", "polop", "second", "palap"));
+        try {
+            underTest.create(dbSession, newRule);
+            Fail.failBecauseExceptionWasNotThrown(BadRequestException.class);
+        } catch (BadRequestException badRequest) {
+            assertThat(badRequest.errors().toString()).contains("palap").contains("polop");
+        }
+    }
+
+    @Test
+    public void fail_to_create_custom_rule_with_empty_description() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRuleWithTwoIntParams();
+
+        // Create custom rule
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("Some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY)
+                .setMarkdownDescription("");
+        assertThatExceptionOfType(BadRequestException.class)
+          .isThrownBy(() -> underTest.create(dbSession, newRule))
+          .withMessage("The description is missing");
+    }
+
+    @Test
+    public void reactivate_custom_rule_if_already_exists_in_removed_status() {
+        String key = "CUSTOM_RULE";
+
+        // insert template rule
+        RuleDto templateRule = createTemplateRule();
+
+        // insert a removed rule
+        RuleDto rule = RuleTesting.newCustomRule(templateRule)
+                .setRuleKey(key)
+                .setStatus(RuleStatus.REMOVED)
+                .setName("Old name")
+                .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "Old description"))
+                .setDescriptionFormat(Format.MARKDOWN)
+                .setSeverity(Severity.INFO);
+        dbTester.rules().insert(rule);
+        dbTester.rules().insertRuleParam(rule, param -> param.setDefaultValue("a.*"));
+        dbSession.commit();
+
+        // Create custom rule with same key, but with different values
+        NewCustomRule newRule = NewCustomRule.createForCustomRule(key, templateRule.getKey())
+                .setName("New name")
+                .setMarkdownDescription("New description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("regex", "c.*"));
+        RuleKey customRuleKey = underTest.create(dbSession, newRule);
+
+        RuleDto result = dbTester.getDbClient().ruleDao().selectOrFailByKey(dbSession, customRuleKey);
+        assertThat(result.getKey()).isEqualTo(RuleKey.of("java", key));
+        assertThat(result.getStatus()).isEqualTo(RuleStatus.READY);
+
+        // These values should be the same than before
+        assertThat(result.getName()).isEqualTo("Old name");
+        assertThat(result.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Old description");
+        assertThat(result.getSeverityString()).isEqualTo(Severity.INFO);
+
+        List<RuleParamDto> params = dbTester.getDbClient().ruleDao().selectRuleParamsByRuleKey(dbSession, customRuleKey);
+        assertThat(params).hasSize(1);
+        assertThat(params.get(0).getDefaultValue()).isEqualTo("a.*");
+    }
+
+    @Test
+    public void generate_reactivation_exception_when_rule_exists_in_removed_status_and_prevent_reactivation_parameter_is_true() {
+        String key = "CUSTOM_RULE";
+        // insert template rule
+        RuleDto templateRule = createTemplateRule();
+        // insert a removed rule
+        RuleDto rule = RuleTesting.newCustomRule(templateRule)
+                .setRuleKey(key)
+                .setStatus(RuleStatus.REMOVED)
+                .setName("Old name")
+                .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "Old description"))
+                .setSeverity(Severity.INFO);
+        dbTester.rules().insert(rule);
+        dbTester.rules().insertRuleParam(rule, param -> param.setDefaultValue("a.*"));
+        dbSession.commit();
+
+        // Create custom rule with same key, but with different values
+        NewCustomRule newRule = NewCustomRule.createForCustomRule(key, templateRule.getKey())
+                .setName("New name")
+                .setMarkdownDescription("some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("regex", "c.*"))
+                .setPreventReactivation(true);
+
+        try {
+            underTest.create(dbSession, newRule);
+            fail();
+        } catch (Exception e) {
+            assertThat(e).isInstanceOf(ReactivationException.class);
+            ReactivationException reactivationException = (ReactivationException) e;
+            assertThat(reactivationException.ruleKey()).isEqualTo(rule.getKey());
+        }
+    }
+
+    @Test
+    public void fail_to_create_custom_rule_when_invalid_key() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRule();
+
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("*INVALID*", templateRule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("regex", "a.*"));
+
+        assertThatThrownBy(() -> underTest.create(dbSession, newRule))
+                .isInstanceOf(BadRequestException.class)
+                .hasMessage("The rule key \"*INVALID*\" is invalid, it should only contain: a-z, 0-9, \"_\"");
+    }
+
+    @Test
+    public void fail_to_create_custom_rule_when_rule_key_already_exists() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRule();
+        // Create a custom rule
+        AtomicReference<NewCustomRule> newRule = new AtomicReference<>(NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("regex", "a.*")));
+        underTest.create(dbSession, newRule.get());
+
+        // Create another custom rule having same key
+        newRule.set(NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                .setName("My another custom")
+                .setMarkdownDescription("some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("regex", "a.*")));
+
+        assertThatThrownBy(() -> underTest.create(dbSession, newRule.get()))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessage("A rule with the key 'CUSTOM_RULE' already exists");
+    }
+
+    @Test
+    public void fail_to_create_custom_rule_when_missing_name() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRule();
+
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                .setMarkdownDescription("some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("regex", "a.*"));
+
+        assertThatThrownBy(() -> underTest.create(dbSession, newRule))
+                .isInstanceOf(BadRequestException.class)
+                .hasMessage("The name is missing");
+    }
+
+    @Test
+    public void fail_to_create_custom_rule_when_missing_description() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRule();
+
+        assertThatThrownBy(() -> {
+            NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                    .setName("My custom")
+                    .setSeverity(Severity.MAJOR)
+                    .setStatus(RuleStatus.READY)
+                    .setParameters(ImmutableMap.of("regex", "a.*"));
+            underTest.create(dbSession, newRule);
+        })
+                .isInstanceOf(BadRequestException.class)
+                .hasMessage("The description is missing");
+    }
+
+    @Test
+    public void fail_to_create_custom_rule_when_missing_severity() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRule();
+
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("some description")
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("regex", "a.*"));
+
+        assertThatThrownBy(() -> underTest.create(dbSession, newRule))
+                .isInstanceOf(BadRequestException.class)
+                .hasMessage("The severity is missing");
+    }
+
+    @Test
+    public void fail_to_create_custom_rule_when_invalid_severity() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRule();
+
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("some description")
+                .setSeverity("INVALID")
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("regex", "a.*"));
+
+        assertThatThrownBy(() -> underTest.create(dbSession, newRule))
+                .isInstanceOf(BadRequestException.class)
+                .hasMessage("Severity \"INVALID\" is invalid");
+    }
+
+    @Test
+    public void fail_to_create_custom_rule_when_missing_status() {
+        // insert template rule
+        RuleDto templateRule = createTemplateRule();
+
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("some description")
+                .setSeverity(Severity.MAJOR)
+                .setParameters(ImmutableMap.of("regex", "a.*"));
+
+        assertThatThrownBy(() -> underTest.create(dbSession, newRule))
+                .isInstanceOf(BadRequestException.class)
+                .hasMessage("The status is missing");
+    }
+
+    @Test
+    public void fail_to_create_custom_rule_when_wrong_rule_template() {
+        // insert rule
+        RuleDto rule = newRule(RuleKey.of("java", "S001")).setIsTemplate(false);
+        dbTester.rules().insert(rule);
+        dbSession.commit();
+
+        // Create custom rule with unknown template rule
+        NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", rule.getKey())
+                .setName("My custom")
+                .setMarkdownDescription("some description")
+                .setSeverity(Severity.MAJOR)
+                .setStatus(RuleStatus.READY)
+                .setParameters(ImmutableMap.of("regex", "a.*"));
+
+        assertThatThrownBy(() -> underTest.create(dbSession, newRule))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessage("This rule is not a template rule: java:S001");
+    }
+
+    @Test
+    public void fail_to_create_custom_rule_when_null_template() {
+        assertThatThrownBy(() -> {
+            // Create custom rule
+            NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", null)
+                    .setName("My custom")
+                    .setMarkdownDescription("Some description")
+                    .setSeverity(Severity.MAJOR)
+                    .setStatus(RuleStatus.READY);
+            underTest.create(dbSession, newRule);
+        })
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessage("Template key should be set");
+    }
+
+    @Test
+    public void fail_to_create_custom_rule_when_unknown_template() {
+        assertThatThrownBy(() -> {
+            // Create custom rule
+            NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", RuleKey.of("java", "S001"))
+                    .setName("My custom")
+                    .setMarkdownDescription("Some description")
+                    .setSeverity(Severity.MAJOR)
+                    .setStatus(RuleStatus.READY);
+            underTest.create(dbSession, newRule);
+        })
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessage("The template key doesn't exist: java:S001");
+    }
+
+    private RuleDto createTemplateRule() {
+        RuleDto templateRule = RuleTesting.newDto(RuleKey.of("java", "S001"))
+                .setIsTemplate(true)
+                .setLanguage("java")
+                .setPluginKey("sonarjava")
+                .setConfigKey("S001")
+                .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
+                .setDefRemediationGapMultiplier("1h")
+                .setDefRemediationBaseEffort("5min")
+                .setGapDescription("desc")
+                .setTags(Sets.newHashSet("usertag1", "usertag2"))
+                .setSystemTags(Sets.newHashSet("tag1", "tag4"))
+                .setSecurityStandards(Sets.newHashSet("owaspTop10:a1", "cwe:123"))
+                .setCreatedAt(new Date().getTime())
+                .setUpdatedAt(new Date().getTime());
+        dbTester.rules().insert(templateRule);
+        dbTester.rules().insertOrUpdateMetadata(templateRule.getMetadata().setRuleUuid(templateRule.getUuid()));
+        dbTester.rules().insertRuleParam(templateRule, param -> param.setName("regex").setType("STRING").setDescription("Reg ex").setDefaultValue(".*"));
+        ruleIndexer.commitAndIndex(dbTester.getSession(), templateRule.getUuid());
+        return templateRule;
+    }
+
+    private RuleDto createTemplateRuleWithIntArrayParam() {
+        RuleDto templateRule = newRule(RuleKey.of("java", "S002"))
+                .setIsTemplate(true)
+                .setLanguage("java")
+                .setConfigKey("S002")
+                .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
+                .setDefRemediationGapMultiplier("1h")
+                .setDefRemediationBaseEffort("5min")
+                .setGapDescription("desc")
+                .setCreatedAt(new Date().getTime())
+                .setUpdatedAt(new Date().getTime());
+        dbTester.rules().insert(templateRule);
+        dbTester.rules().insertRuleParam(templateRule,
+                param -> param.setName("myIntegers").setType("INTEGER,multiple=true,values=1;2;3").setDescription("My Integers").setDefaultValue("1"));
+        ruleIndexer.commitAndIndex(dbTester.getSession(), templateRule.getUuid());
+        return templateRule;
+    }
+
+    private RuleDto createTemplateRuleWithTwoIntParams() {
+        RuleDto templateRule = newRule(RuleKey.of("java", "S003"))
+                .setIsTemplate(true)
+                .setLanguage("java")
+                .setConfigKey("S003")
+                .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
+                .setDefRemediationGapMultiplier("1h")
+                .setDefRemediationBaseEffort("5min")
+                .setGapDescription("desc")
+                .setCreatedAt(new Date().getTime())
+                .setUpdatedAt(new Date().getTime());
+        dbTester.rules().insert(templateRule);
+        dbTester.rules().insertRuleParam(templateRule, param -> param.setName("first").setType("INTEGER").setDescription("First integer").setDefaultValue("0"));
+        dbTester.rules().insertRuleParam(templateRule, param -> param.setName("second").setType("INTEGER").setDescription("Second integer").setDefaultValue("0"));
+        return templateRule;
     }
-  }
-
-  @Test
-  public void fail_to_create_custom_rule_when_invalid_key() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRule();
-
-    NewCustomRule newRule = NewCustomRule.createForCustomRule("*INVALID*", templateRule.getKey())
-      .setName("My custom")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("regex", "a.*"));
-
-    assertThatThrownBy(() -> underTest.create(dbSession, newRule))
-      .isInstanceOf(BadRequestException.class)
-      .hasMessage("The rule key \"*INVALID*\" is invalid, it should only contain: a-z, 0-9, \"_\"");
-  }
-
-  @Test
-  public void fail_to_create_custom_rule_when_rule_key_already_exists() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRule();
-    // Create a custom rule
-    AtomicReference<NewCustomRule> newRule = new AtomicReference<>(NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-      .setName("My custom")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("regex", "a.*")));
-    underTest.create(dbSession, newRule.get());
-
-    // Create another custom rule having same key
-    newRule.set(NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-      .setName("My another custom")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("regex", "a.*")));
-
-    assertThatThrownBy(() -> underTest.create(dbSession, newRule.get()))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("A rule with the key 'CUSTOM_RULE' already exists");
-  }
-
-  @Test
-  public void fail_to_create_custom_rule_when_missing_name() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRule();
-
-    NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("regex", "a.*"));
-
-    assertThatThrownBy(() -> underTest.create(dbSession, newRule))
-      .isInstanceOf(BadRequestException.class)
-      .hasMessage("The name is missing");
-  }
-
-  @Test
-  public void fail_to_create_custom_rule_when_missing_description() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRule();
-
-    assertThatThrownBy(() -> {
-      NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-        .setName("My custom")
-        .setSeverity(Severity.MAJOR)
-        .setStatus(RuleStatus.READY)
-        .setParameters(ImmutableMap.of("regex", "a.*"));
-      underTest.create(dbSession, newRule);
-    })
-      .isInstanceOf(BadRequestException.class)
-      .hasMessage("The description is missing");
-  }
-
-  @Test
-  public void fail_to_create_custom_rule_when_missing_severity() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRule();
-
-    NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-      .setName("My custom")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("regex", "a.*"));
-
-    assertThatThrownBy(() -> underTest.create(dbSession, newRule))
-      .isInstanceOf(BadRequestException.class)
-      .hasMessage("The severity is missing");
-  }
-
-  @Test
-  public void fail_to_create_custom_rule_when_invalid_severity() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRule();
-
-    NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-      .setName("My custom")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setSeverity("INVALID")
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("regex", "a.*"));
-
-    assertThatThrownBy(() -> underTest.create(dbSession, newRule))
-      .isInstanceOf(BadRequestException.class)
-      .hasMessage("Severity \"INVALID\" is invalid");
-  }
-
-  @Test
-  public void fail_to_create_custom_rule_when_missing_status() {
-    // insert template rule
-    RuleDto templateRule = createTemplateRule();
-
-    NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", templateRule.getKey())
-      .setName("My custom")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setSeverity(Severity.MAJOR)
-      .setParameters(ImmutableMap.of("regex", "a.*"));
-
-    assertThatThrownBy(() -> underTest.create(dbSession, newRule))
-      .isInstanceOf(BadRequestException.class)
-      .hasMessage("The status is missing");
-  }
-
-  @Test
-  public void fail_to_create_custom_rule_when_wrong_rule_template() {
-    // insert rule
-    RuleDto rule = newRule(RuleKey.of("java", "S001")).setIsTemplate(false);
-    dbTester.rules().insert(rule);
-    dbSession.commit();
-
-    // Create custom rule with unknown template rule
-    NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", rule.getKey())
-      .setName("My custom")
-      .setRuleDescriptionSections(Set.of(new NewRuleDescriptionSection("default", "some description")))
-      .setSeverity(Severity.MAJOR)
-      .setStatus(RuleStatus.READY)
-      .setParameters(ImmutableMap.of("regex", "a.*"));
-
-    assertThatThrownBy(() -> underTest.create(dbSession, newRule))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("This rule is not a template rule: java:S001");
-  }
-
-  @Test
-  public void fail_to_create_custom_rule_when_unknown_template() {
-    assertThatThrownBy(() -> {
-      // Create custom rule
-      NewCustomRule newRule = NewCustomRule.createForCustomRule("CUSTOM_RULE", RuleKey.of("java", "S001"))
-        .setName("My custom")
-        .setMarkdownDescription("Some description")
-        .setSeverity(Severity.MAJOR)
-        .setStatus(RuleStatus.READY);
-      underTest.create(dbSession, newRule);
-    })
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("The template key doesn't exist: java:S001");
-  }
-
-  private RuleDto createTemplateRule() {
-    RuleDto templateRule = RuleTesting.newDto(RuleKey.of("java", "S001"))
-      .setIsTemplate(true)
-      .setLanguage("java")
-      .setPluginKey("sonarjava")
-      .setConfigKey("S001")
-      .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
-      .setDefRemediationGapMultiplier("1h")
-      .setDefRemediationBaseEffort("5min")
-      .setGapDescription("desc")
-      .setTags(Sets.newHashSet("usertag1", "usertag2"))
-      .setSystemTags(Sets.newHashSet("tag1", "tag4"))
-      .setSecurityStandards(Sets.newHashSet("owaspTop10:a1", "cwe:123"))
-      .setCreatedAt(new Date().getTime())
-      .setUpdatedAt(new Date().getTime());
-    dbTester.rules().insert(templateRule);
-    dbTester.rules().insertOrUpdateMetadata(templateRule.getMetadata().setRuleUuid(templateRule.getUuid()));
-    dbTester.rules().insertRuleParam(templateRule, param -> param.setName("regex").setType("STRING").setDescription("Reg ex").setDefaultValue(".*"));
-    ruleIndexer.commitAndIndex(dbTester.getSession(), templateRule.getUuid());
-    return templateRule;
-  }
-
-  private RuleDto createTemplateRuleWithIntArrayParam() {
-    RuleDto templateRule = newRule(RuleKey.of("java", "S002"))
-      .setIsTemplate(true)
-      .setLanguage("java")
-      .setConfigKey("S002")
-      .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
-      .setDefRemediationGapMultiplier("1h")
-      .setDefRemediationBaseEffort("5min")
-      .setGapDescription("desc")
-      .setCreatedAt(new Date().getTime())
-      .setUpdatedAt(new Date().getTime());
-    dbTester.rules().insert(templateRule);
-    dbTester.rules().insertRuleParam(templateRule,
-      param -> param.setName("myIntegers").setType("INTEGER,multiple=true,values=1;2;3").setDescription("My Integers").setDefaultValue("1"));
-    ruleIndexer.commitAndIndex(dbTester.getSession(), templateRule.getUuid());
-    return templateRule;
-  }
-
-  private RuleDto createTemplateRuleWithTwoIntParams() {
-    RuleDto templateRule = newRule(RuleKey.of("java", "S003"))
-      .setIsTemplate(true)
-      .setLanguage("java")
-      .setConfigKey("S003")
-      .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name())
-      .setDefRemediationGapMultiplier("1h")
-      .setDefRemediationBaseEffort("5min")
-      .setGapDescription("desc")
-      .setCreatedAt(new Date().getTime())
-      .setUpdatedAt(new Date().getTime());
-    dbTester.rules().insert(templateRule);
-    dbTester.rules().insertRuleParam(templateRule, param -> param.setName("first").setType("INTEGER").setDescription("First integer").setDefaultValue("0"));
-    dbTester.rules().insertRuleParam(templateRule, param -> param.setName("second").setType("INTEGER").setDescription("Second integer").setDefaultValue("0"));
-    return templateRule;
-  }
 
 }