diff options
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(); + } + } |