]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10544 save externally defined Rules if needed
authorGuillaume Jambet <guillaume.jambet@sonarsource.com>
Tue, 10 Apr 2018 12:01:46 +0000 (14:01 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 26 Apr 2018 18:20:50 +0000 (20:20 +0200)
14 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDefinitionDto.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/AddRuleExternalTest.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewExternalRule.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/RuleRepository.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/RuleRepositoryImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistExternalRulesStep.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputationSteps.java
server/sonar-server/src/main/java/org/sonar/server/rule/RuleCreator.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/NewExternalRuleTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/RuleRepositoryImplTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/RuleRepositoryRule.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistExternalRulesStepTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStepTest.java

index 65f78d58d9ff393b470fb4be05ed6d2cd7ef7dcf..ebf033b83166ac50cd2052bb73028479b9dfe3ac 100644 (file)
@@ -25,6 +25,7 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
 import javax.annotation.Nullable;
+
 import org.apache.ibatis.session.ResultHandler;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.RuleQuery;
@@ -49,6 +50,11 @@ public class RuleDao implements Dao {
     return ofNullable(res);
   }
 
+  public RuleDto selectOrFailByKey(DbSession session, RuleKey key) {
+    RuleDefinitionDto ruleDefinitionDto = selectOrFailDefinitionByKey(session, key);
+    return new RuleDto(ruleDefinitionDto, new RuleMetadataDto());
+  }
+
   public Optional<RuleDefinitionDto> selectDefinitionByKey(DbSession session, RuleKey key) {
     return ofNullable(mapper(session).selectDefinitionByKey(key));
   }
index 1b072c4659492cdd1352c8b3771d33ec74d84826..c41800284803d180ef36652166c38fcb8db11c1e 100644 (file)
@@ -122,11 +122,12 @@ public class RuleDefinitionDto {
     return this;
   }
 
+  @CheckForNull
   public String getDescriptionURL() {
     return descriptionURL;
   }
 
-  public RuleDefinitionDto setDescriptionURL(String descriptionURL) {
+  public RuleDefinitionDto setDescriptionURL(@Nullable String descriptionURL) {
     this.descriptionURL = descriptionURL;
     return this;
   }
index 850bf0c761024a5b77be083c16e786b22f9967cf..c8ce8263fc7de60ce248263e124a1320f2a5402b 100644 (file)
@@ -42,7 +42,11 @@ public class AddRuleExternalTest {
 
   @Test
   public void column_are_added_to_table() throws SQLException {
+    dbTester.assertColumnDoesNotExist("rules", "is_external");
+    dbTester.assertColumnDoesNotExist("rules", "description_url");
+
     underTest.execute();
+
     dbTester.assertColumnDefinition("rules", "is_external", BOOLEAN, null, true);
     dbTester.assertColumnDefinition("rules", "description_url", VARCHAR, 256, true);
   }
index a74632d02ff062031fb5ce5cdabaaa92e208cd75..1196c807e713829029f7b7b97d661778a8d70d42 100644 (file)
@@ -36,6 +36,7 @@ public class NewExternalRule implements Rule {
   private final String descriptionUrl;
   private final String severity;
   private final RuleType type;
+  private final String pluginKey;
 
   private NewExternalRule(Builder builder) {
     this.key = checkNotNull(builder.key, "key");
@@ -43,6 +44,7 @@ public class NewExternalRule implements Rule {
     this.descriptionUrl = builder.descriptionUrl;
     this.severity = checkNotEmpty(builder.severity, "severity");
     this.type = checkNotNull(builder.type, "type");
+    this.pluginKey = builder.pluginKey;
   }
 
   private static String checkNotEmpty(String str, String name) {
@@ -110,7 +112,7 @@ public class NewExternalRule implements Rule {
 
   @Override
   public String getPluginKey() {
-    return null;
+    return pluginKey;
   }
 
   public static class Builder {
@@ -119,6 +121,7 @@ public class NewExternalRule implements Rule {
     private String descriptionUrl;
     private String severity;
     private RuleType type;
+    private String pluginKey;
 
     public Builder setKey(RuleKey key) {
       this.key = key;
@@ -164,5 +167,10 @@ public class NewExternalRule implements Rule {
     public NewExternalRule build() {
       return new NewExternalRule(this);
     }
+
+    public Builder setPluginKey(String pluginKey) {
+      this.pluginKey = pluginKey;
+      return this;
+    }
   }
 }
index 347638cddc4118ff628be87741b1b6a891c703e2..adb1dd8d18e42dc577285de39d2d3980952a15de 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.computation.task.projectanalysis.issue;
 import java.util.Optional;
 import java.util.function.Supplier;
 import org.sonar.api.rule.RuleKey;
+import org.sonar.db.DbSession;
 
 /**
  * Repository of every rule in DB (including manual rules) whichever their status.
@@ -45,6 +46,9 @@ public interface RuleRepository {
   Optional<Rule> findByKey(RuleKey key);
 
   Optional<Rule> findById(int id);
-  
+
   void insertNewExternalRuleIfAbsent(RuleKey ruleKey, Supplier<NewExternalRule> ruleSupplier);
+
+  void persistNewExternalRules(DbSession dbSession);
+
 }
index e7f3cd61d80a323b47015206be7ef65689f63834..32fbac077f614e816ec0062c255ba7996176cf2e 100644 (file)
  */
 package org.sonar.server.computation.task.projectanalysis.issue;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Multimap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.function.Supplier;
-import javax.annotation.CheckForNull;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
@@ -33,6 +27,13 @@ import org.sonar.db.DbSession;
 import org.sonar.db.rule.DeprecatedRuleKeyDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.server.rule.RuleCreator;
+
+import javax.annotation.CheckForNull;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Supplier;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
@@ -43,23 +44,39 @@ public class RuleRepositoryImpl implements RuleRepository {
   private Map<RuleKey, Rule> rulesByKey;
   @CheckForNull
   private Map<Integer, Rule> rulesById;
-  @CheckForNull
-  private Map<RuleKey, NewExternalRule> newExternalRulesByKey;
 
+  private final RuleCreator creator;
   private final DbClient dbClient;
   private final AnalysisMetadataHolder analysisMetadataHolder;
 
-  public RuleRepositoryImpl(DbClient dbClient, AnalysisMetadataHolder analysisMetadataHolder) {
+  public RuleRepositoryImpl(RuleCreator creator, DbClient dbClient, AnalysisMetadataHolder analysisMetadataHolder) {
+    this.creator = creator;
     this.dbClient = dbClient;
     this.analysisMetadataHolder = analysisMetadataHolder;
   }
 
   public void insertNewExternalRuleIfAbsent(RuleKey ruleKey, Supplier<NewExternalRule> ruleSupplier) {
+    ensureInitialized();
+
     if (!rulesByKey.containsKey(ruleKey)) {
-      newExternalRulesByKey.computeIfAbsent(ruleKey, s -> ruleSupplier.get());
+      rulesByKey.computeIfAbsent(ruleKey, s -> ruleSupplier.get());
     }
   }
 
+  @Override public void persistNewExternalRules(DbSession dbSession) {
+    ensureInitialized();
+
+    rulesByKey.values().stream()
+      .filter(NewExternalRule.class::isInstance)
+      .forEach(extRule -> persistAndIndex(dbSession, (NewExternalRule) extRule));
+  }
+
+  private void persistAndIndex(DbSession dbSession, NewExternalRule external) {
+    Rule rule = creator.create(dbSession, external);
+    rulesById.put(rule.getId(), rule);
+    rulesByKey.put(external.getKey(), rule);
+  }
+
   @Override
   public Rule getByKey(RuleKey key) {
     verifyKeyArgument(key);
@@ -109,20 +126,17 @@ public class RuleRepositoryImpl implements RuleRepository {
   }
 
   private void loadRulesFromDb(DbSession dbSession) {
-    ImmutableMap.Builder<RuleKey, Rule> rulesByKeyBuilder = ImmutableMap.builder();
-    ImmutableMap.Builder<Integer, Rule> rulesByIdBuilder = ImmutableMap.builder();
+    this.rulesByKey = new HashMap<>();
+    this.rulesById = new HashMap<>();
     String organizationUuid = analysisMetadataHolder.getOrganization().getUuid();
     Multimap<Integer, DeprecatedRuleKeyDto> deprecatedRuleKeysByRuleId = dbClient.ruleDao().selectAllDeprecatedRuleKeys(dbSession).stream()
       .collect(MoreCollectors.index(DeprecatedRuleKeyDto::getRuleId));
     for (RuleDto ruleDto : dbClient.ruleDao().selectAll(dbSession, organizationUuid)) {
       Rule rule = new RuleImpl(ruleDto);
-      rulesByKeyBuilder.put(ruleDto.getKey(), rule);
-      rulesByIdBuilder.put(ruleDto.getId(), rule);
-      deprecatedRuleKeysByRuleId.get(ruleDto.getId()).forEach(t -> rulesByKeyBuilder.put(RuleKey.of(t.getOldRepositoryKey(), t.getOldRuleKey()), rule));
+      rulesByKey.put(ruleDto.getKey(), rule);
+      rulesById.put(ruleDto.getId(), rule);
+      deprecatedRuleKeysByRuleId.get(ruleDto.getId()).forEach(t -> rulesByKey.put(RuleKey.of(t.getOldRepositoryKey(), t.getOldRuleKey()), rule));
     }
-    this.rulesByKey = rulesByKeyBuilder.build();
-    this.rulesById = rulesByIdBuilder.build();
-    this.newExternalRulesByKey = new LinkedHashMap<>();
   }
 
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistExternalRulesStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistExternalRulesStep.java
new file mode 100644 (file)
index 0000000..aac5e71
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.computation.task.projectanalysis.step;
+
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.server.computation.task.projectanalysis.issue.RuleRepository;
+import org.sonar.server.computation.task.step.ComputationStep;
+
+public class PersistExternalRulesStep implements ComputationStep {
+
+  private final DbClient dbClient;
+  private final RuleRepository ruleRepository;
+
+  public PersistExternalRulesStep(DbClient dbClient, RuleRepository ruleRepository) {
+    this.dbClient = dbClient;
+    this.ruleRepository = ruleRepository;
+  }
+
+  @Override
+  public void execute() {
+
+    try (DbSession dbSession = dbClient.openSession(true)) {
+      ruleRepository.persistNewExternalRules(dbSession);
+      dbSession.flushStatements();
+      dbSession.commit();
+    }
+
+  }
+
+  @Override
+  public String getDescription() {
+    return "Persist new externally defined Rules";
+  }
+
+}
index 9c9aa5116ea14e88209ee13c0bd06f1de8eb7943..8cb71963b0b72abcf3d5d9377d6902cdff02a0c4 100644 (file)
@@ -89,6 +89,7 @@ public class ReportComputationSteps extends AbstractComputationSteps {
     PersistAnalysisPropertiesStep.class,
     PersistMeasuresStep.class,
     PersistLiveMeasuresStep.class,
+    PersistExternalRulesStep.class,
     PersistIssuesStep.class,
     PersistProjectLinksStep.class,
     PersistEventsStep.class,
index b6823f0e3ef6d00d7ef20ca9ef8af9c161e9da00..73ab988cadee202715e813c37041e3a770238d53 100644 (file)
@@ -36,11 +36,14 @@ import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.rule.RuleDao;
 import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleDto.Format;
 import org.sonar.db.rule.RuleMetadataDto;
 import org.sonar.db.rule.RuleParamDto;
+import org.sonar.server.computation.task.projectanalysis.issue.NewExternalRule;
+import org.sonar.server.computation.task.projectanalysis.issue.RuleImpl;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.rule.index.RuleIndexer;
@@ -49,6 +52,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 org.sonar.db.rule.RuleDto.Scope.ALL;
 import static org.sonar.server.ws.WsUtils.checkRequest;
 
 @ServerSide
@@ -89,6 +93,20 @@ public class RuleCreator {
     return customRuleKey;
   }
 
+  public org.sonar.server.computation.task.projectanalysis.issue.Rule create(DbSession dbSession, NewExternalRule external) {
+    RuleDao dao = dbClient.ruleDao();
+    dao.insert(dbSession, new RuleDefinitionDto()
+      .setRuleKey(external.getKey())
+      .setPluginKey(external.getPluginKey())
+      .setIsExternal(true)
+      .setName(external.getName())
+      .setDescriptionURL(external.getDescriptionUrl())
+      .setType(external.getType())
+      .setScope(ALL)
+      .setSeverity(external.getSeverity()));
+    return new RuleImpl(dao.selectOrFailByKey(dbSession, external.getKey()));
+  }
+
   private void validateCustomRule(NewCustomRule newRule, DbSession dbSession, RuleKey templateKey) {
     List<String> errors = new ArrayList<>();
 
index 4b5f1944350ecaefa0a4eba2af36e7523eabace9..dd8b0978c2187d87329612da5902204fcdd6d167 100644 (file)
@@ -35,6 +35,7 @@ public class NewExternalRuleTest {
     NewExternalRule.Builder builder = new NewExternalRule.Builder()
       .setDescriptionUrl("url")
       .setKey(RuleKey.of("repo", "rule"))
+      .setPluginKey("repo")
       .setName("name")
       .setSeverity("MAJOR")
       .setType(RuleType.BUG);
@@ -49,7 +50,7 @@ public class NewExternalRuleTest {
 
     assertThat(rule.getDescriptionUrl()).isEqualTo("url");
     assertThat(rule.getName()).isEqualTo("name");
-    assertThat(rule.getPluginKey()).isNull();
+    assertThat(rule.getPluginKey()).isEqualTo("repo");
     assertThat(rule.getSeverity()).isEqualTo("MAJOR");
     assertThat(rule.getType()).isEqualTo(RuleType.BUG);
     assertThat(rule.getDescriptionUrl()).isEqualTo("url");
index 6dd3eee57a092f36686435afbb13daecad32cf25..24400902b3d9f5f1214b18e37fbea1c4c56b0cc5 100644 (file)
@@ -21,21 +21,35 @@ package org.sonar.server.computation.task.projectanalysis.issue;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+
 import java.util.Optional;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.RuleType;
+import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
 import org.sonar.db.rule.DeprecatedRuleKeyDto;
 import org.sonar.db.rule.RuleDao;
+import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.organization.TestDefaultOrganizationProvider;
+import org.sonar.server.rule.RuleCreator;
+import org.sonar.server.rule.index.RuleIndexDefinition;
+import org.sonar.server.rule.index.RuleIndexer;
+import org.sonar.server.util.TypeValidationsTesting;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
@@ -45,6 +59,10 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 import static org.mockito.internal.verification.VerificationModeFactory.times;
+import static org.sonar.api.rule.Severity.BLOCKER;
+import static org.sonar.api.rules.RuleType.BUG;
+import static org.sonar.server.organization.TestDefaultOrganizationProvider.from;
+import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidations;
 
 public class RuleRepositoryImplTest {
 
@@ -64,11 +82,19 @@ public class RuleRepositoryImplTest {
   public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
     .setOrganizationUuid(ORGANIZATION_UUID, QUALITY_GATE_UUID);
 
+  @org.junit.Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @org.junit.Rule
+  public EsTester es = new EsTester(new RuleIndexDefinition(new MapSettings().asConfig()));
+
+  private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient());
+  private RuleCreator creator = new RuleCreator(System2.INSTANCE, ruleIndexer, db.getDbClient(), newFullTypeValidations(), from(db));
+
   private DbClient dbClient = mock(DbClient.class);
   private DbSession dbSession = mock(DbSession.class);
   private RuleDao ruleDao = mock(RuleDao.class);
 
-  private RuleRepositoryImpl underTest = new RuleRepositoryImpl(dbClient, analysisMetadataHolder);
+  private RuleRepositoryImpl underTest = new RuleRepositoryImpl(creator, dbClient, analysisMetadataHolder);
 
   @Before
   public void setUp() throws Exception {
@@ -255,6 +281,64 @@ public class RuleRepositoryImplTest {
     assertIsABRule(rule.get());
   }
 
+  @Test
+  public void accept_new_externally_defined_Rules() {
+    DbClient dbClient = db.getDbClient();
+    underTest = new RuleRepositoryImpl(creator, dbClient, analysisMetadataHolder);
+
+    RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign");
+
+    underTest.insertNewExternalRuleIfAbsent(ruleKey, () -> new NewExternalRule.Builder()
+      .setKey(ruleKey)
+      .setPluginKey("eslint")
+      .setName("disallow assignment operators in conditional statements (no-cond-assign)")
+      .setDescriptionUrl("https://eslint.org/docs/rules/no-cond-assign")
+      .setSeverity(BLOCKER)
+      .setType(BUG)
+      .build());
+
+
+    assertThat(underTest.getByKey(ruleKey)).isNotNull();
+    assertThat(underTest.getByKey(ruleKey).getPluginKey()).isEqualTo("eslint");
+    assertThat(underTest.getByKey(ruleKey).getName()).isEqualTo("disallow assignment operators in conditional statements (no-cond-assign)");
+    assertThat(underTest.getByKey(ruleKey).getType()).isEqualTo(BUG);
+
+    RuleDao ruleDao = dbClient.ruleDao();
+    Optional<RuleDefinitionDto> ruleDefinitionDto = ruleDao.selectDefinitionByKey(dbClient.openSession(false), ruleKey);
+    assertThat(ruleDefinitionDto).isNotPresent();
+  }
+
+  @Test
+  public void persist_new_externally_defined_Rules() {
+    DbClient dbClient = db.getDbClient();
+    DbSession dbSession = dbClient.openSession(false);
+    underTest = new RuleRepositoryImpl(creator, dbClient, analysisMetadataHolder);
+
+    RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign");
+    underTest.insertNewExternalRuleIfAbsent(ruleKey, () -> new NewExternalRule.Builder()
+      .setKey(ruleKey)
+      .setPluginKey("eslint")
+      .setName("disallow assignment operators in conditional statements (no-cond-assign)")
+      .setDescriptionUrl("https://eslint.org/docs/rules/no-cond-assign")
+      .setSeverity(BLOCKER)
+      .setType(BUG)
+      .build());
+
+
+    underTest.persistNewExternalRules(dbSession);
+
+    dbSession.commit();
+
+    RuleDao ruleDao = dbClient.ruleDao();
+    Optional<RuleDefinitionDto> ruleDefinitionDto = ruleDao.selectDefinitionByKey(dbClient.openSession(false), ruleKey);
+    assertThat(ruleDefinitionDto).isPresent();
+
+    Rule rule = underTest.getByKey(ruleKey);
+    assertThat(rule).isNotNull();
+
+    assertThat(underTest.getById(ruleDefinitionDto.get().getId())).isNotNull();
+  }
+
   private void expectNullRuleKeyNPE() {
     expectedException.expect(NullPointerException.class);
     expectedException.expectMessage("RuleKey can not be null");
index 698954885aa3e9e8c94749a6711be5ad55864488..57300aca811f4e97e83840619bb8fc891042be38 100644 (file)
@@ -23,8 +23,10 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
 import java.util.function.Supplier;
+
 import org.junit.rules.ExternalResource;
 import org.sonar.api.rule.RuleKey;
+import org.sonar.db.DbSession;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
@@ -65,6 +67,10 @@ public class RuleRepositoryRule extends ExternalResource implements RuleReposito
     return Optional.ofNullable(rulesById.get(id));
   }
 
+  @Override public void persistNewExternalRules(DbSession dbSession) {
+    throw new UnsupportedOperationException();
+  }
+
   public DumbRule add(RuleKey key) {
     DumbRule rule = new DumbRule(key);
     rule.setId(key.hashCode());
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistExternalRulesStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistExternalRulesStepTest.java
new file mode 100644 (file)
index 0000000..a9f0fda
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.computation.task.projectanalysis.step;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.rule.RuleDao;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.server.computation.task.projectanalysis.issue.NewExternalRule;
+import org.sonar.server.computation.task.projectanalysis.issue.RuleRepositoryImpl;
+import org.sonar.server.computation.task.step.ComputationStep;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.rule.RuleCreator;
+import org.sonar.server.rule.index.RuleIndexDefinition;
+import org.sonar.server.rule.index.RuleIndexer;
+
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.rule.Severity.BLOCKER;
+import static org.sonar.api.rules.RuleType.BUG;
+import static org.sonar.server.organization.TestDefaultOrganizationProvider.from;
+import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidations;
+
+public class PersistExternalRulesStepTest extends BaseStepTest {
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
+    .setOrganizationUuid("org-1", "qg-uuid-1");
+
+  private DbClient dbClient = db.getDbClient();
+  private System2 system2 = System2.INSTANCE;
+
+  private ComputationStep underTest;
+  private RuleRepositoryImpl ruleRepository;
+
+  @org.junit.Rule
+  public EsTester es = new EsTester(new RuleIndexDefinition(new MapSettings().asConfig()));
+
+  private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient());
+  private RuleCreator creator = new RuleCreator(System2.INSTANCE, ruleIndexer, db.getDbClient(), newFullTypeValidations(), from(db));
+
+
+  @Override
+  protected ComputationStep step() {
+    return underTest;
+  }
+
+  @Before
+  public void setup() {
+    ruleRepository = new RuleRepositoryImpl(creator, dbClient, analysisMetadataHolder);
+    underTest = new PersistExternalRulesStep(dbClient, ruleRepository);
+  }
+
+  @Test
+  public void persist_new_external_rules() {
+
+    RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign");
+    ruleRepository.insertNewExternalRuleIfAbsent(ruleKey, () -> new NewExternalRule.Builder()
+      .setKey(ruleKey)
+      .setPluginKey("eslint")
+      .setName("disallow assignment operators in conditional statements (no-cond-assign)")
+      .setDescriptionUrl("https://eslint.org/docs/rules/no-cond-assign")
+      .setSeverity(BLOCKER)
+      .setType(BUG)
+      .build());
+
+    underTest.execute();
+
+    RuleDao ruleDao = dbClient.ruleDao();
+    Optional<RuleDefinitionDto> ruleDefinitionDtoOptional = ruleDao.selectDefinitionByKey(dbClient.openSession(false), ruleKey);
+    assertThat(ruleDefinitionDtoOptional).isPresent();
+
+    RuleDefinitionDto reloaded = ruleDefinitionDtoOptional.get();
+    assertThat(reloaded.getRuleKey()).isEqualTo("no-cond-assign");
+    assertThat(reloaded.getRepositoryKey()).isEqualTo("eslint");
+    assertThat(reloaded.isExternal()).isTrue();
+    assertThat(reloaded.getType()).isEqualTo(2);
+    assertThat(reloaded.getSeverity()).isEqualTo(4);
+    assertThat(reloaded.getDescriptionURL()).isEqualTo("https://eslint.org/docs/rules/no-cond-assign");
+    assertThat(reloaded.getName()).isEqualTo("disallow assignment operators in conditional statements (no-cond-assign)");
+    assertThat(reloaded.getPluginKey()).isEqualTo("eslint");
+
+
+  }
+
+}
index 684ad94b3b4e77bd93e9edeb6b3252f5fdd5087e..2d3590b42fb38e71c1c570a62e0946933111a3e8 100644 (file)
@@ -27,6 +27,7 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.RuleType;
 import org.sonar.api.utils.System2;
@@ -50,6 +51,10 @@ import org.sonar.server.computation.task.projectanalysis.issue.IssueCache;
 import org.sonar.server.computation.task.projectanalysis.issue.RuleRepositoryImpl;
 import org.sonar.server.computation.task.projectanalysis.issue.UpdateConflictResolver;
 import org.sonar.server.computation.task.step.ComputationStep;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.rule.RuleCreator;
+import org.sonar.server.rule.index.RuleIndexDefinition;
+import org.sonar.server.rule.index.RuleIndexer;
 import org.sonar.server.util.cache.DiskCache;
 
 import static java.util.Collections.singletonList;
@@ -61,6 +66,8 @@ import static org.sonar.api.issue.Issue.STATUS_CLOSED;
 import static org.sonar.api.issue.Issue.STATUS_OPEN;
 import static org.sonar.api.rule.Severity.BLOCKER;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.server.organization.TestDefaultOrganizationProvider.from;
+import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidations;
 
 public class PersistIssuesStepTest extends BaseStepTest {
 
@@ -82,6 +89,12 @@ public class PersistIssuesStepTest extends BaseStepTest {
   private IssueCache issueCache;
   private ComputationStep step;
 
+  @org.junit.Rule
+  public EsTester es = new EsTester(new RuleIndexDefinition(new MapSettings().asConfig()));
+
+  private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient());
+  private RuleCreator creator = new RuleCreator(System2.INSTANCE, ruleIndexer, db.getDbClient(), newFullTypeValidations(), from(db));
+
   @Override
   protected ComputationStep step() {
     return step;
@@ -94,7 +107,7 @@ public class PersistIssuesStepTest extends BaseStepTest {
     when(system2.now()).thenReturn(NOW);
     reportReader.setMetadata(ScannerReport.Metadata.getDefaultInstance());
 
-    step = new PersistIssuesStep(dbClient, system2, new UpdateConflictResolver(), new RuleRepositoryImpl(dbClient, analysisMetadataHolder), issueCache);
+    step = new PersistIssuesStep(dbClient, system2, new UpdateConflictResolver(), new RuleRepositoryImpl(creator, dbClient, analysisMetadataHolder), issueCache);
   }
 
   @After