| // Copyright (C) 2014 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.acceptance.server.change; |
| |
| import static com.google.common.collect.ImmutableSet.toImmutableSet; |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth8.assertThat; |
| import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME; |
| import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT; |
| import static com.google.gerrit.entities.Patch.COMMIT_MSG; |
| import static com.google.gerrit.entities.Patch.PATCHSET_LEVEL; |
| import static com.google.gerrit.testing.GerritJUnit.assertThrows; |
| import static com.google.gerrit.truth.MapSubject.assertThatMap; |
| import static java.util.stream.Collectors.toList; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.gerrit.acceptance.AbstractDaemonTest; |
| import com.google.gerrit.acceptance.NoHttpd; |
| import com.google.gerrit.acceptance.PushOneCommit; |
| import com.google.gerrit.acceptance.testsuite.account.AccountOperations; |
| import com.google.gerrit.acceptance.testsuite.change.ChangeOperations; |
| import com.google.gerrit.acceptance.testsuite.change.TestHumanComment; |
| import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; |
| import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; |
| import com.google.gerrit.common.RawInputUtil; |
| import com.google.gerrit.entities.Account; |
| import com.google.gerrit.entities.Change; |
| import com.google.gerrit.entities.HumanComment; |
| import com.google.gerrit.entities.Patch; |
| import com.google.gerrit.entities.PatchSet; |
| import com.google.gerrit.entities.RefNames; |
| import com.google.gerrit.extensions.api.changes.DeleteCommentInput; |
| import com.google.gerrit.extensions.api.changes.DraftInput; |
| import com.google.gerrit.extensions.api.changes.ReviewInput; |
| import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput; |
| import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling; |
| import com.google.gerrit.extensions.api.changes.ReviewerInput; |
| import com.google.gerrit.extensions.client.Comment; |
| import com.google.gerrit.extensions.client.Side; |
| import com.google.gerrit.extensions.common.ChangeInfo; |
| import com.google.gerrit.extensions.common.ChangeInput; |
| import com.google.gerrit.extensions.common.CommentInfo; |
| import com.google.gerrit.extensions.restapi.AuthException; |
| import com.google.gerrit.extensions.restapi.BadRequestException; |
| import com.google.gerrit.extensions.restapi.IdString; |
| import com.google.gerrit.extensions.restapi.ResourceNotFoundException; |
| import com.google.gerrit.extensions.restapi.TopLevelResource; |
| import com.google.gerrit.server.change.ChangeResource; |
| import com.google.gerrit.server.change.RevisionResource; |
| import com.google.gerrit.server.notedb.ChangeNoteUtil; |
| import com.google.gerrit.server.notedb.DeleteCommentRewriter; |
| import com.google.gerrit.server.restapi.change.ChangesCollection; |
| import com.google.gerrit.server.restapi.change.PostReview; |
| import com.google.gerrit.testing.FakeEmailSender; |
| import com.google.gerrit.testing.FakeEmailSender.Message; |
| import com.google.inject.Inject; |
| import com.google.inject.Provider; |
| import java.time.Instant; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.function.Function; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import org.eclipse.jgit.lib.ObjectReader; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.notes.NoteMap; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| @NoHttpd |
| public class CommentsIT extends AbstractDaemonTest { |
| @Inject private ChangeNoteUtil noteUtil; |
| @Inject private FakeEmailSender email; |
| @Inject private ProjectOperations projectOperations; |
| @Inject private Provider<ChangesCollection> changes; |
| @Inject private Provider<PostReview> postReview; |
| @Inject private ChangeOperations changeOperations; |
| @Inject private AccountOperations accountOperations; |
| @Inject private RequestScopeOperations requestScopeOperations; |
| |
| private final Integer[] lines = {0, 1}; |
| |
| @Before |
| public void setUp() { |
| requestScopeOperations.setApiUser(user.id()); |
| } |
| |
| @Test |
| public void getNonExistingComment() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| assertThrows( |
| ResourceNotFoundException.class, |
| () -> getPublishedComment(changeId, revId, "non-existing")); |
| } |
| |
| @Test |
| public void createDraft() throws Exception { |
| for (Integer line : lines) { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| String path = "file1"; |
| DraftInput comment = CommentsUtil.newDraft(path, Side.REVISION, line, "comment 1"); |
| addDraft(changeId, revId, comment); |
| Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId); |
| assertThat(result).hasSize(1); |
| CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path)); |
| assertThat(comment).isEqualTo(infoToDraft(path).apply(actual)); |
| |
| List<CommentInfo> list = getDraftCommentsAsList(changeId); |
| assertThat(list).hasSize(1); |
| actual = list.get(0); |
| assertThat(comment).isEqualTo(infoToDraft(path).apply(actual)); |
| } |
| } |
| |
| @Test |
| public void createDraftOnMergeCommitChange() throws Exception { |
| for (Integer line : lines) { |
| PushOneCommit.Result r = createMergeCommitChange("refs/for/master"); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| String path = "file1"; |
| DraftInput c1 = CommentsUtil.newDraft(path, Side.REVISION, line, "ps-1"); |
| DraftInput c2 = CommentsUtil.newDraft(path, Side.PARENT, line, "auto-merge of ps-1"); |
| DraftInput c3 = CommentsUtil.newDraftOnParent(path, 1, line, "parent-1 of ps-1"); |
| DraftInput c4 = CommentsUtil.newDraftOnParent(path, 2, line, "parent-2 of ps-1"); |
| addDraft(changeId, revId, c1); |
| addDraft(changeId, revId, c2); |
| addDraft(changeId, revId, c3); |
| addDraft(changeId, revId, c4); |
| Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId); |
| assertThat(result).hasSize(1); |
| assertThat(result.get(path).stream().map(infoToDraft(path))).containsExactly(c1, c2, c3, c4); |
| |
| List<CommentInfo> list = getDraftCommentsAsList(changeId); |
| assertThat(list).hasSize(4); |
| assertThat(list.stream().map(infoToDraft(path))).containsExactly(c1, c2, c3, c4); |
| } |
| } |
| |
| @Test |
| public void postComment() throws Exception { |
| for (Integer line : lines) { |
| String file = "file"; |
| String contents = "contents " + line; |
| PushOneCommit push = |
| pushFactory.create(admin.newIdent(), testRepo, "first subject", file, contents); |
| PushOneCommit.Result r = push.to("refs/for/master"); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| ReviewInput input = new ReviewInput(); |
| CommentInput comment = CommentsUtil.newComment(file, Side.REVISION, line, "comment 1", false); |
| input.comments = new HashMap<>(); |
| input.comments.put(comment.path, Lists.newArrayList(comment)); |
| revision(r).review(input); |
| Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId); |
| assertThat(result).isNotEmpty(); |
| CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path)); |
| assertThat(comment).isEqualTo(infoToInput(file).apply(actual)); |
| assertThat(comment) |
| .isEqualTo(infoToInput(file).apply(getPublishedComment(changeId, revId, actual.id))); |
| } |
| } |
| |
| @Test |
| public void commentWithRangeAndLine_lineIsIgnored() throws Exception { |
| String file = "file"; |
| String contents = "contents"; |
| PushOneCommit push = |
| pushFactory.create(admin.newIdent(), testRepo, "first subject", file, contents); |
| PushOneCommit.Result r = push.to("refs/for/master"); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| ReviewInput input = new ReviewInput(); |
| CommentInput comment = CommentsUtil.newComment(file, Side.REVISION, 1, "comment 1", false); |
| int rangeEndLine = 3; |
| comment.range = createRange(1, 1, rangeEndLine, 3); |
| input.comments = new HashMap<>(); |
| input.comments.put(comment.path, Lists.newArrayList(comment)); |
| revision(r).review(input); |
| Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId); |
| assertThat(result).isNotEmpty(); |
| CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path)); |
| assertThat(actual.line).isEqualTo(rangeEndLine); |
| input = new ReviewInput(); |
| comment = CommentsUtil.newComment(file, Side.REVISION, 1, "comment 1 reply", false); |
| comment.range = createRange(1, 1, rangeEndLine, 3); |
| // Post another comment in reply, and the line is still fixed to the range.endLine |
| comment.inReplyTo = actual.id; |
| input.comments = new HashMap<>(); |
| input.comments.put(comment.path, Lists.newArrayList(comment)); |
| revision(r).review(input); |
| result = getPublishedComments(changeId, revId); |
| assertThat(result.get(comment.path)).hasSize(2); |
| assertThat(result.get(comment.path).stream().allMatch(c -> c.line == rangeEndLine)).isTrue(); |
| } |
| |
| @Test |
| public void patchsetLevelCommentCanBeAddedAndRetrieved() throws Exception { |
| PushOneCommit.Result result = createChange(); |
| String changeId = result.getChangeId(); |
| String ps1 = result.getCommit().name(); |
| |
| CommentInput comment = |
| CommentsUtil.newCommentWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| CommentsUtil.addComments(gApi, changeId, ps1, comment); |
| |
| Map<String, List<CommentInfo>> results = getPublishedComments(changeId, ps1); |
| assertThatMap(results).keys().containsExactly(PATCHSET_LEVEL); |
| } |
| |
| @Test |
| public void deletePatchsetLevelComment() throws Exception { |
| requestScopeOperations.setApiUser(admin.id()); |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| String commentMessage = "to be deleted"; |
| CommentInput comment = |
| CommentsUtil.newCommentWithOnlyMandatoryFields(PATCHSET_LEVEL, commentMessage); |
| CommentsUtil.addComments(gApi, changeId, revId, comment); |
| |
| Map<String, List<CommentInfo>> results = getPublishedComments(changeId, revId); |
| CommentInfo oldComment = Iterables.getOnlyElement(results.get(PATCHSET_LEVEL)); |
| |
| DeleteCommentInput input = new DeleteCommentInput("reason"); |
| gApi.changes().id(changeId).revision(revId).comment(oldComment.id).delete(input); |
| CommentInfo updatedComment = |
| Iterables.getOnlyElement(getPublishedComments(changeId, revId).get(PATCHSET_LEVEL)); |
| |
| assertThat(updatedComment.message).doesNotContain(commentMessage); |
| } |
| |
| @Test |
| public void deletedCommentsAreResolved() throws Exception { |
| requestScopeOperations.setApiUser(admin.id()); |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| String commentMessage = "to be deleted"; |
| CommentInput comment = |
| CommentsUtil.newComment( |
| COMMIT_MSG, Side.REVISION, /*line= */ 0, commentMessage, /*unresolved= */ true); |
| CommentsUtil.addComments(gApi, changeId, revId, comment); |
| |
| Map<String, List<CommentInfo>> results = getPublishedComments(changeId, revId); |
| CommentInfo oldComment = Iterables.getOnlyElement(results.get(COMMIT_MSG)); |
| |
| DeleteCommentInput input = new DeleteCommentInput("reason"); |
| gApi.changes().id(changeId).revision(revId).comment(oldComment.id).delete(input); |
| CommentInfo updatedComment = |
| Iterables.getOnlyElement(getPublishedComments(changeId, revId).get(COMMIT_MSG)); |
| |
| assertThat(updatedComment.message).doesNotContain(commentMessage); |
| assertThat(updatedComment.unresolved).isFalse(); |
| } |
| |
| @Test |
| public void patchsetLevelCommentEmailNotification() throws Exception { |
| PushOneCommit.Result result = createChange(); |
| String changeId = result.getChangeId(); |
| String ps1 = result.getCommit().name(); |
| |
| CommentInput comment = |
| CommentsUtil.newCommentWithOnlyMandatoryFields( |
| PATCHSET_LEVEL, "The change looks good, LGTM"); |
| CommentsUtil.addComments(gApi, changeId, ps1, comment); |
| |
| String emailBody = Iterables.getOnlyElement(email.getMessages()).body(); |
| assertThat(emailBody).contains("Patchset"); |
| assertThat(emailBody).doesNotContain("/PATCHSET_LEVEL"); |
| assertThat(emailBody).contains("The change looks good, LGTM"); |
| } |
| |
| @Test |
| public void patchsetLevelCommentCantHaveLine() throws Exception { |
| PushOneCommit.Result result = createChange(); |
| String changeId = result.getChangeId(); |
| String ps1 = result.getCommit().name(); |
| |
| CommentInput input = CommentsUtil.newCommentWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| input.line = 1; |
| BadRequestException ex = |
| assertThrows( |
| BadRequestException.class, () -> CommentsUtil.addComments(gApi, changeId, ps1, input)); |
| assertThat(ex.getMessage()).contains("line"); |
| } |
| |
| @Test |
| public void patchsetLevelCommentCantHaveRange() throws Exception { |
| PushOneCommit.Result result = createChange(); |
| String changeId = result.getChangeId(); |
| String ps1 = result.getCommit().name(); |
| |
| CommentInput input = CommentsUtil.newCommentWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| input.range = createLineRange(1, 3); |
| BadRequestException ex = |
| assertThrows( |
| BadRequestException.class, () -> CommentsUtil.addComments(gApi, changeId, ps1, input)); |
| assertThat(ex.getMessage()).contains("range"); |
| } |
| |
| @Test |
| public void patchsetLevelCommentCantHaveSide() throws Exception { |
| PushOneCommit.Result result = createChange(); |
| String changeId = result.getChangeId(); |
| String ps1 = result.getCommit().name(); |
| |
| CommentInput input = CommentsUtil.newCommentWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| input.side = Side.REVISION; |
| BadRequestException ex = |
| assertThrows( |
| BadRequestException.class, () -> CommentsUtil.addComments(gApi, changeId, ps1, input)); |
| assertThat(ex.getMessage()).contains("side"); |
| } |
| |
| @Test |
| public void patchsetLevelDraftCommentCanBeAddedAndRetrieved() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput comment = CommentsUtil.newDraftWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| addDraft(changeId, revId, comment); |
| Map<String, List<CommentInfo>> results = getDraftComments(changeId, revId); |
| assertThatMap(results).keys().containsExactly(PATCHSET_LEVEL); |
| } |
| |
| @Test |
| public void deletePatchsetLevelDraft() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput draft = CommentsUtil.newDraftWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment 1"); |
| CommentInfo returned = addDraft(changeId, revId, draft); |
| deleteDraft(changeId, revId, returned.id); |
| Map<String, List<CommentInfo>> drafts = getDraftComments(changeId, revId); |
| assertThat(drafts).isEmpty(); |
| } |
| |
| @Test |
| public void patchsetLevelDraftCommentCantHaveLine() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput comment = CommentsUtil.newDraftWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| comment.line = 1; |
| BadRequestException ex = |
| assertThrows(BadRequestException.class, () -> addDraft(changeId, revId, comment)); |
| assertThat(ex.getMessage()).contains("line"); |
| } |
| |
| @Test |
| public void patchsetLevelDraftCommentCantHaveRange() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput comment = CommentsUtil.newDraftWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| comment.range = createLineRange(1, 3); |
| BadRequestException ex = |
| assertThrows(BadRequestException.class, () -> addDraft(changeId, revId, comment)); |
| assertThat(ex.getMessage()).contains("range"); |
| } |
| |
| @Test |
| public void patchsetLevelDraftCommentCantHaveSide() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput comment = CommentsUtil.newDraftWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| comment.side = Side.REVISION; |
| BadRequestException ex = |
| assertThrows(BadRequestException.class, () -> addDraft(changeId, revId, comment)); |
| assertThat(ex.getMessage()).contains("range"); |
| } |
| |
| @Test |
| public void patchsetLevelDraftCommentCantBeUpdatedToHaveLine() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput comment = CommentsUtil.newDraftWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| addDraft(changeId, revId, comment); |
| Map<String, List<CommentInfo>> results = getDraftComments(changeId, revId); |
| DraftInput update = CommentsUtil.newDraftWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| update.id = Iterables.getOnlyElement(results.get(PATCHSET_LEVEL)).id; |
| update.line = 1; |
| BadRequestException ex = |
| assertThrows( |
| BadRequestException.class, () -> updateDraft(changeId, revId, update, update.id)); |
| assertThat(ex.getMessage()).contains("line"); |
| } |
| |
| @Test |
| public void patchsetLevelDraftCommentCantBeUpdatedToHaveRange() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput comment = CommentsUtil.newDraftWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| addDraft(changeId, revId, comment); |
| Map<String, List<CommentInfo>> results = getDraftComments(changeId, revId); |
| DraftInput update = CommentsUtil.newDraftWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| update.id = Iterables.getOnlyElement(results.get(PATCHSET_LEVEL)).id; |
| update.range = createLineRange(1, 3); |
| BadRequestException ex = |
| assertThrows( |
| BadRequestException.class, () -> updateDraft(changeId, revId, update, update.id)); |
| assertThat(ex.getMessage()).contains("range"); |
| } |
| |
| @Test |
| public void patchsetLevelDraftCommentCantBeUpdatedToHaveSide() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput comment = CommentsUtil.newDraftWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| addDraft(changeId, revId, comment); |
| Map<String, List<CommentInfo>> results = getDraftComments(changeId, revId); |
| DraftInput update = CommentsUtil.newDraftWithOnlyMandatoryFields(PATCHSET_LEVEL, "comment"); |
| update.id = Iterables.getOnlyElement(results.get(PATCHSET_LEVEL)).id; |
| update.side = Side.REVISION; |
| BadRequestException ex = |
| assertThrows( |
| BadRequestException.class, () -> updateDraft(changeId, revId, update, update.id)); |
| assertThat(ex.getMessage()).contains("side"); |
| } |
| |
| @Test |
| public void postCommentWithReply() throws Exception { |
| for (Integer line : lines) { |
| String file = "file"; |
| String contents = "contents " + line; |
| PushOneCommit push = |
| pushFactory.create(admin.newIdent(), testRepo, "first subject", file, contents); |
| PushOneCommit.Result r = push.to("refs/for/master"); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| ReviewInput input = new ReviewInput(); |
| CommentInput comment = CommentsUtil.newComment(file, Side.REVISION, line, "comment 1", false); |
| input.comments = new HashMap<>(); |
| input.comments.put(comment.path, Lists.newArrayList(comment)); |
| revision(r).review(input); |
| Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId); |
| CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path)); |
| |
| input = new ReviewInput(); |
| comment = CommentsUtil.newComment(file, Side.REVISION, line, "comment 1 reply", false); |
| comment.inReplyTo = actual.id; |
| input.comments = new HashMap<>(); |
| input.comments.put(comment.path, Lists.newArrayList(comment)); |
| revision(r).review(input); |
| result = getPublishedComments(changeId, revId); |
| actual = result.get(comment.path).get(1); |
| assertThat(comment).isEqualTo(infoToInput(file).apply(actual)); |
| assertThat(comment) |
| .isEqualTo(infoToInput(file).apply(getPublishedComment(changeId, revId, actual.id))); |
| } |
| } |
| |
| @Test |
| public void postCommentWithUnresolved() throws Exception { |
| for (Integer line : lines) { |
| String file = "file"; |
| String contents = "contents " + line; |
| PushOneCommit push = |
| pushFactory.create(admin.newIdent(), testRepo, "first subject", file, contents); |
| PushOneCommit.Result r = push.to("refs/for/master"); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| ReviewInput input = new ReviewInput(); |
| CommentInput comment = CommentsUtil.newComment(file, Side.REVISION, line, "comment 1", true); |
| input.comments = new HashMap<>(); |
| input.comments.put(comment.path, Lists.newArrayList(comment)); |
| revision(r).review(input); |
| Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId); |
| assertThat(result).isNotEmpty(); |
| CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path)); |
| assertThat(comment).isEqualTo(infoToInput(file).apply(actual)); |
| assertThat(comment) |
| .isEqualTo(infoToInput(file).apply(getPublishedComment(changeId, revId, actual.id))); |
| } |
| } |
| |
| @Test |
| public void postCommentOnMergeCommitChange() throws Exception { |
| for (Integer line : lines) { |
| String file = "foo"; |
| PushOneCommit.Result r = createMergeCommitChange("refs/for/master", file); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| ReviewInput input = new ReviewInput(); |
| CommentInput c1 = CommentsUtil.newComment(file, Side.REVISION, line, "ps-1", false); |
| CommentInput c2 = |
| CommentsUtil.newComment(file, Side.PARENT, line, "auto-merge of ps-1", false); |
| CommentInput c3 = CommentsUtil.newCommentOnParent(file, 1, line, "parent-1 of ps-1"); |
| CommentInput c4 = CommentsUtil.newCommentOnParent(file, 2, line, "parent-2 of ps-1"); |
| input.comments = new HashMap<>(); |
| input.comments.put(file, ImmutableList.of(c1, c2, c3, c4)); |
| revision(r).review(input); |
| Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId); |
| assertThat(result).isNotEmpty(); |
| assertThat(result.get(file).stream().map(infoToInput(file))).containsExactly(c1, c2, c3, c4); |
| |
| List<CommentInfo> list = getPublishedCommentsAsList(changeId); |
| assertThat(list.stream().map(infoToInput(file))).containsExactly(c1, c2, c3, c4); |
| } |
| |
| // for the commit message comments on the auto-merge are not possible |
| for (Integer line : lines) { |
| String file = Patch.COMMIT_MSG; |
| PushOneCommit.Result r = createMergeCommitChange("refs/for/master"); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| ReviewInput input = new ReviewInput(); |
| CommentInput c1 = CommentsUtil.newComment(file, Side.REVISION, line, "ps-1", false); |
| CommentInput c2 = CommentsUtil.newCommentOnParent(file, 1, line, "parent-1 of ps-1"); |
| CommentInput c3 = CommentsUtil.newCommentOnParent(file, 2, line, "parent-2 of ps-1"); |
| input.comments = new HashMap<>(); |
| input.comments.put(file, ImmutableList.of(c1, c2, c3)); |
| revision(r).review(input); |
| Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId); |
| assertThat(result).isNotEmpty(); |
| assertThat(result.get(file).stream().map(infoToInput(file))).containsExactly(c1, c2, c3); |
| |
| List<CommentInfo> list = getPublishedCommentsAsList(changeId); |
| assertThat(list.stream().map(infoToInput(file))).containsExactly(c1, c2, c3); |
| } |
| } |
| |
| @Test |
| public void postCommentOnCommitMessageOnAutoMerge() throws Exception { |
| PushOneCommit.Result r = createMergeCommitChange("refs/for/master"); |
| ReviewInput input = new ReviewInput(); |
| CommentInput c = |
| CommentsUtil.newComment(Patch.COMMIT_MSG, Side.PARENT, 0, "comment on auto-merge", false); |
| input.comments = new HashMap<>(); |
| input.comments.put(Patch.COMMIT_MSG, ImmutableList.of(c)); |
| BadRequestException thrown = |
| assertThrows(BadRequestException.class, () -> revision(r).review(input)); |
| assertThat(thrown) |
| .hasMessageThat() |
| .contains("cannot comment on " + Patch.COMMIT_MSG + " on auto-merge"); |
| } |
| |
| @Test |
| public void postCommentsReplacingDrafts() throws Exception { |
| String file = "file"; |
| PushOneCommit push = |
| pushFactory.create(admin.newIdent(), testRepo, "first subject", file, "contents"); |
| PushOneCommit.Result r = push.to("refs/for/master"); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| |
| DraftInput draft = CommentsUtil.newDraft(file, Side.REVISION, 0, "comment"); |
| addDraft(changeId, revId, draft); |
| Map<String, List<CommentInfo>> drafts = getDraftComments(changeId, revId); |
| CommentInfo draftInfo = Iterables.getOnlyElement(drafts.get(draft.path)); |
| |
| ReviewInput reviewInput = new ReviewInput(); |
| reviewInput.drafts = DraftHandling.KEEP; |
| reviewInput.message = "foo"; |
| CommentInput comment = CommentsUtil.newComment(file, Side.REVISION, 0, "comment", false); |
| // Replace the existing draft. |
| comment.id = draftInfo.id; |
| reviewInput.comments = new HashMap<>(); |
| reviewInput.comments.put(comment.path, ImmutableList.of(comment)); |
| revision(r).review(reviewInput); |
| |
| // DraftHandling.KEEP is ignored on publishing a comment. |
| drafts = getDraftComments(changeId, revId); |
| assertThat(drafts).isEmpty(); |
| } |
| |
| @Test |
| public void postCommentsUnreachableData() throws Exception { |
| String file = "file"; |
| PushOneCommit push = |
| pushFactory.create(admin.newIdent(), testRepo, "first subject", file, "l1\nl2\n"); |
| |
| String dest = "refs/for/master"; |
| PushOneCommit.Result r1 = push.to(dest); |
| r1.assertOkStatus(); |
| String changeId = r1.getChangeId(); |
| String revId = r1.getCommit().getName(); |
| |
| PushOneCommit.Result r2 = amendChange(r1.getChangeId()); |
| r2.assertOkStatus(); |
| |
| String draftRefName = RefNames.refsDraftComments(r1.getChange().getId(), admin.id()); |
| |
| DraftInput draft = CommentsUtil.newDraft(file, Side.REVISION, 1, "comment"); |
| addDraft(changeId, "1", draft); |
| ReviewInput reviewInput = new ReviewInput(); |
| reviewInput.drafts = DraftHandling.PUBLISH; |
| reviewInput.message = "foo"; |
| gApi.changes().id(r1.getChangeId()).revision(1).review(reviewInput); |
| |
| addDraft(changeId, "2", CommentsUtil.newDraft(file, Side.REVISION, 2, "comment2")); |
| reviewInput = new ReviewInput(); |
| reviewInput.drafts = DraftHandling.PUBLISH_ALL_REVISIONS; |
| reviewInput.message = "bar"; |
| gApi.changes().id(r1.getChangeId()).revision(2).review(reviewInput); |
| |
| Map<String, List<CommentInfo>> drafts = getDraftComments(changeId, revId); |
| assertThat(drafts.isEmpty()).isTrue(); |
| |
| try (Repository repo = repoManager.openRepository(allUsers)) { |
| Ref ref = repo.exactRef(draftRefName); |
| assertThat(ref).isNull(); |
| } |
| } |
| |
| @Test |
| public void listComments() throws Exception { |
| String file = "file"; |
| PushOneCommit push = |
| pushFactory.create(admin.newIdent(), testRepo, "first subject", file, "contents"); |
| PushOneCommit.Result r = push.to("refs/for/master"); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| assertThat(getPublishedComments(changeId, revId)).isEmpty(); |
| assertThat(getPublishedCommentsAsList(changeId)).isEmpty(); |
| |
| List<CommentInput> expectedComments = new ArrayList<>(); |
| for (Integer line : lines) { |
| ReviewInput input = new ReviewInput(); |
| CommentInput comment = |
| CommentsUtil.newComment(file, Side.REVISION, line, "comment " + line, false); |
| expectedComments.add(comment); |
| input.comments = new HashMap<>(); |
| input.comments.put(comment.path, Lists.newArrayList(comment)); |
| revision(r).review(input); |
| } |
| |
| Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId); |
| assertThat(result).isNotEmpty(); |
| List<CommentInfo> actualComments = result.get(file); |
| assertThat(actualComments.stream().map(infoToInput(file))) |
| .containsExactlyElementsIn(expectedComments); |
| |
| List<CommentInfo> list = getPublishedCommentsAsList(changeId); |
| assertThat(list.stream().map(infoToInput(file))).containsExactlyElementsIn(expectedComments); |
| } |
| |
| /** |
| * This test makes sure that the commits in the refs/draft-comments ref in NoteDb have no parent |
| * commits. This is important so that each new draft update (add, modify, delete) does not keep |
| * track of previous history. |
| */ |
| @Test |
| public void commitsInDraftCommentsRefHaveNoParent() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| String draftRefName = RefNames.refsDraftComments(r.getChange().getId(), user.id()); |
| |
| DraftInput comment1 = CommentsUtil.newDraft("file_1", Side.REVISION, 1, "comment 1"); |
| CommentInfo commentInfo1 = addDraft(changeId, revId, comment1); |
| assertThat(getHeadOfDraftCommentsRef(draftRefName).getParentCount()).isEqualTo(0); |
| |
| DraftInput comment2 = CommentsUtil.newDraft("file_2", Side.REVISION, 2, "comment 2"); |
| CommentInfo commentInfo2 = addDraft(changeId, revId, comment2); |
| assertThat(getHeadOfDraftCommentsRef(draftRefName).getParentCount()).isEqualTo(0); |
| |
| deleteDraft(changeId, revId, commentInfo1.id); |
| assertThat(getHeadOfDraftCommentsRef(draftRefName).getParentCount()).isEqualTo(0); |
| assertThat( |
| getDraftComments(changeId, revId).values().stream() |
| .flatMap(List::stream) |
| .map(commentInfo -> commentInfo.message)) |
| .containsExactly("comment 2"); |
| |
| deleteDraft(changeId, revId, commentInfo2.id); |
| assertThat(projectOperations.project(allUsers).hasHead(draftRefName)).isFalse(); |
| assertThat(getDraftComments(changeId, revId).values().stream().flatMap(List::stream)).isEmpty(); |
| } |
| |
| @Test |
| public void putDraft() throws Exception { |
| for (Integer line : lines) { |
| PushOneCommit.Result r = createChange(); |
| Instant origLastUpdated = r.getChange().change().getLastUpdatedOn(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| String path = "file1"; |
| DraftInput comment = CommentsUtil.newDraft(path, Side.REVISION, line, "comment 1"); |
| addDraft(changeId, revId, comment); |
| Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId); |
| CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path)); |
| assertThat(comment).isEqualTo(infoToDraft(path).apply(actual)); |
| String uuid = actual.id; |
| comment.message = "updated comment 1"; |
| updateDraft(changeId, revId, comment, uuid); |
| result = getDraftComments(changeId, revId); |
| actual = Iterables.getOnlyElement(result.get(comment.path)); |
| assertThat(comment).isEqualTo(infoToDraft(path).apply(actual)); |
| |
| // Posting a draft comment doesn't cause lastUpdatedOn to change. |
| assertThat(r.getChange().change().getLastUpdatedOn()).isEqualTo(origLastUpdated); |
| } |
| } |
| |
| @Test |
| public void putDraft_humanInReplyTo() throws Exception { |
| Change.Id changeId = changeOperations.newChange().create(); |
| String parentCommentUuid = |
| changeOperations.change(changeId).currentPatchset().newComment().create(); |
| |
| DraftInput draft = CommentsUtil.newDraft(COMMIT_MSG, Side.REVISION, 0, "foo"); |
| draft.inReplyTo = parentCommentUuid; |
| String createdDraftUuid = addDraft(changeId, draft).id; |
| TestHumanComment actual = |
| changeOperations.change(changeId).draftComment(createdDraftUuid).get(); |
| assertThat(actual.parentUuid()).hasValue(parentCommentUuid); |
| } |
| |
| @Test |
| public void putDraft_robotInReplyTo() throws Exception { |
| Change.Id changeId = changeOperations.newChange().create(); |
| String parentRobotCommentUuid = |
| changeOperations.change(changeId).currentPatchset().newRobotComment().create(); |
| |
| DraftInput draft = CommentsUtil.newDraft(COMMIT_MSG, Side.REVISION, 0, "foo"); |
| draft.inReplyTo = parentRobotCommentUuid; |
| String createdDraftUuid = addDraft(changeId, draft).id; |
| TestHumanComment actual = |
| changeOperations.change(changeId).draftComment(createdDraftUuid).get(); |
| assertThat(actual.parentUuid()).hasValue(parentRobotCommentUuid); |
| } |
| |
| @Test |
| public void putDraft_idMismatch() throws Exception { |
| String file = "file"; |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput comment = CommentsUtil.newDraft(file, Side.REVISION, 0, "foo"); |
| CommentInfo commentInfo = addDraft(changeId, revId, comment); |
| DraftInput draftInput = CommentsUtil.newDraft(file, Side.REVISION, 0, "bar"); |
| draftInput.id = "anything_but_" + commentInfo.id; |
| BadRequestException e = |
| assertThrows( |
| BadRequestException.class, |
| () -> updateDraft(changeId, revId, draftInput, commentInfo.id)); |
| assertThat(e).hasMessageThat().contains("id must match URL"); |
| } |
| |
| @Test |
| public void putDraft_negativeLine() throws Exception { |
| String file = "file"; |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput comment = CommentsUtil.newDraft(file, Side.REVISION, -666, "foo"); |
| BadRequestException e = |
| assertThrows(BadRequestException.class, () -> addDraft(changeId, revId, comment)); |
| assertThat(e).hasMessageThat().contains("line must be >= 0"); |
| } |
| |
| @Test |
| public void putDraft_invalidRange() throws Exception { |
| String file = "file"; |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput draftInput = |
| CommentsUtil.newDraft(file, Side.REVISION, createLineRange(2, 3), "bar"); |
| draftInput.line = 666; |
| BadRequestException e = |
| assertThrows(BadRequestException.class, () -> addDraft(changeId, revId, draftInput)); |
| assertThat(e) |
| .hasMessageThat() |
| .contains("range endLine must be on the same line as the comment"); |
| } |
| |
| @Test |
| public void putDraft_invalidInReplyTo() throws Exception { |
| Change.Id changeId = changeOperations.newChange().create(); |
| DraftInput draft = CommentsUtil.newDraft(COMMIT_MSG, Side.REVISION, 0, "foo"); |
| draft.inReplyTo = "invalid"; |
| BadRequestException exception = |
| assertThrows(BadRequestException.class, () -> addDraft(changeId, draft)); |
| assertThat(exception.getMessage()).contains(String.format("%s not found", draft.inReplyTo)); |
| } |
| |
| @Test |
| public void putDraft_updatePath() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput comment = CommentsUtil.newDraft("file_foo", Side.REVISION, 0, "foo"); |
| CommentInfo commentInfo = addDraft(changeId, revId, comment); |
| assertThat(getDraftComments(changeId, revId).keySet()).containsExactly("file_foo"); |
| DraftInput draftInput = CommentsUtil.newDraft("file_bar", Side.REVISION, 0, "bar"); |
| updateDraft(changeId, revId, draftInput, commentInfo.id); |
| assertThat(getDraftComments(changeId, revId).keySet()).containsExactly("file_bar"); |
| } |
| |
| @Test |
| public void putDraft_updateInvalidInReplyTo() throws Exception { |
| Change.Id changeId = changeOperations.newChange().create(); |
| DraftInput originalDraftInput = CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 0, "foo"); |
| CommentInfo originalDraft = addDraft(changeId, originalDraftInput); |
| |
| DraftInput updatedDraftInput = CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 0, "bar"); |
| updatedDraftInput.inReplyTo = "invalid"; |
| BadRequestException exception = |
| assertThrows( |
| BadRequestException.class, |
| () -> updateDraft(changeId, updatedDraftInput, originalDraft.id)); |
| assertThat(exception.getMessage()).contains(String.format("Invalid inReplyTo")); |
| } |
| |
| @Test |
| public void putDraft_updateHumanInReplyTo() throws Exception { |
| Change.Id changeId = changeOperations.newChange().create(); |
| String parentCommentUuid = |
| changeOperations.change(changeId).currentPatchset().newComment().create(); |
| DraftInput originalDraftInput = CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 0, "foo"); |
| CommentInfo originalDraft = addDraft(changeId, originalDraftInput); |
| |
| DraftInput updateDraftInput = CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 0, "bar"); |
| updateDraftInput.inReplyTo = parentCommentUuid; |
| updateDraft(changeId, updateDraftInput, originalDraft.id); |
| assertThat(changeOperations.change(changeId).draftComment(originalDraft.id).get().parentUuid()) |
| .hasValue(parentCommentUuid); |
| } |
| |
| @Test |
| public void putDraft_updateRobotInReplyTo() throws Exception { |
| Change.Id changeId = changeOperations.newChange().create(); |
| String parentRobotCommentUuid = |
| changeOperations.change(changeId).currentPatchset().newRobotComment().create(); |
| DraftInput originalDraftInput = CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 0, "foo"); |
| CommentInfo originalDraft = addDraft(changeId, originalDraftInput); |
| |
| DraftInput updateDraftInput = CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 0, "bar"); |
| updateDraftInput.inReplyTo = parentRobotCommentUuid; |
| updateDraft(changeId, updateDraftInput, originalDraft.id); |
| assertThat(changeOperations.change(changeId).draftComment(originalDraft.id).get().parentUuid()) |
| .hasValue(parentRobotCommentUuid); |
| } |
| |
| @Test |
| public void putDraft_updateTag() throws Exception { |
| Change.Id changeId = changeOperations.newChange().create(); |
| DraftInput originalDraftInput = CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 0, "foo"); |
| CommentInfo originalDraft = addDraft(changeId, originalDraftInput); |
| |
| DraftInput updateDraftInput = CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 0, "bar"); |
| String tag = "täg"; |
| updateDraftInput.tag = tag; |
| updateDraft(changeId, updateDraftInput, originalDraft.id); |
| assertThat(changeOperations.change(changeId).draftComment(originalDraft.id).get().tag()) |
| .hasValue(tag); |
| } |
| |
| @Test |
| public void updatedDraftStillPointsToParentComment() throws Exception { |
| Account.Id accountId = accountOperations.newAccount().create(); |
| Change.Id changeId = changeOperations.newChange().create(); |
| PatchSet.Id patchsetId = changeOperations.change(changeId).currentPatchset().get().patchsetId(); |
| String parentCommentUuid = |
| changeOperations.change(changeId).patchset(patchsetId).newComment().create(); |
| String draftCommentUuid = |
| changeOperations |
| .change(changeId) |
| .patchset(patchsetId) |
| .newDraftComment() |
| .parentUuid(parentCommentUuid) |
| .author(accountId) |
| .create(); |
| |
| // Each user can only see their own drafts. |
| requestScopeOperations.setApiUser(accountId); |
| DraftInput draftInput = CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 0, "bar"); |
| draftInput.message = "Another comment text."; |
| gApi.changes() |
| .id(changeId.get()) |
| .revision(patchsetId.get()) |
| .draft(draftCommentUuid) |
| .update(draftInput); |
| |
| TestHumanComment comment = |
| changeOperations.change(changeId).draftComment(draftCommentUuid).get(); |
| assertThat(comment.parentUuid()).hasValue(parentCommentUuid); |
| } |
| |
| @Test |
| public void listDrafts() throws Exception { |
| String file = "file"; |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| assertThat(getDraftComments(changeId, revId)).isEmpty(); |
| |
| List<DraftInput> expectedDrafts = new ArrayList<>(); |
| for (Integer line : lines) { |
| DraftInput comment = CommentsUtil.newDraft(file, Side.REVISION, line, "comment " + line); |
| expectedDrafts.add(comment); |
| addDraft(changeId, revId, comment); |
| } |
| |
| Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId); |
| assertThat(result).isNotEmpty(); |
| List<CommentInfo> actualComments = result.get(file); |
| assertThat(actualComments.stream().map(infoToDraft(file))) |
| .containsExactlyElementsIn(expectedDrafts); |
| } |
| |
| @Test |
| public void getDraft() throws Exception { |
| for (Integer line : lines) { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| String path = "file1"; |
| DraftInput comment = CommentsUtil.newDraft(path, Side.REVISION, line, "comment 1"); |
| CommentInfo returned = addDraft(changeId, revId, comment); |
| CommentInfo actual = getDraftComment(changeId, revId, returned.id); |
| assertThat(comment).isEqualTo(infoToDraft(path).apply(actual)); |
| } |
| } |
| |
| @Test |
| public void deleteDraft() throws Exception { |
| for (Integer line : lines) { |
| PushOneCommit.Result r = createChange(); |
| Instant origLastUpdated = r.getChange().change().getLastUpdatedOn(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput draft = CommentsUtil.newDraft("file1", Side.REVISION, line, "comment 1"); |
| CommentInfo returned = addDraft(changeId, revId, draft); |
| deleteDraft(changeId, revId, returned.id); |
| Map<String, List<CommentInfo>> drafts = getDraftComments(changeId, revId); |
| assertThat(drafts).isEmpty(); |
| |
| // Deleting a draft comment doesn't cause lastUpdatedOn to change. |
| assertThat(r.getChange().change().getLastUpdatedOn()).isEqualTo(origLastUpdated); |
| } |
| } |
| |
| @Test |
| public void insertCommentsWithHistoricTimestamp() throws Exception { |
| Instant timestamp = Instant.EPOCH; |
| for (Integer line : lines) { |
| String file = "file"; |
| String contents = "contents " + line; |
| PushOneCommit push = |
| pushFactory.create(admin.newIdent(), testRepo, "first subject", file, contents); |
| PushOneCommit.Result r = push.to("refs/for/master"); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| Instant origLastUpdated = r.getChange().change().getLastUpdatedOn(); |
| |
| ReviewInput input = new ReviewInput(); |
| CommentInput comment = CommentsUtil.newComment(file, Side.REVISION, line, "comment 1", false); |
| comment.setUpdated(timestamp); |
| input.comments = new HashMap<>(); |
| input.comments.put(comment.path, Lists.newArrayList(comment)); |
| ChangeResource changeRsrc = |
| changes.get().parse(TopLevelResource.INSTANCE, IdString.fromDecoded(changeId)); |
| RevisionResource revRsrc = revisions.parse(changeRsrc, IdString.fromDecoded(revId)); |
| postReview.get().apply(revRsrc, input, timestamp); |
| Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId); |
| assertThat(result).isNotEmpty(); |
| CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path)); |
| CommentInput ci = infoToInput(file).apply(actual); |
| ci.updated = comment.updated; |
| assertThat(comment).isEqualTo(ci); |
| assertThat(actual.updated).isEqualTo(gApi.changes().id(r.getChangeId()).info().created); |
| |
| // Updating historic comments doesn't cause lastUpdatedOn to regress. |
| assertThat(r.getChange().change().getLastUpdatedOn()).isEqualTo(origLastUpdated); |
| } |
| } |
| |
| @Test |
| public void addDuplicateComments() throws Exception { |
| PushOneCommit.Result r1 = createChange(); |
| String changeId = r1.getChangeId(); |
| String revId = r1.getCommit().getName(); |
| CommentsUtil.addComment(gApi, r1, "nit: trailing whitespace"); |
| CommentsUtil.addComment(gApi, r1, "nit: trailing whitespace"); |
| Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId); |
| assertThat(result.get(FILE_NAME)).hasSize(2); |
| CommentsUtil.addComment(gApi, r1, "nit: trailing whitespace", true, false, null); |
| result = getPublishedComments(changeId, revId); |
| assertThat(result.get(FILE_NAME)).hasSize(2); |
| |
| PushOneCommit.Result r2 = |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "content") |
| .to("refs/for/master"); |
| changeId = r2.getChangeId(); |
| revId = r2.getCommit().getName(); |
| CommentsUtil.addComment(gApi, r2, "nit: trailing whitespace", true, false, null); |
| result = getPublishedComments(changeId, revId); |
| assertThat(result.get(FILE_NAME)).hasSize(1); |
| } |
| |
| @Test |
| public void listChangeDrafts() throws Exception { |
| PushOneCommit.Result r1 = createChange(); |
| |
| PushOneCommit.Result r2 = |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "new content", r1.getChangeId()) |
| .to("refs/for/master"); |
| |
| requestScopeOperations.setApiUser(admin.id()); |
| addDraft( |
| r1.getChangeId(), |
| r1.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 1, "nit: trailing whitespace")); |
| addDraft( |
| r2.getChangeId(), |
| r2.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 1, "typo: content")); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| addDraft( |
| r2.getChangeId(), |
| r2.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 1, "+1, please fix")); |
| |
| requestScopeOperations.setApiUser(admin.id()); |
| Map<String, List<CommentInfo>> actual = gApi.changes().id(r1.getChangeId()).drafts(); |
| assertThat(actual.keySet()).containsExactly(FILE_NAME); |
| List<CommentInfo> comments = actual.get(FILE_NAME); |
| assertThat(comments).hasSize(2); |
| |
| CommentInfo c1 = comments.get(0); |
| assertThat(c1.author).isNull(); |
| assertThat(c1.patchSet).isEqualTo(1); |
| assertThat(c1.message).isEqualTo("nit: trailing whitespace"); |
| assertThat(c1.side).isNull(); |
| assertThat(c1.line).isEqualTo(1); |
| |
| CommentInfo c2 = comments.get(1); |
| assertThat(c2.author).isNull(); |
| assertThat(c2.patchSet).isEqualTo(2); |
| assertThat(c2.message).isEqualTo("typo: content"); |
| assertThat(c2.side).isNull(); |
| assertThat(c2.line).isEqualTo(1); |
| } |
| |
| @Test |
| public void listChangeDraftsAnonymousThrowsAuthException() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| |
| requestScopeOperations.setApiUserAnonymous(); |
| assertThrows(AuthException.class, () -> gApi.changes().id(changeId).draftsAsList()); |
| } |
| |
| @Test |
| public void listChangeComments() throws Exception { |
| PushOneCommit.Result r1 = createChange(); |
| |
| PushOneCommit.Result r2 = |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "new cntent", r1.getChangeId()) |
| .to("refs/for/master"); |
| |
| CommentsUtil.addComment(gApi, r1, "nit: trailing whitespace"); |
| CommentsUtil.addComment(gApi, r2, "typo: content"); |
| |
| Map<String, List<CommentInfo>> actual = |
| gApi.changes().id(r2.getChangeId()).commentsRequest().get(); |
| assertThat(actual.keySet()).containsExactly(FILE_NAME); |
| |
| List<CommentInfo> comments = actual.get(FILE_NAME); |
| assertThat(comments).hasSize(2); |
| |
| // Comment context is disabled by default |
| assertThat(comments.stream().filter(c -> c.contextLines != null)).isEmpty(); |
| |
| CommentInfo c1 = comments.get(0); |
| assertThat(c1.author._accountId).isEqualTo(user.id().get()); |
| assertThat(c1.patchSet).isEqualTo(1); |
| assertThat(c1.message).isEqualTo("nit: trailing whitespace"); |
| assertThat(c1.side).isNull(); |
| assertThat(c1.line).isEqualTo(1); |
| |
| CommentInfo c2 = comments.get(1); |
| assertThat(c2.author._accountId).isEqualTo(user.id().get()); |
| assertThat(c2.patchSet).isEqualTo(2); |
| assertThat(c2.message).isEqualTo("typo: content"); |
| assertThat(c2.side).isNull(); |
| assertThat(c2.line).isEqualTo(1); |
| } |
| |
| @Test |
| public void listChangeCommentsAnonymousDoesNotRequireAuth() throws Exception { |
| PushOneCommit.Result r1 = createChange(); |
| |
| PushOneCommit.Result r2 = |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "new cntent", r1.getChangeId()) |
| .to("refs/for/master"); |
| |
| CommentsUtil.addComment(gApi, r1, "nit: trailing whitespace"); |
| CommentsUtil.addComment(gApi, r2, "typo: content"); |
| |
| List<CommentInfo> comments = gApi.changes().id(r1.getChangeId()).commentsRequest().getAsList(); |
| assertThat(comments.stream().map(c -> c.message).collect(toList())) |
| .containsExactly("nit: trailing whitespace", "typo: content"); |
| |
| requestScopeOperations.setApiUserAnonymous(); |
| comments = gApi.changes().id(r1.getChangeId()).commentsRequest().getAsList(); |
| assertThat(comments.stream().map(c -> c.message).collect(toList())) |
| .containsExactly("nit: trailing whitespace", "typo: content"); |
| } |
| |
| @Test |
| public void listChangeWithDrafts() throws Exception { |
| for (Integer line : lines) { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String revId = r.getCommit().getName(); |
| DraftInput comment = CommentsUtil.newDraft("file1", Side.REVISION, line, "comment 1"); |
| addDraft(changeId, revId, comment); |
| assertThat(gApi.changes().query("change:" + changeId + " has:draft").get()).hasSize(1); |
| } |
| } |
| |
| @Test |
| public void publishCommentsAllRevisions() throws Exception { |
| PushOneCommit.Result result = createChange(); |
| String changeId = result.getChangeId(); |
| |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "initial content\n", changeId) |
| .to("refs/heads/master"); |
| |
| PushOneCommit.Result r1 = |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "old boring content\n") |
| .to("refs/for/master"); |
| |
| PushOneCommit.Result r2 = |
| pushFactory |
| .create( |
| admin.newIdent(), |
| testRepo, |
| SUBJECT, |
| FILE_NAME, |
| "new interesting\ncntent\n", |
| r1.getChangeId()) |
| .to("refs/for/master"); |
| |
| addDraft( |
| r1.getChangeId(), |
| r1.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, createLineRange(4, 10), "Is it that bad?")); |
| addDraft( |
| r1.getChangeId(), |
| r1.getCommit().getName(), |
| CommentsUtil.newDraft( |
| FILE_NAME, Side.PARENT, createLineRange(0, 7), "what happened to this?")); |
| addDraft( |
| r2.getChangeId(), |
| r2.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, createLineRange(4, 15), "better now")); |
| addDraft( |
| r2.getChangeId(), |
| r2.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 2, "typo: content")); |
| addDraft( |
| r2.getChangeId(), |
| r2.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.PARENT, 1, "comment 1 on base")); |
| addDraft( |
| r2.getChangeId(), |
| r2.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.PARENT, 2, "comment 2 on base")); |
| |
| PushOneCommit.Result other = createChange(); |
| // Drafts on other changes aren't returned. |
| addDraft( |
| other.getChangeId(), |
| other.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 1, "unrelated comment")); |
| |
| requestScopeOperations.setApiUser(admin.id()); |
| // Drafts by other users aren't returned. |
| addDraft( |
| r2.getChangeId(), |
| r2.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 2, "oops")); |
| requestScopeOperations.setApiUser(user.id()); |
| |
| ReviewInput reviewInput = new ReviewInput(); |
| reviewInput.drafts = DraftHandling.PUBLISH_ALL_REVISIONS; |
| reviewInput.message = "comments"; |
| gApi.changes().id(r2.getChangeId()).current().review(reviewInput); |
| |
| assertThat(gApi.changes().id(r1.getChangeId()).revision(r1.getCommit().name()).drafts()) |
| .isEmpty(); |
| Map<String, List<CommentInfo>> ps1Map = |
| gApi.changes().id(r1.getChangeId()).revision(r1.getCommit().name()).comments(); |
| assertThat(ps1Map.keySet()).containsExactly(FILE_NAME); |
| List<CommentInfo> ps1List = ps1Map.get(FILE_NAME); |
| assertThat(ps1List).hasSize(2); |
| assertThat(ps1List.get(0).message).isEqualTo("what happened to this?"); |
| assertThat(ps1List.get(0).side).isEqualTo(Side.PARENT); |
| assertThat(ps1List.get(1).message).isEqualTo("Is it that bad?"); |
| assertThat(ps1List.get(1).side).isNull(); |
| |
| assertThat(gApi.changes().id(r2.getChangeId()).revision(r2.getCommit().name()).drafts()) |
| .isEmpty(); |
| Map<String, List<CommentInfo>> ps2Map = |
| gApi.changes().id(r2.getChangeId()).revision(r2.getCommit().name()).comments(); |
| assertThat(ps2Map.keySet()).containsExactly(FILE_NAME); |
| List<CommentInfo> ps2List = ps2Map.get(FILE_NAME); |
| assertThat(ps2List).hasSize(4); |
| assertThat(ps2List.get(0).message).isEqualTo("comment 1 on base"); |
| assertThat(ps2List.get(1).message).isEqualTo("comment 2 on base"); |
| assertThat(ps2List.get(2).message).isEqualTo("better now"); |
| assertThat(ps2List.get(3).message).isEqualTo("typo: content"); |
| |
| List<Message> messages = email.getMessages(r2.getChangeId(), "comment"); |
| assertThat(messages).hasSize(1); |
| String url = canonicalWebUrl.get(); |
| int c = r1.getChange().getId().get(); |
| assertThat(extractComments(messages.get(0).body())) |
| .isEqualTo( |
| "Patch Set 2:\n" |
| + "\n" |
| + "(6 comments)\n" |
| + "\n" |
| + "comments\n" |
| + "\n" |
| + "File a.txt:\n" |
| + "\n" |
| + url |
| + "c/" |
| + project.get() |
| + "/+/" |
| + c |
| + "/comment/" |
| + ps1List.get(0).id |
| + " :\n" |
| + "PS1, Line 1: initial\n" |
| + "what happened to this?\n" |
| + "\n" |
| + "\n" |
| + url |
| + "c/" |
| + project.get() |
| + "/+/" |
| + c |
| + "/comment/" |
| + ps1List.get(1).id |
| + " :\n" |
| + "PS1, Line 1: boring\n" |
| + "Is it that bad?\n" |
| + "\n" |
| + "\n" |
| + "File a.txt:\n" |
| + "\n" |
| + url |
| + "c/" |
| + project.get() |
| + "/+/" |
| + c |
| + "/comment/" |
| + ps2List.get(0).id |
| + " :\n" |
| + "PS2, Line 1: initial content\n" |
| + "comment 1 on base\n" |
| + "\n" |
| + "\n" |
| + url |
| + "c/" |
| + project.get() |
| + "/+/" |
| + c |
| + "/comment/" |
| + ps2List.get(1).id |
| + " :\n" |
| + "PS2, Line 2: \n" |
| + "comment 2 on base\n" |
| + "\n" |
| + "\n" |
| + url |
| + "c/" |
| + project.get() |
| + "/+/" |
| + c |
| + "/comment/" |
| + ps2List.get(2).id |
| + " :\n" |
| + "PS2, Line 1: interesting\n" |
| + "better now\n" |
| + "\n" |
| + "\n" |
| + url |
| + "c/" |
| + project.get() |
| + "/+/" |
| + c |
| + "/comment/" |
| + ps2List.get(3).id |
| + " :\n" |
| + "PS2, Line 2: cntent\n" |
| + "typo: content\n" |
| + "\n" |
| + "\n"); |
| } |
| |
| @Test |
| public void publishPartialDraftsAllRevisions() throws Exception { |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "initial content\n") |
| .to("refs/heads/master"); |
| |
| PushOneCommit.Result r1 = |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "File content in PS1\n") |
| .to("refs/for/master"); |
| |
| PushOneCommit.Result r2 = |
| pushFactory |
| .create( |
| admin.newIdent(), |
| testRepo, |
| SUBJECT, |
| FILE_NAME, |
| "File content in PS2\n", |
| r1.getChangeId()) |
| .to("refs/for/master"); |
| |
| CommentInfo draftOnePs1 = |
| addDraft( |
| r1.getChangeId(), |
| r1.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, createLineRange(4, 10), "comment 1")); |
| |
| CommentInfo draftTwoPs1 = |
| addDraft( |
| r1.getChangeId(), |
| r1.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, createLineRange(4, 15), "comment 2")); |
| |
| CommentInfo draftThreePs2 = |
| addDraft( |
| r1.getChangeId(), |
| r2.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, createLineRange(3, 12), "comment 3")); |
| |
| ReviewInput reviewInput = |
| createReviewInput( |
| DraftHandling.PUBLISH_ALL_REVISIONS, |
| "review message", |
| /* draftIdsToPublish= */ ImmutableList.of(draftOnePs1.id, draftThreePs2.id)); |
| gApi.changes().id(r1.getChangeId()).current().review(reviewInput); |
| |
| assertThat( |
| gApi.changes().id(r1.getChangeId()).commentsRequest().getAsList().stream() |
| .map(c -> c.id)) |
| .containsExactly(draftOnePs1.id, draftThreePs2.id); |
| |
| assertThat( |
| gApi.changes().id(r1.getChangeId()).draftsRequest().getAsList().stream().map(c -> c.id)) |
| .containsExactly(draftTwoPs1.id); |
| } |
| |
| @Test |
| public void publishPartialDrafts_whenDraftHandlingIsKeep_doesNotPublishDrafts() throws Exception { |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "initial content\n") |
| .to("refs/heads/master"); |
| |
| PushOneCommit.Result r1 = |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "File content in PS1\n") |
| .to("refs/for/master"); |
| |
| CommentInfo draftPs1 = |
| addDraft( |
| r1.getChangeId(), |
| r1.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, createLineRange(4, 10), "comment 1")); |
| |
| ReviewInput reviewInput = |
| createReviewInput( |
| DraftHandling.KEEP, |
| "review message", |
| /* draftIdsToPublish= */ ImmutableList.of(draftPs1.id)); |
| |
| gApi.changes().id(r1.getChangeId()).current().review(reviewInput); |
| |
| assertThat(gApi.changes().id(r1.getChangeId()).commentsRequest().getAsList()).isEmpty(); |
| assertThat( |
| gApi.changes().id(r1.getChangeId()).draftsRequest().getAsList().stream().map(c -> c.id)) |
| .containsExactly(draftPs1.id); |
| } |
| |
| @Test |
| public void publishPartialDrafts_whenDraftHandlingIsPublish_isNotAllowedForOtherRevisions() |
| throws Exception { |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "initial content\n") |
| .to("refs/heads/master"); |
| |
| PushOneCommit.Result r1 = |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "File content in PS1\n") |
| .to("refs/for/master"); |
| |
| PushOneCommit.Result r2 = |
| pushFactory |
| .create( |
| admin.newIdent(), |
| testRepo, |
| SUBJECT, |
| FILE_NAME, |
| "File content in PS2\n", |
| r1.getChangeId()) |
| .to("refs/for/master"); |
| |
| CommentInfo draftPs1 = |
| addDraft( |
| r1.getChangeId(), |
| r1.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, createLineRange(4, 10), "comment 1")); |
| |
| addDraft( |
| r1.getChangeId(), |
| r2.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, createLineRange(3, 12), "comment 3")); |
| |
| ReviewInput reviewInput = |
| createReviewInput( |
| DraftHandling.PUBLISH, |
| "review message", |
| /* draftIdsToPublish= */ ImmutableList.of(draftPs1.id)); |
| |
| // Request to publish draft of PS1, while sending review for PS2 with DraftHandling=PUBLISH |
| Exception error = |
| assertThrows( |
| BadRequestException.class, |
| () -> gApi.changes().id(r1.getChangeId()).current().review(reviewInput)); |
| assertThat(error) |
| .hasMessageThat() |
| .isEqualTo( |
| String.format( |
| "Draft comments for other revisions cannot be published when DraftHandling = PUBLISH." |
| + " (draft IDs: [%s])", |
| draftPs1.id)); |
| } |
| |
| @Test |
| public void publishPartialDraftsWithInvalidDraftIdsIsNotAllowed() throws Exception { |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "initial content\n") |
| .to("refs/heads/master"); |
| |
| PushOneCommit.Result r1 = |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "File content in PS1\n") |
| .to("refs/for/master"); |
| |
| addDraft( |
| r1.getChangeId(), |
| r1.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, createLineRange(4, 10), "comment 1")); |
| |
| ReviewInput reviewInput = |
| createReviewInput( |
| DraftHandling.PUBLISH_ALL_REVISIONS, |
| "review message", |
| /* draftIdsToPublish= */ ImmutableList.of("1234")); |
| |
| Exception error = |
| assertThrows( |
| BadRequestException.class, |
| () -> gApi.changes().id(r1.getChangeId()).current().review(reviewInput)); |
| assertThat(error).hasMessageThat().isEqualTo("Non-existing draft IDs: [1234]"); |
| } |
| |
| @Test |
| public void publishPartialDraftsForAnotherUserIsNotAllowed() throws Exception { |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "initial content\n") |
| .to("refs/heads/master"); |
| |
| PushOneCommit.Result r1 = |
| pushFactory |
| .create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "File content in PS1\n") |
| .to("refs/for/master"); |
| |
| // Add drafts with user scope |
| requestScopeOperations.setApiUser(accountCreator.user1().id()); |
| CommentInfo draft = |
| addDraft( |
| r1.getChangeId(), |
| r1.getCommit().getName(), |
| CommentsUtil.newDraft(FILE_NAME, Side.REVISION, createLineRange(4, 10), "comment 1")); |
| |
| ReviewInput reviewInput = |
| createReviewInput( |
| DraftHandling.PUBLISH_ALL_REVISIONS, |
| "review message", |
| /* draftIdsToPublish= */ ImmutableList.of(draft.id)); |
| |
| // Try to publish the drafts using user2 scope |
| requestScopeOperations.setApiUser(accountCreator.user2().id()); |
| Exception error = |
| assertThrows( |
| BadRequestException.class, |
| () -> gApi.changes().id(r1.getChangeId()).current().review(reviewInput)); |
| assertThat(error) |
| .hasMessageThat() |
| .isEqualTo(String.format("Non-existing draft IDs: [%s]", draft.id)); |
| |
| // Request will succeed if done by user |
| requestScopeOperations.setApiUser(accountCreator.user1().id()); |
| gApi.changes().id(r1.getChangeId()).current().review(reviewInput); |
| assertThat( |
| gApi.changes().id(r1.getChangeId()).commentsRequest().getAsList().stream() |
| .map(c -> c.id)) |
| .containsExactly(draft.id); |
| |
| assertThat(gApi.changes().id(r1.getChangeId()).draftsRequest().getAsList()).isEmpty(); |
| } |
| |
| @Test |
| public void commentTags() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| |
| CommentInput pub = new CommentInput(); |
| pub.line = 1; |
| pub.message = "published comment"; |
| pub.path = FILE_NAME; |
| ReviewInput rin = CommentsUtil.newInput(pub); |
| rin.tag = "tag1"; |
| gApi.changes().id(r.getChangeId()).current().review(rin); |
| |
| List<CommentInfo> comments = gApi.changes().id(r.getChangeId()).current().commentsAsList(); |
| assertThat(comments).hasSize(1); |
| assertThat(comments.get(0).tag).isEqualTo("tag1"); |
| |
| DraftInput draft = new DraftInput(); |
| draft.line = 2; |
| draft.message = "draft comment"; |
| draft.path = FILE_NAME; |
| draft.tag = "tag2"; |
| addDraft(r.getChangeId(), r.getCommit().name(), draft); |
| |
| List<CommentInfo> drafts = gApi.changes().id(r.getChangeId()).current().draftsAsList(); |
| assertThat(drafts).hasSize(1); |
| assertThat(drafts.get(0).tag).isEqualTo("tag2"); |
| } |
| |
| @Test |
| public void draftCommentsWithTagPublishPatchset() throws Exception { |
| PushOneCommit.Result result = createChange(); |
| |
| DraftInput draft = CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 2, "draft"); |
| draft.tag = "old_tag"; |
| addDraft(result.getChangeId(), result.getCommit().name(), draft); |
| |
| ReviewInput reviewInput = new ReviewInput(); |
| reviewInput.tag = "new_tag"; |
| reviewInput.drafts = DraftHandling.PUBLISH; |
| gApi.changes().id(result.getChangeId()).current().review(reviewInput); |
| |
| assertThat( |
| Iterables.getOnlyElement( |
| gApi.changes().id(result.getChangeId()).current().commentsAsList()) |
| .tag) |
| .isEqualTo("new_tag"); |
| } |
| |
| @Test |
| public void draftCommentsWithTagPublishAllRevisions() throws Exception { |
| PushOneCommit.Result result = createChange(); |
| |
| DraftInput draft = CommentsUtil.newDraft(FILE_NAME, Side.REVISION, 2, "draft"); |
| draft.tag = "old_tag"; |
| addDraft(result.getChangeId(), result.getCommit().name(), draft); |
| |
| amendChange(result.getChangeId()); |
| |
| ReviewInput reviewInput = new ReviewInput(); |
| reviewInput.tag = "new_tag"; |
| reviewInput.drafts = DraftHandling.PUBLISH_ALL_REVISIONS; |
| gApi.changes().id(result.getChangeId()).current().review(reviewInput); |
| |
| assertThat( |
| Iterables.getOnlyElement( |
| gApi.changes() |
| .id(result.getChangeId()) |
| .revision(result.getCommit().name()) |
| .commentsAsList()) |
| .tag) |
| .isEqualTo("new_tag"); |
| } |
| |
| @Test |
| public void queryChangesWithCommentCount() throws Exception { |
| // PS1 has three comments in three different threads, PS2 has one comment in one thread. |
| PushOneCommit.Result result = createChange("change 1", FILE_NAME, "content 1"); |
| String changeId1 = result.getChangeId(); |
| CommentsUtil.addComment(gApi, result, "comment 1", false, true, null); |
| CommentsUtil.addComment(gApi, result, "comment 2", false, null, null); |
| CommentsUtil.addComment(gApi, result, "comment 3", false, false, null); |
| PushOneCommit.Result result2 = amendChange(changeId1); |
| CommentsUtil.addComment(gApi, result2, "comment4", false, true, null); |
| |
| // Change2 has two comments in one thread, the first is unresolved and the second is resolved. |
| result = createChange("change 2", FILE_NAME, "content 2"); |
| String changeId2 = result.getChangeId(); |
| CommentsUtil.addComment(gApi, result, "comment 1", false, true, null); |
| Map<String, List<CommentInfo>> comments = |
| getPublishedComments(changeId2, result.getCommit().name()); |
| assertThat(comments).hasSize(1); |
| assertThat(comments.get(FILE_NAME)).hasSize(1); |
| CommentsUtil.addComment( |
| gApi, result, "comment 2", false, false, comments.get(FILE_NAME).get(0).id); |
| |
| // Change3 has two comments in one thread, the first is resolved, the second is unresolved. |
| result = createChange("change 3", FILE_NAME, "content 3"); |
| String changeId3 = result.getChangeId(); |
| CommentsUtil.addComment(gApi, result, "comment 1", false, false, null); |
| comments = getPublishedComments(result.getChangeId(), result.getCommit().name()); |
| assertThat(comments).hasSize(1); |
| assertThat(comments.get(FILE_NAME)).hasSize(1); |
| CommentsUtil.addComment( |
| gApi, result, "comment 2", false, true, comments.get(FILE_NAME).get(0).id); |
| |
| try (AutoCloseable ignored = disableNoteDb()) { |
| ChangeInfo changeInfo1 = Iterables.getOnlyElement(query(changeId1)); |
| ChangeInfo changeInfo2 = Iterables.getOnlyElement(query(changeId2)); |
| ChangeInfo changeInfo3 = Iterables.getOnlyElement(query(changeId3)); |
| assertThat(changeInfo1.unresolvedCommentCount).isEqualTo(2); |
| assertThat(changeInfo1.totalCommentCount).isEqualTo(4); |
| assertThat(changeInfo2.unresolvedCommentCount).isEqualTo(0); |
| assertThat(changeInfo2.totalCommentCount).isEqualTo(2); |
| assertThat(changeInfo3.unresolvedCommentCount).isEqualTo(1); |
| assertThat(changeInfo3.totalCommentCount).isEqualTo(2); |
| } |
| } |
| |
| @Test |
| public void deleteCommentCannotBeAppliedByUser() throws Exception { |
| PushOneCommit.Result result = createChange(); |
| CommentInput targetComment = CommentsUtil.addComment(gApi, result.getChangeId()); |
| |
| Map<String, List<CommentInfo>> commentsMap = |
| getPublishedComments(result.getChangeId(), result.getCommit().name()); |
| |
| assertThat(commentsMap).hasSize(1); |
| assertThat(commentsMap.get(FILE_NAME)).hasSize(1); |
| |
| String uuid = commentsMap.get(targetComment.path).get(0).id; |
| DeleteCommentInput input = new DeleteCommentInput("contains confidential information"); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| assertThrows( |
| AuthException.class, |
| () -> gApi.changes().id(result.getChangeId()).current().comment(uuid).delete(input)); |
| } |
| |
| @Test |
| public void deleteCommentByRewritingCommitHistory() throws Exception { |
| // Creates the following commit history on the meta branch of the test change. Then tries to |
| // delete the comments one by one, which will rewrite most of the commits on the 'meta' branch. |
| // Commits will be rewritten N times for N added comments. After each deletion, the meta branch |
| // should keep its previous state except that the target comment's message should be updated. |
| |
| // 1st commit: Create PS1. |
| PushOneCommit.Result result1 = createChange(SUBJECT, "a.txt", "a"); |
| Change.Id id = result1.getChange().getId(); |
| String changeId = result1.getChangeId(); |
| String ps1 = result1.getCommit().name(); |
| |
| // 2nd commit: Add (c1) to PS1. |
| CommentInput c1 = CommentsUtil.newComment("a.txt", "comment 1"); |
| CommentsUtil.addComments(gApi, changeId, ps1, c1); |
| |
| // 3rd commit: Add (c2, c3) to PS1. |
| CommentInput c2 = CommentsUtil.newComment("a.txt", "comment 2"); |
| CommentInput c3 = CommentsUtil.newComment("a.txt", "comment 3"); |
| CommentsUtil.addComments(gApi, changeId, ps1, c2, c3); |
| |
| // 4th commit: Add (c4) to PS1. |
| CommentInput c4 = CommentsUtil.newComment("a.txt", "comment 4"); |
| CommentsUtil.addComments(gApi, changeId, ps1, c4); |
| |
| // 5th commit: Create PS2. |
| PushOneCommit.Result result2 = amendChange(changeId, "refs/for/master", "b.txt", "b"); |
| String ps2 = result2.getCommit().name(); |
| |
| // 6th commit: Add (c5) to PS1. |
| CommentInput c5 = CommentsUtil.newComment("a.txt", "comment 5"); |
| CommentsUtil.addComments(gApi, changeId, ps1, c5); |
| |
| // 7th commit: Add (c6) to PS2. |
| CommentInput c6 = CommentsUtil.newComment("b.txt", "comment 6"); |
| CommentsUtil.addComments(gApi, changeId, ps2, c6); |
| |
| // 8th commit: Create PS3. |
| PushOneCommit.Result result3 = amendChange(changeId); |
| String ps3 = result3.getCommit().name(); |
| |
| // 9th commit: Create PS4. |
| PushOneCommit.Result result4 = amendChange(changeId, "refs/for/master", "c.txt", "c"); |
| String ps4 = result4.getCommit().name(); |
| |
| // 10th commit: Add (c7, c8) to PS4. |
| CommentInput c7 = CommentsUtil.newComment("c.txt", "comment 7"); |
| CommentInput c8 = CommentsUtil.newComment("b.txt", "comment 8"); |
| CommentsUtil.addComments(gApi, changeId, ps4, c7, c8); |
| |
| // 11th commit: Add (c9) to PS2. |
| CommentInput c9 = CommentsUtil.newCommentWithOnlyMandatoryFields("b.txt", "comment 9"); |
| CommentsUtil.addComments(gApi, changeId, ps2, c9); |
| |
| List<CommentInfo> commentsBeforeDelete = getChangeSortedComments(id.get()); |
| assertThat(commentsBeforeDelete).hasSize(9); |
| // PS1 has comments [c1, c2, c3, c4, c5]. |
| assertThat(getRevisionComments(changeId, ps1)).hasSize(5); |
| // PS2 has comments [c6, c9]. |
| assertThat(getRevisionComments(changeId, ps2)).hasSize(2); |
| // PS3 has no comment. |
| assertThat(getRevisionComments(changeId, ps3)).isEmpty(); |
| // PS4 has comments [c7, c8]. |
| assertThat(getRevisionComments(changeId, ps4)).hasSize(2); |
| |
| requestScopeOperations.setApiUser(admin.id()); |
| for (int i = 0; i < commentsBeforeDelete.size(); i++) { |
| List<RevCommit> commitsBeforeDelete = getChangeMetaCommitsInReverseOrder(id); |
| |
| CommentInfo comment = commentsBeforeDelete.get(i); |
| String uuid = comment.id; |
| int patchSet = comment.patchSet; |
| // 'oldComment' has some fields unset compared with 'comment'. |
| CommentInfo oldComment = gApi.changes().id(changeId).revision(patchSet).comment(uuid).get(); |
| |
| DeleteCommentInput input = new DeleteCommentInput("delete comment " + uuid); |
| CommentInfo updatedComment = |
| gApi.changes().id(changeId).revision(patchSet).comment(uuid).delete(input); |
| |
| String expectedMsg = |
| String.format("Comment removed by: %s; Reason: %s", admin.fullName(), input.reason); |
| assertThat(updatedComment.message).isEqualTo(expectedMsg); |
| oldComment.message = expectedMsg; |
| assertThat(updatedComment).isEqualTo(oldComment); |
| |
| // Check the NoteDb state after the deletion. |
| assertMetaBranchCommitsAfterRewriting(commitsBeforeDelete, id, uuid, expectedMsg); |
| |
| comment.message = expectedMsg; |
| commentsBeforeDelete.set(i, comment); |
| List<CommentInfo> commentsAfterDelete = getChangeSortedComments(id.get()); |
| assertThat(commentsAfterDelete).isEqualTo(commentsBeforeDelete); |
| } |
| |
| // Make sure that comments can still be added correctly. |
| CommentInput c10 = CommentsUtil.newComment("a.txt", "comment 10"); |
| CommentInput c11 = CommentsUtil.newComment("b.txt", "comment 11"); |
| CommentInput c12 = CommentsUtil.newComment("a.txt", "comment 12"); |
| CommentInput c13 = CommentsUtil.newComment("c.txt", "comment 13"); |
| CommentsUtil.addComments(gApi, changeId, ps1, c10); |
| CommentsUtil.addComments(gApi, changeId, ps2, c11); |
| CommentsUtil.addComments(gApi, changeId, ps3, c12); |
| CommentsUtil.addComments(gApi, changeId, ps4, c13); |
| |
| assertThat(getChangeSortedComments(id.get())).hasSize(13); |
| assertThat(getRevisionComments(changeId, ps1)).hasSize(6); |
| assertThat(getRevisionComments(changeId, ps2)).hasSize(3); |
| assertThat(getRevisionComments(changeId, ps3)).hasSize(1); |
| assertThat(getRevisionComments(changeId, ps4)).hasSize(3); |
| } |
| |
| @Test |
| public void deleteOneCommentMultipleTimes() throws Exception { |
| PushOneCommit.Result result = createChange(); |
| Change.Id id = result.getChange().getId(); |
| String changeId = result.getChangeId(); |
| String ps1 = result.getCommit().name(); |
| |
| CommentInput c1 = CommentsUtil.newComment(FILE_NAME, "comment 1"); |
| CommentInput c2 = CommentsUtil.newComment(FILE_NAME, "comment 2"); |
| CommentInput c3 = CommentsUtil.newComment(FILE_NAME, "comment 3"); |
| CommentsUtil.addComments(gApi, changeId, ps1, c1); |
| CommentsUtil.addComments(gApi, changeId, ps1, c2); |
| CommentsUtil.addComments(gApi, changeId, ps1, c3); |
| |
| List<CommentInfo> commentsBeforeDelete = getChangeSortedComments(id.get()); |
| assertThat(commentsBeforeDelete).hasSize(3); |
| Optional<CommentInfo> targetComment = |
| commentsBeforeDelete.stream().filter(c -> c.message.equals("comment 2")).findFirst(); |
| assertThat(targetComment).isPresent(); |
| String uuid = targetComment.get().id; |
| CommentInfo oldComment = gApi.changes().id(changeId).revision(ps1).comment(uuid).get(); |
| |
| List<RevCommit> commitsBeforeDelete = getChangeMetaCommitsInReverseOrder(id); |
| |
| requestScopeOperations.setApiUser(admin.id()); |
| for (int i = 0; i < 3; i++) { |
| DeleteCommentInput input = new DeleteCommentInput("delete comment 2, iteration: " + i); |
| gApi.changes().id(changeId).revision(ps1).comment(uuid).delete(input); |
| } |
| |
| CommentInfo updatedComment = gApi.changes().id(changeId).revision(ps1).comment(uuid).get(); |
| String expectedMsg = |
| String.format( |
| "Comment removed by: %s; Reason: %s", |
| admin.fullName(), "delete comment 2, iteration: 2"); |
| assertThat(updatedComment.message).isEqualTo(expectedMsg); |
| oldComment.message = expectedMsg; |
| assertThat(updatedComment).isEqualTo(oldComment); |
| |
| assertMetaBranchCommitsAfterRewriting(commitsBeforeDelete, id, uuid, expectedMsg); |
| assertThat(getChangeSortedComments(id.get())).hasSize(3); |
| } |
| |
| @Test |
| public void canCreateHumanCommentWithRobotCommentAsParentAndUnsetUnresolved() throws Exception { |
| Change.Id changeId = changeOperations.newChange().create(); |
| String parentRobotCommentUuid = |
| changeOperations.change(changeId).currentPatchset().newRobotComment().create(); |
| |
| CommentInput createdCommentInput = CommentsUtil.newComment(COMMIT_MSG, "comment reply"); |
| createdCommentInput.inReplyTo = parentRobotCommentUuid; |
| createdCommentInput.unresolved = null; |
| CommentsUtil.addComments(gApi, changeId, createdCommentInput); |
| |
| CommentInfo resultNewComment = |
| Iterables.getOnlyElement( |
| getPublishedCommentsAsList(changeId).stream() |
| .filter(c -> c.message.equals("comment reply")) |
| .collect(toImmutableSet())); |
| |
| assertThat(resultNewComment.inReplyTo).isEqualTo(parentRobotCommentUuid); |
| |
| // Default unresolved is false. |
| assertThat(resultNewComment.unresolved).isFalse(); |
| } |
| |
| @Test |
| public void canCreateHumanCommentWithHumanCommentAsParent() throws Exception { |
| Change.Id changeId = changeOperations.newChange().create(); |
| String parentCommentUuid = |
| changeOperations.change(changeId).currentPatchset().newComment().create(); |
| |
| CommentInput createdCommentInput = CommentsUtil.newComment(COMMIT_MSG, "comment reply"); |
| createdCommentInput.inReplyTo = parentCommentUuid; |
| CommentsUtil.addComments(gApi, changeId, createdCommentInput); |
| |
| CommentInfo resultNewComment = |
| Iterables.getOnlyElement( |
| getPublishedCommentsAsList(changeId).stream() |
| .filter(c -> c.message.equals("comment reply")) |
| .collect(toImmutableSet())); |
| assertThat(resultNewComment.inReplyTo).isEqualTo(parentCommentUuid); |
| } |
| |
| @Test |
| public void canCreateHumanCommentWithRobotCommentAsParent() throws Exception { |
| Change.Id changeId = changeOperations.newChange().create(); |
| String parentRobotCommentUuid = |
| changeOperations.change(changeId).currentPatchset().newRobotComment().create(); |
| |
| CommentInput createdCommentInput = CommentsUtil.newComment(COMMIT_MSG, "comment reply"); |
| createdCommentInput.inReplyTo = parentRobotCommentUuid; |
| CommentsUtil.addComments(gApi, changeId, createdCommentInput); |
| |
| CommentInfo resultNewComment = |
| Iterables.getOnlyElement( |
| getPublishedCommentsAsList(changeId).stream() |
| .filter(c -> c.message.equals("comment reply")) |
| .collect(toImmutableSet())); |
| assertThat(resultNewComment.inReplyTo).isEqualTo(parentRobotCommentUuid); |
| } |
| |
| @Test |
| public void cannotCreateCommentWithInvalidInReplyTo() throws Exception { |
| Change.Id changeId = changeOperations.newChange().create(); |
| |
| CommentInput comment = CommentsUtil.newComment(COMMIT_MSG, "comment 1 reply"); |
| comment.inReplyTo = "invalid"; |
| |
| BadRequestException exception = |
| assertThrows( |
| BadRequestException.class, () -> CommentsUtil.addComments(gApi, changeId, comment)); |
| assertThat(exception.getMessage()).contains(String.format("%s not found", comment.inReplyTo)); |
| } |
| |
| @Test |
| public void commentsOnRootCommitsAreIncludedInEmails() throws Exception { |
| // Create a change in a new branch, making the patch-set commit a root commit. |
| ChangeInfo changeInfo = createChangeInNewBranch("newBranch"); |
| Change.Id changeId = Change.Id.tryParse(Integer.toString(changeInfo._number)).get(); |
| |
| // Add a file. |
| gApi.changes().id(changeId.get()).edit().modifyFile("f1.txt", RawInputUtil.create("content")); |
| gApi.changes().id(changeId.get()).edit().publish(); |
| email.clear(); |
| |
| ReviewerInput reviewerInput = new ReviewerInput(); |
| reviewerInput.reviewer = admin.email(); |
| gApi.changes().id(changeId.get()).addReviewer(reviewerInput); |
| changeInfo = gApi.changes().id(changeId.get()).get(); |
| assertThat(email.getMessages()).hasSize(1); |
| Message message = email.getMessages().get(0); |
| assertThat(message.body()).contains("f1.txt"); |
| email.clear(); |
| |
| // Send a comment. Make sure the email that is sent includes the comment text. |
| CommentInput c1 = |
| CommentsUtil.newComment( |
| "f1.txt", |
| Side.REVISION, |
| /* line= */ 1, |
| /* message= */ "Comment text", |
| /* unresolved= */ false); |
| CommentsUtil.addComments(gApi, changeId.toString(), changeInfo.currentRevision, c1); |
| assertThat(email.getMessages()).hasSize(1); |
| Message commentMessage = email.getMessages().get(0); |
| assertThat(commentMessage.body()) |
| .contains("Patch Set 2:\n" + "\n" + "(1 comment)\n" + "\n" + "File f1.txt:"); |
| assertThat(commentMessage.body()).contains("PS2, Line 1: content\n" + "Comment text"); |
| } |
| |
| @Test |
| public void commentsOnDeletedFileIsIncludedInEmails() throws Exception { |
| // Create a change with a file. |
| createChange("subject", "f1.txt", "content"); |
| |
| // Stack a second change that deletes the file. |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| gApi.changes().id(changeId).edit().deleteFile("f1.txt"); |
| gApi.changes().id(changeId).edit().publish(); |
| String currentRevision = gApi.changes().id(changeId).get().currentRevision; |
| |
| // Add a comment on the deleted file on the parent side. |
| email.clear(); |
| CommentInput commentInput = |
| CommentsUtil.newComment( |
| "f1.txt", |
| Side.PARENT, |
| /* line= */ 1, |
| /* message= */ "Comment text", |
| /* unresolved= */ false); |
| CommentsUtil.addComments(gApi, changeId, currentRevision, commentInput); |
| |
| // Assert email contains the comment text. |
| assertThat(email.getMessages()).hasSize(1); |
| Message commentMessage = email.getMessages().get(0); |
| assertThat(commentMessage.body()).contains("Patch Set 2:\n\n(1 comment)\n\nFile f1.txt:"); |
| assertThat(commentMessage.body()).contains("PS2, Line 1: content\nComment text"); |
| } |
| |
| private List<CommentInfo> getRevisionComments(String changeId, String revId) throws Exception { |
| return getPublishedComments(changeId, revId).values().stream() |
| .flatMap(List::stream) |
| .collect(toList()); |
| } |
| |
| /** |
| * All the commits, which contain the target comment before, should still contain the comment with |
| * the updated message. All the other metas of the commits should be exactly the same. |
| */ |
| private void assertMetaBranchCommitsAfterRewriting( |
| List<RevCommit> beforeDelete, |
| Change.Id changeId, |
| String targetCommentUuid, |
| String expectedMessage) |
| throws Exception { |
| List<RevCommit> afterDelete = getChangeMetaCommitsInReverseOrder(changeId); |
| assertThat(afterDelete).hasSize(beforeDelete.size()); |
| |
| try (Repository repo = repoManager.openRepository(project); |
| ObjectReader reader = repo.newObjectReader()) { |
| for (int i = 0; i < beforeDelete.size(); i++) { |
| RevCommit commitBefore = beforeDelete.get(i); |
| RevCommit commitAfter = afterDelete.get(i); |
| |
| Map<String, HumanComment> commentMapBefore = |
| DeleteCommentRewriter.getPublishedComments( |
| noteUtil, reader, NoteMap.read(reader, commitBefore)); |
| Map<String, HumanComment> commentMapAfter = |
| DeleteCommentRewriter.getPublishedComments( |
| noteUtil, reader, NoteMap.read(reader, commitAfter)); |
| |
| if (commentMapBefore.containsKey(targetCommentUuid)) { |
| assertThat(commentMapAfter).containsKey(targetCommentUuid); |
| HumanComment comment = commentMapAfter.get(targetCommentUuid); |
| assertThat(comment.message).isEqualTo(expectedMessage); |
| comment.message = commentMapBefore.get(targetCommentUuid).message; |
| commentMapAfter.put(targetCommentUuid, comment); |
| assertThat(commentMapAfter).isEqualTo(commentMapBefore); |
| } else { |
| assertThat(commentMapAfter).doesNotContainKey(targetCommentUuid); |
| } |
| |
| // Other metas should be exactly the same. |
| assertThat(commitAfter.getFullMessage()).isEqualTo(commitBefore.getFullMessage()); |
| assertThat(commitAfter.getCommitterIdent()).isEqualTo(commitBefore.getCommitterIdent()); |
| assertThat(commitAfter.getAuthorIdent()).isEqualTo(commitBefore.getAuthorIdent()); |
| assertThat(commitAfter.getEncoding()).isEqualTo(commitBefore.getEncoding()); |
| assertThat(commitAfter.getEncodingName()).isEqualTo(commitBefore.getEncodingName()); |
| } |
| } |
| } |
| |
| private RevCommit getHeadOfDraftCommentsRef(String refName) throws Exception { |
| try (Repository repo = repoManager.openRepository(allUsers)) { |
| return getHead(repo, refName); |
| } |
| } |
| |
| private static String extractComments(String msg) { |
| // Extract lines between start "....." and end "-- ". |
| Pattern p = Pattern.compile(".*[.]{5}\n+(.*)\\n+-- \n.*", Pattern.DOTALL); |
| Matcher m = p.matcher(msg); |
| return m.matches() ? m.group(1) : msg; |
| } |
| |
| private CommentInfo addDraft(String changeId, String revId, DraftInput in) throws Exception { |
| return gApi.changes().id(changeId).revision(revId).createDraft(in).get(); |
| } |
| |
| private CommentInfo addDraft(Change.Id changeId, DraftInput in) throws Exception { |
| return gApi.changes().id(changeId.get()).current().createDraft(in).get(); |
| } |
| |
| private void updateDraft(String changeId, String revId, DraftInput in, String uuid) |
| throws Exception { |
| gApi.changes().id(changeId).revision(revId).draft(uuid).update(in); |
| } |
| |
| private void updateDraft(Change.Id changeId, DraftInput in, String uuid) throws Exception { |
| gApi.changes().id(changeId.get()).current().draft(uuid).update(in); |
| } |
| |
| private void deleteDraft(String changeId, String revId, String uuid) throws Exception { |
| gApi.changes().id(changeId).revision(revId).draft(uuid).delete(); |
| } |
| |
| private CommentInfo getPublishedComment(String changeId, String revId, String uuid) |
| throws Exception { |
| return gApi.changes().id(changeId).revision(revId).comment(uuid).get(); |
| } |
| |
| private Map<String, List<CommentInfo>> getPublishedComments(String changeId, String revId) |
| throws Exception { |
| return gApi.changes().id(changeId).revision(revId).comments(); |
| } |
| |
| private List<CommentInfo> getPublishedCommentsAsList(String changeId) throws Exception { |
| return gApi.changes().id(changeId).commentsRequest().getAsList(); |
| } |
| |
| private List<CommentInfo> getPublishedCommentsAsList(Change.Id changeId) throws Exception { |
| return gApi.changes().id(changeId.get()).commentsRequest().getAsList(); |
| } |
| |
| private Map<String, List<CommentInfo>> getDraftComments(String changeId, String revId) |
| throws Exception { |
| return gApi.changes().id(changeId).revision(revId).drafts(); |
| } |
| |
| private List<CommentInfo> getDraftCommentsAsList(String changeId) throws Exception { |
| return gApi.changes().id(changeId).draftsAsList(); |
| } |
| |
| private CommentInfo getDraftComment(String changeId, String revId, String uuid) throws Exception { |
| return gApi.changes().id(changeId).revision(revId).draft(uuid).get(); |
| } |
| |
| private static Comment.Range createLineRange(int startChar, int endChar) { |
| Comment.Range range = new Comment.Range(); |
| range.startLine = 1; |
| range.startCharacter = startChar; |
| range.endLine = 1; |
| range.endCharacter = endChar; |
| return range; |
| } |
| |
| private static Comment.Range createRange( |
| int startLine, int startCharacter, int endLine, int endCharacter) { |
| Comment.Range range = new Comment.Range(); |
| range.startLine = startLine; |
| range.startCharacter = startCharacter; |
| range.endLine = endLine; |
| range.endCharacter = endCharacter; |
| return range; |
| } |
| |
| private static Function<CommentInfo, CommentInput> infoToInput(String path) { |
| return info -> { |
| CommentInput commentInput = new CommentInput(); |
| commentInput.path = path; |
| commentInput.unresolved = info.unresolved; |
| copy(info, commentInput); |
| return commentInput; |
| }; |
| } |
| |
| private static Function<CommentInfo, DraftInput> infoToDraft(String path) { |
| return info -> { |
| DraftInput draftInput = new DraftInput(); |
| draftInput.path = path; |
| draftInput.unresolved = info.unresolved; |
| copy(info, draftInput); |
| return draftInput; |
| }; |
| } |
| |
| private static void copy(Comment from, Comment to) { |
| to.side = from.side == null ? Side.REVISION : from.side; |
| to.parent = from.parent; |
| to.line = from.line; |
| to.message = from.message; |
| to.range = from.range; |
| to.inReplyTo = from.inReplyTo; |
| } |
| |
| private ReviewInput createReviewInput( |
| DraftHandling handling, String message, List<String> draftIdsToPublish) { |
| ReviewInput reviewInput = new ReviewInput(); |
| reviewInput.drafts = handling; |
| reviewInput.message = message; |
| reviewInput.draftIdsToPublish = draftIdsToPublish; |
| return reviewInput; |
| } |
| |
| private ChangeInfo createChangeInNewBranch(String branchName) throws Exception { |
| ChangeInput in = new ChangeInput(); |
| in.project = project.get(); |
| in.branch = branchName; |
| in.newBranch = true; |
| in.subject = "New changes"; |
| return gApi.changes().create(in).get(); |
| } |
| } |