]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12725 drop manual vulnerabilities
authorJacek <jacek.poreda@sonarsource.com>
Fri, 6 Dec 2019 09:11:44 +0000 (10:11 +0100)
committerSonarTech <sonartech@sonarsource.com>
Mon, 13 Jan 2020 19:46:26 +0000 (20:46 +0100)
* drop manual vulnerabilities
* remove issues `from_hotspot` column usage

27 files changed:
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesLoaderTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueMapperTest.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/MigrateManualVulnerabilitiesToSecurityHotSpots.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81Test.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/MigrateManualVulnerabilitiesToSecurityHotSpotsTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/MigrateManualVulnerabilitiesToSecurityHotSpotsTest/schema.sql [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IsManualVulnerability.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IsNotHotspot.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IsNotHotspotNorManualVulnerability.java [deleted file]
server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForSecurityHotspotsTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/SetTypeAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/DefaultTransitions.java

index 4505d7489971446735172f0223fd2df1887abb56..7c01c43f0130be7d3b067ee83380982c67bdcf2e 100644 (file)
@@ -76,7 +76,6 @@ public class IssueLifecycle {
     issue.setCreationDate(changeContext.date());
     issue.setUpdateDate(changeContext.date());
     issue.setEffort(debtCalculator.calculate(issue));
-    issue.setIsFromHotspot(rule.getType() == RuleType.SECURITY_HOTSPOT);
     setType(issue, rule);
     setStatus(issue, rule);
   }
@@ -165,20 +164,9 @@ public class IssueLifecycle {
       // In case issue was moved from module or folder to the root project
       raw.setChanged(true);
     }
-    raw.setIsFromHotspot(rule.getType() == RuleType.SECURITY_HOTSPOT);
     setType(raw, rule);
     copyFields(raw, base);
     base.changes().forEach(raw::addChange);
-    if (raw.isFromHotspot() != base.isFromHotspot()) {
-      // This is to force DB update of the issue
-      raw.setChanged(true);
-    }
-    if (raw.isFromHotspot() && !base.isFromHotspot()) {
-      // First analysis after rule type was changed to security_hotspot. Issue will be reset to an open hotspot
-      updater.setType(raw, RuleType.SECURITY_HOTSPOT, changeContext);
-      updater.setStatus(raw, Issue.STATUS_TO_REVIEW, changeContext);
-      updater.setResolution(raw, null, changeContext);
-    }
 
     if (base.manualSeverity()) {
       raw.setManualSeverity(true);
index 746dd126efcdab86d6cbd0f32e56a52f6edb4bc5..09e165377ff2ec6dca108fe636c44bdf091233a1 100644 (file)
@@ -80,7 +80,7 @@ public class ComponentIssuesLoaderTest {
     ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
     RuleDefinitionDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
     Date issueDate = addDays(NOW, -10);
-    IssueDto issue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL).setIsFromHotspot(false));
+    IssueDto issue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
     db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(issueDate, 10));
     db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 3), 20));
     db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 1), 30));
@@ -100,7 +100,7 @@ public class ComponentIssuesLoaderTest {
     ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
     RuleDefinitionDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
     Date issueDate = addDays(NOW, -10);
-    IssueDto issue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL).setIsFromHotspot(false));
+    IssueDto issue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
     db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(issueDate, 10));
     db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 2), null));
     db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 1), 30));
@@ -120,9 +120,9 @@ public class ComponentIssuesLoaderTest {
     ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
     RuleDefinitionDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
     Date issueDate = addDays(NOW, -10);
-    IssueDto closedIssue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL).setIsFromHotspot(false));
+    IssueDto closedIssue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
     db.issues().insertFieldDiffs(closedIssue, newToClosedDiffsWithLine(issueDate, 10));
-    IssueDto issueNoCloseDate = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIsFromHotspot(false));
+    IssueDto issueNoCloseDate = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED));
     db.issues().insertFieldDiffs(issueNoCloseDate, newToClosedDiffsWithLine(issueDate, 10));
     when(system2.now()).thenReturn(NOW.getTime());
 
@@ -198,7 +198,7 @@ public class ComponentIssuesLoaderTest {
     };
     IssueDto[] issues = Arrays.stream(issueDates)
       .map(issueDate -> {
-        IssueDto closedIssue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL).setIsFromHotspot(false));
+        IssueDto closedIssue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
         db.issues().insertFieldDiffs(closedIssue, newToClosedDiffsWithLine(issueDate, 10));
         return closedIssue;
       })
index 0ce772abef6926482ffeb3809cce1a2a4db78971..56629df1fe2cead9cf14e09b6a4d311b8bf59794 100644 (file)
@@ -87,7 +87,6 @@ public class IssueLifecycleTest {
     assertThat(issue.effort()).isEqualTo(DEFAULT_DURATION);
     assertThat(issue.isNew()).isTrue();
     assertThat(issue.isCopied()).isFalse();
-    assertThat(issue.isFromHotspot()).isFalse();
   }
 
   @Test
@@ -107,7 +106,6 @@ public class IssueLifecycleTest {
     assertThat(issue.effort()).isEqualTo(DEFAULT_DURATION);
     assertThat(issue.isNew()).isTrue();
     assertThat(issue.isCopied()).isFalse();
-    assertThat(issue.isFromHotspot()).isTrue();
   }
 
   @Test
@@ -303,70 +301,6 @@ public class IssueLifecycleTest {
     verify(updater).setPastLocations(raw, issueLocations);
   }
 
