]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10809 Analyzers need to declare whether a rule is external
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Wed, 30 May 2018 08:45:00 +0000 (10:45 +0200)
committerSonarTech <sonartech@sonarsource.com>
Fri, 1 Jun 2018 18:20:47 +0000 (20:20 +0200)
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssuePerLineSensor.java
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssueWithDetailsPerLineSensor.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java
server/sonar-server/src/main/java/org/sonar/server/rule/RegisterRules.java
server/sonar-server/src/test/java/org/sonar/server/rule/RegisterRulesTest.java
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionTest.java
tests/src/test/java/org/sonarqube/tests/issue/ExternalIssueTest.java

index 3b626b8ed9f8f8e3511df82de69c9a5c0f00ba78..639d51c21724fe1fd197679ddcc574e3e6aec0ee 100644 (file)
@@ -51,6 +51,7 @@ import org.sonar.xoo.rule.OneBlockerIssuePerFileSensor;
 import org.sonar.xoo.rule.OneBugIssuePerLineSensor;
 import org.sonar.xoo.rule.OneDayDebtPerFileSensor;
 import org.sonar.xoo.rule.OneExternalIssuePerLineSensor;
+import org.sonar.xoo.rule.OneExternalIssueWithDetailsPerLineSensor;
 import org.sonar.xoo.rule.OneIssueOnDirPerFileSensor;
 import org.sonar.xoo.rule.OneIssuePerDirectorySensor;
 import org.sonar.xoo.rule.OneIssuePerFileSensor;
@@ -176,6 +177,7 @@ public class XooPlugin implements Plugin {
     if (context.getSonarQubeVersion().isGreaterThanOrEqual(Version.create(7, 2))) {
       context.addExtensions(
         OneExternalIssuePerLineSensor.class,
+        OneExternalIssueWithDetailsPerLineSensor.class,
         SignificantCodeSensor.class);
     }
   }
