--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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()));
+ }
+
+}