-  @Test
-  public void mergeExistingOpenIssue_vulnerability_changed_to_hotspot_should_be_to_review() {
-    rule.setType(RuleType.SECURITY_HOTSPOT);
-    DefaultIssue raw = new DefaultIssue()
-      .setNew(true)
-      .setKey("RAW_KEY")
-      .setRuleKey(XOO_X1)
-      .setCreationDate(parseDate("2015-10-01"))
-      .setUpdateDate(parseDate("2015-10-02"))
-      .setCloseDate(parseDate("2015-10-03"));
-
-    DbIssues.Locations issueLocations = DbIssues.Locations.newBuilder()
-      .setTextRange(DbCommons.TextRange.newBuilder()
-        .setStartLine(10)
-        .setEndLine(12)
-        .build())
-      .build();
-    DefaultIssue base = new DefaultIssue()
-      .setKey("BASE_KEY")
-      .setType(RuleType.VULNERABILITY)
-      // First analysis before rule was changed to hotspot
-      .setIsFromHotspot(false)
-      .setCreationDate(parseDate("2015-01-01"))
-      .setUpdateDate(parseDate("2015-01-02"))
-      .setResolution(RESOLUTION_FALSE_POSITIVE)
-      .setStatus(STATUS_RESOLVED)
-      .setSeverity(BLOCKER)
-      .setAssigneeUuid("base assignee uuid")
-      .setAuthorLogin("base author")
-      .setTags(newArrayList("base tag"))
-      .setSelectedAt(1000L)
-      .setLine(10)
-      .setMessage("message")
-      .setGap(15d)
-      .setEffort(Duration.create(15L))
-      .setManualSeverity(false)
-      .setLocations(issueLocations);
-
-    when(debtCalculator.calculate(raw)).thenReturn(DEFAULT_DURATION);
-
-    underTest.mergeExistingOpenIssue(raw, base);
-
-    assertThat(raw.isNew()).isFalse();
-    assertThat(raw.key()).isEqualTo("BASE_KEY");
-    assertThat(raw.creationDate()).isEqualTo(base.creationDate());
-    assertThat(raw.updateDate()).isEqualTo(base.updateDate());
-    assertThat(raw.assignee()).isEqualTo("base assignee uuid");
-    assertThat(raw.authorLogin()).isEqualTo("base author");
-    assertThat(raw.tags()).containsOnly("base tag");
-    assertThat(raw.effort()).isEqualTo(DEFAULT_DURATION);
-    assertThat(raw.selectedAt()).isEqualTo(1000L);
-    assertThat(raw.isFromHotspot()).isTrue();
-    assertThat(raw.isChanged()).isTrue();
-
-    verify(updater).setType(raw, RuleType.SECURITY_HOTSPOT, issueChangeContext);
-    verify(updater).setStatus(raw, STATUS_TO_REVIEW, issueChangeContext);
-    verify(updater).setResolution(raw, null, issueChangeContext);
-    verify(updater).setPastSeverity(raw, BLOCKER, issueChangeContext);
-    verify(updater).setPastLine(raw, 10);
-    verify(updater).setPastMessage(raw, "message", issueChangeContext);
-    verify(updater).setPastEffort(raw, Duration.create(15L), issueChangeContext);
-    verify(updater).setPastLocations(raw, issueLocations);
-  }
-
   @Test
   public void mergeExistingOpenIssue_with_manual_severity() {
     DefaultIssue raw = new DefaultIssue()
index de6a05fe6a9e43a328aea0ffb6d323a65a4cff66..2c151d26e79198d8a607a70ee32dc4a512baf861 100644 (file)
@@ -122,7 +122,6 @@ public final class IssueDto implements Serializable {
       .setRuleId(ruleId)
       .setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
       .setExternal(issue.isFromExternalRuleEngine())
-      .setIsFromHotspot(issue.isFromHotspot())
       .setTags(issue.tags())
       .setComponentUuid(issue.componentUuid())
       .setComponentKey(issue.componentKey())
@@ -171,7 +170,6 @@ public final class IssueDto implements Serializable {
       .setAuthorLogin(issue.authorLogin())
       .setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
       .setExternal(issue.isFromExternalRuleEngine())
-      .setIsFromHotspot(issue.isFromHotspot())
       .setTags(issue.tags())
       .setComponentUuid(issue.componentUuid())
       .setComponentKey(issue.componentKey())
@@ -489,15 +487,6 @@ public final class IssueDto implements Serializable {
     return this;
   }
 
-  public boolean isFromHotspot() {
-    return isFromHotspot;
-  }
-
-  public IssueDto setIsFromHotspot(boolean value) {
-    isFromHotspot = value;
-    return this;
-  }
-
   public String getComponentKey() {
     return componentKey;
   }
@@ -742,7 +731,6 @@ public final class IssueDto implements Serializable {
     issue.setSelectedAt(selectedAt);
     issue.setLocations(parseLocations());
     issue.setIsFromExternalRuleEngine(isExternal);
-    issue.setIsFromHotspot(isFromHotspot);
     return issue;
   }
 }
index 8aba7f1cd67f6cb785c2eb024eb9b0d541089791..37ea21fbe49d6e7b0df9f10d94d5853555774de0 100644 (file)
@@ -38,8 +38,7 @@
     p.path as filePath,
     root.kee as projectKey,
     i.project_uuid as projectUuid,
-    i.issue_type as type,
-    i.from_hotspot as "isFromHotspot"
+    i.issue_type as type
   </sql>
 
   <sql id="sortColumn">
@@ -96,8 +95,7 @@
     p.scope,
     p.organization_uuid as "organizationUuid",
     i.tags,
-    i.issue_type as "issueType",
-    i.from_hotspot as "isFromHotspot"
+    i.issue_type as "issueType"
   </sql>
 
 
     INSERT INTO issues (kee, rule_id, severity, manual_severity,
     message, line, locations, gap, effort, status, tags,
     resolution, checksum, assignee, author_login, issue_attributes, issue_creation_date, issue_update_date,
-    issue_close_date, created_at, updated_at, component_uuid, project_uuid, issue_type, from_hotspot)
+    issue_close_date, created_at, updated_at, component_uuid, project_uuid, issue_type)
     VALUES (#{kee,jdbcType=VARCHAR}, #{ruleId,jdbcType=INTEGER},
     #{severity,jdbcType=VARCHAR},
     #{manualSeverity,jdbcType=BOOLEAN}, #{message,jdbcType=VARCHAR}, #{line,jdbcType=INTEGER},
     #{issueAttributes,jdbcType=VARCHAR},
     #{issueCreationTime,jdbcType=BIGINT},#{issueUpdateTime,jdbcType=BIGINT}, #{issueCloseTime,jdbcType=BIGINT},
     #{createdAt,jdbcType=BIGINT}, #{updatedAt,jdbcType=BIGINT},
-    #{componentUuid,jdbcType=VARCHAR}, #{projectUuid,jdbcType=VARCHAR}, #{type,jdbcType=INTEGER}, #{isFromHotspot,jdbcType=BOOLEAN})
+    #{componentUuid,jdbcType=VARCHAR}, #{projectUuid,jdbcType=VARCHAR}, #{type,jdbcType=INTEGER})
   </insert>
 
   <!--
     issue_update_date=#{issueUpdateTime,jdbcType=BIGINT},
     issue_close_date=#{issueCloseTime,jdbcType=BIGINT},
     updated_at=#{updatedAt,jdbcType=BIGINT},
-    issue_type=#{type,jdbcType=INTEGER},
-    from_hotspot=#{isFromHotspot,jdbcType=BOOLEAN}
+    issue_type=#{type,jdbcType=INTEGER}
     where kee = #{kee}
   </update>
 
     issue_update_date=#{issueUpdateTime,jdbcType=BIGINT},
     issue_close_date=#{issueCloseTime,jdbcType=BIGINT},
     updated_at=#{updatedAt,jdbcType=BIGINT},
-    issue_type=#{type,jdbcType=INTEGER},
-    from_hotspot=#{isFromHotspot,jdbcType=BOOLEAN}
+    issue_type=#{type,jdbcType=INTEGER}
     where kee = #{kee} and updated_at &lt;= #{selectedAt}
   </update>
 
     (r.is_external is NULL or r.is_external = ${_false}) and
     i.component_uuid = #{componentUuid,jdbcType=VARCHAR} and
     i.status &lt;&gt; 'CLOSED' and
-    i.issue_type &lt;&gt; 4 and (i.from_hotspot is NULL or i.from_hotspot = ${_false})
+    i.issue_type &lt;&gt; 4
   </select>
 
   <select id="scrollClosedByComponentUuid" resultType="Issue" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
       and i.issue_close_date is not null
       and i.issue_close_date >= #{closeDateAfter,jdbcType=BIGINT}
       and i.issue_type &lt;&gt; 4
-      and (i.from_hotspot is null or i.from_hotspot = ${_false})
     order by
       i.kee, ic.issue_change_creation_date desc
   </select>
     i.project_uuid = #{projectUuid, jdbcType=VARCHAR} and
     p.module_uuid_path like  #{likeModuleUuidPath, jdbcType=VARCHAR} escape '/' and
     i.status &lt;&gt; 'CLOSED' and
-    i.issue_type &lt;&gt; 4 and (i.from_hotspot is NULL or i.from_hotspot = ${_false})
+    i.issue_type &lt;&gt; 4
   </select>
 
   <select id="selectIssueGroupsByBaseComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map">
index b390b33913da650923b327cf7a2d3e23baa9bed2..8d661bed97b9fb4b33b03eb7ee0c73272e588206 100644 (file)
@@ -142,7 +142,6 @@ public class IssueDaoTest {
     IssueDto openIssueOnProject = db.issues().insert(rule, project, project, i -> i.setStatus("OPEN").setResolution(null).setType(randomRuleTypeExceptHotspot()));
 
     IssueDto securityHotspot = db.issues().insert(rule, project, file, i -> i.setType(RuleType.SECURITY_HOTSPOT));
-    IssueDto manualVulnerability = db.issues().insert(rule, project, file, i -> i.setType(RuleType.VULNERABILITY).setIsFromHotspot(true));
 
     RuleDefinitionDto external = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto.setIsExternal(true));
     IssueDto issueFromExteralruleOnFile = db.issues().insert(external, project, file, i -> i.setKee("ON_FILE_FROM_EXTERNAL").setType(randomRuleTypeExceptHotspot()));
@@ -174,7 +173,6 @@ public class IssueDaoTest {
       i -> i.setStatus("OPEN").setResolution(null).setType(randomRuleTypeExceptHotspot()));
 
     IssueDto securityHotspot = db.issues().insert(rule, project, file, i -> i.setType(RuleType.SECURITY_HOTSPOT));
-    IssueDto manualVulnerability = db.issues().insert(rule, project, file, i -> i.setType(RuleType.VULNERABILITY).setIsFromHotspot(true));
 
     RuleDefinitionDto external = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto.setIsExternal(true));
     IssueDto issueFromExteralruleOnFile = db.issues().insert(external, project, file, i -> i.setKee("ON_FILE_FROM_EXTERNAL").setType(randomRuleTypeExceptHotspot()));
index 76e3401e99565dcccbc4e95594d05b5242bd1284..8e7b4ab7b4c17e77b69e8e10a7db4e45f462845c 100644 (file)
@@ -340,27 +340,6 @@ public class IssueMapperTest {
       .containsOnly(tuple(issue.getKey(), issueChange.getChangeData()));
   }
 
-  @Test
-  public void scrollClosedByComponentUuid_returns_closed_issues_without_isHotspot_flag() {
-    RuleType ruleType = randomSupportedRuleType();
-    OrganizationDto organization = dbTester.organizations().insert();
-    ComponentDto component = randomComponent(organization);
-    IssueDto noHotspotFlagIssue = insertNewClosedIssue(component, ruleType);
-    IssueChangeDto noFlagIssueChange = insertToClosedDiff(noHotspotFlagIssue);
-    manuallySetToNullFromHotpotsColumn(noHotspotFlagIssue);
-    IssueDto issue = insertNewClosedIssue(component, ruleType);
-    IssueChangeDto issueChange = insertToClosedDiff(issue);
-
-    RecorderResultHandler resultHandler = new RecorderResultHandler();
-    underTest.scrollClosedByComponentUuid(component.uuid(), NO_FILTERING_ON_CLOSE_DATE, resultHandler);
-
-    assertThat(resultHandler.issues)
-      .extracting(IssueDto::getKey, t -> t.getClosedChangeData().get())
-      .containsOnly(
-        tuple(issue.getKey(), issueChange.getChangeData()),
-        tuple(noHotspotFlagIssue.getKey(), noFlagIssueChange.getChangeData()));
-  }
-
   @Test
   public void scrollClosedByComponentUuid_does_not_return_closed_issues_without_close_date() {
     RuleType ruleType = randomSupportedRuleType();
@@ -428,29 +407,6 @@ public class IssueMapperTest {
       .containsOnly(issues[3].getKey(), issues[1].getKey(), issues[2].getKey(), issues[0].getKey());
   }
 
-  private void manuallySetToNullFromHotpotsColumn(IssueDto fromHostSpotIssue) {
-    dbTester.executeUpdateSql("update issues set from_hotspot = null where kee = '" + fromHostSpotIssue.getKey() + "'");
-    dbTester.commit();
-  }
-
-  @Test
-  @UseDataProvider("closedIssuesSupportedRuleTypes")
-  public void scrollClosedByComponentUuid_does_not_return_closed_issues_with_isHotspot_flag_true(RuleType ruleType) {
-    OrganizationDto organization = dbTester.organizations().insert();
-    ComponentDto component = randomComponent(organization);
-    IssueDto fromHostSpotIssue = insertNewClosedIssue(component, ruleType, t -> t.setIsFromHotspot(true));
-    insertToClosedDiff(fromHostSpotIssue);
-    IssueDto issue = insertNewClosedIssue(component, ruleType);
-    IssueChangeDto issueChange = insertToClosedDiff(issue);
-
-    RecorderResultHandler resultHandler = new RecorderResultHandler();
-    underTest.scrollClosedByComponentUuid(component.uuid(), NO_FILTERING_ON_CLOSE_DATE, resultHandler);
-
-    assertThat(resultHandler.issues)
-      .extracting(IssueDto::getKey, t -> t.getClosedChangeData().get())
-      .containsOnly(tuple(issue.getKey(), issueChange.getChangeData()));
-  }
-
   @Test
   @UseDataProvider("closedIssuesSupportedRuleTypes")
   public void scrollClosedByComponentUuid_return_one_row_per_status_diff_to_CLOSED_sorted_by_most_recent_creation_date_first(RuleType ruleType) {
index f9a518f89788dcfce36e4da55ed76bde47e83a09..f09d1c15fa76c4a6328713a4f4635dc1e2d6b840 100644 (file)
@@ -43,6 +43,7 @@ public class DbVersion81 implements DbVersion {
       .add(3112, "Migrate short and long living branches types to common BRANCH type", MigrateSlbsAndLlbsToCommonType.class)
       .add(3113, "Migrate short and long living branches types to common BRANCH type in ce tasks table",
         MigrateSlbsAndLlbsToCommonTypeInCeTasks.class)
-      .add(3114, "Drop 'In Review' Security Hotspots status ", DropSecurityHotSpotsInReviewStatus.class);
+      .add(3114, "Drop 'In Review' Security Hotspots status ", DropSecurityHotSpotsInReviewStatus.class)
+      .add(3115, "Migrate Manual Vulnerabilities to Security Hotspots ", MigrateManualVulnerabilitiesToSecurityHotSpots.class);
   }
 }
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/MigrateManualVulnerabilitiesToSecurityHotSpots.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/MigrateManualVulnerabilitiesToSecurityHotSpots.java
new file mode 100644 (file)
index 0000000..8c3e16a
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.platform.db.migration.version.v81;
+
+import java.sql.SQLException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+
+import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
+import static org.sonar.api.rules.RuleType.VULNERABILITY;
+
+public class MigrateManualVulnerabilitiesToSecurityHotSpots extends DataChange {
+  private System2 system;
+
+  public MigrateManualVulnerabilitiesToSecurityHotSpots(Database db, System2 system) {
+    super(db);
+    this.system = system;
+  }
+
+  @Override
+  protected void execute(Context context) throws SQLException {
+    MassUpdate updateIssues = context.prepareMassUpdate();
+    updateIssues.select("select id, kee, project_uuid, component_uuid from issues where from_hotspot = ? and issue_type = ?")
+      .setBoolean(1, true)
+      .setInt(2, 3);
+    updateIssues.update("update issues set issue_type = ?, status = ? where id = ? and from_hotspot = ? and issue_type = ?");
+    updateIssues.update("insert into issue_changes(issue_key, change_type, change_data, created_at, updated_at, issue_change_creation_date) " +
+      "VALUES(?, ?, ?, ?, ?, ?)");
+
+    updateIssues.execute((row, update, updateIndex) -> {
+      if (updateIndex == 0) {
+        update.setInt(1, 4)
+          .setString(2, STATUS_TO_REVIEW)
+          .setLong(3, row.getLong(1))
+          .setBoolean(4, true)
+          .setInt(5, VULNERABILITY.getDbConstant());
+      } else if (updateIndex == 1) {
+        long currentTime = system.now();
+        update.setString(1, row.getString(2))
+          .setString(2, "diff")
+          .setString(3, "type=VULNERABILITY|SECURITY_HOTSPOT,status=OPEN|TO_REVIEW")
+          .setLong(4, currentTime)
+          .setLong(5, currentTime)
+          .setLong(6, currentTime);
+      }
+      return true;
+    });
+  }
+}
index 05e5efc4be1e82106942be56cc8982b39be85b7f..1bbfa86ae5597558edef94876e448d32cd81e474 100644 (file)
@@ -36,7 +36,7 @@ public class DbVersion81Test {
 
   @Test
   public void verify_migration_count() {
-    verifyMigrationCount(underTest, 15);
+    verifyMigrationCount(underTest, 16);
   }
 
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/MigrateManualVulnerabilitiesToSecurityHotSpotsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/MigrateManualVulnerabilitiesToSecurityHotSpotsTest.java
new file mode 100644 (file)
index 0000000..975e3ea
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.platform.db.migration.version.v81;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.DataChange;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.rules.RuleType.BUG;
+import static org.sonar.api.rules.RuleType.CODE_SMELL;
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
+import static org.sonar.api.rules.RuleType.VULNERABILITY;
+
+public class MigrateManualVulnerabilitiesToSecurityHotSpotsTest {
+
+  private final static String ISSUES_TABLE_NAME = "issues";
+  private final static int TOTAL_NUMBER_OF_ISSUES = 9;
+
+  @Rule
+  public CoreDbTester db = CoreDbTester.createForSchema(MigrateManualVulnerabilitiesToSecurityHotSpotsTest.class, "schema.sql");
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private System2 system2 = System2.INSTANCE;
+
+  private DataChange underTest = new MigrateManualVulnerabilitiesToSecurityHotSpots(db.database(), system2);
+
+  @Test
+  public void should_migrate_manual_vulnerabilities_only() throws SQLException {
+    Random random = new Random();
+    List<Integer> range = IntStream.range(0, TOTAL_NUMBER_OF_ISSUES).boxed()
+      .collect(Collectors.toCollection(ArrayList::new));
+    Collections.shuffle(range);
+
+    insertIssue(range.get(0), CODE_SMELL.getDbConstant(), random.nextBoolean());
+    insertIssue(range.get(1), BUG.getDbConstant(), random.nextBoolean());
+    insertIssue(range.get(2), VULNERABILITY.getDbConstant(), false);
+    insertIssue(range.get(3), SECURITY_HOTSPOT.getDbConstant(), random.nextBoolean());
+    insertIssue(range.get(4), -1, random.nextBoolean());
+
+    insertIssue(range.get(5), VULNERABILITY.getDbConstant(), true);
+    insertIssue(range.get(6), VULNERABILITY.getDbConstant(), true);
+    insertIssue(range.get(7), VULNERABILITY.getDbConstant(), true);
+    insertIssue(range.get(8), VULNERABILITY.getDbConstant(), true);
+
+    underTest.execute();
+
+    assertIssueNotChanged(range.get(0), CODE_SMELL.getDbConstant());
+    assertIssueNotChanged(range.get(1), BUG.getDbConstant());
+    assertIssueNotChanged(range.get(2), VULNERABILITY.getDbConstant());
+    assertIssueNotChanged(range.get(3), SECURITY_HOTSPOT.getDbConstant());
+    assertIssueNotChanged(range.get(4), -1);
+
+    assertIssueChanged(range.get(5));
+    assertIssueChanged(range.get(6));
+    assertIssueChanged(range.get(7));
+    assertIssueChanged(range.get(8));
+
+    // should not fail if executed twice
+    underTest.execute();
+  }
+
+  @Test
+  public void should_not_fail_if_no_issues() throws SQLException {
+    underTest.execute();
+    assertThat(db.countRowsOfTable("issues")).isEqualTo(0);
+  }
+
+  private void assertIssueChanged(int issueId) {
+    List<Map<String, Object>> row = db.select(String.format("select status from issues where kee = '%s'", "issue-key-" + issueId));
+    assertThat(row).hasSize(1);
+    assertThat(row.get(0).get("STATUS"))
+      .isEqualTo("TO_REVIEW");
+
+    List<Map<String, Object>> changelogRows = db.select(String.format("select change_type, change_data, created_at, updated_at, issue_change_creation_date" +
+      " from issue_changes where issue_key = '%s'", "issue-key-" + issueId));
+    assertThat(changelogRows).hasSize(1);
+
+    Map<String, Object> changelogRow = changelogRows.get(0);
+    assertThat(changelogRow.get("CHANGE_TYPE")).isEqualTo("diff");
+    assertThat(changelogRow.get("CHANGE_DATA")).isEqualTo("type=VULNERABILITY|SECURITY_HOTSPOT,status=OPEN|TO_REVIEW");
+
+    assertThat(changelogRow.get("CREATED_AT")).isNotNull();
+    assertThat(changelogRow.get("UPDATED_AT")).isNotNull();
+    assertThat(changelogRow.get("ISSUE_CHANGE_CREATION_DATE")).isNotNull();
+  }
+
+  private void assertIssueNotChanged(int issueId, int expectedType) {
+    List<Map<String, Object>> row = db.select(String.format("select issue_type, status from issues where kee = '%s'", "issue-key-" + issueId));
+    assertThat(row).hasSize(1);
+
+    Map<String, Object> issueData = row.get(0);
+    assertThat(issueData.get("STATUS"))
+      .isNull();
+    assertThat(issueData.get("ISSUE_TYPE"))
+      .isEqualTo(expectedType);
+
+    List<Map<String, Object>> changelogRows = db.select(String.format("select change_type, change_data, created_at, updated_at, issue_change_creation_date" +
+      " from issue_changes where issue_key = '%s'", "issue-key-" + issueId));
+    assertThat(changelogRows).isEmpty();
+  }
+
+  private void insertIssue(int issueId, int issueType, boolean fromHotspot) {
+    db.executeInsert(ISSUES_TABLE_NAME,
+      "kee", "issue-key-" + issueId,
+      "issue_type", issueType,
+      "from_hotspot", fromHotspot,
+      "manual_severity", false);
+  }
+
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/MigrateManualVulnerabilitiesToSecurityHotSpotsTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/MigrateManualVulnerabilitiesToSecurityHotSpotsTest/schema.sql
new file mode 100644 (file)
index 0000000..d8f2d43
--- /dev/null
@@ -0,0 +1,54 @@
+CREATE TABLE "ISSUES"(
+    "ID" BIGINT NOT NULL AUTO_INCREMENT (1,1),
+    "KEE" VARCHAR(50) NOT NULL,
+    "RULE_ID" INTEGER,
+    "SEVERITY" VARCHAR(10),
+    "MANUAL_SEVERITY" BOOLEAN NOT NULL,
+    "MESSAGE" VARCHAR(4000),
+    "LINE" INTEGER,
+    "GAP" DOUBLE,
+    "STATUS" VARCHAR(20),
+    "RESOLUTION" VARCHAR(20),
+    "CHECKSUM" VARCHAR(1000),
+    "REPORTER" VARCHAR(255),
+    "ASSIGNEE" VARCHAR(255),
+    "AUTHOR_LOGIN" VARCHAR(255),
+    "ACTION_PLAN_KEY" VARCHAR(50),
+    "ISSUE_ATTRIBUTES" VARCHAR(4000),
+    "EFFORT" INTEGER,
+    "CREATED_AT" BIGINT,
+    "UPDATED_AT" BIGINT,
+    "ISSUE_CREATION_DATE" BIGINT,
+    "ISSUE_UPDATE_DATE" BIGINT,
+    "ISSUE_CLOSE_DATE" BIGINT,
+    "TAGS" VARCHAR(4000),
+    "COMPONENT_UUID" VARCHAR(50),
+    "PROJECT_UUID" VARCHAR(50),
+    "LOCATIONS" BLOB,
+    "ISSUE_TYPE" TINYINT,
+    "FROM_HOTSPOT" BOOLEAN
+);
+ALTER TABLE "ISSUES" ADD CONSTRAINT "PK_ISSUES" PRIMARY KEY("ID");
+CREATE INDEX "ISSUES_ASSIGNEE" ON "ISSUES"("ASSIGNEE");
+CREATE INDEX "ISSUES_COMPONENT_UUID" ON "ISSUES"("COMPONENT_UUID");
+CREATE INDEX "ISSUES_CREATION_DATE" ON "ISSUES"("ISSUE_CREATION_DATE");
+CREATE UNIQUE INDEX "ISSUES_KEE" ON "ISSUES"("KEE");
+CREATE INDEX "ISSUES_PROJECT_UUID" ON "ISSUES"("PROJECT_UUID");
+CREATE INDEX "ISSUES_RESOLUTION" ON "ISSUES"("RESOLUTION");
+CREATE INDEX "ISSUES_RULE_ID" ON "ISSUES"("RULE_ID");
+CREATE INDEX "ISSUES_UPDATED_AT" ON "ISSUES"("UPDATED_AT");
+
+CREATE TABLE "ISSUE_CHANGES"(
+    "ID" BIGINT NOT NULL AUTO_INCREMENT (1,1),
+    "KEE" VARCHAR(50),
+    "ISSUE_KEY" VARCHAR(50) NOT NULL,
+    "USER_LOGIN" VARCHAR(255),
+    "CHANGE_TYPE" VARCHAR(20),
+    "CHANGE_DATA" CLOB(2147483647),
+    "CREATED_AT" BIGINT,
+    "UPDATED_AT" BIGINT,
+    "ISSUE_CHANGE_CREATION_DATE" BIGINT
+);
+ALTER TABLE "ISSUE_CHANGES" ADD CONSTRAINT "PK_ISSUE_CHANGES" PRIMARY KEY("ID");
+CREATE INDEX "ISSUE_CHANGES_ISSUE_KEY" ON "ISSUE_CHANGES"("ISSUE_KEY");
+CREATE INDEX "ISSUE_CHANGES_KEE" ON "ISSUE_CHANGES"("KEE");
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IsManualVulnerability.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IsManualVulnerability.java
deleted file mode 100644 (file)
index 7a465d0..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2020 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.issue.workflow;
-
-import org.sonar.api.issue.Issue;
-import org.sonar.api.rules.RuleType;
-import org.sonar.core.issue.DefaultIssue;
-
-/**
- * The vulnerability originally come from a hotspot that was moved to vulnerability by a security auditor.
- */
-enum IsManualVulnerability implements Condition {
-  INSTANCE;
-
-  @Override
-  public boolean matches(Issue issue) {
-    return ((DefaultIssue) issue).type() == RuleType.VULNERABILITY && ((DefaultIssue) issue).isFromHotspot();
-  }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IsNotHotspot.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IsNotHotspot.java
new file mode 100644 (file)
index 0000000..7e08611
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.issue.workflow;
+
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rules.RuleType;
+import org.sonar.core.issue.DefaultIssue;
+
+enum IsNotHotspot implements Condition {
+  INSTANCE;
+
+  @Override
+  public boolean matches(Issue issue) {
+    return ((DefaultIssue) issue).type() != RuleType.SECURITY_HOTSPOT;
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IsNotHotspotNorManualVulnerability.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IsNotHotspotNorManualVulnerability.java
deleted file mode 100644 (file)
index bf0c430..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2020 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.issue.workflow;
-
-import org.sonar.api.issue.Issue;
-import org.sonar.api.rules.RuleType;
-import org.sonar.core.issue.DefaultIssue;
-
-enum IsNotHotspotNorManualVulnerability implements Condition {
-  INSTANCE;
-
-  @Override
-  public boolean matches(Issue issue) {
-    return ((DefaultIssue) issue).type() != RuleType.SECURITY_HOTSPOT && !((DefaultIssue) issue).isFromHotspot();
-  }
-}
index fd7046b917aa949845ac8f43e3db667e7019a6e0..c8c56bb9dc616b3698b8a291c2685e143386eede 100644 (file)
@@ -75,31 +75,31 @@ public class IssueWorkflow implements Startable {
       // confirm
       .transition(Transition.builder(DefaultTransitions.CONFIRM)
         .from(STATUS_OPEN).to(STATUS_CONFIRMED)
-        .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(IsNotHotspot.INSTANCE)
         .functions(new SetResolution(null))
         .build())
       .transition(Transition.builder(DefaultTransitions.CONFIRM)
         .from(STATUS_REOPENED).to(STATUS_CONFIRMED)
-        .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(IsNotHotspot.INSTANCE)
         .functions(new SetResolution(null))
         .build())
 
       // resolve as fixed
       .transition(Transition.builder(DefaultTransitions.RESOLVE)
         .from(STATUS_OPEN).to(STATUS_RESOLVED)
-        .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(IsNotHotspot.INSTANCE)
         .functions(new SetResolution(RESOLUTION_FIXED))
         .requiredProjectPermission(UserRole.ISSUE_ADMIN)
         .build())
       .transition(Transition.builder(DefaultTransitions.RESOLVE)
         .from(STATUS_REOPENED).to(STATUS_RESOLVED)
-        .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(IsNotHotspot.INSTANCE)
         .functions(new SetResolution(RESOLUTION_FIXED))
         .requiredProjectPermission(UserRole.ISSUE_ADMIN)
         .build())
       .transition(Transition.builder(DefaultTransitions.RESOLVE)
         .from(STATUS_CONFIRMED).to(STATUS_RESOLVED)
-        .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(IsNotHotspot.INSTANCE)
         .functions(new SetResolution(RESOLUTION_FIXED))
         .requiredProjectPermission(UserRole.ISSUE_ADMIN)
         .build())
@@ -107,31 +107,31 @@ public class IssueWorkflow implements Startable {
       // reopen
       .transition(Transition.builder(DefaultTransitions.UNCONFIRM)
         .from(STATUS_CONFIRMED).to(STATUS_REOPENED)
-        .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(IsNotHotspot.INSTANCE)
         .functions(new SetResolution(null))
         .build())
       .transition(Transition.builder(DefaultTransitions.REOPEN)
         .from(STATUS_RESOLVED).to(STATUS_REOPENED)
-        .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(IsNotHotspot.INSTANCE)
         .functions(new SetResolution(null))
         .build())
 
       // resolve as false-positive
       .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
         .from(STATUS_OPEN).to(STATUS_RESOLVED)
-        .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(IsNotHotspot.INSTANCE)
         .functions(new SetResolution(RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
         .requiredProjectPermission(UserRole.ISSUE_ADMIN)
         .build())
       .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
         .from(STATUS_REOPENED).to(STATUS_RESOLVED)
-        .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(IsNotHotspot.INSTANCE)
         .functions(new SetResolution(RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
         .requiredProjectPermission(UserRole.ISSUE_ADMIN)
         .build())
       .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
         .from(STATUS_CONFIRMED).to(STATUS_RESOLVED)
-        .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(IsNotHotspot.INSTANCE)
         .functions(new SetResolution(RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
         .requiredProjectPermission(UserRole.ISSUE_ADMIN)
         .build())
@@ -139,19 +139,19 @@ public class IssueWorkflow implements Startable {
       // resolve as won't fix
       .transition(Transition.builder(DefaultTransitions.WONT_FIX)
         .from(STATUS_OPEN).to(STATUS_RESOLVED)
-        .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(IsNotHotspot.INSTANCE)
         .functions(new SetResolution(RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
         .requiredProjectPermission(UserRole.ISSUE_ADMIN)
         .build())
       .transition(Transition.builder(DefaultTransitions.WONT_FIX)
         .from(STATUS_REOPENED).to(STATUS_RESOLVED)
-        .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(IsNotHotspot.INSTANCE)
         .functions(new SetResolution(RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
         .requiredProjectPermission(UserRole.ISSUE_ADMIN)
         .build())
       .transition(Transition.builder(DefaultTransitions.WONT_FIX)
         .from(STATUS_CONFIRMED).to(STATUS_RESOLVED)
-        .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(IsNotHotspot.INSTANCE)
         .functions(new SetResolution(RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
         .requiredProjectPermission(UserRole.ISSUE_ADMIN)
         .build());
@@ -165,39 +165,13 @@ public class IssueWorkflow implements Startable {
         .functions(new SetResolution(RESOLUTION_FIXED))
         .requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
         .build())
-      .transition(Transition.builder(DefaultTransitions.RESOLVE_AS_REVIEWED)
-        .from(STATUS_OPEN).to(STATUS_REVIEWED)
-        .conditions(new HasType(RuleType.VULNERABILITY), IsManualVulnerability.INSTANCE)
-        .functions(new SetType(RuleType.SECURITY_HOTSPOT), new SetResolution(RESOLUTION_FIXED))
-        .requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
-        .build())
-
-      .transition(Transition.builder(DefaultTransitions.OPEN_AS_VULNERABILITY)
-        .from(STATUS_REVIEWED).to(STATUS_OPEN)
-        .conditions(new HasType(RuleType.SECURITY_HOTSPOT))
-        .functions(new SetResolution(null), new SetType(RuleType.VULNERABILITY))
-        .requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
-        .build())
-      .transition(Transition.builder(DefaultTransitions.OPEN_AS_VULNERABILITY)
-        .from(STATUS_TO_REVIEW).to(STATUS_OPEN)
-        .conditions(new HasType(RuleType.SECURITY_HOTSPOT))
-        .functions(new SetType(RuleType.VULNERABILITY))
-        .requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
-        .build())
 
       .transition(Transition.builder(DefaultTransitions.RESET_AS_TO_REVIEW)
         .from(STATUS_REVIEWED).to(STATUS_TO_REVIEW)
         .conditions(new HasType(RuleType.SECURITY_HOTSPOT))
         .functions(new SetResolution(null))
         .requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
-        .build())
-      .transition(Transition.builder(DefaultTransitions.RESET_AS_TO_REVIEW)
-        .from(STATUS_OPEN).to(STATUS_TO_REVIEW)
-        .conditions(new HasType(RuleType.VULNERABILITY), IsManualVulnerability.INSTANCE)
-        .functions(new SetType(RuleType.SECURITY_HOTSPOT), new SetResolution(null))
-        .requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
-        .build())
-      ;
+        .build());
   }
 
   private static void buildAutomaticTransitions(StateMachine.Builder builder) {
@@ -243,7 +217,7 @@ public class IssueWorkflow implements Startable {
       // Reopen issues that are marked as resolved but that are still alive.
       .transition(Transition.builder("automaticreopen")
         .from(STATUS_RESOLVED).to(STATUS_REOPENED)
-        .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(RESOLUTION_FIXED), IsNotHotspotNorManualVulnerability.INSTANCE)
+        .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(RESOLUTION_FIXED), IsNotHotspot.INSTANCE)
         .functions(new SetResolution(null), UnsetCloseDate.INSTANCE)
         .automatic()
         .build())
@@ -253,7 +227,7 @@ public class IssueWorkflow implements Startable {
         .conditions(
           new PreviousStatusWas(STATUS_OPEN),
           new HasResolution(RESOLUTION_REMOVED, RESOLUTION_FIXED),
-          IsNotHotspotNorManualVulnerability.INSTANCE)
+          IsNotHotspot.INSTANCE)
         .functions(RestoreResolutionFunction.INSTANCE, UnsetCloseDate.INSTANCE)
         .automatic()
         .build())
@@ -262,7 +236,7 @@ public class IssueWorkflow implements Startable {
         .conditions(
           new PreviousStatusWas(STATUS_REOPENED),
           new HasResolution(RESOLUTION_REMOVED, RESOLUTION_FIXED),
-          IsNotHotspotNorManualVulnerability.INSTANCE)
+          IsNotHotspot.INSTANCE)
         .functions(RestoreResolutionFunction.INSTANCE, UnsetCloseDate.INSTANCE)
         .automatic()
         .build())
@@ -271,7 +245,7 @@ public class IssueWorkflow implements Startable {
         .conditions(
           new PreviousStatusWas(STATUS_CONFIRMED),
           new HasResolution(RESOLUTION_REMOVED, RESOLUTION_FIXED),
-          IsNotHotspotNorManualVulnerability.INSTANCE)
+          IsNotHotspot.INSTANCE)
         .functions(RestoreResolutionFunction.INSTANCE, UnsetCloseDate.INSTANCE)
         .automatic()
         .build())
@@ -280,7 +254,7 @@ public class IssueWorkflow implements Startable {
         .conditions(
           new PreviousStatusWas(STATUS_RESOLVED),
           new HasResolution(RESOLUTION_REMOVED, RESOLUTION_FIXED),
-          IsNotHotspotNorManualVulnerability.INSTANCE)
+          IsNotHotspot.INSTANCE)
         .functions(RestoreResolutionFunction.INSTANCE, UnsetCloseDate.INSTANCE)
         .automatic()
         .build());
index d63b4f6cf3a32250f943f4f9fe894d83806c3132..5b915f3f6b470cece665bfe7a858c0bf53d8028f 100644 (file)
@@ -31,7 +31,6 @@ import org.apache.commons.lang.time.DateUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.sonar.api.issue.DefaultTransitions;
-import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.RuleType;
 import org.sonar.core.issue.DefaultIssue;
@@ -44,7 +43,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
 import static org.sonar.api.issue.Issue.RESOLUTION_REMOVED;
 import static org.sonar.api.issue.Issue.STATUS_CLOSED;
-import static org.sonar.api.issue.Issue.STATUS_OPEN;
 import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
 import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
 import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
@@ -93,7 +91,7 @@ public class IssueWorkflowForSecurityHotspotsTest {
 
     List<Transition> transitions = underTest.outTransitions(issue);
 
-    assertThat(keys(transitions)).containsExactlyInAnyOrder("resolveasreviewed", "openasvulnerability");
+    assertThat(keys(transitions)).containsExactlyInAnyOrder("resolveasreviewed");
   }
 
   @Test
@@ -103,17 +101,7 @@ public class IssueWorkflowForSecurityHotspotsTest {
 
     List<Transition> transitions = underTest.outTransitions(issue);
 
-    assertThat(keys(transitions)).containsExactlyInAnyOrder("openasvulnerability", "resetastoreview");
-  }
-
-  @Test
-  public void list_out_vulnerability_transitions_in_status_open() {
-    underTest.start();
-    DefaultIssue issue = new DefaultIssue().setType(RuleType.VULNERABILITY).setResolution(RESOLUTION_FIXED).setStatus(STATUS_OPEN).setIsFromHotspot(true);
-
-    List<Transition> transitions = underTest.outTransitions(issue);
-
-    assertThat(keys(transitions)).containsExactlyInAnyOrder("resolveasreviewed", "resetastoreview");
+    assertThat(keys(transitions)).containsExactlyInAnyOrder("resetastoreview");
   }
 
   @Test
@@ -121,7 +109,6 @@ public class IssueWorkflowForSecurityHotspotsTest {
     underTest.start();
     DefaultIssue issue = new DefaultIssue()
       .setType(RuleType.SECURITY_HOTSPOT)
-      .setIsFromHotspot(true)
       .setStatus(STATUS_TO_REVIEW);
 
     boolean result = underTest.doManualTransition(issue, DefaultTransitions.RESOLVE_AS_REVIEWED, IssueChangeContext.createUser(new Date(), "USER1"));
@@ -131,46 +118,11 @@ public class IssueWorkflowForSecurityHotspotsTest {
     assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
   }
 
-  @Test
-  public void open_as_vulnerability_from_to_review() {
-    underTest.start();
-    DefaultIssue issue = new DefaultIssue()
-      .setType(RuleType.SECURITY_HOTSPOT)
-      .setIsFromHotspot(true)
-      .setStatus(STATUS_TO_REVIEW)
-      .setResolution(null);
-
-    boolean result = underTest.doManualTransition(issue, DefaultTransitions.OPEN_AS_VULNERABILITY, IssueChangeContext.createUser(new Date(), "USER1"));
-
-    assertThat(result).isTrue();
-    assertThat(issue.type()).isEqualTo(RuleType.VULNERABILITY);
-    assertThat(issue.getStatus()).isEqualTo(Issue.STATUS_OPEN);
-    assertThat(issue.resolution()).isNull();
-  }
-
-  @Test
-  public void open_as_vulnerability_from_reviewed() {
-    underTest.start();
-    DefaultIssue issue = new DefaultIssue()
-      .setType(RuleType.SECURITY_HOTSPOT)
-      .setIsFromHotspot(true)
-      .setResolution(RESOLUTION_FIXED)
-      .setStatus(STATUS_REVIEWED);
-
-    boolean result = underTest.doManualTransition(issue, DefaultTransitions.OPEN_AS_VULNERABILITY, IssueChangeContext.createUser(new Date(), "USER1"));
-
-    assertThat(result).isTrue();
-    assertThat(issue.type()).isEqualTo(RuleType.VULNERABILITY);
-    assertThat(issue.getStatus()).isEqualTo(Issue.STATUS_OPEN);
-    assertThat(issue.resolution()).isNull();
-  }
-
   @Test
   public void reset_as_to_review_from_reviewed() {
     underTest.start();
     DefaultIssue issue = new DefaultIssue()
       .setType(RuleType.SECURITY_HOTSPOT)
-      .setIsFromHotspot(true)
       .setStatus(STATUS_REVIEWED)
       .setResolution(RESOLUTION_FIXED);
 
@@ -181,22 +133,6 @@ public class IssueWorkflowForSecurityHotspotsTest {
     assertThat(issue.resolution()).isNull();
   }
 
-  @Test
-  public void reset_as_to_review_from_opened_as_vulnerability() {
-    underTest.start();
-    DefaultIssue issue = new DefaultIssue()
-      .setType(RuleType.VULNERABILITY)
-      .setIsFromHotspot(true)
-      .setStatus(STATUS_OPEN)
-      .setResolution(null);
-
-    boolean result = underTest.doManualTransition(issue, DefaultTransitions.RESET_AS_TO_REVIEW, IssueChangeContext.createUser(new Date(), "USER1"));
-    assertThat(result).isTrue();
-    assertThat(issue.type()).isEqualTo(RuleType.SECURITY_HOTSPOT);
-    assertThat(issue.getStatus()).isEqualTo(STATUS_TO_REVIEW);
-    assertThat(issue.resolution()).isNull();
-  }
-
   @Test
   public void automatically_close_resolved_security_hotspots_in_status_to_review() {
     underTest.start();
@@ -235,26 +171,6 @@ public class IssueWorkflowForSecurityHotspotsTest {
     assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
   }
 
-  @Test
-  public void automatically_close_hotspots_opened_as_vulnerability() {
-    underTest.start();
-    DefaultIssue issue = new DefaultIssue()
-      .setType(RuleType.VULNERABILITY)
-      .setResolution(null)
-      .setStatus(STATUS_OPEN)
-      .setIsFromHotspot(true)
-      .setNew(false)
-      .setBeingClosed(true);
-    Date now = new Date();
-
-    underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
-
-    assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
-    assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
-    assertThat(issue.closeDate()).isNotNull();
-    assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
-  }
-
   @Test
   @UseDataProvider("allStatusesLeadingToClosed")
   public void do_not_automatically_reopen_closed_issues_of_security_hotspots(String previousStatus) {
@@ -292,43 +208,6 @@ public class IssueWorkflowForSecurityHotspotsTest {
     assertThat(issue.resolution()).isNull();
   }
 
-  @Test
-  @UseDataProvider("allStatusesLeadingToClosed")
-  public void do_not_automatically_reopen_closed_issues_of_manual_vulnerability(String previousStatus) {
-    DefaultIssue[] issues = Arrays.stream(SUPPORTED_RESOLUTIONS_FOR_UNCLOSING)
-      .map(resolution -> {
-        DefaultIssue issue = newClosedIssue(resolution);
-        setStatusPreviousToClosed(issue, previousStatus);
-        issue.setIsFromHotspot(true);
-        return issue;
-      })
-      .toArray(DefaultIssue[]::new);
-    Date now = new Date();
-    underTest.start();
-
-    Arrays.stream(issues).forEach(issue -> {
-      underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
-
-      assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
-      assertThat(issue.updateDate()).isNull();
-    });
-  }
-
-  @Test
-  public void do_not_allow_to_doManualTransition_when_condition_fails() {
-    underTest.start();
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      // Detect is only available on hotspot
-      .setType(RuleType.VULNERABILITY)
-      .setIsFromHotspot(false)
-      .setStatus(STATUS_OPEN)
-      .setResolution(null)
-      .setRuleKey(XOO_X1);
-
-    assertThat(underTest.doManualTransition(issue, DefaultTransitions.RESET_AS_TO_REVIEW, IssueChangeContext.createScan(new Date()))).isFalse();
-  }
-
   private Collection<String> keys(List<Transition> transitions) {
     return transitions.stream().map(Transition::key).collect(MoreCollectors.toList());
   }
index 6e7c989091d691a5d08b698da03adcc3f74c1733..d1433581a599669d233099e5274ce59e47531331 100644 (file)
@@ -22,10 +22,10 @@ package org.sonar.server.issue;
 import java.util.Collection;
 import java.util.Map;
 import org.sonar.api.issue.Issue;
-import org.sonar.server.issue.workflow.IsUnResolved;
 import org.sonar.api.rules.RuleType;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.issue.DefaultIssue;
+import org.sonar.server.issue.workflow.IsUnResolved;
 import org.sonar.server.user.UserSession;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -47,7 +47,7 @@ public class SetTypeAction extends Action {
   }
 
   private boolean isCurrentUserIssueAdmin(Issue issue) {
-    return !((DefaultIssue) issue).isFromHotspot() && userSession.hasComponentUuidPermission(UserRole.ISSUE_ADMIN, issue.projectUuid());
+    return userSession.hasComponentUuidPermission(UserRole.ISSUE_ADMIN, issue.projectUuid());
   }
 
   @Override
index 40201112b0bdb8cc0edb2d31bf88efba8439357b..c17846dfbca89824c18ab81f98cc17ee3e581e00 100644 (file)
@@ -80,7 +80,9 @@ import static java.lang.String.format;
 import static java.util.Objects.requireNonNull;
 import static java.util.function.Function.identity;
 import static java.util.stream.Collectors.toMap;
+import static org.sonar.api.issue.DefaultTransitions.OPEN_AS_VULNERABILITY;
 import static org.sonar.api.issue.DefaultTransitions.REOPEN;
+import static org.sonar.api.issue.DefaultTransitions.SET_AS_IN_REVIEW;
 import static org.sonar.api.rule.Severity.BLOCKER;
 import static org.sonar.api.rules.RuleType.BUG;
 import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
@@ -143,6 +145,8 @@ public class BulkChangeAction implements IssuesWsAction {
         "Requires authentication.")
       .setSince("3.7")
       .setChangelog(
+        new Change("8.1", OPEN_AS_VULNERABILITY + " transition is no more supported"),
+        new Change("8.1", SET_AS_IN_REVIEW + " transition is no more supported"),
         new Change("6.3", "'actions' parameter is ignored"))
       .setHandler(this)
       .setResponseExample(getClass().getResource("bulk_change-example.json"))
index a81c7b984965002b2741a810b620367af4be5294..7f65f6bbd0506b0acd53f760aa2b492290a31c60 100644 (file)
@@ -75,7 +75,8 @@ public class DoTransitionAction implements IssuesWsAction {
         "The transitions involving security hotspots require the permission 'Administer Security Hotspot'.")
       .setSince("3.6")
       .setChangelog(
-        new Change("8.1", SET_AS_IN_REVIEW + " transition has been deprecated"),
+        new Change("8.1", OPEN_AS_VULNERABILITY + " transition is no more supported"),
+        new Change("8.1", SET_AS_IN_REVIEW + " transition is no more supported"),
         new Change("7.8", format("added '%s', %s, %s and %s transitions for security hotspots ", SET_AS_IN_REVIEW, RESOLVE_AS_REVIEWED, OPEN_AS_VULNERABILITY, RESET_AS_TO_REVIEW)),
         new Change("7.3", "added transitions for security hotspots"),
         new Change("6.5", "the database ids of the components are removed from the response"),
index b2942ef5deb7919701afd22a042bdb431cfc687c..e51e384c0e5aa764eac65be0d1fd7f1319d43064 100644 (file)
@@ -199,6 +199,7 @@ public class SearchAction implements IssuesWsAction, Startable {
         PARAM_COMPONENT_KEYS, PARAM_COMPONENT_UUIDS)
       .setSince("3.6")
       .setChangelog(
+        new Change("8.1", "response field 'fromHotspot' has been deprecated and is no more populated"),
         new Change("8.1", format("Status %s for Security Hotspots has been deprecated", STATUS_IN_REVIEW)),
         new Change("7.8", format("added new Security Hotspots statuses : %s, %s and %s", STATUS_TO_REVIEW, STATUS_IN_REVIEW, STATUS_REVIEWED)),
         new Change("7.8", "Security hotspots are returned by default"),
index 8b628b17185487f2429459c335a2bcd54450f43b..bb3f1d3af21388eda7a5968185276aecd124ee97 100644 (file)
@@ -188,7 +188,6 @@ public class SearchResponseFormat {
     if (dto.isExternal()) {
       issueBuilder.setExternalRuleEngine(engineNameFrom(dto.getRuleKey()));
     }
-    issueBuilder.setFromHotspot(dto.isFromHotspot());
     if (dto.getType() != RuleType.SECURITY_HOTSPOT.getDbConstant()) {
       issueBuilder.setSeverity(Common.Severity.valueOf(dto.getSeverity()));
     }
index 5238d413ffae7635409c950ae09a1406bf4375f9..3be0d99f653b97f23b8b6677b6fa4ea6f74c8180 100644 (file)
@@ -234,10 +234,8 @@ public class SearchResponseLoader {
     }
     availableActions.add(ASSIGN_KEY);
     availableActions.add("set_tags");
-    if (!issue.isFromHotspot() && userSession.hasComponentPermission(ISSUE_ADMIN, project)) {
+    if (ruleType != RuleType.SECURITY_HOTSPOT && userSession.hasComponentPermission(ISSUE_ADMIN, project)) {
       availableActions.add(SET_TYPE_KEY);
-    }
-    if ((ruleType != RuleType.SECURITY_HOTSPOT && userSession.hasComponentPermission(ISSUE_ADMIN, project))) {
       availableActions.add(SET_SEVERITY_KEY);
     }
     return availableActions;
index 3d87f095ff1091d0617f6bcacaae560f630274e2..b6bd4847dd3240ffcc5d6488c9a27818bd64d0ce 100644 (file)
@@ -37,6 +37,7 @@ import org.sonar.server.issue.IssueFieldsSetter;
 import org.sonar.server.issue.IssueFinder;
 import org.sonar.server.user.UserSession;
 
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
 import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SET_TYPE;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
@@ -105,9 +106,11 @@ public class SetTypeAction implements IssuesWsAction {
   private SearchResponseData setType(DbSession session, String issueKey, RuleType ruleType) {
     IssueDto issueDto = issueFinder.getByKey(session, issueKey);
     DefaultIssue issue = issueDto.toDefaultIssue();
-    if (issue.isFromHotspot()) {
+
+    if (SECURITY_HOTSPOT == issue.type()) {
       throw new IllegalArgumentException("Changing type of a security hotspot is not permitted");
     }
+
     userSession.checkComponentUuidPermission(ISSUE_ADMIN, issue.projectUuid());
 
     IssueChangeContext context = IssueChangeContext.createUser(new Date(system2.now()), userSession.getUuid());
index f71a6c2d7f004cdc7ad4464ed3bc118dc74c7949..8f600cacb3717028eea9bbc9e1ecd68b74c38886 100644 (file)
  */
 package org.sonar.server.issue.ws;
 
+import com.google.common.collect.Sets;
+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.Set;
+import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.sonar.api.rules.RuleType;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
@@ -65,13 +74,14 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.sonar.api.rules.RuleType.BUG;
 import static org.sonar.api.rules.RuleType.CODE_SMELL;
-import static org.sonar.api.rules.RuleType.VULNERABILITY;
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
 import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
 import static org.sonar.api.web.UserRole.USER;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.issue.IssueTesting.newDto;
 import static org.sonar.db.rule.RuleTesting.newRuleDto;
 
+@RunWith(DataProviderRunner.class)
 public class SetTypeActionTest {
 
   @Rule
@@ -102,34 +112,28 @@ public class SetTypeActionTest {
     responseWriter, system2));
 
   @Test
-  public void set_type() {
+  @UseDataProvider("allTypesFromToExceptHotspots")
+  public void set_type(RuleType from, RuleType to) {
     long now = 1_999_777_234L;
     when(system2.now()).thenReturn(now);
-    IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(CODE_SMELL));
+    IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(from));
     setUserWithBrowseAndAdministerIssuePermission(issueDto);
 
-    call(issueDto.getKey(), BUG.name());
+    call(issueDto.getKey(), to.name());
 
     verify(responseWriter).write(eq(issueDto.getKey()), preloadedSearchResponseDataCaptor.capture(), any(Request.class), any(Response.class));
-    verifyContentOfPreloadedSearchResponseData(issueDto);
     IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get();
-    assertThat(issueReloaded.getType()).isEqualTo(BUG.getDbConstant());
+    assertThat(issueReloaded.getType()).isEqualTo(to.getDbConstant());
 
-    assertThat(issueChangePostProcessor.calledComponents())
-      .extracting(ComponentDto::uuid)
-      .containsExactlyInAnyOrder(issueDto.getComponentUuid());
-  }
-
-  @Test
-  public void prevent_changing_type_security_hotspot() {
-    long now = 1_999_777_234L;
-    when(system2.now()).thenReturn(now);
-    IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(VULNERABILITY).setIsFromHotspot(true));
-    setUserWithBrowseAndAdministerIssuePermission(issueDto);
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Changing type of a security hotspot is not permitted");
-    call(issueDto.getKey(), BUG.name());
+    if (from != to) {
+      verifyContentOfPreloadedSearchResponseData(issueDto);
+      assertThat(issueChangePostProcessor.calledComponents())
+        .extracting(ComponentDto::uuid)
+        .containsExactlyInAnyOrder(issueDto.getComponentUuid());
+    } else {
+      assertThat(issueChangePostProcessor.wasCalled())
+        .isFalse();
+    }
   }
 
   @Test
@@ -174,12 +178,25 @@ public class SetTypeActionTest {
   }
 
   @Test
-  public void fail_when_missing_administer_issue_permission() {
-    IssueDto issueDto = issueDbTester.insertIssue();
+  @UseDataProvider("allTypesExceptSecurityHotspot")
+  public void fail_type_except_hotspot_when_missing_administer_issue_permission(RuleType type) {
+    IssueDto issueDto = issueDbTester.insertIssue(issue -> issue.setType(type));
     logInAndAddProjectPermission("john", issueDto, USER);
 
     expectedException.expect(ForbiddenException.class);
-    call(issueDto.getKey(), BUG.name());
+    call(issueDto.getKey(), type.name());
+  }
+
+  @Test
+  @UseDataProvider("allTypesExceptSecurityHotspot")
+  public void fail_if_trying_to_change_type_of_a_hotspot(RuleType type) {
+    long now = 1_999_777_234L;
+    when(system2.now()).thenReturn(now);
+    IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(SECURITY_HOTSPOT));
+    setUserWithBrowseAndAdministerIssuePermission(issueDto);
+
+    expectedException.expect(IllegalArgumentException.class);
+    call(issueDto.getKey(), type.name());
   }
 
   @Test
@@ -229,4 +246,25 @@ public class SetTypeActionTest {
       .extracting(ComponentDto::uuid)
       .containsOnly(issue.getComponentUuid(), issue.getProjectUuid());
   }
+
+  @DataProvider
+  public static Object[][] allTypesExceptSecurityHotspot() {
+    return EnumSet.allOf(RuleType.class)
+      .stream()
+      .filter(ruleType -> SECURITY_HOTSPOT != ruleType)
+      .map(t -> new Object[] {t})
+      .toArray(Object[][]::new);
+  }
+
+  @DataProvider
+  public static Object[][] allTypesFromToExceptHotspots() {
+    Set<RuleType> set = EnumSet.allOf(RuleType.class)
+      .stream()
+      .filter(ruleType -> SECURITY_HOTSPOT != ruleType)
+      .collect(Collectors.toSet());
+    return Sets.cartesianProduct(set, set)
+      .stream()
+      .map(ruleTypes -> new Object[] {ruleTypes.get(0), ruleTypes.get(1)})
+      .toArray(Object[][]::new);
+  }
 }
index 68872e62e171fdd197d6c5221ea64ee79eb9d91a..5a81de71b63ea81f4032d8c99bcecd0ef60dc07b 100644 (file)
@@ -89,8 +89,6 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
   private Date updateDate;
   private Date closeDate;
 
-  private boolean isFromHotspot = false;
-
   // Current changes
   private FieldDiffs currentChange = null;
 
@@ -607,15 +605,6 @@ public class DefaultIssue implements Issue, Trackable, org.sonar.api.ce.measure.
     }
   }
 
-  public DefaultIssue setIsFromHotspot(boolean value) {
-    this.isFromHotspot = value;
-    return this;
-  }
-
-  public boolean isFromHotspot() {
-    return isFromHotspot;
-  }
-
   public DefaultIssue setTags(Collection<String> tags) {
     this.tags = new LinkedHashSet<>(tags);
     return this;
index defdfc3219b2d0396f12c0edfa3a317bb36ebe3b..58279aa676eb13d6453fd707e9e135ddfc17e333 100644 (file)
@@ -53,7 +53,9 @@ public interface DefaultTransitions {
 
   /**
    * @since 7.8
+   * @deprecated since 8.1, security hotspots can no longer be converted to vulnerabilities
    */
+  @Deprecated
   String OPEN_AS_VULNERABILITY = "openasvulnerability";
 
   /**
@@ -65,5 +67,5 @@ public interface DefaultTransitions {
    * @since 4.4
    */
   List<String> ALL = unmodifiableList(asList(CONFIRM, UNCONFIRM, REOPEN, RESOLVE, FALSE_POSITIVE, WONT_FIX, CLOSE,
-    SET_AS_IN_REVIEW, RESOLVE_AS_REVIEWED, OPEN_AS_VULNERABILITY,RESET_AS_TO_REVIEW));
+    SET_AS_IN_REVIEW, RESOLVE_AS_REVIEWED, RESET_AS_TO_REVIEW));
 }