index d9fe4b59e821858a945299202895d573e85847d5..c8d0970437113542ccb989f30f7064cc65a5db4a 100644 (file)
@@ -38,7 +38,7 @@ public class OneExternalIssuePerLineSensor implements Sensor {
   public static final String ENGINE_KEY = "XooEngine";
   public static final String SEVERITY = "MAJOR";
   public static final Long EFFORT = 10l;
-  public static final RuleType type = RuleType.BUG;
+  public static final RuleType TYPE = RuleType.BUG;
   public static final String ACTIVATE_EXTERNAL_ISSUES = "sonar.oneExternalIssuePerLine.activate";
   private static final String NAME = "One External Issue Per Line";
 
@@ -76,7 +76,7 @@ public class OneExternalIssuePerLineSensor implements Sensor {
           .message("This issue is generated on each line"))
         .severity(Severity.valueOf(SEVERITY))
         .remediationEffortMinutes(EFFORT)
-        .type(type)
+        .type(TYPE)
         .save();
     }
   }
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssueWithDetailsPerLineSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssueWithDetailsPerLineSensor.java
new file mode 100644 (file)
index 0000000..0598b7a
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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.xoo.rule;
+
+import org.sonar.api.batch.fs.FilePredicates;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.InputFile.Type;
+import org.sonar.api.batch.rule.Severity;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.issue.NewExternalIssue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
+import org.sonar.xoo.Xoo;
+import org.sonar.xoo.Xoo2;
+
+public class OneExternalIssueWithDetailsPerLineSensor implements Sensor {
+  public static final String RULE_KEY = "OneExternalIssueWithDetailsPerLine";
+  public static final String ENGINE_KEY = "XooEngine";
+  public static final String SEVERITY = "MAJOR";
+  public static final Long EFFORT = 10l;
+  public static final RuleType TYPE = RuleType.BUG;
+  public static final String ACTIVATE_EXTERNAL_ISSUES = "sonar.oneExternalIssueWithDetailsPerLine.activate";
+  private static final String NAME = "One External Issue Per Line";
+
+  @Override
+  public void describe(SensorDescriptor descriptor) {
+    descriptor
+      .name(NAME)
+      .onlyOnLanguages(Xoo.KEY, Xoo2.KEY)
+      .onlyWhenConfiguration(c -> c.getBoolean(ACTIVATE_EXTERNAL_ISSUES).orElse(false));
+  }
+
+  @Override
+  public void execute(SensorContext context) {
+    analyse(context, Xoo.KEY, XooRulesDefinition.XOO_REPOSITORY);
+    analyse(context, Xoo2.KEY, XooRulesDefinition.XOO2_REPOSITORY);
+  }
+
+  private void analyse(SensorContext context, String language, String repo) {
+    FileSystem fs = context.fileSystem();
+    FilePredicates p = fs.predicates();
+    for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(language), p.hasType(Type.MAIN)))) {
+      createIssues(file, context, repo);
+    }
+  }
+
+  private void createIssues(InputFile file, SensorContext context, String repo) {
+    RuleKey ruleKey = RuleKey.of(repo, RULE_KEY);
+    for (int line = 1; line <= file.lines(); line++) {
+      NewExternalIssue newIssue = context.newExternalIssue();
+      newIssue
+        .forRule(ruleKey)
+        .at(newIssue.newLocation()
+          .on(file)
+          .at(file.selectLine(line))
+          .message("This issue is generated on each line and the rule contains details"))
+        .severity(Severity.valueOf(SEVERITY))
+        .remediationEffortMinutes(EFFORT)
+        .type(TYPE)
+        .save();
+    }
+  }
+}
index 13c238cae7a586719b70a6c1640b69841d870fa4..4f119f9ebe0030e73cbf20686cc9b7aa2330d27f 100644 (file)
@@ -35,6 +35,7 @@ public class XooRulesDefinition implements RulesDefinition {
 
   public static final String XOO_REPOSITORY = "xoo";
   public static final String XOO2_REPOSITORY = "xoo2";
+  public static final String XOO_EXTERNAL_REPOSITORY = "xoo";
 
   private static final String TEN_MIN = "10min";
 
@@ -42,6 +43,7 @@ public class XooRulesDefinition implements RulesDefinition {
   public void define(Context context) {
     defineRulesXoo(context);
     defineRulesXoo2(context);
+    defineRulesXooExternal(context);
   }
 
   private static void defineRulesXoo2(Context context) {
@@ -156,4 +158,17 @@ public class XooRulesDefinition implements RulesDefinition {
 
   }
 
+  private static void defineRulesXooExternal(Context context) {
+    NewRepository repo = context.createExternalRepository(XOO_EXTERNAL_REPOSITORY, Xoo.KEY).setName("XooExternal");
+
+    repo.createRule(OneExternalIssueWithDetailsPerLineSensor.RULE_KEY)
+      .setSeverity(OneExternalIssueWithDetailsPerLineSensor.SEVERITY)
+      .setType(OneExternalIssueWithDetailsPerLineSensor.TYPE)
+      .setScope(RuleScope.ALL)
+      .setHtmlDescription("Generates one external issue in each line")
+      .setName("One external issue per line");
+
+    repo.done();
+  }
+
 }
index dacd5b3f575aa932a7c50316c648817b21bc84e3..e66d56e45c83ff12b999f5ab0853593db84fa6bf 100644 (file)
@@ -62,6 +62,6 @@ public class XooPluginTest {
     SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("7.2"), SonarQubeSide.SCANNER);
     Plugin.Context context = new PluginContextImpl.Builder().setSonarRuntime(runtime).build();
     new XooPlugin().define(context);
-    assertThat(context.getExtensions()).hasSize(54).contains(OneExternalIssuePerLineSensor.class);
+    assertThat(context.getExtensions()).hasSize(55).contains(OneExternalIssuePerLineSensor.class);
   }
 }
index 7de2430f0c15ac5215c0c7237804639110d12dbf..9658ab8e3a4f75b194d1262a6a5c0239b9e93e2d 100644 (file)
@@ -51,6 +51,15 @@ public class XooRulesDefinitionTest {
     assertThat(rule.debtRemediationFunction().baseEffort()).isNull();
     assertThat(rule.gapDescription()).isNotEmpty();
   }
