diff options
author | Jacek <jacek.poreda@sonarsource.com> | 2020-02-13 11:14:34 +0100 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2020-02-21 20:46:18 +0100 |
commit | 7421f46c36752c57290af1c53ea2e57e0cfa32fa (patch) | |
tree | 5c6199672477ed46cfca2ec9bf4a9955f9a2b433 | |
parent | b4545b42dba5949fdea5155b23dbf00ac695574f (diff) | |
download | sonarqube-7421f46c36752c57290af1c53ea2e57e0cfa32fa.tar.gz sonarqube-7421f46c36752c57290af1c53ea2e57e0cfa32fa.zip |
SONAR-12922 add edit hotspot comment ws
9 files changed, 417 insertions, 1 deletions
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AddCommentAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AddCommentAction.java index 4a2be100abc..df698cfbcb2 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AddCommentAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AddCommentAction.java @@ -35,6 +35,7 @@ import org.sonar.server.issue.ws.IssueUpdater; public class AddCommentAction implements HotspotsWsAction { private static final String PARAM_HOTSPOT_KEY = "hotspot"; private static final String PARAM_COMMENT = "comment"; + private static final Integer MAXIMUM_COMMENT_LENGTH = 1000; private final DbClient dbClient; private final HotspotWsSupport hotspotWsSupport; @@ -66,6 +67,7 @@ public class AddCommentAction implements HotspotsWsAction { .setRequired(true); action.createParam(PARAM_COMMENT) .setDescription("Comment text.") + .setMaximumLength(MAXIMUM_COMMENT_LENGTH) .setExampleValue("This is safe because user input is validated by the calling code") .setRequired(true); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/EditCommentAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/EditCommentAction.java new file mode 100644 index 00000000000..06c6023e7fe --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/EditCommentAction.java @@ -0,0 +1,133 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.hotspot.ws; + +import java.util.Date; +import java.util.Objects; +import java.util.Optional; +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.DateUtils; +import org.sonar.api.utils.System2; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.issue.IssueChangeDto; +import org.sonar.db.user.UserDto; +import org.sonar.markdown.Markdown; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.user.UserSession; +import org.sonarqube.ws.Common.Comment; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; +import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; +import static org.sonar.server.ws.WsUtils.writeProtobuf; + +public class EditCommentAction implements HotspotsWsAction { + private static final String PARAM_COMMENT = "comment"; + private static final String PARAM_TEXT = "text"; + private static final Integer MAXIMUM_COMMENT_LENGTH = 1000; + + private final DbClient dbClient; + private final HotspotWsSupport hotspotWsSupport; + private final UserSession userSession; + private final System2 system2; + + public EditCommentAction(DbClient dbClient, HotspotWsSupport hotspotWsSupport, UserSession userSession, System2 system2) { + this.dbClient = dbClient; + this.hotspotWsSupport = hotspotWsSupport; + this.userSession = userSession; + this.system2 = system2; + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction("edit_comment") + .setDescription("Edit a comment.<br/>" + + "Requires authentication and the following permission: 'Browse' on the project of the specified hotspot.") + .setSince("8.2") + .setHandler(this) + .setPost(true) + .setInternal(true) + .setResponseExample(getClass().getResource("edit-comment-example.json")); + + action.createParam(PARAM_COMMENT) + .setDescription("Comment key") + .setRequired(true) + .setExampleValue(UUID_EXAMPLE_01); + action.createParam(PARAM_TEXT) + .setDescription("Comment text") + .setMaximumLength(MAXIMUM_COMMENT_LENGTH) + .setRequired(true) + .setExampleValue("Safe because it doesn't apply to the context"); + } + + @Override + public void handle(Request request, Response response) throws Exception { + hotspotWsSupport.checkLoggedIn(); + + String commentKey = request.mandatoryParam(PARAM_COMMENT); + String text = request.mandatoryParam(PARAM_TEXT); + + try (DbSession dbSession = dbClient.openSession(false)) { + IssueChangeDto hotspotComment = getHotspotComment(commentKey, dbSession); + validate(dbSession, hotspotComment); + editComment(dbSession, hotspotComment, text); + Comment commentData = prepareResponse(dbSession, hotspotComment); + writeProtobuf(commentData, request, response); + } + } + + private IssueChangeDto getHotspotComment(String commentKey, DbSession dbSession) { + return dbClient.issueChangeDao().selectCommentByKey(dbSession, commentKey) + .orElseThrow(() -> new NotFoundException(format("Comment with key '%s' does not exist", commentKey))); + } + + private void validate(DbSession dbSession, IssueChangeDto issueChangeDto) { + hotspotWsSupport.loadAndCheckProject(dbSession, issueChangeDto.getIssueKey()); + checkArgument(Objects.equals(issueChangeDto.getUserUuid(), userSession.getUuid()), "You can only edit your own comments"); + } + + private void editComment(DbSession dbSession, IssueChangeDto hotspotComment, String text) { + hotspotComment.setUpdatedAt(system2.now()); + hotspotComment.setChangeData(text); + dbClient.issueChangeDao().update(dbSession, hotspotComment); + dbSession.commit(); + } + + private Comment prepareResponse(DbSession dbSession, IssueChangeDto hotspotComment) { + Comment.Builder commentBuilder = Comment.newBuilder(); + commentBuilder.clear() + .setKey(hotspotComment.getKey()) + .setUpdatable(true) + .setCreatedAt(DateUtils.formatDateTime(new Date(hotspotComment.getIssueChangeCreationDate()))); + getUserByUuid(dbSession, hotspotComment.getUserUuid()).ifPresent(user -> commentBuilder.setLogin(user.getLogin())); + String markdown = hotspotComment.getChangeData(); + commentBuilder + .setHtmlText(Markdown.convertToHtml(markdown)) + .setMarkdown(markdown); + return commentBuilder.build(); + } + + private Optional<UserDto> getUserByUuid(DbSession dbSession, String userUuid) { + return Optional.ofNullable(dbClient.userDao().selectByUuid(dbSession, userUuid)); + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotsWsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotsWsModule.java index 1979b02e076..a1d0024d93f 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotsWsModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotsWsModule.java @@ -33,6 +33,7 @@ public class HotspotsWsModule extends Module { ChangeStatusAction.class, AddCommentAction.class, DeleteCommentAction.class, + EditCommentAction.class, HotspotsWs.class); } } diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/hotspot/ws/edit-comment-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/hotspot/ws/edit-comment-example.json new file mode 100644 index 00000000000..e3f3d00d49f --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/hotspot/ws/edit-comment-example.json @@ -0,0 +1,8 @@ +{ + "key": "AXA-7jPwFopJOMlkuOJl", + "login": "KK0Odk5YEWrylVRK5l2wWwYkVDY43I", + "htmlText": "new comment", + "markdown": "new comment", + "updatable": true, + "createdAt": "2019-01-01T15:22:43+0100" +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/AddCommentActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/AddCommentActionTest.java index 195d04ba75b..185ce575fe7 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/AddCommentActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/AddCommentActionTest.java @@ -86,6 +86,8 @@ public class AddCommentActionTest { @Test public void ws_is_internal() { assertThat(actionTester.getDef().isInternal()).isTrue(); + assertThat(actionTester.getDef().isPost()).isTrue(); + assertThat(actionTester.getDef().param("comment").maximumLength()).isEqualTo(1000); } @Test diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/EditCommentActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/EditCommentActionTest.java new file mode 100644 index 00000000000..c4cd190fb92 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/EditCommentActionTest.java @@ -0,0 +1,192 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.hotspot.ws; + +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.server.ws.WebService.Param; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.issue.IssueChangeDto; +import org.sonar.db.issue.IssueDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.exceptions.UnauthorizedException; +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 org.sonarqube.ws.Common; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +public class EditCommentActionTest { + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + @Rule + public UserSessionRule userSessionRule = UserSessionRule.standalone(); + + private DbClient dbClient = dbTester.getDbClient(); + private System2 system2 = mock(System2.class); + private HotspotWsSupport hotspotWsSupport = new HotspotWsSupport(dbClient, userSessionRule, system2); + + private EditCommentAction underTest = new EditCommentAction(dbClient, hotspotWsSupport, userSessionRule, system2); + private WsActionTester actionTester = new WsActionTester(underTest); + + @Test + public void verify_ws_def() { + assertThat(actionTester.getDef().isInternal()).isTrue(); + assertThat(actionTester.getDef().isPost()).isTrue(); + + Param commentKeyParam = actionTester.getDef().param("comment"); + assertThat(commentKeyParam).isNotNull(); + assertThat(commentKeyParam.isRequired()).isTrue(); + + Param textParam = actionTester.getDef().param("text"); + assertThat(textParam).isNotNull(); + assertThat(textParam.isRequired()).isTrue(); + assertThat(textParam.maximumLength()).isEqualTo(1000); + } + + @Test + public void edit_comment_from_hotspot_private_project() { + UserDto userEditingOwnComment = dbTester.users().insertUser(); + + ComponentDto project = dbTester.components().insertPrivateProject(); + + IssueDto hotspot = dbTester.issues().insertHotspot(h -> h.setProject(project)); + IssueChangeDto comment = dbTester.issues().insertComment(hotspot, userEditingOwnComment, "Some comment"); + + assertThat(getHotspotCommentByKey(comment.getKey())) + .isNotEmpty(); + + userSessionRule.logIn(userEditingOwnComment); + userSessionRule.addProjectPermission(UserRole.USER, project); + + TestRequest request = newRequest(comment.getKey(), "new comment"); + + Common.Comment modifiedComment = request.executeProtobuf(Common.Comment.class); + assertThat(modifiedComment.getKey()).isEqualTo(comment.getKey()); + assertThat(modifiedComment.getMarkdown()).isEqualTo("new comment"); + assertThat(modifiedComment.getHtmlText()).isEqualTo("new comment"); + assertThat(modifiedComment.getLogin()).isEqualTo(userEditingOwnComment.getLogin()); + } + + @Test + public void edit_comment_from_hotspot_public_project() { + UserDto userEditingComment = dbTester.users().insertUser(); + + ComponentDto project = dbTester.components().insertPublicProject(); + + IssueDto hotspot = dbTester.issues().insertHotspot(h -> h.setProject(project)); + IssueChangeDto comment = dbTester.issues().insertComment(hotspot, userEditingComment, "Some comment"); + + userSessionRule.logIn(userEditingComment); + userSessionRule.registerComponents(project); + + TestRequest request = newRequest(comment.getKey(), "new comment"); + + Common.Comment modifiedComment = request.executeProtobuf(Common.Comment.class); + assertThat(modifiedComment.getKey()).isEqualTo(comment.getKey()); + assertThat(modifiedComment.getMarkdown()).isEqualTo("new comment"); + assertThat(modifiedComment.getHtmlText()).isEqualTo("new comment"); + assertThat(modifiedComment.getLogin()).isEqualTo(userEditingComment.getLogin()); + } + + @Test + public void fails_with_UnauthorizedException_if_user_is_anonymous() { + userSessionRule.anonymous(); + + TestRequest request = actionTester.newRequest(); + + assertThatThrownBy(request::execute) + .isInstanceOf(UnauthorizedException.class) + .hasMessage("Authentication is required"); + } + + @Test + public void fails_if_comment_with_provided_key_does_not_exist() { + userSessionRule.logIn(); + + TestRequest request = newRequest("not-existing-comment-key", "some new comment"); + + assertThatThrownBy(request::execute) + .isInstanceOf(NotFoundException.class) + .hasMessage("Comment with key 'not-existing-comment-key' does not exist"); + } + + @Test + public void fails_if_trying_to_edit_comment_of_another_user_in_private_project() { + UserDto userTryingToDelete = dbTester.users().insertUser(); + UserDto userWithHotspotComment = dbTester.users().insertUser(); + + ComponentDto project = dbTester.components().insertPrivateProject(); + + IssueDto hotspot = dbTester.issues().insertHotspot(h -> h.setProject(project)); + IssueChangeDto comment = dbTester.issues().insertComment(hotspot, userWithHotspotComment, "Some comment"); + + userSessionRule.logIn(userTryingToDelete); + userSessionRule.addProjectPermission(UserRole.USER, project); + + TestRequest request = newRequest(comment.getKey(), "new comment"); + + assertThatThrownBy(request::execute) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("You can only edit your own comments"); + } + + @Test + public void fails_if_trying_to_delete_comment_of_another_user_in_public_project() { + UserDto userTryingToEdit = dbTester.users().insertUser(); + UserDto userWithHotspotComment = dbTester.users().insertUser(); + + ComponentDto project = dbTester.components().insertPublicProject(); + + IssueDto hotspot = dbTester.issues().insertHotspot(h -> h.setProject(project)); + IssueChangeDto comment = dbTester.issues().insertComment(hotspot, userWithHotspotComment, "Some comment"); + + userSessionRule.logIn(userTryingToEdit) + .registerComponents(project); + + TestRequest request = newRequest(comment.getKey(), "new comment"); + + assertThatThrownBy(request::execute) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("You can only edit your own comments"); + } + + private TestRequest newRequest(String commentKey, String text) { + return actionTester.newRequest() + .setParam("comment", commentKey) + .setParam("text", text); + } + + private Optional<IssueChangeDto> getHotspotCommentByKey(String commentKey) { + return dbClient.issueChangeDao().selectCommentByKey(dbTester.getSession(), commentKey); + } +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/HotspotsWsModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/HotspotsWsModuleTest.java index 099aecbd6a2..a3278771037 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/HotspotsWsModuleTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/HotspotsWsModuleTest.java @@ -30,7 +30,7 @@ public class HotspotsWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new HotspotsWsModule().configure(container); - assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 9); + assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 10); } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/EditCommentRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/EditCommentRequest.java new file mode 100644 index 00000000000..f52fcb56951 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/EditCommentRequest.java @@ -0,0 +1,61 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.ws.client.hotspots; + +import javax.annotation.Generated; + +/** + * This is part of the internal API. + * This is a POST request. + * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/hotspots/edit_comment">Further information about this action online (including a response example)</a> + * @since 8.2 + */ +@Generated("sonar-ws-generator") +public class EditCommentRequest { + + private String comment; + private String text; + + /** + * This is a mandatory parameter. + * Example value: "AU-Tpxb--iU5OvuD2FLy" + */ + public EditCommentRequest setComment(String comment) { + this.comment = comment; + return this; + } + + public String getComment() { + return comment; + } + + /** + * This is a mandatory parameter. + * Example value: "Safe because it doesn't apply to the context" + */ + public EditCommentRequest setText(String text) { + this.text = text; + return this; + } + + public String getText() { + return text; + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/HotspotsService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/HotspotsService.java index 4b37ba48a43..709876ac0a1 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/HotspotsService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/HotspotsService.java @@ -21,6 +21,7 @@ package org.sonarqube.ws.client.hotspots; import java.util.stream.Collectors; import javax.annotation.Generated; +import org.sonarqube.ws.Common; import org.sonarqube.ws.Hotspots; import org.sonarqube.ws.MediaTypes; import org.sonarqube.ws.client.BaseService; @@ -90,6 +91,22 @@ public class HotspotsService extends BaseService { * * This is part of the internal API. * This is a POST request. + * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/hotspots/edit_comment">Further information about this action online (including a response example)</a> + * @since 8.2 + */ + public Common.Comment editComment(EditCommentRequest request) { + return call( + new PostRequest(path("edit_comment")) + .setParam("comment", request.getComment()) + .setParam("text", request.getText()) + .setMediaType(MediaTypes.JSON), + Common.Comment.parser()); + } + + /** + * + * This is part of the internal API. + * This is a POST request. * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/hotspots/delete_comment">Further information about this action online (including a response example)</a> * @since 8.2 */ |