]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12822 assign user to hotspot action
authorJacek <jacek.poreda@sonarsource.com>
Tue, 17 Dec 2019 07:36:22 +0000 (08:36 +0100)
committerSonarTech <sonartech@sonarsource.com>
Mon, 13 Jan 2020 19:46:28 +0000 (20:46 +0100)
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AssignAction.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotsWsModule.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/AssignActionTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/HotspotsWsModuleTest.java

diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AssignAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AssignAction.java
new file mode 100644 (file)
index 0000000..55be257
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * 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.hotspot.ws;
+
+import javax.annotation.Nullable;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.ws.IssueUpdater;
+
+import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
+import static org.sonar.server.exceptions.NotFoundException.checkFound;
+import static org.sonar.server.exceptions.NotFoundException.checkFoundWithOptional;
+
+public class AssignAction implements HotspotsWsAction {
+  private static final String ACTION_ASSIGN = "assign";
+
+  private static final String PARAM_HOTSPOT_KEY = "hotspot";
+  private static final String PARAM_ASSIGNEE = "assignee";
+  private static final String PARAM_COMMENT = "comment";
+
+  private final DbClient dbClient;
+  private final HotspotWsSupport hotspotWsSupport;
+  private final IssueFieldsSetter issueFieldsSetter;
+  private final IssueUpdater issueUpdater;
+
+  public AssignAction(DbClient dbClient, HotspotWsSupport hotspotWsSupport, IssueFieldsSetter issueFieldsSetter,
+    IssueUpdater issueUpdater) {
+    this.dbClient = dbClient;
+    this.hotspotWsSupport = hotspotWsSupport;
+    this.issueFieldsSetter = issueFieldsSetter;
+    this.issueUpdater = issueUpdater;
+  }
+
+  @Override
+  public void define(WebService.NewController controller) {
+    WebService.NewAction action = controller.createAction(ACTION_ASSIGN)
+      .setDescription("Assign a hotspot to an active user. Requires authentication and Browse permission on project")
+      .setSince("8.2")
+      .setHandler(this)
+      .setInternal(true)
+      .setPost(true);
+
+    action.createParam(PARAM_HOTSPOT_KEY)
+      .setDescription("Hotspot key")
+      .setRequired(true)
+      .setExampleValue(Uuids.UUID_EXAMPLE_01);
+
+    action.createParam(PARAM_ASSIGNEE)
+      .setDescription("Login of the assignee")
+      .setRequired(true)
+      .setExampleValue("admin");
+
+    action.createParam(PARAM_COMMENT)
+      .setDescription("A comment provided with assign action")
+      .setExampleValue("Hey Bob! Could you please have a look and confirm my assertion that we are safe here, please");
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    String assignee = request.mandatoryParam(PARAM_ASSIGNEE);
+    String key = request.mandatoryParam(PARAM_HOTSPOT_KEY);
+    String comment = request.param(PARAM_COMMENT);
+
+    assign(key, assignee, comment);
+
+    response.noContent();
+  }
+
+  private void assign(String hotspotKey, String login, @Nullable String comment) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+
+      IssueDto hotspotDto = hotspotWsSupport.loadHotspot(dbSession, hotspotKey);
+
+      checkIfHotspotToReview(hotspotDto);
+      hotspotWsSupport.loadAndCheckProject(dbSession, hotspotDto);
+      UserDto assignee = getAssignee(dbSession, login);
+
+      IssueChangeContext context = hotspotWsSupport.newIssueChangeContext();
+
+      DefaultIssue defaultIssue = hotspotDto.toDefaultIssue();
+
+      if (comment != null) {
+        issueFieldsSetter.addComment(defaultIssue, comment, context);
+      }
+
+      checkAssigneeProjectPermission(dbSession, assignee, hotspotDto.getProjectUuid());
+
+      if (issueFieldsSetter.assign(defaultIssue, assignee, context)) {
+        issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, defaultIssue, context, false);
+      }
+    }
+  }
+
+  private static void checkIfHotspotToReview(IssueDto hotspotDto) {
+    if (!STATUS_TO_REVIEW.equals(hotspotDto.getStatus())) {
+      throw new IllegalArgumentException(String.format("Assignee can only be changed on Security Hotspots with status '%s'", STATUS_TO_REVIEW));
+    }
+  }
+
+  private UserDto getAssignee(DbSession dbSession, String assignee) {
+    return checkFound(dbClient.userDao().selectActiveUserByLogin(dbSession, assignee), "Unknown user: %s", assignee);
+  }
+
+  private void checkAssigneeProjectPermission(DbSession dbSession, UserDto assignee, String projectUuid) {
+    ComponentDto componentDto = checkFoundWithOptional(dbClient.componentDao().selectByUuid(dbSession, projectUuid),
+      "Could not find project for issue");
+    if (componentDto.isPrivate() && !hasProjectPermission(dbSession, assignee.getId(), projectUuid)) {
+      throw new IllegalArgumentException(String.format("Provided user with login '%s' does not have access to project", assignee.getLogin()));
+    }
+  }
+
+  private boolean hasProjectPermission(DbSession dbSession, long userId, String projectUuid) {
+    return dbClient.authorizationDao().selectProjectPermissions(dbSession, projectUuid, userId).contains(UserRole.USER);
+  }
+}
index 5d4450c4486b91d4615ad0331dec665cdf26d514..73c05653e5f8f110c97edd8f76fb17de99a9f937 100644 (file)
@@ -27,6 +27,7 @@ public class HotspotsWsModule extends Module {
     add(
       HotspotWsResponseFormatter.class,
       HotspotWsSupport.class,
+      AssignAction.class,
       SearchAction.class,
       ShowAction.class,
       ChangeStatusAction.class,
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/AssignActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/AssignActionTest.java
new file mode 100644 (file)
index 0000000..71d0d1e
--- /dev/null
@@ -0,0 +1,459 @@
+/*
+ * 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.hotspot.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.assertj.core.api.Condition;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleTesting;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.ws.IssueUpdater;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.issue.Issue.STATUSES;
+import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.issue.IssueTesting.newIssue;
+
+@RunWith(DataProviderRunner.class)
+public class AssignActionTest {
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public UserSessionRule userSessionRule = UserSessionRule.standalone();
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private IssueUpdater issueUpdater = mock(IssueUpdater.class);
+  private System2 system2 = mock(System2.class);
+  private IssueFieldsSetter issueFieldsSetter = mock(IssueFieldsSetter.class);
+  private HotspotWsSupport hotspotWsSupport = new HotspotWsSupport(dbClient, userSessionRule, system2);
+
+  private AssignAction underTest = new AssignAction(dbClient, hotspotWsSupport, issueFieldsSetter, issueUpdater);
+  private WsActionTester actionTester = new WsActionTester(underTest);
+
+  @Test
+  public void ws_definition_check() {
+    WebService.Action wsDefinition = actionTester.getDef();
+
+    assertThat(wsDefinition.isPost()).isTrue();
+    assertThat(wsDefinition.isInternal()).isTrue();
+    assertThat(wsDefinition.params()).hasSize(3);
+    assertThat(wsDefinition.param("hotspot").isRequired()).isTrue();
+    assertThat(wsDefinition.param("assignee").isRequired()).isTrue();
+    assertThat(wsDefinition.param("comment").isRequired()).isFalse();
+    assertThat(wsDefinition.since()).isEqualTo("8.2");
+  }
+
+  @Test
+  public void assign_hotspot_to_someone_for_public_project() {
+    ComponentDto project = dbTester.components().insertPublicProject();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto hotspot = dbTester.issues().insertIssue(newHotspot(project, file, rule, STATUS_TO_REVIEW));
+
+    UserDto userDto = insertUser(randomAlphanumeric(10));
+    userSessionRule.logIn(userDto).registerComponents(project);
+
+    UserDto assignee = insertUser(randomAlphanumeric(15));
+    when(issueFieldsSetter.assign(eq(hotspot.toDefaultIssue()), userMatcher(assignee), any(IssueChangeContext.class))).thenReturn(true);
+
+    executeRequest(hotspot, assignee.getLogin(), null);
+
+    verifyFieldSetters(assignee, null);
+  }
+
+  @Test
+  public void assign_hotspot_to_me_for_public_project() {
+    ComponentDto project = dbTester.components().insertPublicProject();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto hotspot = dbTester.issues().insertIssue(newHotspot(project, file, rule, STATUS_TO_REVIEW));
+
+    UserDto me = insertUser(randomAlphanumeric(10));
+    userSessionRule.logIn(me).registerComponents(project);
+
+    when(issueFieldsSetter.assign(eq(hotspot.toDefaultIssue()), userMatcher(me), any(IssueChangeContext.class))).thenReturn(true);
+
+    executeRequest(hotspot, me.getLogin(), null);
+
+    verifyFieldSetters(me, null);
+  }
+
+  @Test
+  public void assign_hotspot_to_someone_for_private_project() {
+    ComponentDto project = dbTester.components().insertPrivateProject();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto hotspot = dbTester.issues().insertIssue(newHotspot(project, file, rule, STATUS_TO_REVIEW));
+
+    insertAndLoginAsUserWithProjectUserPermission(randomAlphanumeric(10), hotspot, project, UserRole.USER);
+    UserDto assignee = insertUserWithProjectUserPermission(randomAlphanumeric(15), project);
+
+    when(issueFieldsSetter.assign(eq(hotspot.toDefaultIssue()), userMatcher(assignee), any(IssueChangeContext.class))).thenReturn(true);
+
+    executeRequest(hotspot, assignee.getLogin(), null);
+
+    verifyFieldSetters(assignee, null);
+  }
+
+  @Test
+  public void fail_if_assignee_does_not_have_access_for_private_project() {
+    ComponentDto project = dbTester.components().insertPrivateProject();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto hotspot = dbTester.issues().insertIssue(newHotspot(project, file, rule, STATUS_TO_REVIEW));
+
+    insertAndLoginAsUserWithProjectUserPermission(randomAlphanumeric(10), hotspot, project, UserRole.USER);
+    UserDto assignee = insertUser(randomAlphanumeric(15));
+
+    when(issueFieldsSetter.assign(eq(hotspot.toDefaultIssue()), userMatcher(assignee), any(IssueChangeContext.class))).thenReturn(true);
+
+    assertThatThrownBy(() -> executeRequest(hotspot, assignee.getLogin(), null))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Provided user with login '%s' does not have access to project", assignee.getLogin());
+  }
+
+  @Test
+  public void assign_hotspot_to_me_for_private_project() {
+    ComponentDto project = dbTester.components().insertPrivateProject();
+
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto hotspot = dbTester.issues().insertIssue(newHotspot(project, file, rule, STATUS_TO_REVIEW));
+
+    UserDto me = insertAndLoginAsUserWithProjectUserPermission(randomAlphanumeric(10), hotspot, project, UserRole.USER);
+
+    when(issueFieldsSetter.assign(eq(hotspot.toDefaultIssue()), userMatcher(me), any(IssueChangeContext.class))).thenReturn(true);
+
+    executeRequest(hotspot, me.getLogin(), null);
+
+    verifyFieldSetters(me, null);
+  }
+
+  @Test
+  public void assign_hotspot_with_comment() {
+    ComponentDto project = dbTester.components().insertPublicProject();
+
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto hotspot = dbTester.issues().insertIssue(newHotspot(project, file, rule, STATUS_TO_REVIEW));
+
+    UserDto userDto = insertUser(randomAlphanumeric(10));
+    userSessionRule.logIn(userDto).registerComponents(project);
+
+    UserDto assignee = insertUser(randomAlphanumeric(15));
+
+    when(issueFieldsSetter.assign(eq(hotspot.toDefaultIssue()), userMatcher(assignee), any(IssueChangeContext.class))).thenReturn(true);
+
+    String comment = "some comment";
+    executeRequest(hotspot, assignee.getLogin(), comment);
+
+    verifyFieldSetters(assignee, comment);
+  }
+
+  @Test
+  public void assign_twice_same_user_to_hotspot_does_not_reload() {
+    ComponentDto project = dbTester.components().insertPublicProject();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto hotspot = dbTester.issues().insertIssue(newHotspot(project, file, rule, STATUS_TO_REVIEW));
+
+    UserDto userDto = insertUser(randomAlphanumeric(10));
+    userSessionRule.logIn(userDto).registerComponents(project);
+
+    UserDto assignee = insertUser(randomAlphanumeric(15));
+
+    when(issueFieldsSetter.assign(eq(hotspot.toDefaultIssue()), userMatcher(assignee), any(IssueChangeContext.class))).thenReturn(false);
+
+    executeRequest(hotspot, assignee.getLogin(), "some comment");
+
+    verify(issueFieldsSetter).assign(eq(hotspot.toDefaultIssue()), userMatcher(assignee), any(IssueChangeContext.class));
+    verifyNoMoreInteractions(issueUpdater);
+  }
+
+  @Test
+  public void fail_if_assigning_to_not_existing_user() {
+    ComponentDto project = dbTester.components().insertPublicProject();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto hotspot = dbTester.issues().insertIssue(newHotspot(project, file, rule, STATUS_TO_REVIEW));
+
+    UserDto userDto = insertUser(randomAlphanumeric(10));
+    userSessionRule.logIn(userDto).registerComponents(project);
+
+    String notExistingUserLogin = randomAlphanumeric(10);
+
+    assertThatThrownBy(() -> executeRequest(hotspot, notExistingUserLogin, null))
+      .isInstanceOf(NotFoundException.class)
+      .hasMessage("Unknown user: " + notExistingUserLogin);
+  }
+
+  @Test
+  @UseDataProvider("allIssueStatusesExceptToReview")
+  public void fail_if_assign_user_to_hotspot_for_OTHER_STATUSES_for_public_project(String status) {
+    ComponentDto project = dbTester.components().insertPublicProject();
+
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto hotspot = dbTester.issues().insertIssue(newHotspot(project, file, rule, status));
+
+    UserDto userDto = insertUser(randomAlphanumeric(10));
+    userSessionRule.logIn(userDto).registerComponents(project);
+
+    assertThatThrownBy(() -> executeRequest(hotspot, userSessionRule.getLogin(), null))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Assignee can only be changed on Security Hotspots with status 'TO_REVIEW'");
+  }
+
+  @Test
+  @UseDataProvider("allIssueStatusesExceptToReview")
+  public void fail_if_assign_user_to_hotspot_for_OTHER_STATUSES_for_private_project(String status) {
+    ComponentDto project = dbTester.components().insertPrivateProject();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto hotspot = dbTester.issues().insertIssue(newHotspot(project, file, rule, status));
+
+    UserDto userDto = insertUser(randomAlphanumeric(10));
+    userSessionRule.logIn(userDto).registerComponents(project);
+
+    assertThatThrownBy(() -> executeRequest(hotspot, userSessionRule.getLogin(), null))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Assignee can only be changed on Security Hotspots with status 'TO_REVIEW'");
+  }
+
+  @Test
+  public void fail_if_not_authenticated() {
+    ComponentDto project = dbTester.components().insertPublicProject();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto hotspot = dbTester.issues().insertIssue(newHotspot(project, file, rule, STATUS_TO_REVIEW));
+
+    userSessionRule.anonymous();
+
+    UserDto assignee = insertUser(randomAlphanumeric(15));
+
+    assertThatThrownBy(() -> executeRequest(hotspot, assignee.getLogin(), null))
+      .isInstanceOf(ForbiddenException.class)
+      .hasMessage("Insufficient privileges");
+  }
+
+  @Test
+  public void fail_if_missing_browse_permission() {
+    ComponentDto project = dbTester.components().insertPrivateProject();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+    IssueDto hotspot = dbTester.issues().insertIssue(newHotspot(project, file, rule, STATUS_TO_REVIEW));
+
+    UserDto me = insertAndLoginAsUserWithProjectUserPermission(randomAlphanumeric(10), hotspot, project, UserRole.CODEVIEWER);
+
+    when(issueFieldsSetter.assign(eq(hotspot.toDefaultIssue()), userMatcher(me), any(IssueChangeContext.class))).thenReturn(true);
+
+    assertThatThrownBy(() -> executeRequest(hotspot, me.getLogin(), null))
+      .isInstanceOf(ForbiddenException.class)
+      .hasMessage("Insufficient privileges");
+  }
+
+  @Test
+  public void fail_if_hotspot_does_not_exist() {
+    ComponentDto project = dbTester.components().insertPublicProject();
+
+    UserDto me = insertUser(randomAlphanumeric(10));
+    userSessionRule.logIn().registerComponents(project);
+
+    String notExistingHotspotKey = randomAlphanumeric(10);
+    assertThatThrownBy(() -> executeRequest(notExistingHotspotKey, me.getLogin(), null))
+      .isInstanceOf(NotFoundException.class)
+      .hasMessage("Hotspot '%s' does not exist", notExistingHotspotKey);
+  }
+
+  @Test
+  @UseDataProvider("allRuleTypesWithStatusesExceptHotspot")
+  public void fail_if_trying_to_assign_issue(RuleType ruleType, String status) {
+    ComponentDto project = dbTester.components().insertPublicProject();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+
+    RuleDefinitionDto rule = newRule(ruleType);
+    IssueDto issue = dbTester.issues().insertIssue(
+      newIssue(rule, project, file)
+        .setStatus(status)
+        .setType(ruleType));
+
+    UserDto me = insertUser(randomAlphanumeric(10));
+    userSessionRule.logIn().registerComponents(project);
+
+    assertThatThrownBy(() -> executeRequest(issue, me.getLogin(), null))
+      .isInstanceOf(NotFoundException.class)
+      .hasMessage("Hotspot '%s' does not exist", issue.getKey());
+  }
+
+  @DataProvider
+  public static Object[][] allIssueStatusesExceptToReview() {
+    return STATUSES.stream()
+      .filter(status -> !STATUS_TO_REVIEW.equals(status))
+      .map(status -> new Object[] {status})
+      .toArray(Object[][]::new);
+  }
+
+  @DataProvider
+  public static Object[][] allRuleTypesWithStatusesExceptHotspot() {
+    Set<RuleType> ruleTypes = EnumSet.allOf(RuleType.class)
+      .stream()
+      .filter(ruleType -> SECURITY_HOTSPOT != ruleType)
+      .collect(Collectors.toSet());
+    Set<String> statuses = STATUSES
+      .stream()
+      .filter(status -> !STATUS_TO_REVIEW.equals(status))
+      .collect(Collectors.toSet());
+    return Sets.cartesianProduct(ruleTypes, statuses)
+      .stream()
+      .map(elements -> new Object[] {elements.get(0), elements.get(1)})
+      .toArray(Object[][]::new);
+  }
+
+  private void verifyFieldSetters(UserDto assignee, @Nullable String comment) {
+    ArgumentCaptor<DefaultIssue> defaultIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
+    short capturedArgsCount = 0;
+    if (comment != null) {
+      verify(issueFieldsSetter).addComment(defaultIssueCaptor.capture(), eq(comment), any(IssueChangeContext.class));
+      capturedArgsCount++;
+    }
+
+    verify(issueFieldsSetter).assign(defaultIssueCaptor.capture(), userMatcher(assignee), any(IssueChangeContext.class));
+    verify(issueUpdater).saveIssueAndPreloadSearchResponseData(
+      any(DbSession.class),
+      defaultIssueCaptor.capture(),
+      any(IssueChangeContext.class),
+      eq(false));
+
+    capturedArgsCount += 2;
+
+    // because it is mutated by FieldSetter and IssueUpdater, the same object must be passed to all methods
+    List<DefaultIssue> capturedDefaultIssues = defaultIssueCaptor.getAllValues();
+    assertThat(capturedDefaultIssues).hasSize(capturedArgsCount);
+    assertThat(capturedDefaultIssues)
+      .are(new Condition<DefaultIssue>() {
+        @Override
+        public boolean matches(DefaultIssue value) {
+          return value == capturedDefaultIssues.get(0);
+        }
+      });
+  }
+
+  private static IssueDto newHotspot(ComponentDto project, ComponentDto file, RuleDefinitionDto rule, String status) {
+    return newIssue(rule, project, file).setStatus(status).setType(SECURITY_HOTSPOT);
+  }
+
+  private void executeRequest(IssueDto hotspot, @Nullable String assignee, @Nullable String comment) {
+    executeRequest(hotspot.getKey(), assignee, comment);
+  }
+
+  private void executeRequest(String hotspotKey, @Nullable String assignee, @Nullable String comment) {
+    TestRequest request = actionTester.newRequest()
+      .setParam("hotspot", hotspotKey);
+
+    if (assignee != null) {
+      request.setParam("assignee", assignee);
+    }
+
+    if (comment != null) {
+      request.setParam("comment", comment);
+    }
+    request.execute().assertNoContent();
+  }
+
+  private RuleDefinitionDto newRule(RuleType ruleType) {
+    RuleDefinitionDto ruleDefinition = RuleTesting.newRule()
+      .setType(ruleType);
+    dbTester.rules().insert(ruleDefinition);
+    return ruleDefinition;
+  }
+
+  private UserDto insertUser(String login) {
+    UserDto user = dbTester.users().insertUser(login);
+    dbTester.organizations().addMember(dbTester.getDefaultOrganization(), user);
+    return user;
+  }
+
+  private UserDto insertUserWithProjectPermission(String login, ComponentDto project, String permission) {
+    UserDto user = dbTester.users().insertUser(login);
+    dbTester.organizations().addMember(dbTester.getDefaultOrganization(), user);
+    dbTester.users().insertProjectPermissionOnUser(user, permission, project);
+    return user;
+  }
+
+  private UserDto insertUserWithProjectUserPermission(String login, ComponentDto project) {
+    return insertUserWithProjectPermission(login, project, UserRole.USER);
+  }
+
+  private UserDto insertAndLoginAsUserWithProjectUserPermission(String login, IssueDto issue, ComponentDto project, String permission) {
+    UserDto user = insertUserWithProjectUserPermission(login, project);
+    userSessionRule.logIn(user)
+      .addProjectPermission(permission,
+        dbClient.componentDao().selectByUuid(dbTester.getSession(), issue.getProjectUuid()).get(),
+        dbClient.componentDao().selectByUuid(dbTester.getSession(), issue.getComponentUuid()).get());
+    return user;
+  }
+
+  private static UserDto userMatcher(UserDto user) {
+    return argThat(argument -> argument.getLogin().equals(user.getLogin()) &&
+      argument.getUuid().equals(user.getUuid()));
+  }
+
+}
index b7bf86a0a18911b0ec458a70cd8a16450aee95b7..f0618b661288e68d4901e95e83660336e74f3b8e 100644 (file)
@@ -30,7 +30,7 @@ public class HotspotsWsModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new HotspotsWsModule().configure(container);
-    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 7);
+    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 8);
   }
 
 }