+  
+  @Test
+  public void define_xooExternal_rules() {
+    RulesDefinition.Repository repo = context.repository("external_xoo");
+    assertThat(repo).isNotNull();
+    assertThat(repo.name()).isEqualTo("XooExternal");
+    assertThat(repo.language()).isEqualTo("xoo");
+    assertThat(repo.rules()).hasSize(1);
+  }
 
   @Test
   public void define_xoo2_rules() {
index ba7e348ea02087f11a1f3f3a30d7145a3b72b43e..51c5549ba3d0f392c206977e9338a750f55d4f4a 100644 (file)
@@ -392,7 +392,7 @@ public class RegisterRules implements Startable {
       .setSystemTags(ruleDef.tags())
       .setType(RuleType.valueOf(ruleDef.type().name()))
       .setScope(toDtoScope(ruleDef.scope()))
-      .setIsExternal(false)
+      .setIsExternal(ruleDef.repository().isExternal())
       .setCreatedAt(system2.now())
       .setUpdatedAt(system2.now());
     if (ruleDef.htmlDescription() != null) {
@@ -746,7 +746,6 @@ public class RegisterRules implements Startable {
     dbClient.ruleDao().update(session, rule);
   }
 
-
   private static void verifyRuleKeyConsistency(List<RulesDefinition.ExtendedRepository> repositories, RegisterRulesContext registerRulesContext) {
     List<RulesDefinition.Rule> definedRules = repositories.stream()
       .flatMap(r -> r.rules().stream())
index 580e22dc528f444aa3375d256fdd029fb2557616..2d70a76fa5e5659c40f5cafae3f2416e4726506e 100644 (file)
@@ -92,6 +92,8 @@ public class RegisterRulesTest {
   private static final Date DATE2 = DateUtils.parseDateTime("2014-02-01T12:10:03+0100");
   private static final Date DATE3 = DateUtils.parseDateTime("2014-03-01T12:10:03+0100");
 
+  private static final RuleKey EXTERNAL_RULE_KEY1 = RuleKey.of("external_eslint", "rule1");
+
   private static final RuleKey RULE_KEY1 = RuleKey.of("fake", "rule1");
   private static final RuleKey RULE_KEY2 = RuleKey.of("fake", "rule2");
   private static final RuleKey RULE_KEY3 = RuleKey.of("fake", "rule3");
@@ -148,6 +150,7 @@ public class RegisterRulesTest {
     assertThat(rule1.getDefRemediationBaseEffort()).isEqualTo("10h");
     assertThat(rule1.getType()).isEqualTo(RuleType.CODE_SMELL.getDbConstant());
     assertThat(rule1.getPluginKey()).isEqualTo(FAKE_PLUGIN_KEY);
+    assertThat(rule1.isExternal()).isFalse();
 
     List<RuleParamDto> params = dbClient.ruleDao().selectRuleParamsByRuleKey(dbTester.getSession(), RULE_KEY1);
     assertThat(params).hasSize(2);
@@ -163,6 +166,31 @@ public class RegisterRulesTest {
     assertThat(dbClient.ruleRepositoryDao().selectAll(dbTester.getSession())).extracting(RuleRepositoryDto::getKey).containsOnly("fake");
   }
 
+  @Test
+  public void insert_new_external_rule() {
+    execute(new ExternalRuleRepository());
+
+    // verify db
+    assertThat(dbClient.ruleDao().selectAllDefinitions(dbTester.getSession())).hasSize(1);
+    RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(dbTester.getSession(), dbTester.getDefaultOrganization(), EXTERNAL_RULE_KEY1);
+    assertThat(rule1.getName()).isEqualTo("One");
+    assertThat(rule1.getDescription()).isEqualTo("Description of One");
+    assertThat(rule1.getSeverityString()).isEqualTo(BLOCKER);
+    assertThat(rule1.getTags()).isEmpty();
+    assertThat(rule1.getSystemTags()).containsOnly("tag1", "tag2", "tag3");
+    assertThat(rule1.getConfigKey()).isEqualTo("config1");
+    assertThat(rule1.getStatus()).isEqualTo(RuleStatus.BETA);
+    assertThat(rule1.getCreatedAt()).isEqualTo(DATE1.getTime());
+    assertThat(rule1.getScope()).isEqualTo(Scope.ALL);
+    assertThat(rule1.getUpdatedAt()).isEqualTo(DATE1.getTime());
+    assertThat(rule1.getDefRemediationFunction()).isNull();
+    assertThat(rule1.getDefRemediationGapMultiplier()).isNull();
+    assertThat(rule1.getDefRemediationBaseEffort()).isNull();
+    assertThat(rule1.getType()).isEqualTo(RuleType.CODE_SMELL.getDbConstant());
+    assertThat(rule1.getPluginKey()).isEqualTo(FAKE_PLUGIN_KEY);
+    assertThat(rule1.isExternal()).isTrue();
+  }
+
   @Test
   public void insert_then_remove_rule() {
     String ruleKey = randomAlphanumeric(5);
@@ -496,7 +524,7 @@ public class RegisterRulesTest {
 
   @DataProvider
   public static Object[][] allRenamingCases() {
-    return new Object[][]{
+    return new Object[][] {
       {"repo1", "rule1", "repo1", "rule2"},
       {"repo1", "rule1", "repo2", "rule1"},
       {"repo1", "rule1", "repo2", "rule2"},
@@ -857,8 +885,7 @@ public class RegisterRulesTest {
 
     expectedException.expect(IllegalStateException.class);
     expectedException.expectMessage("An incorrect state of deprecated rule keys has been detected.\n " +
-      "The deprecated rule key [javascript:linelength] was previously deprecated by [javascript:s103]. [javascript:s103] should be a deprecated key of [sonarjs:s103],"
-    );
+      "The deprecated rule key [javascript:linelength] was previously deprecated by [javascript:s103]. [javascript:s103] should be a deprecated key of [sonarjs:s103],");
 
     // This rule should have been moved to another repository
     execute(context -> createRule(context, "javascript", "sonarjs", "s103",
@@ -992,6 +1019,23 @@ public class RegisterRulesTest {
     }
   }
 
+  static class ExternalRuleRepository implements RulesDefinition {
+    @Override
+    public void define(Context context) {
+      NewRepository repo = context.createExternalRepository("eslint", "js");
+      repo.createRule(RULE_KEY1.rule())
+        .setName("One")
+        .setHtmlDescription("Description of One")
+        .setSeverity(BLOCKER)
+        .setInternalKey("config1")
+        .setTags("tag1", "tag2", "tag3")
+        .setScope(RuleScope.ALL)
+        .setType(RuleType.CODE_SMELL)
+        .setStatus(RuleStatus.BETA);
+      repo.done();
+    }
+  }
+
   static class BigRepository implements RulesDefinition {
     static final int SIZE = 500;
 
index cb1ce8ceac372aaa05ecf8e9a11e259abac3617b..8c30bd2e82bde487a89dd87bbfedc0ebdef91d33 100644 (file)
@@ -397,7 +397,17 @@ public interface RulesDefinition {
      * to execute {@link org.sonar.api.server.rule.RulesDefinition.NewRepository#setName(String)}
      */
     public NewRepository createRepository(String key, String language) {
-      return new NewRepositoryImpl(this, key, language);
+      return new NewRepositoryImpl(this, key, language, false);
+    }
+
+    /**
+     * Creates a repository of rules from external rule engines.
+     * The key will always be prefixed with "external_".
+     * 
+     * @since 7.2
+     */
+    public NewRepository createExternalRepository(String key, String language) {
+      return new NewRepositoryImpl(this, key, language, true);
     }
 
     /**
@@ -472,19 +482,32 @@ public interface RulesDefinition {
 
   interface NewRepository extends NewExtendedRepository {
     NewRepository setName(String s);
+
+    /**
+     * @since 7.2
+     */
+    boolean isExternal();
   }
 
   class NewRepositoryImpl implements NewRepository {
     private final Context context;
     private final String key;
+    private final boolean isExternal;
     private String language;
     private String name;
     private final Map<String, NewRule> newRules = new HashMap<>();
 
-    private NewRepositoryImpl(Context context, String key, String language) {
+    private NewRepositoryImpl(Context context, String key, String language, boolean isExternal) {
       this.context = context;
-      this.key = this.name = key;
+      this.key = isExternal ? (RuleKey.EXTERNAL_RULE_REPO_PREFIX + key) : key;
+      this.name = key;
       this.language = language;
+      this.isExternal = isExternal;
+    }
+
+    @Override
+    public boolean isExternal() {
+      return isExternal;
     }
 
     @Override
@@ -550,6 +573,11 @@ public interface RulesDefinition {
 
   interface Repository extends ExtendedRepository {
     String name();
+
+    /**
+     * @since 7.2
+     */
+    boolean isExternal();
   }
 
   @Immutable
@@ -557,12 +585,13 @@ public interface RulesDefinition {
     private final String key;
     private final String language;
     private final String name;
+    private final boolean isExternal;
     private final Map<String, Rule> rulesByKey;
 
     private RepositoryImpl(NewRepositoryImpl newRepository, @Nullable Repository mergeInto) {
       this.key = newRepository.key;
       this.language = newRepository.language;
-
+      this.isExternal = newRepository.isExternal;
       Map<String, Rule> ruleBuilder = new HashMap<>();
       if (mergeInto != null) {
         if (!StringUtils.equals(newRepository.language, mergeInto.language()) || !StringUtils.equals(newRepository.key, mergeInto.key())) {
@@ -600,6 +629,11 @@ public interface RulesDefinition {
       return name;
     }
 
+    @Override
+    public boolean isExternal() {
+      return isExternal;
+    }
+
     @Override
     @CheckForNull
     public Rule rule(String ruleKey) {
@@ -1036,7 +1070,6 @@ public interface RulesDefinition {
 
     /**
      * @since 7.1
-     * @return
      */
     public RuleScope scope() {
       return scope;
index 8398f50a2aed9a0800eda05e05b271378281ea5f..0eefbb801aee11370456cb9dd7daebaff3eec61f 100644 (file)
@@ -103,6 +103,7 @@ public class RulesDefinitionTest {
 
     RulesDefinition.Repository repo = context.repository("findbugs");
     assertThat(repo.rules()).hasSize(2);
+    assertThat(repo.isExternal()).isFalse();
 
     RulesDefinition.Rule rule = repo.rule("NPE");
     assertThat(rule.scope()).isEqualTo(RuleScope.ALL);
@@ -165,6 +166,51 @@ public class RulesDefinitionTest {
     assertThat(rule.debtRemediationFunction()).isNull();
   }
 
+  @Test
+  public void define_external_rules() {
+    RulesDefinition.NewRepository newRepo = context.createExternalRepository("eslint", "js");
+    newRepo.createRule("NPE")
+      .setName("Detect NPE")
+      .setHtmlDescription("Detect <code>java.lang.NullPointerException</code>")
+      .setSeverity(Severity.BLOCKER)
+      .setInternalKey("/something")
+      .setStatus(RuleStatus.BETA)
+      .setTags("one", "two")
+      .setScope(RuleScope.ALL)
+      .addTags("two", "three", "four");
+
+    newRepo.createRule("ABC").setName("ABC").setMarkdownDescription("ABC");
+    newRepo.done();
+
+    assertThat(context.repository("eslint")).isNull();
+    RulesDefinition.Repository repo = context.repository("external_eslint");
+    assertThat(repo.rules()).hasSize(2);
+    assertThat(repo.isExternal()).isTrue();
+
+    RulesDefinition.Rule rule = repo.rule("NPE");
+    assertThat(rule.scope()).isEqualTo(RuleScope.ALL);
+    assertThat(rule.key()).isEqualTo("NPE");
+    assertThat(rule.name()).isEqualTo("Detect NPE");
+    assertThat(rule.severity()).isEqualTo(Severity.BLOCKER);
+    assertThat(rule.htmlDescription()).isEqualTo("Detect <code>java.lang.NullPointerException</code>");
+    assertThat(rule.markdownDescription()).isNull();
+    assertThat(rule.tags()).containsOnly("one", "two", "three", "four");
+    assertThat(rule.params()).isEmpty();
+    assertThat(rule.internalKey()).isEqualTo("/something");
+    assertThat(rule.template()).isFalse();
+    assertThat(rule.status()).isEqualTo(RuleStatus.BETA);
+    assertThat(rule.toString()).isEqualTo("[repository=external_eslint, key=NPE]");
+    assertThat(rule.repository()).isSameAs(repo);
+
+    RulesDefinition.Rule otherRule = repo.rule("ABC");
+    assertThat(otherRule.htmlDescription()).isNull();
+    assertThat(otherRule.markdownDescription()).isEqualTo("ABC");
+
+    // test equals() and hashCode()
+    assertThat(rule).isEqualTo(rule).isNotEqualTo(otherRule).isNotEqualTo("NPE").isNotEqualTo(null);
+    assertThat(rule.hashCode()).isEqualTo(rule.hashCode());
+  }
+
   @Test
   public void define_rule_parameters() {
     RulesDefinition.NewRepository newFindbugs = context.createRepository("findbugs", "java");
index 5aa33193a6b4cfb2a20823e979fd0126cb51cc39..c71120b7adec74d31e9b59b284cd546336378698 100644 (file)
@@ -79,6 +79,33 @@ public class ExternalIssueTest {
     assertThat(issuesList).hasSize(17);
   }
 
+  @Test
+  public void should_import_external_issues_with_details_provided_by_code_analyzers() {
+    noIssues();
+    ruleExistsWithDetails("external_xoo:OneExternalIssueWithDetailsPerLine");
+
+    ItUtils.runProjectAnalysis(orchestrator, "shared/xoo-sample",
+      "sonar.oneExternalIssueWithDetailsPerLine.activate", "true");
+    List<Issue> issuesList = tester.wsClient().issues().search(new SearchRequest()).getIssuesList();
+    assertThat(issuesList).hasSize(17);
+
+    assertThat(issuesList).allMatch(issue -> "external_xoo:OneExternalIssueWithDetailsPerLine".equals(issue.getRule()));
+    assertThat(issuesList).allMatch(issue -> "This issue is generated on each line and the rule contains details".equals(issue.getMessage()));
+    assertThat(issuesList).allMatch(issue -> Severity.MAJOR.equals(issue.getSeverity()));
+    assertThat(issuesList).allMatch(issue -> RuleType.BUG.equals(issue.getType()));
+    assertThat(issuesList).allMatch(issue -> "sample:src/main/xoo/sample/Sample.xoo".equals(issue.getComponent()));
+    assertThat(issuesList).allMatch(issue -> "OPEN".equals(issue.getStatus()));
+    assertThat(issuesList).allMatch(issue -> issue.getExternalRuleEngine().equals("xoo"));
+
+    ruleExistsWithDetails("external_xoo:OneExternalIssueWithDetailsPerLine");
+
+    // second analysis, issue tracking should work
+    ItUtils.runProjectAnalysis(orchestrator, "shared/xoo-sample",
+      "sonar.oneExternalIssueWithDetailsPerLine.activate", "true");
+    issuesList = tester.wsClient().issues().search(new SearchRequest()).getIssuesList();
+    assertThat(issuesList).hasSize(17);
+  }
+
   @Test
   public void should_import_external_issues_from_json_report_and_create_external_rules() {
     noIssues();
@@ -127,7 +154,7 @@ public class ExternalIssueTest {
 
   }
 
-  private void ruleExists(String key) {
+  private org.sonarqube.ws.Rules.Rule ruleExists(String key) {
     List<org.sonarqube.ws.Rules.Rule> rulesList = tester.wsClient().rules()
       .search(new org.sonarqube.ws.client.rules.SearchRequest()
         .setRuleKey(key)
@@ -140,6 +167,23 @@ public class ExternalIssueTest {
     assertThat(rulesList.get(0).getIsExternal()).isTrue();
     assertThat(rulesList.get(0).getTags().getTagsCount()).isEqualTo(0);
     assertThat(rulesList.get(0).getScope()).isEqualTo(RuleScope.ALL);
+
+    // if flag to include external is not given, shouldn't be able to find it
+    List<org.sonarqube.ws.Rules.Rule> rulesListWithoutExternal = tester.wsClient().rules()
+      .search(new org.sonarqube.ws.client.rules.SearchRequest()
+        .setRuleKey(key))
+      .getRulesList();
+    assertThat(rulesListWithoutExternal).isEmpty();
+
+    return rulesList.get(0);
+  }
+
+  private void ruleExistsWithDetails(String key) {
+    org.sonarqube.ws.Rules.Rule rule = ruleExists(key);
+    assertThat(rule.getHtmlDesc()).isEqualTo("Generates one external issue in each line");
+    assertThat(rule.getSeverity()).isEqualTo("MAJOR");
+    assertThat(rule.getType()).isEqualTo(RuleType.BUG);
+    assertThat(rule.getName()).isEqualTo("One external issue per line");
   }
 
   private void noIssues() {