aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--it/it-tests/src/test/java/it/issue/IssueActionTest.java109
-rw-r--r--it/it-tests/src/test/java/util/ItUtils.java9
-rw-r--r--it/it-tests/src/test/java/util/issue/IssueRule.java73
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java10
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java69
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/AddCommentAction.java101
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java19
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceMediumTest.java142
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceTest.java87
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/AddCommentActionTest.java204
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java2
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb28
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesService.java17
-rw-r--r--sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java2
-rw-r--r--sonar-ws/src/test/java/org/sonarqube/ws/client/issue/IssuesServiceTest.java15
17 files changed, 486 insertions, 404 deletions
diff --git a/it/it-tests/src/test/java/it/issue/IssueActionTest.java b/it/it-tests/src/test/java/it/issue/IssueActionTest.java
index e0b55d8ce79..00097f46327 100644
--- a/it/it-tests/src/test/java/it/issue/IssueActionTest.java
+++ b/it/it-tests/src/test/java/it/issue/IssueActionTest.java
@@ -22,18 +22,24 @@ package it.issue;
import java.util.List;
import org.assertj.core.api.Assertions;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
-import org.sonar.wsclient.base.HttpException;
-import org.sonar.wsclient.issue.Issue;
-import org.sonar.wsclient.issue.IssueComment;
import org.sonar.wsclient.issue.IssueQuery;
-import org.sonar.wsclient.issue.Issues;
+import org.sonarqube.ws.Issues;
+import org.sonarqube.ws.Issues.Issue;
+import org.sonarqube.ws.client.issue.IssuesService;
+import org.sonarqube.ws.client.issue.SearchWsRequest;
import util.ProjectAnalysis;
import util.ProjectAnalysisRule;
+import util.issue.IssueRule;
+import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
+import static org.sonarqube.ws.Common.Severity.BLOCKER;
+import static util.ItUtils.newAdminWsClient;
+import static util.ItUtils.toDatetime;
import static util.ItUtils.verifyHttpException;
public class IssueActionTest extends AbstractIssueTest {
@@ -41,8 +47,13 @@ public class IssueActionTest extends AbstractIssueTest {
@Rule
public final ProjectAnalysisRule projectAnalysisRule = ProjectAnalysisRule.from(ORCHESTRATOR);
- Issue issue;
- ProjectAnalysis projectAnalysis;
+ @ClassRule
+ public static final IssueRule issueRule = IssueRule.from(ORCHESTRATOR);
+
+ private ProjectAnalysis projectAnalysis;
+ private IssuesService issuesService;
+
+ private Issue randomIssue;
@Before
public void setup() {
@@ -51,29 +62,31 @@ public class IssueActionTest extends AbstractIssueTest {
this.projectAnalysis = projectAnalysisRule.newProjectAnalysis(projectKey).withQualityProfile(qualityProfileKey);
this.projectAnalysis.run();
- this.issue = searchRandomIssue();
+ this.issuesService = newAdminWsClient(ORCHESTRATOR).issues();
+ this.randomIssue = issueRule.getRandomIssue();
}
@Test
public void no_comments_by_default() throws Exception {
- assertThat(issue.comments()).isEmpty();
+ assertThat(randomIssue.getComments().getCommentsList()).isEmpty();
}
@Test
public void add_comment() throws Exception {
- IssueComment comment = adminIssueClient().addComment(issue.key(), "this is my *comment*");
- assertThat(comment.key()).isNotNull();
- assertThat(comment.htmlText()).isEqualTo("this is my <strong>comment</strong>");
- assertThat(comment.login()).isEqualTo("admin");
- assertThat(comment.createdAt()).isNotNull();
+ Issues.Comment comment = issuesService.addComment(randomIssue.getKey(), "this is my *comment*").getIssue().getComments().getComments(0);
+
+ assertThat(comment.getKey()).isNotNull();
+ assertThat(comment.getHtmlText()).isEqualTo("this is my <strong>comment</strong>");
+ assertThat(comment.getLogin()).isEqualTo("admin");
+ assertThat(comment.getCreatedAt()).isNotNull();
// reload issue
- Issue reloaded = searchIssue(issue.key(), true);
+ Issue reloaded = issueRule.getByKey(randomIssue.getKey());
- assertThat(reloaded.comments()).hasSize(1);
- assertThat(reloaded.comments().get(0).key()).isEqualTo(comment.key());
- assertThat(reloaded.comments().get(0).htmlText()).isEqualTo("this is my <strong>comment</strong>");
- assertThat(reloaded.updateDate().before(issue.creationDate())).isFalse();
+ assertThat(reloaded.getComments().getCommentsList()).hasSize(1);
+ assertThat(reloaded.getComments().getComments(0).getKey()).isEqualTo(comment.getKey());
+ assertThat(reloaded.getComments().getComments(0).getHtmlText()).isEqualTo("this is my <strong>comment</strong>");
+ assertThat(toDatetime(reloaded.getUpdateDate())).isAfter(toDatetime(randomIssue.getUpdateDate()));
}
/**
@@ -82,14 +95,14 @@ public class IssueActionTest extends AbstractIssueTest {
@Test
public void should_reject_blank_comment() throws Exception {
try {
- adminIssueClient().addComment(issue.key(), " ");
+ issuesService.addComment(randomIssue.getKey(), " ");
fail();
- } catch (HttpException ex) {
- assertThat(ex.status()).isEqualTo(400);
+ } catch (org.sonarqube.ws.client.HttpException ex) {
+ assertThat(ex.code()).isEqualTo(400);
}
- Issue reloaded = searchIssueByKey(issue.key());
- assertThat(reloaded.comments()).hasSize(0);
+ Issue reloaded = issueRule.getByKey(randomIssue.getKey());
+ assertThat(reloaded.getComments().getCommentsList()).isEmpty();
}
/**
@@ -103,17 +116,17 @@ public class IssueActionTest extends AbstractIssueTest {
assertThat(searchIssuesBySeverities(componentKey, "BLOCKER")).isEmpty();
// increase the severity of an issue
- adminIssueClient().setSeverity(issue.key(), "BLOCKER");
+ adminIssueClient().setSeverity(randomIssue.getKey(), "BLOCKER");
assertThat(searchIssuesBySeverities(componentKey, "BLOCKER")).hasSize(1);
projectAnalysis.run();
- Issue reloaded = searchIssueByKey(issue.key());
- assertThat(reloaded.severity()).isEqualTo("BLOCKER");
- assertThat(reloaded.status()).isEqualTo("OPEN");
- assertThat(reloaded.resolution()).isNull();
- assertThat(reloaded.creationDate()).isEqualTo(issue.creationDate());
- assertThat(reloaded.creationDate().before(reloaded.updateDate())).isTrue();
+ Issue reloaded = issueRule.getByKey(randomIssue.getKey());
+ assertThat(reloaded.getSeverity()).isEqualTo(BLOCKER);
+ assertThat(reloaded.getStatus()).isEqualTo("OPEN");
+ assertThat(reloaded.hasResolution()).isFalse();
+ assertThat(reloaded.getCreationDate()).isEqualTo(randomIssue.getCreationDate());
+ assertThat(toDatetime(reloaded.getCreationDate())).isBefore(toDatetime(reloaded.getUpdateDate()));
}
/**
@@ -121,26 +134,26 @@ public class IssueActionTest extends AbstractIssueTest {
*/
@Test
public void assign() {
- assertThat(issue.assignee()).isNull();
- Issues issues = search(IssueQuery.create().issues(issue.key()));
- assertThat(issues.users()).isEmpty();
+ assertThat(randomIssue.hasAssignee()).isFalse();
+ Issues.SearchWsResponse response = issueRule.search(new SearchWsRequest().setIssues(singletonList(randomIssue.getKey())));
+ assertThat(response.getUsers().getUsersList()).isEmpty();
- adminIssueClient().assign(issue.key(), "admin");
- Assertions.assertThat(searchIssues(IssueQuery.create().assignees("admin"))).hasSize(1);
+ adminIssueClient().assign(randomIssue.getKey(), "admin");
+ assertThat(issueRule.search(new SearchWsRequest().setAssignees(singletonList("admin"))).getIssuesList()).hasSize(1);
projectAnalysis.run();
- Issue reloaded = searchIssueByKey(issue.key());
- assertThat(reloaded.assignee()).isEqualTo("admin");
- assertThat(reloaded.creationDate()).isEqualTo(issue.creationDate());
+ Issue reloaded = issueRule.getByKey(randomIssue.getKey());
+ assertThat(reloaded.getAssignee()).isEqualTo("admin");
+ assertThat(reloaded.getCreationDate()).isEqualTo(randomIssue.getCreationDate());
- issues = search(IssueQuery.create().issues(issue.key()));
- assertThat(issues.user("admin")).isNotNull();
- assertThat(issues.user("admin").name()).isEqualTo("Administrator");
+ response = issueRule.search(new SearchWsRequest().setIssues(singletonList(randomIssue.getKey())).setAdditionalFields(singletonList("users")));
+ assertThat(response.getUsers().getUsersList().stream().filter(user -> "admin".equals(user.getLogin())).findFirst()).isPresent();
+ assertThat(response.getUsers().getUsersList().stream().filter(user -> "Administrator".equals(user.getName())).findFirst()).isPresent();
// unassign
- adminIssueClient().assign(issue.key(), null);
- reloaded = searchIssueByKey(issue.key());
- assertThat(reloaded.assignee()).isNull();
+ adminIssueClient().assign(randomIssue.getKey(), null);
+ reloaded = issueRule.getByKey(randomIssue.getKey());
+ assertThat(reloaded.hasAssignee()).isFalse();
Assertions.assertThat(searchIssues(IssueQuery.create().assignees("admin"))).isEmpty();
}
@@ -149,17 +162,17 @@ public class IssueActionTest extends AbstractIssueTest {
*/
@Test
public void fail_assign_if_assignee_does_not_exist() {
- assertThat(issue.assignee()).isNull();
+ assertThat(randomIssue.hasAssignee()).isFalse();
try {
- adminIssueClient().assign(issue.key(), "unknown");
+ adminIssueClient().assign(randomIssue.getKey(), "unknown");
fail();
} catch (Exception e) {
verifyHttpException(e, 400);
}
}
- private static List<Issue> searchIssuesBySeverities(String componentKey, String... severities) {
- return searchIssues(IssueQuery.create().componentRoots(componentKey).severities(severities));
+ private static List<Issue> searchIssuesBySeverities(String projectKey, String severity) {
+ return issueRule.search(new SearchWsRequest().setProjectKeys(singletonList(projectKey)).setSeverities(singletonList(severity))).getIssuesList();
}
}
diff --git a/it/it-tests/src/test/java/util/ItUtils.java b/it/it-tests/src/test/java/util/ItUtils.java
index 483ecc2a781..6d8bce1a259 100644
--- a/it/it-tests/src/test/java/util/ItUtils.java
+++ b/it/it-tests/src/test/java/util/ItUtils.java
@@ -275,6 +275,15 @@ public class ItUtils {
}
}
+ public static Date toDatetime(String sDate) {
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
+ return sdf.parse(sDate);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
public static String formatDate(Date d) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(d);
diff --git a/it/it-tests/src/test/java/util/issue/IssueRule.java b/it/it-tests/src/test/java/util/issue/IssueRule.java
new file mode 100644
index 00000000000..ad81b47087d
--- /dev/null
+++ b/it/it-tests/src/test/java/util/issue/IssueRule.java
@@ -0,0 +1,73 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 util.issue;
+
+import com.sonar.orchestrator.Orchestrator;
+import java.util.List;
+import org.junit.rules.ExternalResource;
+import org.sonarqube.ws.Issues.Issue;
+import org.sonarqube.ws.Issues.SearchWsResponse;
+import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.issue.SearchWsRequest;
+
+import static java.util.Collections.singletonList;
+import static java.util.Objects.requireNonNull;
+import static org.assertj.core.api.Assertions.assertThat;
+import static util.ItUtils.newAdminWsClient;
+
+public class IssueRule extends ExternalResource {
+
+ private final Orchestrator orchestrator;
+
+ private WsClient adminWsClient;
+
+ private IssueRule(Orchestrator orchestrator) {
+ this.orchestrator = orchestrator;
+ }
+
+ public static IssueRule from(Orchestrator orchestrator) {
+ return new IssueRule(requireNonNull(orchestrator, "Orchestrator instance can not be null"));
+ }
+
+ public SearchWsResponse search(SearchWsRequest request) {
+ return adminWsClient().issues().search(request);
+ }
+
+ public Issue getRandomIssue() {
+ List<Issue> issues = search(new SearchWsRequest()).getIssuesList();
+ assertThat(issues).isNotEmpty();
+ return issues.get(0);
+ }
+
+ public Issue getByKey(String issueKey) {
+ List<Issue> issues = search(new SearchWsRequest().setIssues(singletonList(issueKey)).setAdditionalFields(singletonList("_all"))).getIssuesList();
+ assertThat(issues).hasSize(1);
+ return issues.iterator().next();
+ }
+
+ private WsClient adminWsClient() {
+ if (adminWsClient == null) {
+ adminWsClient = newAdminWsClient(orchestrator);
+ }
+ return adminWsClient;
+ }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java
index 3473dd669cf..34c516b4f69 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java
@@ -51,16 +51,6 @@ public class InternalRubyIssueService {
this.userSession = userSession;
}
- public Result<IssueComment> addComment(String issueKey, String text) {
- Result<IssueComment> result = Result.of();
- try {
- result.set(commentService.addComment(issueKey, text));
- } catch (Exception e) {
- result.addError(e.getMessage());
- }
- return result;
- }
-
public IssueComment deleteComment(String commentKey) {
return commentService.deleteComment(commentKey);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java
index ddb97357d3f..1cb5dbefd60 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java
@@ -20,91 +20,30 @@
package org.sonar.server.issue;
import com.google.common.base.Strings;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.issue.IssueComment;
import org.sonar.api.utils.System2;
-import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.DefaultIssueComment;
-import org.sonar.core.issue.IssueChangeContext;
import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
import org.sonar.db.issue.IssueChangeDto;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
-import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.user.UserSession;
-import static com.google.common.collect.Lists.newArrayList;
-
public class IssueCommentService {
private final DbClient dbClient;
private final IssueService issueService;
- private final IssueFieldsSetter updater;
private final UserSession userSession;
- public IssueCommentService(DbClient dbClient, IssueService issueService, IssueFieldsSetter updater, UserSession userSession) {
+ public IssueCommentService(DbClient dbClient, IssueService issueService, UserSession userSession) {
this.dbClient = dbClient;
this.issueService = issueService;
- this.updater = updater;
this.userSession = userSession;
}
- public List<DefaultIssueComment> findComments(String issueKey) {
- return findComments(newArrayList(issueKey));
- }
-
- public List<DefaultIssueComment> findComments(DbSession dbSession, String issueKey) {
- return findComments(dbSession, newArrayList(issueKey));
- }
-
- public List<DefaultIssueComment> findComments(Collection<String> issueKeys) {
- DbSession session = dbClient.openSession(false);
- try {
- return findComments(session, issueKeys);
- } finally {
- session.close();
- }
- }
-
- public List<DefaultIssueComment> findComments(DbSession session, Collection<String> issueKeys) {
- return dbClient.issueChangeDao().selectCommentsByIssues(session, issueKeys);
- }
-
- public IssueComment findComment(String commentKey) {
- return dbClient.issueChangeDao().selectCommentByKey(commentKey);
- }
-
- public IssueComment addComment(String issueKey, String text) {
- verifyLoggedIn(userSession);
- if (StringUtils.isBlank(text)) {
- throw new BadRequestException("Cannot add empty comments to an issue");
- }
-
- DbSession session = dbClient.openSession(false);
- try {
- DefaultIssue issue = issueService.getByKeyForUpdate(session, issueKey).toDefaultIssue();
- IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.getLogin());
- updater.addComment(issue, text, context);
-
- issueService.saveIssue(session, issue, context, text);
- session.commit();
-
- List<DefaultIssueComment> comments = findComments(issueKey);
- if (comments.isEmpty()) {
- throw new BadRequestException(String.format("Fail to add a comment on issue %s", issueKey));
- }
- return comments.get(comments.size() - 1);
- } finally {
- session.close();
- }
- }
-
public IssueComment deleteComment(String commentKey) {
DefaultIssueComment comment = dbClient.issueChangeDao().selectCommentByKey(commentKey);
if (comment == null) {
@@ -143,10 +82,4 @@ public class IssueCommentService {
return comment;
}
-
- private void verifyLoggedIn(UserSession userSession) {
- if (!userSession.isLoggedIn()) {
- throw new UnauthorizedException("User is not logged in");
- }
- }
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AddCommentAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AddCommentAction.java
new file mode 100644
index 00000000000..be614172d06
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AddCommentAction.java
@@ -0,0 +1,101 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.ws;
+
+import java.util.Date;
+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.utils.System2;
+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.issue.IssueDto;
+import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.IssueFinder;
+import org.sonar.server.issue.IssueUpdater;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.client.issue.IssuesWsParameters;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TEXT;
+
+public class AddCommentAction implements IssuesWsAction {
+
+ private final System2 system2;
+ private final UserSession userSession;
+ private final DbClient dbClient;
+ private final IssueFinder issueFinder;
+ private final IssueUpdater issueUpdater;
+ private final IssueFieldsSetter issueFieldsSetter;
+ private final OperationResponseWriter responseWriter;
+
+ public AddCommentAction(System2 system2, UserSession userSession, DbClient dbClient, IssueFinder issueFinder, IssueUpdater issueUpdater, IssueFieldsSetter issueFieldsSetter,
+ OperationResponseWriter responseWriter) {
+ this.system2 = system2;
+ this.userSession = userSession;
+ this.dbClient = dbClient;
+ this.issueFinder = issueFinder;
+ this.issueUpdater = issueUpdater;
+ this.issueFieldsSetter = issueFieldsSetter;
+ this.responseWriter = responseWriter;
+ }
+
+ @Override
+ public void define(WebService.NewController context) {
+ WebService.NewAction action = context.createAction(IssuesWsParameters.ACTION_ADD_COMMENT)
+ .setDescription("Add a comment.<br/>" +
+ "Requires authentication and the following permission: 'Browse' on the project of the specified issue.<br/>" +
+ "Since 6.3, the response contains the issue with all details, not only the added comment")
+ .setSince("3.6")
+ .setHandler(this)
+ .setPost(true);
+
+ action.createParam(PARAM_ISSUE)
+ .setDescription("Issue key")
+ .setRequired(true)
+ .setExampleValue(UUID_EXAMPLE_01);
+ action.createParam(PARAM_TEXT)
+ .setDescription("Comment text")
+ .setRequired(true)
+ .setExampleValue("Won't fix because it doesn't apply to the context");
+ }
+
+ @Override
+ public void handle(Request request, Response response) {
+ userSession.checkLoggedIn();
+ String issueKey = request.mandatoryParam(PARAM_ISSUE);
+ String commentText = request.mandatoryParam(PARAM_TEXT);
+ checkArgument(!isNullOrEmpty(commentText), "Cannot add empty comment to an issue");
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ IssueDto issueDto = issueFinder.getByKey(dbSession, issueKey);
+ IssueChangeContext context = IssueChangeContext.createUser(new Date(system2.now()), userSession.getLogin());
+ DefaultIssue defaultIssue = issueDto.toDefaultIssue();
+ issueFieldsSetter.addComment(defaultIssue, commentText, context);
+ issueUpdater.saveIssue(dbSession, defaultIssue, context, commentText);
+ responseWriter.write(issueKey, request, response);
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
index 0fe6a4bf934..da32cb2a920 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
@@ -75,7 +75,7 @@ public class DoTransitionAction implements IssuesWsAction {
}
@Override
- public void handle(Request request, Response response) throws Exception {
+ public void handle(Request request, Response response) {
userSession.checkLoggedIn();
String issue = request.mandatoryParam("issue");
try (DbSession dbSession = dbClient.openSession(false)) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java
index 5e4050afc5c..981c162e3ff 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java
@@ -57,6 +57,7 @@ public class IssueWsModule extends Module {
SearchResponseFormat.class,
OperationResponseWriter.class,
WsResponseCommonFormat.class,
+ AddCommentAction.class,
AssignAction.class,
DoTransitionAction.class,
SearchAction.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java
index 2df191f77eb..6a79ea19c69 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java
@@ -29,7 +29,6 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.CONTROLLER_ISSUES
public class IssuesWs implements WebService {
- public static final String ADD_COMMENT_ACTION = "add_comment";
public static final String DELETE_COMMENT_ACTION = "delete_comment";
public static final String EDIT_COMMENT_ACTION = "edit_comment";
public static final String BULK_CHANGE_ACTION = "bulk_change";
@@ -53,29 +52,11 @@ public class IssuesWs implements WebService {
}
private static void defineRailsActions(NewController controller) {
- defineAddCommentAction(controller);
defineDeleteCommentAction(controller);
defineEditCommentAction(controller);
defineBulkChangeAction(controller);
}
- private static void defineAddCommentAction(NewController controller) {
- WebService.NewAction action = controller.createAction(ADD_COMMENT_ACTION)
- .setDescription("Add a comment. Requires authentication and Browse permission on project")
- .setSince("3.6")
- .setHandler(RailsHandler.INSTANCE)
- .setPost(true);
-
- action.createParam("issue")
- .setDescription("Key of the issue")
- .setRequired(true)
- .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef");
- action.createParam("text")
- .setDescription("Comment")
- .setExampleValue("blabla...");
- RailsHandler.addFormatParam(action);
- }
-
private static void defineDeleteCommentAction(NewController controller) {
WebService.NewAction action = controller.createAction(DELETE_COMMENT_ACTION)
.setDescription("Delete a comment. Requires authentication and Browse permission on project")
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceMediumTest.java
deleted file mode 100644
index 40272299207..00000000000
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceMediumTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact 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;
-
-import java.util.List;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.web.UserRole;
-import org.sonar.core.issue.DefaultIssueComment;
-import org.sonar.core.permission.GlobalPermissions;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentDao;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.component.SnapshotDao;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.component.SnapshotTesting;
-import org.sonar.db.issue.IssueDao;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.rule.RuleDao;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleTesting;
-import org.sonar.server.issue.index.IssueIndexer;
-import org.sonar.server.permission.GroupPermissionChange;
-import org.sonar.server.permission.PermissionChange;
-import org.sonar.server.permission.PermissionUpdater;
-import org.sonar.server.permission.ProjectId;
-import org.sonar.server.tester.ServerTester;
-import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.usergroups.ws.GroupIdOrAnyone;
-
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class IssueCommentServiceMediumTest {
-
- @ClassRule
- public static ServerTester tester = new ServerTester().withStartupTasks().withEsIndexes();
-
- @Rule
- public UserSessionRule userSessionRule = UserSessionRule.forServerTester(tester);
-
- DbClient db;
- DbSession session;
- IssueCommentService service;
-
- RuleDto rule;
- ComponentDto project;
- ComponentDto file;
-
- @Before
- public void setUp() {
- tester.clearDbAndIndexes();
- db = tester.get(DbClient.class);
- session = db.openSession(false);
- service = tester.get(IssueCommentService.class);
-
- rule = RuleTesting.newXooX1();
- tester.get(RuleDao.class).insert(session, rule);
-
- project = ComponentTesting.newProjectDto();
- tester.get(ComponentDao.class).insert(session, project);
- SnapshotDto projectSnapshot = SnapshotTesting.newAnalysis(project);
- tester.get(SnapshotDao.class).insert(session, projectSnapshot);
-
- file = ComponentTesting.newFileDto(project, null);
- tester.get(ComponentDao.class).insert(session, file);
-
- // project can be seen by anyone
- session.commit();
- userSessionRule.login("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
- // TODO correctly feed default organization. Not a problem as long as issues search does not support "anyone"
- // for each organization
- GroupPermissionChange permissionChange = new GroupPermissionChange(PermissionChange.Operation.ADD, UserRole.USER, new ProjectId(project), GroupIdOrAnyone.forAnyone("TODO"));
- tester.get(PermissionUpdater.class).apply(session, asList(permissionChange));
-
- userSessionRule.login("gandalf");
-
- session.commit();
- }
-
- @After
- public void after() {
- session.close();
- }
-
- @Test
- public void add_comment() {
- IssueDto issue = IssueTesting.newDto(rule, file, project);
- tester.get(IssueDao.class).insert(session, issue);
- session.commit();
- tester.get(IssueIndexer.class).indexAll();
-
- service.addComment(issue.getKey(), "my comment");
-
- List<DefaultIssueComment> comments = service.findComments(issue.getKey());
- assertThat(comments).hasSize(1);
- assertThat(comments.get(0).markdownText()).isEqualTo("my comment");
- }
-
- @Test
- public void add_comment_on_removed_issue() {
- RuleDto removedRule = RuleTesting.newDto(RuleKey.of("removed", "rule")).setStatus(RuleStatus.REMOVED);
- tester.get(RuleDao.class).insert(session, removedRule);
-
- IssueDto issue = IssueTesting.newDto(removedRule, file, project).setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_REMOVED);
- tester.get(IssueDao.class).insert(session, issue);
- session.commit();
- tester.get(IssueIndexer.class).indexAll();
-
- service.addComment(issue.getKey(), "my comment");
-
- List<DefaultIssueComment> comments = service.findComments(issue.getKey());
- assertThat(comments).hasSize(1);
- assertThat(comments.get(0).markdownText()).isEqualTo("my comment");
- }
-
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceTest.java
index cc84fb71ed1..fd6ccb153af 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueCommentServiceTest.java
@@ -19,7 +19,6 @@
*/
package org.sonar.server.issue;
-import java.util.Collections;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -27,32 +26,21 @@ import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.DefaultIssueComment;
-import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentTesting;
import org.sonar.db.issue.IssueChangeDao;
import org.sonar.db.issue.IssueChangeDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.rule.RuleTesting;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
-import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.tester.UserSessionRule;
-import static com.google.common.collect.Lists.newArrayList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
@@ -68,9 +56,6 @@ public class IssueCommentServiceTest {
private IssueService issueService;
@Mock
- private IssueFieldsSetter updater;
-
- @Mock
private IssueChangeDao changeDao;
@Mock
@@ -86,77 +71,7 @@ public class IssueCommentServiceTest {
when(dbClient.openSession(false)).thenReturn(session);
when(dbClient.issueChangeDao()).thenReturn(changeDao);
- issueCommentService = new IssueCommentService(dbClient, issueService, updater, userSessionRule);
- }
-
- @Test
- public void find_comments() {
- issueCommentService.findComments("ABCD");
- verify(changeDao).selectCommentsByIssues(session, newArrayList("ABCD"));
- }
-
- @Test
- public void should_find_comment() {
- issueCommentService.findComment("ABCD");
- verify(changeDao).selectCommentByKey("ABCD");
- }
-
- @Test
- public void should_add_comment() {
- IssueDto issueDto = IssueTesting.newDto(RuleTesting.newXooX1().setId(500), ComponentTesting.newFileDto(ComponentTesting.newProjectDto(), null), ComponentTesting.newProjectDto());
- when(issueService.getByKeyForUpdate(session, "ABCD")).thenReturn(issueDto);
- when(issueCommentService.findComments(session, "ABCD")).thenReturn(newArrayList(new DefaultIssueComment()));
-
- issueCommentService.addComment("ABCD", "my comment");
-
- verify(updater).addComment(eq(issueDto.toDefaultIssue()), eq("my comment"), any(IssueChangeContext.class));
- verify(issueService).saveIssue(eq(session), eq(issueDto.toDefaultIssue()), any(IssueChangeContext.class), eq("my comment"));
- }
-
- @Test
- public void should_be_logged_when_adding_comment() {
- throwable.expect(UnauthorizedException.class);
- userSessionRule.anonymous();
-
- issueCommentService.addComment("myIssue", "my comment");
-
- verify(updater, never()).addComment(any(DefaultIssue.class), anyString(), any(IssueChangeContext.class));
- verifyZeroInteractions(issueService);
- }
-
- @Test
- public void should_prevent_adding_empty_comment() {
- throwable.expect(BadRequestException.class);
-
- issueCommentService.addComment("myIssue", " ");
-
- verify(updater, never()).addComment(any(DefaultIssue.class), anyString(), any(IssueChangeContext.class));
- verifyZeroInteractions(issueService);
- }
-
- @Test
- public void should_prevent_adding_null_comment() {
- throwable.expect(BadRequestException.class);
-
- issueCommentService.addComment("myIssue", null);
-
- verify(updater, never()).addComment(any(DefaultIssue.class), anyString(), any(IssueChangeContext.class));
- verifyZeroInteractions(issueService);
- }
-
- @Test
- public void fail_if_comment_not_inserted_in_db() {
- IssueDto issueDto = IssueTesting.newDto(RuleTesting.newXooX1().setId(500), ComponentTesting.newFileDto(ComponentTesting.newProjectDto(), null), ComponentTesting.newProjectDto());
- when(issueService.getByKeyForUpdate(session, "ABCD")).thenReturn(issueDto);
- // Comment has not be inserted in db
- when(issueCommentService.findComments(session, "ABCD")).thenReturn(Collections.<DefaultIssueComment>emptyList());
-
- try {
- issueCommentService.addComment("ABCD", "my comment");
- fail();
- } catch (Exception e) {
- assertThat(e).isInstanceOf(BadRequestException.class).hasMessage("Fail to add a comment on issue ABCD");
- }
+ issueCommentService = new IssueCommentService(dbClient, issueService, userSessionRule);
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AddCommentActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AddCommentActionTest.java
new file mode 100644
index 00000000000..ab6198e3452
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AddCommentActionTest.java
@@ -0,0 +1,204 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.ws;
+
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.MapSettings;
+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.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueChangeDto;
+import org.sonar.db.issue.IssueDbTester;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.rule.RuleDbTester;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.IssueFinder;
+import org.sonar.server.issue.IssueUpdater;
+import org.sonar.server.issue.ServerIssueStorage;
+import org.sonar.server.issue.index.IssueIndexDefinition;
+import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.notification.NotificationManager;
+import org.sonar.server.rule.DefaultRuleFinder;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.TestResponse;
+import org.sonar.server.ws.WsActionTester;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.issue.Issue.STATUS_OPEN;
+import static org.sonar.api.web.UserRole.CODEVIEWER;
+import static org.sonar.api.web.UserRole.USER;
+import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.issue.IssueChangeDto.TYPE_COMMENT;
+import static org.sonar.db.rule.RuleTesting.newRuleDto;
+import static org.sonar.server.issue.IssueTesting.newDto;
+
+public class AddCommentActionTest {
+
+ private static final long NOW = 10_000_000_000L;
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Rule
+ public DbTester dbTester = DbTester.create();
+
+ @Rule
+ public EsTester esTester = new EsTester(new IssueIndexDefinition(new MapSettings()));
+
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+
+ private System2 system2 = mock(System2.class);
+
+ private DbClient dbClient = dbTester.getDbClient();
+
+ private RuleDbTester ruleDbTester = new RuleDbTester(dbTester);
+ private IssueDbTester issueDbTester = new IssueDbTester(dbTester);
+ private ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
+
+ private IssueUpdater issueUpdater = new IssueUpdater(dbClient,
+ new ServerIssueStorage(new DefaultRuleFinder(dbClient), dbClient, new IssueIndexer(system2, dbClient, esTester.client())), mock(NotificationManager.class));
+ private OperationResponseWriter responseWriter = mock(OperationResponseWriter.class);
+
+ private WsActionTester tester = new WsActionTester(
+ new AddCommentAction(system2, userSession, dbClient, new IssueFinder(dbClient, userSession), issueUpdater, new IssueFieldsSetter(), responseWriter));
+
+ @Before
+ public void setUp() throws Exception {
+ when(system2.now()).thenReturn(NOW);
+ }
+
+ @Test
+ public void add_comment() throws Exception {
+ IssueDto issueDto = issueDbTester.insertIssue(newIssue().setStatus(STATUS_OPEN).setResolution(null));
+ userSession.login("john").addProjectUuidPermissions(USER, issueDto.getProjectUuid());
+
+ call(issueDto.getKey(), "please fix it");
+
+ verify(responseWriter).write(eq(issueDto.getKey()), any(Request.class), any(Response.class));
+ IssueChangeDto issueComment = dbClient.issueChangeDao().selectByTypeAndIssueKeys(dbTester.getSession(), singletonList(issueDto.getKey()), TYPE_COMMENT).get(0);
+ assertThat(issueComment.getKey()).isNotNull();
+ assertThat(issueComment.getUserLogin()).isEqualTo("john");
+ assertThat(issueComment.getChangeType()).isEqualTo(TYPE_COMMENT);
+ assertThat(issueComment.getChangeData()).isEqualTo("please fix it");
+ assertThat(issueComment.getCreatedAt()).isNotNull();
+ assertThat(issueComment.getUpdatedAt()).isNotNull();
+ assertThat(issueComment.getIssueKey()).isEqualTo(issueDto.getKey());
+ assertThat(issueComment.getIssueChangeCreationDate()).isNotNull();
+
+ IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get();
+ assertThat(issueReloaded.getIssueUpdateTime()).isEqualTo(NOW);
+ }
+
+ @Test
+ public void fail_when_missing_issue_key() throws Exception {
+ userSession.login("john");
+
+ expectedException.expect(IllegalArgumentException.class);
+ call(null, "please fix it");
+ }
+
+ @Test
+ public void fail_when_issue_does_not_exist() throws Exception {
+ userSession.login("john");
+
+ expectedException.expect(NotFoundException.class);
+ call("ABCD", "please fix it");
+ }
+
+ @Test
+ public void fail_when_missing_comment_text() throws Exception {
+ userSession.login("john");
+
+ expectedException.expect(IllegalArgumentException.class);
+ call("ABCD", null);
+ }
+
+ @Test
+ public void fail_when_empty_comment_text() throws Exception {
+ IssueDto issueDto = issueDbTester.insertIssue(newIssue().setStatus(STATUS_OPEN).setResolution(null));
+ userSession.login("john").addProjectUuidPermissions(USER, issueDto.getProjectUuid());
+
+ expectedException.expect(IllegalArgumentException.class);
+ call(issueDto.getKey(), "");
+ }
+
+ @Test
+ public void fail_when_not_authenticated() throws Exception {
+ expectedException.expect(UnauthorizedException.class);
+ call("ABCD", "please fix it");
+ }
+
+ @Test
+ public void fail_when_not_enough_permission() throws Exception {
+ IssueDto issueDto = issueDbTester.insertIssue(newIssue().setStatus(STATUS_OPEN).setResolution(null));
+ userSession.login("john").addProjectUuidPermissions(CODEVIEWER, issueDto.getProjectUuid());
+
+ expectedException.expect(ForbiddenException.class);
+ call(issueDto.getKey(), "please fix it");
+ }
+
+ @Test
+ public void test_definition() {
+ WebService.Action action = tester.getDef();
+ assertThat(action.key()).isEqualTo("add_comment");
+ assertThat(action.isPost()).isTrue();
+ assertThat(action.isInternal()).isFalse();
+ assertThat(action.params()).hasSize(2);
+ assertThat(action.responseExample()).isNull();
+ }
+
+ private TestResponse call(@Nullable String issueKey, @Nullable String commentText) {
+ TestRequest request = tester.newRequest();
+ setNullable(issueKey, issue -> request.setParam("issue", issue));
+ setNullable(commentText, text -> request.setParam("text", text));
+ return request.execute();
+ }
+
+ private IssueDto newIssue() {
+ RuleDto rule = ruleDbTester.insertRule(newRuleDto());
+ ComponentDto project = componentDbTester.insertProject();
+ ComponentDto file = componentDbTester.insertComponent(newFileDto(project));
+ return newDto(rule, file, project);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java
index 6ae3ed69b16..b873b4be48c 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java
@@ -29,6 +29,6 @@ public class IssueWsModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new IssueWsModule().configure(container);
- assertThat(container.size()).isEqualTo(2 + 28);
+ assertThat(container.size()).isEqualTo(2 + 29);
}
}
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb
index eeddc513044..fc54c82edde 100644
--- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb
@@ -22,34 +22,6 @@
class Api::IssuesController < Api::ApiController
#
- # POST /api/issues/add_comment?issue=<key>&text=<text>
- #
- # -- Mandatory parameters
- # 'issue' is the key of an existing issue
- # 'text' is the markdown message. It can be set as an URL parameter or as the post request body.
- #
- # -- Example
- # curl -X POST -v -u admin:admin 'http://localhost:9000/api/issues/add_comment?issue=4a2881e7-825e-4140-a154-01f420c43d11&text=foooo'
- #
- def add_comment
- verify_post_request
- require_parameters :issue, :text
-
- text = Api::Utils.read_post_request_param(params[:text])
- result = Internal.issues.addComment(params[:issue], text)
-
- http_status = (result.ok ? 200 : 400)
- hash = result_to_hash(result)
- hash[:comment] = Issue.comment_to_hash(result.get) if result.get
-
- respond_to do |format|
- # if the request header "Accept" is "*/*", then the default format is the first one (json)
- format.json { render :json => jsonp(hash), :status => result.httpStatus }
- format.xml { render :xml => hash.to_xml(:skip_types => true, :root => 'sonar', :status => http_status) }
- end
- end
-
- #
# POST /api/issues/delete_comment?key=<key>
#
# -- Mandatory parameters
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesService.java
index 88aebe35474..d5f19f5bddf 100644
--- a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesService.java
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesService.java
@@ -19,12 +19,16 @@
*/
package org.sonarqube.ws.client.issue;
+import org.sonarqube.ws.Issues;
import org.sonarqube.ws.Issues.ChangelogWsResponse;
import org.sonarqube.ws.Issues.SearchWsResponse;
import org.sonarqube.ws.client.BaseService;
import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsConnector;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_ADD_COMMENT;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_CHANGELOG;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ADDITIONAL_FIELDS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ASC;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ASSIGNED;
@@ -48,6 +52,8 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.ISSUES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.LANGUAGES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.MODULE_UUIDS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ON_COMPONENT_ONLY;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TEXT;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PROJECTS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PROJECT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PROJECT_UUIDS;
@@ -110,6 +116,15 @@ public class IssuesService extends BaseService {
}
public ChangelogWsResponse changelog(String issueKey) {
- return call(new GetRequest(path("changelog")).setParam(IssuesWsParameters.PARAM_ISSUE, issueKey), ChangelogWsResponse.parser());
+ return call(new GetRequest(path(ACTION_CHANGELOG))
+ .setParam(PARAM_ISSUE, issueKey),
+ ChangelogWsResponse.parser());
+ }
+
+ public Issues.Operation addComment(String issueKey, String commentText) {
+ return call(new PostRequest(path(ACTION_ADD_COMMENT))
+ .setParam(PARAM_ISSUE, issueKey)
+ .setParam(PARAM_TEXT, commentText),
+ Issues.Operation.parser());
}
}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java
index 6a27ae483d4..9e22315ed17 100644
--- a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java
+++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java
@@ -32,8 +32,10 @@ public class IssuesWsParameters {
public static final String CONTROLLER_ISSUES = "api/issues";
public static final String ACTION_CHANGELOG = "changelog";
+ public static final String ACTION_ADD_COMMENT = "add_comment";
public static final String PARAM_ISSUE = "issue";
+ public static final String PARAM_TEXT = "text";
public static final String ISSUES = "issues";
public static final String SEVERITIES = "severities";
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/issue/IssuesServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/issue/IssuesServiceTest.java
index 37a7db67129..685105519e3 100644
--- a/sonar-ws/src/test/java/org/sonarqube/ws/client/issue/IssuesServiceTest.java
+++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/issue/IssuesServiceTest.java
@@ -24,12 +24,14 @@ import org.junit.Rule;
import org.junit.Test;
import org.sonarqube.ws.Issues;
import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.ServiceTester;
import org.sonarqube.ws.client.WsConnector;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TEXT;
public class IssuesServiceTest {
@@ -50,4 +52,17 @@ public class IssuesServiceTest {
.andNoOtherParam();
}
+ @Test
+ public void add_comment() {
+ underTest.addComment("ABCD", "Please help me to fix this issue");
+
+ PostRequest request = serviceTester.getPostRequest();
+
+ assertThat(serviceTester.getPostParser()).isSameAs(Issues.Operation.parser());
+ serviceTester.assertThat(request)
+ .hasParam(PARAM_ISSUE, "ABCD")
+ .hasParam(PARAM_TEXT, "Please help me to fix this issue")
+ .andNoOtherParam();
+ }
+
}