| // Copyright (C) 2013 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.server.notedb; |
| |
| import static com.google.common.collect.ImmutableList.toImmutableList; |
| import static com.google.common.collect.ImmutableSet.toImmutableSet; |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| import static com.google.common.truth.Truth8.assertThat; |
| import static com.google.gerrit.entities.RefNames.changeMetaRef; |
| import static com.google.gerrit.entities.RefNames.refsDraftComments; |
| import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC; |
| import static com.google.gerrit.server.notedb.ReviewerStateInternal.REMOVED; |
| import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER; |
| import static com.google.gerrit.testing.GerritJUnit.assertThrows; |
| import static com.google.gerrit.testing.TestActionRefUpdateContext.testRefAction; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| import static java.util.Comparator.comparing; |
| import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; |
| import static org.mockito.Mockito.mock; |
| |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableListMultimap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.ImmutableSortedMap; |
| import com.google.common.collect.ImmutableTable; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.ListMultimap; |
| import com.google.common.collect.Lists; |
| import com.google.gerrit.common.Nullable; |
| import com.google.gerrit.entities.Account; |
| import com.google.gerrit.entities.Address; |
| import com.google.gerrit.entities.AttentionSetUpdate; |
| import com.google.gerrit.entities.AttentionSetUpdate.Operation; |
| import com.google.gerrit.entities.BranchNameKey; |
| import com.google.gerrit.entities.Change; |
| import com.google.gerrit.entities.ChangeMessage; |
| import com.google.gerrit.entities.CommentRange; |
| import com.google.gerrit.entities.HumanComment; |
| import com.google.gerrit.entities.LabelId; |
| import com.google.gerrit.entities.PatchSet; |
| import com.google.gerrit.entities.PatchSetApproval; |
| import com.google.gerrit.entities.SubmissionId; |
| import com.google.gerrit.entities.SubmitRecord; |
| import com.google.gerrit.exceptions.StorageException; |
| import com.google.gerrit.server.CurrentUser; |
| import com.google.gerrit.server.DraftCommentsReader; |
| import com.google.gerrit.server.IdentifiedUser; |
| import com.google.gerrit.server.ReviewerSet; |
| import com.google.gerrit.server.git.validators.TopicValidator; |
| import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk; |
| import com.google.gerrit.server.util.AccountTemplateUtil; |
| import com.google.gerrit.server.util.time.TimeUtil; |
| import com.google.gerrit.server.validators.ValidationException; |
| import com.google.gerrit.testing.TestChanges; |
| import com.google.inject.Inject; |
| import java.time.Instant; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.TreeMap; |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| import org.eclipse.jgit.lib.ObjectId; |
| 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.eclipse.jgit.revwalk.RevWalk; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| public class ChangeNotesTest extends AbstractChangeNotesTest { |
| @Inject private ChangeNoteJson changeNoteJson; |
| |
| @Inject private DraftCommentsReader draftCommentsReader; |
| |
| private TopicValidator topicValidator; |
| |
| @Before |
| public void setUp() throws Exception { |
| topicValidator = mock(TopicValidator.class); |
| } |
| |
| @Test |
| public void tagChangeMessage() throws Exception { |
| String tag = "jenkins"; |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setChangeMessage("verification from jenkins"); |
| update.setTag(tag); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| |
| assertThat(notes.getChangeMessages()).hasSize(1); |
| assertThat(notes.getChangeMessages().get(0).getTag()).isEqualTo(tag); |
| } |
| |
| @Test |
| public void patchSetDescription() throws Exception { |
| String description = "descriptive"; |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setPsDescription(description); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getCurrentPatchSet().description()).hasValue(description); |
| |
| description = "new, now more descriptive!"; |
| update = newUpdate(c, changeOwner); |
| update.setPsDescription(description); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getCurrentPatchSet().description()).hasValue(description); |
| } |
| |
| @Test |
| public void tagInlineComments() throws Exception { |
| String tag = "jenkins"; |
| Change c = newChange(); |
| RevCommit commit = tr.commit().message("PS2").create(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putComment( |
| HumanComment.Status.PUBLISHED, |
| newComment( |
| c.currentPatchSetId(), |
| "a.txt", |
| "uuid1", |
| new CommentRange(1, 2, 3, 4), |
| 1, |
| changeOwner, |
| null, |
| TimeUtil.now(), |
| "Comment", |
| (short) 1, |
| commit, |
| false)); |
| update.setTag(tag); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| |
| ImmutableListMultimap<ObjectId, HumanComment> comments = notes.getHumanComments(); |
| assertThat(comments).hasSize(1); |
| assertThat(comments.entries().asList().get(0).getValue().tag).isEqualTo(tag); |
| } |
| |
| @Test |
| public void tagApprovals() throws Exception { |
| String tag1 = "jenkins"; |
| String tag2 = "ip"; |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.VERIFIED, (short) -1); |
| update.setTag(tag1); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.VERIFIED, (short) 1); |
| update.setTag(tag2); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| |
| ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals = notes.getApprovals().all(); |
| assertThat(approvals).hasSize(1); |
| assertThat(approvals.entries().asList().get(0).getValue().tag()).hasValue(tag2); |
| } |
| |
| @Test |
| public void multipleTags() throws Exception { |
| String ipTag = "ip"; |
| String coverageTag = "coverage"; |
| String integrationTag = "integration"; |
| Change c = newChange(); |
| |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.VERIFIED, (short) -1); |
| update.setChangeMessage("integration verification"); |
| update.setTag(integrationTag); |
| update.commit(); |
| |
| RevCommit commit = tr.commit().message("PS2").create(); |
| update = newUpdate(c, changeOwner); |
| update.putComment( |
| HumanComment.Status.PUBLISHED, |
| newComment( |
| c.currentPatchSetId(), |
| "a.txt", |
| "uuid1", |
| new CommentRange(1, 2, 3, 4), |
| 1, |
| changeOwner, |
| null, |
| TimeUtil.now(), |
| "Comment", |
| (short) 1, |
| commit, |
| false)); |
| update.setChangeMessage("coverage verification"); |
| update.setTag(coverageTag); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.setChangeMessage("ip clear"); |
| update.setTag(ipTag); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| |
| ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals = notes.getApprovals().all(); |
| assertThat(approvals).hasSize(1); |
| PatchSetApproval approval = approvals.entries().asList().get(0).getValue(); |
| assertThat(approval.tag()).hasValue(integrationTag); |
| assertThat(approval.value()).isEqualTo(-1); |
| |
| ImmutableListMultimap<ObjectId, HumanComment> comments = notes.getHumanComments(); |
| assertThat(comments).hasSize(1); |
| assertThat(comments.entries().asList().get(0).getValue().tag).isEqualTo(coverageTag); |
| |
| ImmutableList<ChangeMessage> messages = notes.getChangeMessages(); |
| assertThat(messages).hasSize(3); |
| assertThat(messages.get(0).getTag()).isEqualTo(integrationTag); |
| assertThat(messages.get(1).getTag()).isEqualTo(coverageTag); |
| assertThat(messages.get(2).getTag()).isEqualTo(ipTag); |
| } |
| |
| @Test |
| public void multipleTargetBranches() throws Exception { |
| Change c = newChange(); |
| |
| // PS1 with target branch = refs/heads/master |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.VERIFIED, (short) 1); |
| update.commit(); |
| |
| // PS2 with target branch = refs/heads/foo |
| incrementPatchSet(c); |
| update = newUpdate(c, changeOwner); |
| update.setBranch("refs/heads/foo"); |
| update.commit(); |
| |
| // PS3 with no change |
| incrementPatchSet(c); |
| |
| // PS4 with target branch = refs/heads/bar |
| incrementPatchSet(c); |
| update = newUpdate(c, changeOwner); |
| update.setBranch("refs/heads/bar"); |
| update.commit(); |
| |
| // PS5 with no change |
| incrementPatchSet(c); |
| |
| ChangeNotes notes = newNotes(c); |
| ImmutableSortedMap<PatchSet.Id, PatchSet> patchSets = notes.getPatchSets(); |
| assertThat(patchSets.get(PatchSet.id(c.getId(), 1)).branch()) |
| .isEqualTo(Optional.of("refs/heads/master")); |
| assertThat(patchSets.get(PatchSet.id(c.getId(), 2)).branch()) |
| .isEqualTo(Optional.of("refs/heads/foo")); |
| assertThat(patchSets.get(PatchSet.id(c.getId(), 3)).branch()) |
| .isEqualTo(Optional.of("refs/heads/foo")); |
| assertThat(patchSets.get(PatchSet.id(c.getId(), 4)).branch()) |
| .isEqualTo(Optional.of("refs/heads/bar")); |
| assertThat(patchSets.get(PatchSet.id(c.getId(), 5)).branch()) |
| .isEqualTo(Optional.of("refs/heads/bar")); |
| } |
| |
| @Test |
| public void approvalsOnePatchSet() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.VERIFIED, (short) 1); |
| update.putApproval(LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getApprovals().all().keySet()).containsExactly(c.currentPatchSetId()); |
| List<PatchSetApproval> psas = notes.getApprovals().all().get(c.currentPatchSetId()); |
| assertThat(psas).hasSize(2); |
| |
| assertThat(psas.get(0).patchSetId()).isEqualTo(c.currentPatchSetId()); |
| assertThat(psas.get(0).accountId().get()).isEqualTo(1); |
| assertThat(psas.get(0).label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(psas.get(0).value()).isEqualTo((short) -1); |
| assertThat(psas.get(0).granted()).isEqualTo(truncate(after(c, 2000))); |
| assertParsedUuid(psas.get(0)); |
| |
| assertThat(psas.get(1).patchSetId()).isEqualTo(c.currentPatchSetId()); |
| assertThat(psas.get(1).accountId().get()).isEqualTo(1); |
| assertThat(psas.get(1).label()).isEqualTo(LabelId.VERIFIED); |
| assertThat(psas.get(1).value()).isEqualTo((short) 1); |
| assertThat(psas.get(1).granted()).isEqualTo(psas.get(0).granted()); |
| assertParsedUuid(psas.get(1)); |
| } |
| |
| @Test |
| public void approvalsMultiplePatchSets() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| PatchSet.Id ps1 = c.currentPatchSetId(); |
| |
| incrementPatchSet(c); |
| update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 1); |
| update.commit(); |
| PatchSet.Id ps2 = c.currentPatchSetId(); |
| |
| ChangeNotes notes = newNotes(c); |
| ListMultimap<PatchSet.Id, PatchSetApproval> psas = notes.getApprovals().all(); |
| assertThat(psas).hasSize(2); |
| |
| PatchSetApproval psa1 = Iterables.getOnlyElement(psas.get(ps1)); |
| assertThat(psa1.patchSetId()).isEqualTo(ps1); |
| assertThat(psa1.accountId().get()).isEqualTo(1); |
| assertThat(psa1.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(psa1.value()).isEqualTo((short) -1); |
| assertThat(psa1.granted()).isEqualTo(truncate(after(c, 2000))); |
| assertParsedUuid(psa1); |
| |
| PatchSetApproval psa2 = Iterables.getOnlyElement(psas.get(ps2)); |
| assertThat(psa2.patchSetId()).isEqualTo(ps2); |
| assertThat(psa2.accountId().get()).isEqualTo(1); |
| assertThat(psa2.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(psa2.value()).isEqualTo((short) +1); |
| assertThat(psa2.granted()).isEqualTo(truncate(after(c, 4000))); |
| assertParsedUuid(psa2); |
| } |
| |
| @Test |
| public void approvalsMultipleApprovals() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| PatchSetApproval psa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(psa.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(psa.value()).isEqualTo((short) -1); |
| assertParsedUuid(psa); |
| |
| update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 1); |
| update.commit(); |
| |
| notes = newNotes(c); |
| psa = Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(psa.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(psa.value()).isEqualTo((short) 1); |
| assertParsedUuid(psa); |
| } |
| |
| @Test |
| public void approvalsMultipleUsers() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| |
| update = newUpdate(c, otherUser); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getApprovals().all().keySet()).containsExactly(c.currentPatchSetId()); |
| List<PatchSetApproval> psas = notes.getApprovals().all().get(c.currentPatchSetId()); |
| assertThat(psas).hasSize(2); |
| |
| assertThat(psas.get(0).patchSetId()).isEqualTo(c.currentPatchSetId()); |
| assertThat(psas.get(0).accountId().get()).isEqualTo(1); |
| assertThat(psas.get(0).label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(psas.get(0).value()).isEqualTo((short) -1); |
| assertThat(psas.get(0).granted()).isEqualTo(truncate(after(c, 2000))); |
| assertParsedUuid(psas.get(0)); |
| |
| assertThat(psas.get(1).patchSetId()).isEqualTo(c.currentPatchSetId()); |
| assertThat(psas.get(1).accountId().get()).isEqualTo(2); |
| assertThat(psas.get(1).label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(psas.get(1).value()).isEqualTo((short) 1); |
| assertThat(psas.get(1).granted()).isEqualTo(truncate(after(c, 3000))); |
| assertParsedUuid(psas.get(1)); |
| } |
| |
| @Test |
| public void approvalsTombstone() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval("Not-For-Long", (short) 1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| PatchSetApproval psa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(psa.accountId().get()).isEqualTo(1); |
| assertThat(psa.label()).isEqualTo("Not-For-Long"); |
| assertThat(psa.value()).isEqualTo((short) 1); |
| assertParsedUuid(psa); |
| |
| update = newUpdate(c, changeOwner); |
| update.removeApproval("Not-For-Long"); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getApprovals().all()) |
| .containsExactlyEntriesIn( |
| ImmutableListMultimap.of( |
| psa.patchSetId(), |
| PatchSetApproval.builder() |
| .key(psa.key()) |
| .value(0) |
| .granted(update.getWhen()) |
| .build())); |
| } |
| |
| @Test |
| public void approval_UUIDGenerated_forAllValues() throws Exception { |
| for (int value = -2; value <= 2; value++) { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) value); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| PatchSetApproval psa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(psa.accountId()).isEqualTo(changeOwner.getAccountId()); |
| assertThat(psa.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(psa.value()).isEqualTo((short) value); |
| assertParsedUuid(psa); |
| } |
| } |
| |
| @Test |
| public void emptyApproval_uuidGenerated() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| PatchSetApproval psa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(psa.accountId()).isEqualTo(changeOwner.getAccountId()); |
| assertThat(psa.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(psa.value()).isEqualTo((short) -1); |
| assertParsedUuid(psa); |
| |
| update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 0); |
| update.commit(); |
| |
| notes = newNotes(c); |
| PatchSetApproval emptyPsa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(psa.patchSetId())); |
| assertThat(emptyPsa.key()).isEqualTo(psa.key()); |
| assertThat(emptyPsa.value()).isEqualTo((short) 0); |
| assertThat(emptyPsa.label()).isEqualTo(psa.label()); |
| assertParsedUuid(emptyPsa); |
| } |
| |
| @Test |
| public void removedApproval_noUuid() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| PatchSetApproval psa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(psa.accountId()).isEqualTo(changeOwner.getAccountId()); |
| assertThat(psa.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(psa.value()).isEqualTo((short) -1); |
| assertParsedUuid(psa); |
| |
| update = newUpdate(c, changeOwner); |
| update.removeApproval(LabelId.CODE_REVIEW); |
| update.commit(); |
| |
| notes = newNotes(c); |
| PatchSetApproval removedPsa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(psa.patchSetId())); |
| assertThat(removedPsa.key()).isEqualTo(psa.key()); |
| assertThat(removedPsa.value()).isEqualTo((short) 0); |
| assertThat(removedPsa.label()).isEqualTo(psa.label()); |
| assertThat(removedPsa.uuid()).isEmpty(); |
| } |
| |
| @Test |
| public void reissuedApproval_samePatchSet_differentUUID() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getApprovals().all().keySet()).containsExactly(c.currentPatchSetId()); |
| PatchSetApproval originalPsa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| |
| assertThat(originalPsa.patchSetId()).isEqualTo(c.currentPatchSetId()); |
| assertThat(originalPsa.accountId()).isEqualTo(changeOwner.getAccountId()); |
| assertThat(originalPsa.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(originalPsa.value()).isEqualTo((short) -1); |
| assertParsedUuid(originalPsa); |
| |
| // Remove approval from current patch set |
| update = newUpdate(c, changeOwner); |
| update.removeApproval(LabelId.CODE_REVIEW); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getApprovals().all().keySet()).hasSize(1); |
| PatchSetApproval removedPsa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(removedPsa.key()).isEqualTo(originalPsa.key()); |
| assertThat(removedPsa.value()).isEqualTo(0); |
| // Add approval with the same author, label, value to the current patch set |
| update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getApprovals().all().keySet()).hasSize(1); |
| PatchSetApproval reAddedPsa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| |
| assertThat(reAddedPsa.key()).isEqualTo(originalPsa.key()); |
| assertThat(reAddedPsa.value()).isEqualTo(originalPsa.value()); |
| // The re-added approval has a different UUID |
| assertParsedUuid(reAddedPsa); |
| assertThat(reAddedPsa.uuid().get()).isNotEqualTo(originalPsa.uuid().get()); |
| } |
| |
| @Test |
| public void reissuedApproval_otherPatchSet_differentUUID() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getApprovals().all().keySet()).containsExactly(c.currentPatchSetId()); |
| PatchSetApproval originalPsa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| |
| assertThat(originalPsa.patchSetId()).isEqualTo(c.currentPatchSetId()); |
| assertThat(originalPsa.accountId().get()).isEqualTo(1); |
| assertThat(originalPsa.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(originalPsa.value()).isEqualTo((short) -1); |
| assertParsedUuid(originalPsa); |
| |
| // Create new PatchSet and re-issue vote |
| incrementPatchSet(c); |
| update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getApprovals().all().keySet()).hasSize(2); |
| PatchSetApproval postUpdateOriginalPsa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(originalPsa.patchSetId())); |
| PatchSetApproval reAddedPsa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| |
| // Same patch set approval for the original patch set is returned after the vote was re-issued |
| // on the next patch set |
| assertThat(postUpdateOriginalPsa).isEqualTo(originalPsa); |
| |
| assertThat(reAddedPsa.accountId()).isEqualTo(originalPsa.accountId()); |
| assertThat(reAddedPsa.label()).isEqualTo(originalPsa.label()); |
| assertThat(reAddedPsa.value()).isEqualTo(originalPsa.value()); |
| |
| // The re-added approval has a different UUID |
| assertParsedUuid(reAddedPsa); |
| assertThat(reAddedPsa.uuid().get()).isNotEqualTo(originalPsa.uuid().get()); |
| } |
| |
| @Test |
| public void approvalUUID_samePatchSet_differentUsers() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) -1); |
| update.putApprovalFor(otherUserId, LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getApprovals().all().keySet()).containsExactly(c.currentPatchSetId()); |
| List<PatchSetApproval> patchSetApprovals = |
| notes.getApprovals().all().get(c.currentPatchSetId()); |
| assertThat(patchSetApprovals).hasSize(2); |
| assertThat( |
| patchSetApprovals.stream() |
| .filter(psa -> psa.value() == (short) -1 && psa.label().equals(LabelId.CODE_REVIEW)) |
| .count()) |
| .isEqualTo(2); |
| // Count UUIDs to make sure they are unique |
| assertThat(patchSetApprovals.stream().map(psa -> psa.uuid().get()).distinct().count()) |
| .isEqualTo(2); |
| assertThat(patchSetApprovals.stream().map(psa -> psa.accountId()).collect(toImmutableSet())) |
| .containsExactly(changeOwner.getAccountId(), otherUserId); |
| } |
| |
| @Test |
| public void approvalUUID_samePatchSet_differentLabels() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.VERIFIED, (short) -1); |
| update.putApproval(LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getApprovals().all().keySet()).containsExactly(c.currentPatchSetId()); |
| List<PatchSetApproval> patchSetApprovals = |
| notes.getApprovals().all().get(c.currentPatchSetId()); |
| assertThat(patchSetApprovals).hasSize(2); |
| assertThat( |
| patchSetApprovals.stream() |
| .filter( |
| psa -> |
| psa.value() == (short) -1 |
| && psa.accountId().equals(changeOwner.getAccountId())) |
| .count()) |
| .isEqualTo(2); |
| |
| // Count UUIDs to make sure they are unique |
| assertThat(patchSetApprovals.stream().map(psa -> psa.uuid().get()).distinct().count()) |
| .isEqualTo(2); |
| assertThat(patchSetApprovals.stream().map(psa -> psa.label()).collect(toImmutableSet())) |
| .containsExactly(LabelId.VERIFIED, LabelId.CODE_REVIEW); |
| } |
| |
| @Test |
| public void approvalUUID_differentChanges() throws Exception { |
| Change c1 = newChange(); |
| ChangeUpdate update1 = newUpdate(c1, changeOwner); |
| update1.putApproval(LabelId.CODE_REVIEW, (short) +2); |
| update1.commit(); |
| |
| Change c2 = newChange(); |
| ChangeUpdate update = newUpdate(c2, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) +2); |
| update.commit(); |
| |
| ChangeNotes notes1 = newNotes(c1); |
| PatchSetApproval psa1 = |
| Iterables.getOnlyElement(notes1.getApprovals().all().get(c1.currentPatchSetId())); |
| ChangeNotes notes2 = newNotes(c2); |
| PatchSetApproval psa2 = |
| Iterables.getOnlyElement(notes2.getApprovals().all().get(c2.currentPatchSetId())); |
| assertThat(psa1.label()).isEqualTo(psa2.label()); |
| assertThat(psa1.accountId()).isEqualTo(psa2.accountId()); |
| assertThat(psa1.value()).isEqualTo(psa2.value()); |
| |
| // UUID is global: different across changes. |
| assertThat(psa1.uuid()).isNotEqualTo(psa2.uuid()); |
| } |
| |
| @Test |
| public void removeOtherUsersApprovals() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, otherUser); |
| update.putApproval("Not-For-Long", (short) 1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| PatchSetApproval psa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(psa.accountId()).isEqualTo(otherUserId); |
| assertThat(psa.label()).isEqualTo("Not-For-Long"); |
| assertThat(psa.value()).isEqualTo((short) 1); |
| assertParsedUuid(psa); |
| |
| update = newUpdate(c, changeOwner); |
| update.removeApprovalFor(otherUserId, "Not-For-Long"); |
| update.commit(); |
| |
| notes = newNotes(c); |
| PatchSetApproval removedPsa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(psa.patchSetId())); |
| assertThat(removedPsa.key()).isEqualTo(psa.key()); |
| assertThat(removedPsa.value()).isEqualTo((short) 0); |
| assertThat(removedPsa.label()).isEqualTo(psa.label()); |
| assertThat(removedPsa.uuid()).isEmpty(); |
| |
| // Add back approval on same label. |
| update = newUpdate(c, otherUser); |
| update.putApproval("Not-For-Long", (short) 2); |
| update.commit(); |
| |
| notes = newNotes(c); |
| psa = Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(psa.accountId()).isEqualTo(otherUserId); |
| assertThat(psa.label()).isEqualTo("Not-For-Long"); |
| assertThat(psa.value()).isEqualTo((short) 2); |
| assertParsedUuid(psa); |
| } |
| |
| @Test |
| public void putOtherUsersApprovals() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 1); |
| update.putApprovalFor(otherUser.getAccountId(), LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| ImmutableList<PatchSetApproval> approvals = |
| notes.getApprovals().all().get(c.currentPatchSetId()).stream() |
| .sorted(comparing(a -> a.accountId().get())) |
| .collect(toImmutableList()); |
| assertThat(approvals).hasSize(2); |
| |
| assertThat(approvals.get(0).accountId()).isEqualTo(changeOwner.getAccountId()); |
| assertThat(approvals.get(0).label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(approvals.get(0).value()).isEqualTo((short) 1); |
| assertParsedUuid(approvals.get(0)); |
| |
| assertThat(approvals.get(1).accountId()).isEqualTo(otherUser.getAccountId()); |
| assertThat(approvals.get(1).label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(approvals.get(1).value()).isEqualTo((short) -1); |
| assertThat(approvals.get(1).uuid()).isPresent(); |
| assertParsedUuid(approvals.get(1)); |
| } |
| |
| @Test |
| public void approvalsPostSubmit() throws Exception { |
| Change c = newChange(); |
| SubmissionId submissionId = new SubmissionId(c); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 1); |
| update.putApproval(LabelId.VERIFIED, (short) 1); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.merge( |
| submissionId, |
| ImmutableList.of( |
| submitRecord( |
| "NOT_READY", |
| null, |
| submitLabel(LabelId.VERIFIED, "OK", changeOwner.getAccountId()), |
| submitLabel(LabelId.CODE_REVIEW, "NEED", null)))); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 2); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| List<PatchSetApproval> approvals = Lists.newArrayList(notes.getApprovals().all().values()); |
| assertThat(approvals).hasSize(2); |
| assertThat(approvals.get(0).label()).isEqualTo(LabelId.VERIFIED); |
| assertThat(approvals.get(0).value()).isEqualTo((short) 1); |
| assertThat(approvals.get(0).postSubmit()).isFalse(); |
| assertParsedUuid(approvals.get(1)); |
| |
| assertThat(approvals.get(1).label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(approvals.get(1).value()).isEqualTo((short) 2); |
| assertThat(approvals.get(1).postSubmit()).isTrue(); |
| assertParsedUuid(approvals.get(1)); |
| } |
| |
| @Test |
| public void approvalsDuringSubmit() throws Exception { |
| Change c = newChange(); |
| SubmissionId submissionId = new SubmissionId(c); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 1); |
| update.putApproval(LabelId.VERIFIED, (short) 1); |
| update.commit(); |
| |
| Account.Id ownerId = changeOwner.getAccountId(); |
| Account.Id otherId = otherUser.getAccountId(); |
| update = newUpdate(c, otherUser); |
| update.merge( |
| submissionId, |
| ImmutableList.of( |
| submitRecord( |
| "NOT_READY", |
| null, |
| submitLabel(LabelId.VERIFIED, "OK", ownerId), |
| submitLabel(LabelId.CODE_REVIEW, "NEED", null)))); |
| update.putApproval("Other-Label", (short) 1); |
| update.putApprovalFor(ownerId, LabelId.CODE_REVIEW, (short) 2); |
| update.commit(); |
| |
| update = newUpdate(c, otherUser); |
| update.putApproval("Other-Label", (short) 2); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| |
| List<PatchSetApproval> approvals = Lists.newArrayList(notes.getApprovals().all().values()); |
| assertThat(approvals).hasSize(3); |
| assertThat(approvals.get(0).accountId()).isEqualTo(ownerId); |
| assertThat(approvals.get(0).label()).isEqualTo(LabelId.VERIFIED); |
| assertThat(approvals.get(0).value()).isEqualTo(1); |
| assertThat(approvals.get(0).postSubmit()).isFalse(); |
| assertParsedUuid(approvals.get(0)); |
| |
| assertThat(approvals.get(1).accountId()).isEqualTo(ownerId); |
| assertThat(approvals.get(1).label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(approvals.get(1).value()).isEqualTo(2); |
| assertThat(approvals.get(1).postSubmit()).isFalse(); // During submit. |
| assertParsedUuid(approvals.get(1)); |
| |
| assertThat(approvals.get(2).accountId()).isEqualTo(otherId); |
| assertThat(approvals.get(2).label()).isEqualTo("Other-Label"); |
| assertThat(approvals.get(2).value()).isEqualTo(2); |
| assertThat(approvals.get(2).postSubmit()).isTrue(); |
| assertParsedUuid(approvals.get(2)); |
| } |
| |
| @Test |
| public void copiedApprovals_keepsUUID() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 2); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| PatchSetApproval originalPsa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(originalPsa.accountId()).isEqualTo(changeOwner.getAccountId()); |
| assertThat(originalPsa.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(originalPsa.value()).isEqualTo(2); |
| assertThat(originalPsa.tag()).isEmpty(); |
| assertThat(originalPsa.realAccountId()).isEqualTo(changeOwner.getAccountId()); |
| assertParsedUuid(originalPsa); |
| |
| // Copied approvals are persisted at the patch set upload, add new patch set |
| incrementPatchSet(c); |
| |
| addCopiedApproval(c, changeOwner, originalPsa); |
| |
| notes = newNotes(c); |
| assertThat(notes.getApprovals().all().keySet()).hasSize(2); |
| PatchSetApproval copiedApproval = |
| Iterables.getOnlyElement( |
| notes.getApprovals().all().get(c.currentPatchSetId()).stream() |
| .filter(a -> a.copied()) |
| .collect(toImmutableList())); |
| PatchSetApproval nonCopiedApproval = |
| Iterables.getOnlyElement( |
| notes.getApprovals().all().get(originalPsa.patchSetId()).stream() |
| .filter(a -> !a.copied()) |
| .collect(toImmutableList())); |
| |
| // Still same original PSA is returned |
| assertThat(nonCopiedApproval).isEqualTo(originalPsa); |
| |
| // The copied approval matches the original approval, including UUID |
| assertCopiedApproval(originalPsa, copiedApproval); |
| } |
| |
| @Test |
| public void copiedApprovals_withRealUserAndTag_keepsUUID() throws Exception { |
| ImmutableList<String> strangeTags = |
| ImmutableList.of(", ", ":\"", ",", "!@#$%^\0&*):\" \n: \r\"#$@,. :"); |
| for (String strangeTag : strangeTags) { |
| Change c = newChange(); |
| CurrentUser otherUserAsOwner = |
| userFactory.runAs(/* remotePeer= */ null, changeOwner.getAccountId(), otherUser); |
| ChangeUpdate update = newUpdate(c, otherUserAsOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 2); |
| update.setTag(strangeTag); |
| update.commit(); |
| ChangeNotes notes = newNotes(c); |
| PatchSetApproval originalPsa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(originalPsa.accountId()).isEqualTo(changeOwner.getAccountId()); |
| assertThat(originalPsa.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(originalPsa.value()).isEqualTo(2); |
| assertThat(originalPsa.tag()).hasValue(NoteDbUtil.sanitizeFooter(strangeTag)); |
| assertThat(originalPsa.realAccountId()).isEqualTo(otherUserId); |
| assertParsedUuid(originalPsa); |
| |
| // Copied approvals are persisted at the patch set upload, add new patch set |
| incrementPatchSet(c); |
| |
| addCopiedApproval(c, changeOwner, originalPsa); |
| |
| notes = newNotes(c); |
| assertThat(notes.getApprovals().all().keySet()).hasSize(2); |
| PatchSetApproval copiedApproval = |
| Iterables.getOnlyElement( |
| notes.getApprovals().all().get(c.currentPatchSetId()).stream() |
| .filter(a -> a.copied()) |
| .collect(toImmutableList())); |
| PatchSetApproval nonCopiedApproval = |
| Iterables.getOnlyElement( |
| notes.getApprovals().all().get(originalPsa.patchSetId()).stream() |
| .filter(a -> !a.copied()) |
| .collect(toImmutableList())); |
| |
| // Still same original PSA is returned |
| assertThat(nonCopiedApproval).isEqualTo(originalPsa); |
| |
| // The copied approval matches the original approval, including UUID |
| assertCopiedApproval(originalPsa, copiedApproval); |
| } |
| } |
| |
| @Test |
| public void copiedApprovals_withTag_keepsUUID() throws Exception { |
| ImmutableList<String> strangeTags = |
| ImmutableList.of(", ", ":\"", ",", "!@#$%^\0&*):\" \n: \r\"#$@,. :"); |
| for (String strangeTag : strangeTags) { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 2); |
| update.setTag(strangeTag); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| PatchSetApproval originalPsa = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(originalPsa.accountId()).isEqualTo(changeOwner.getAccountId()); |
| assertThat(originalPsa.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(originalPsa.value()).isEqualTo(2); |
| assertThat(originalPsa.tag()).hasValue(NoteDbUtil.sanitizeFooter(strangeTag)); |
| assertThat(originalPsa.realAccountId()).isEqualTo(changeOwner.getAccountId()); |
| assertParsedUuid(originalPsa); |
| // Copied approvals are persisted at the patch set upload, add new patch set |
| incrementPatchSet(c); |
| |
| addCopiedApproval(c, changeOwner, originalPsa); |
| |
| notes = newNotes(c); |
| assertThat(notes.getApprovals().all().keySet()).hasSize(2); |
| PatchSetApproval copiedApproval = |
| Iterables.getOnlyElement( |
| notes.getApprovals().all().get(c.currentPatchSetId()).stream() |
| .filter(a -> a.copied()) |
| .collect(toImmutableList())); |
| PatchSetApproval nonCopiedApproval = |
| Iterables.getOnlyElement( |
| notes.getApprovals().all().get(originalPsa.patchSetId()).stream() |
| .filter(a -> !a.copied()) |
| .collect(toImmutableList())); |
| |
| // Still same original PSA is returned |
| assertThat(nonCopiedApproval).isEqualTo(originalPsa); |
| |
| // The copied approval matches the original approval, including UUID |
| assertCopiedApproval(originalPsa, copiedApproval); |
| } |
| } |
| |
| @Test |
| public void copiedApprovals() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putCopiedApproval( |
| PatchSetApproval.builder() |
| .key( |
| PatchSetApproval.key( |
| c.currentPatchSetId(), |
| changeOwner.getAccountId(), |
| LabelId.create(LabelId.CODE_REVIEW))) |
| .value(1) |
| .copied(true) |
| .granted(TimeUtil.now()) |
| .tag("tag") |
| .realAccountId(otherUserId) |
| .build()); |
| update.putApprovalFor(otherUserId, LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| PatchSetApproval approval = |
| Iterables.getOnlyElement(notes.getApprovals().onlyNonCopied().get(c.currentPatchSetId())); |
| assertThat(approval.accountId()).isEqualTo(otherUser.getAccountId()); |
| assertThat(approval.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(approval.value()).isEqualTo((short) -1); |
| assertThat(approval.copied()).isFalse(); |
| |
| ImmutableList<PatchSetApproval> approvals = |
| notes.getApprovals().all().get(c.currentPatchSetId()).stream() |
| .sorted(comparing(a -> a.accountId().get())) |
| .collect(toImmutableList()); |
| assertThat(approvals).hasSize(2); |
| |
| PatchSetApproval copied = approvals.get(0); |
| assertThat(copied.accountId()).isEqualTo(changeOwner.getAccountId()); |
| assertThat(copied.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(copied.value()).isEqualTo((short) 1); |
| assertThat(copied.tag()).hasValue("tag"); |
| assertThat(copied.accountId()).isEqualTo(changeOwner.getAccountId()); |
| assertThat(copied.realAccountId()).isEqualTo(otherUserId); |
| assertThat(copied.copied()).isTrue(); |
| |
| PatchSetApproval nonCopied = approvals.get(1); |
| assertThat(nonCopied.accountId()).isEqualTo(otherUserId); |
| assertThat(nonCopied.realAccountId()).isEqualTo(otherUserId); |
| assertThat(nonCopied.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(nonCopied.value()).isEqualTo((short) -1); |
| } |
| |
| @Test |
| public void copiedApprovalsCanBeRemoved() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putCopiedApproval( |
| PatchSetApproval.builder() |
| .key( |
| PatchSetApproval.key( |
| c.currentPatchSetId(), |
| changeOwner.getAccountId(), |
| LabelId.create(LabelId.CODE_REVIEW))) |
| .value(1) |
| .copied(true) |
| .granted(TimeUtil.now()) |
| .build()); |
| update.commit(); |
| |
| update.removeApproval(LabelId.CODE_REVIEW); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| PatchSetApproval approval = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(approval.accountId()).isEqualTo(changeOwner.getAccountId()); |
| assertThat(approval.label()).isEqualTo(LabelId.CODE_REVIEW); |
| // The vote got removed since the latest patch-set only has one vote and it's "0". The copied |
| // approval will never have a "0" vote, but it can be overridden by a "0" vote of a |
| // non-copied approval. |
| assertThat(approval.value()).isEqualTo((short) 0); |
| assertThat(approval.copied()).isFalse(); |
| } |
| |
| @Test |
| public void copiedApprovalsWithStrangeTags() throws Exception { |
| String strangeTag = "!@#$%^\0&*):\" \n: \r\"#$@,. :"; |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putCopiedApproval( |
| PatchSetApproval.builder() |
| .key( |
| PatchSetApproval.key( |
| c.currentPatchSetId(), |
| changeOwner.getAccountId(), |
| LabelId.create(LabelId.CODE_REVIEW))) |
| .value(1) |
| .copied(true) |
| .granted(TimeUtil.now()) |
| .tag(strangeTag) |
| .build()); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| PatchSetApproval approval = |
| Iterables.getOnlyElement(notes.getApprovals().all().get(c.currentPatchSetId())); |
| assertThat(approval.accountId()).isEqualTo(changeOwner.getAccountId()); |
| assertThat(approval.label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(approval.value()).isEqualTo((short) 1); |
| assertThat(approval.tag()).hasValue(NoteDbUtil.sanitizeFooter(strangeTag)); |
| assertThat(approval.copied()).isTrue(); |
| } |
| |
| @Test |
| public void copiedApprovalsPostSubmit() throws Exception { |
| Change c = newChange(); |
| SubmissionId submissionId = new SubmissionId(c); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putCopiedApproval( |
| PatchSetApproval.builder() |
| .key( |
| PatchSetApproval.key( |
| c.currentPatchSetId(), |
| changeOwner.getAccountId(), |
| LabelId.create(LabelId.CODE_REVIEW))) |
| .value(1) |
| .copied(true) |
| .granted(TimeUtil.now()) |
| .build()); |
| update.putCopiedApproval( |
| PatchSetApproval.builder() |
| .key( |
| PatchSetApproval.key( |
| c.currentPatchSetId(), |
| changeOwner.getAccountId(), |
| LabelId.create(LabelId.VERIFIED))) |
| .value(1) |
| .copied(true) |
| .granted(TimeUtil.now()) |
| .build()); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.merge( |
| submissionId, |
| ImmutableList.of( |
| submitRecord( |
| "NOT_READY", |
| null, |
| submitLabel(LabelId.VERIFIED, "OK", changeOwner.getAccountId()), |
| submitLabel(LabelId.CODE_REVIEW, "NEED", null)))); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.putCopiedApproval( |
| PatchSetApproval.builder() |
| .key( |
| PatchSetApproval.key( |
| c.currentPatchSetId(), |
| changeOwner.getAccountId(), |
| LabelId.create(LabelId.CODE_REVIEW))) |
| .value(2) |
| .copied(true) |
| .granted(TimeUtil.now()) |
| .build()); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| List<PatchSetApproval> approvals = Lists.newArrayList(notes.getApprovals().all().values()); |
| assertThat(approvals).hasSize(2); |
| assertThat(approvals.get(0).label()).isEqualTo(LabelId.VERIFIED); |
| assertThat(approvals.get(0).value()).isEqualTo((short) 1); |
| assertThat(approvals.get(0).postSubmit()).isFalse(); |
| assertThat(approvals.get(1).label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(approvals.get(1).value()).isEqualTo((short) 2); |
| assertThat(approvals.get(1).postSubmit()).isTrue(); |
| } |
| |
| @Test |
| public void multipleReviewers() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewer(changeOwner.getAccount().id(), REVIEWER); |
| update.putReviewer(otherUser.getAccount().id(), REVIEWER); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| Instant ts = update.getWhen(); |
| assertThat(notes.getReviewers()) |
| .isEqualTo( |
| ReviewerSet.fromTable( |
| ImmutableTable.<ReviewerStateInternal, Account.Id, Instant>builder() |
| .put(REVIEWER, Account.id(1), ts) |
| .put(REVIEWER, Account.id(2), ts) |
| .build())); |
| } |
| |
| @Test |
| public void reviewerTypes() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewer(changeOwner.getAccount().id(), REVIEWER); |
| update.putReviewer(otherUser.getAccount().id(), CC); |
| testRefAction(() -> update.commit()); |
| |
| ChangeNotes notes = newNotes(c); |
| Instant ts = update.getWhen(); |
| assertThat(notes.getReviewers()) |
| .isEqualTo( |
| ReviewerSet.fromTable( |
| ImmutableTable.<ReviewerStateInternal, Account.Id, Instant>builder() |
| .put(REVIEWER, Account.id(1), ts) |
| .put(CC, Account.id(2), ts) |
| .build())); |
| } |
| |
| @Test |
| public void oneReviewerMultipleTypes() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewer(otherUser.getAccount().id(), REVIEWER); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| Instant ts = update.getWhen(); |
| assertThat(notes.getReviewers()) |
| .isEqualTo(ReviewerSet.fromTable(ImmutableTable.of(REVIEWER, Account.id(2), ts))); |
| |
| update = newUpdate(c, otherUser); |
| update.putReviewer(otherUser.getAccount().id(), CC); |
| update.commit(); |
| |
| notes = newNotes(c); |
| ts = update.getWhen(); |
| assertThat(notes.getReviewers()) |
| .isEqualTo(ReviewerSet.fromTable(ImmutableTable.of(CC, Account.id(2), ts))); |
| } |
| |
| @Test |
| public void removeReviewer() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewer(otherUser.getAccount().id(), REVIEWER); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 1); |
| update.commit(); |
| |
| update = newUpdate(c, otherUser); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| List<PatchSetApproval> psas = notes.getApprovals().all().get(c.currentPatchSetId()); |
| assertThat(psas).hasSize(2); |
| assertThat(psas.get(0).accountId()).isEqualTo(changeOwner.getAccount().id()); |
| assertThat(psas.get(1).accountId()).isEqualTo(otherUser.getAccount().id()); |
| |
| update = newUpdate(c, changeOwner); |
| update.removeReviewer(otherUser.getAccount().id()); |
| update.commit(); |
| |
| notes = newNotes(c); |
| psas = notes.getApprovals().all().get(c.currentPatchSetId()); |
| assertThat(psas).hasSize(1); |
| assertThat(psas.get(0).accountId()).isEqualTo(changeOwner.getAccount().id()); |
| } |
| |
| @Test |
| public void submitRecords() throws Exception { |
| Change c = newChange(); |
| SubmissionId submissionId = new SubmissionId(c); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setSubjectForCommit("Submit patch set 1"); |
| |
| update.merge( |
| submissionId, |
| ImmutableList.of( |
| submitRecord( |
| "NOT_READY", |
| null, |
| submitLabel(LabelId.VERIFIED, "OK", changeOwner.getAccountId()), |
| submitLabel(LabelId.CODE_REVIEW, "NEED", null)), |
| submitRecord( |
| "NOT_READY", |
| null, |
| submitLabel(LabelId.VERIFIED, "OK", changeOwner.getAccountId()), |
| submitLabel("Alternative-Code-Review", "NEED", null)))); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| List<SubmitRecord> recs = notes.getSubmitRecords(); |
| assertThat(recs).hasSize(2); |
| assertThat(recs.get(0)) |
| .isEqualTo( |
| submitRecord( |
| "NOT_READY", |
| null, |
| submitLabel(LabelId.VERIFIED, "OK", changeOwner.getAccountId()), |
| submitLabel(LabelId.CODE_REVIEW, "NEED", null))); |
| assertThat(recs.get(1)) |
| .isEqualTo( |
| submitRecord( |
| "NOT_READY", |
| null, |
| submitLabel(LabelId.VERIFIED, "OK", changeOwner.getAccountId()), |
| submitLabel("Alternative-Code-Review", "NEED", null))); |
| assertThat(notes.getChange().getSubmissionId()).isEqualTo(submissionId.toString()); |
| } |
| |
| @Test |
| public void latestSubmitRecordsOnly() throws Exception { |
| Change c = newChange(); |
| SubmissionId submissionId = new SubmissionId(c); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setSubjectForCommit("Submit patch set 1"); |
| update.merge( |
| submissionId, |
| ImmutableList.of( |
| submitRecord( |
| "OK", null, submitLabel(LabelId.CODE_REVIEW, "OK", otherUser.getAccountId())))); |
| update.commit(); |
| |
| incrementPatchSet(c); |
| update = newUpdate(c, changeOwner); |
| update.setSubjectForCommit("Submit patch set 2"); |
| update.merge( |
| submissionId, |
| ImmutableList.of( |
| submitRecord( |
| "OK", null, submitLabel(LabelId.CODE_REVIEW, "OK", changeOwner.getAccountId())))); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getSubmitRecords()) |
| .containsExactly( |
| submitRecord( |
| "OK", null, submitLabel(LabelId.CODE_REVIEW, "OK", changeOwner.getAccountId()))); |
| assertThat(notes.getChange().getSubmissionId()).isEqualTo(submissionId.toString()); |
| } |
| |
| @Test |
| public void mergedOnEmptyIfNotSubmitted() throws Exception { |
| Change c = newChange(); |
| |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| // Make sure unrelevent update does not set mergedOn. |
| update.setTopic("topic", topicValidator); |
| update.commit(); |
| assertThat(newNotes(c).getMergedOn()).isEmpty(); |
| } |
| |
| @Test |
| public void mergedOnSetWhenSubmitted() throws Exception { |
| Change c = newChange(); |
| |
| SubmissionId submissionId = new SubmissionId(c); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setSubjectForCommit("Update patch set 1"); |
| update.merge( |
| submissionId, |
| ImmutableList.of( |
| submitRecord( |
| "OK", null, submitLabel(LabelId.CODE_REVIEW, "OK", otherUser.getAccountId())))); |
| update.commit(); |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getMergedOn()).isPresent(); |
| Instant mergedOn = notes.getMergedOn().get(); |
| assertThat(mergedOn).isEqualTo(notes.getChange().getLastUpdatedOn()); |
| |
| // Next update does not change mergedOn date. |
| update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 1); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getMergedOn().get()).isEqualTo(mergedOn); |
| assertThat(notes.getMergedOn().get()).isLessThan(notes.getChange().getLastUpdatedOn()); |
| } |
| |
| @Test |
| public void latestMergedOn() throws Exception { |
| Change c = newChange(); |
| SubmissionId submissionId = new SubmissionId(c); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setSubjectForCommit("Update patch set 1"); |
| update.merge( |
| submissionId, |
| ImmutableList.of( |
| submitRecord( |
| "OK", null, submitLabel(LabelId.CODE_REVIEW, "OK", otherUser.getAccountId())))); |
| update.commit(); |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getMergedOn()).isPresent(); |
| Instant mergedOn = notes.getMergedOn().get(); |
| assertThat(mergedOn).isEqualTo(notes.getChange().getLastUpdatedOn()); |
| |
| incrementPatchSet(c); |
| update = newUpdate(c, changeOwner); |
| update.setSubjectForCommit("Update patch set 2"); |
| update.merge( |
| submissionId, |
| ImmutableList.of( |
| submitRecord( |
| "OK", null, submitLabel(LabelId.CODE_REVIEW, "OK", changeOwner.getAccountId())))); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getMergedOn().get()).isGreaterThan(mergedOn); |
| assertThat(notes.getMergedOn().get()).isEqualTo(notes.getChange().getLastUpdatedOn()); |
| } |
| |
| @Test |
| public void emptyChangeUpdate() throws Exception { |
| Change c = newChange(); |
| Ref initial = repo.exactRef(changeMetaRef(c.getId())); |
| assertThat(initial).isNotNull(); |
| |
| // Empty update doesn't create a new commit. |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.commit(); |
| assertThat(update.getResult()).isNull(); |
| |
| Ref updated = repo.exactRef(changeMetaRef(c.getId())); |
| assertThat(updated.getObjectId()).isEqualTo(initial.getObjectId()); |
| } |
| |
| @Test |
| public void defaultAttentionSetIsEmpty() throws Exception { |
| Change c = newChange(); |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getAttentionSet()).isEmpty(); |
| } |
| |
| @Test |
| public void defaultAttentionSetUpdatesIsEmpty() throws Exception { |
| Change c = newChange(); |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getAttentionSetUpdates()).isEmpty(); |
| } |
| |
| @Test |
| public void addAttentionStatus() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| AttentionSetUpdate attentionSetUpdate = |
| AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test"); |
| update.addToPlannedAttentionSetUpdates(ImmutableSet.of(attentionSetUpdate)); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getAttentionSet()).containsExactly(addTimestamp(attentionSetUpdate, c)); |
| } |
| |
| @Test |
| public void addAllAttentionUpdates() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| AttentionSetUpdate attentionSetUpdate = |
| AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test"); |
| update.addToPlannedAttentionSetUpdates(ImmutableSet.of(attentionSetUpdate)); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getAttentionSetUpdates()).containsExactly(addTimestamp(attentionSetUpdate, c)); |
| } |
| |
| @Test |
| public void filterLatestAttentionStatus() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| AttentionSetUpdate attentionSetUpdate = |
| AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test"); |
| update.addToPlannedAttentionSetUpdates(ImmutableSet.of(attentionSetUpdate)); |
| update.commit(); |
| update = newUpdate(c, changeOwner); |
| attentionSetUpdate = |
| AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.REMOVE, "test"); |
| update.addToPlannedAttentionSetUpdates(ImmutableSet.of(attentionSetUpdate)); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getAttentionSet()).containsExactly(addTimestamp(attentionSetUpdate, c)); |
| } |
| |
| @Test |
| public void DoesNotFilterLatestAttentionSetUpdates() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| AttentionSetUpdate firstAttentionSetUpdate = |
| AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test"); |
| update.addToPlannedAttentionSetUpdates(ImmutableSet.of(firstAttentionSetUpdate)); |
| update.commit(); |
| update = newUpdate(c, changeOwner); |
| firstAttentionSetUpdate = addTimestamp(firstAttentionSetUpdate, c); |
| |
| AttentionSetUpdate secondAttentionSetUpdate = |
| AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.REMOVE, "test"); |
| update.addToPlannedAttentionSetUpdates(ImmutableSet.of(secondAttentionSetUpdate)); |
| update.commit(); |
| secondAttentionSetUpdate = addTimestamp(secondAttentionSetUpdate, c); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getAttentionSetUpdates()) |
| .containsExactly(secondAttentionSetUpdate, firstAttentionSetUpdate); |
| } |
| |
| @Test |
| public void addAttentionStatus_rejectTimestamp() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| AttentionSetUpdate attentionSetUpdate = |
| AttentionSetUpdate.createFromRead( |
| Instant.now(), changeOwner.getAccountId(), Operation.ADD, "test"); |
| IllegalArgumentException thrown = |
| assertThrows( |
| IllegalArgumentException.class, |
| () -> update.addToPlannedAttentionSetUpdates(ImmutableSet.of(attentionSetUpdate))); |
| assertThat(thrown).hasMessageThat().contains("must not specify timestamp for write"); |
| } |
| |
| @Test |
| public void addAttentionStatus_rejectIfSameUserTwice() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| AttentionSetUpdate attentionSetUpdate0 = |
| AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test 0"); |
| AttentionSetUpdate attentionSetUpdate1 = |
| AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test 1"); |
| |
| IllegalArgumentException thrown = |
| assertThrows( |
| IllegalArgumentException.class, |
| () -> |
| update.addToPlannedAttentionSetUpdates( |
| ImmutableSet.of(attentionSetUpdate0, attentionSetUpdate1))); |
| assertThat(thrown) |
| .hasMessageThat() |
| .contains("must not specify multiple updates for single user"); |
| } |
| |
| @Test |
| public void addAttentionStatusForMultipleUsers() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| // put the user as cc to ensure that the user took part in this change. |
| update.putReviewer(otherUser.getAccount().id(), CC); |
| AttentionSetUpdate attentionSetUpdate0 = |
| AttentionSetUpdate.createForWrite(changeOwner.getAccountId(), Operation.ADD, "test"); |
| AttentionSetUpdate attentionSetUpdate1 = |
| AttentionSetUpdate.createForWrite(otherUser.getAccountId(), Operation.ADD, "test"); |
| |
| update.addToPlannedAttentionSetUpdates( |
| ImmutableSet.of(attentionSetUpdate0, attentionSetUpdate1)); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getAttentionSet()) |
| .containsExactly( |
| addTimestamp(attentionSetUpdate0, c), addTimestamp(attentionSetUpdate1, c)); |
| } |
| |
| @Test |
| public void hashtagCommit() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| LinkedHashSet<String> hashtags = new LinkedHashSet<>(); |
| hashtags.add("tag1"); |
| hashtags.add("tag2"); |
| update.setHashtags(hashtags); |
| update.commit(); |
| try (RevWalk walk = new RevWalk(repo)) { |
| RevCommit commit = walk.parseCommit(update.getResult()); |
| walk.parseBody(commit); |
| assertThat(commit.getFullMessage()).contains("Hashtags: tag1,tag2\n"); |
| } |
| } |
| |
| @Test |
| public void hashtagChangeNotes() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| LinkedHashSet<String> hashtags = new LinkedHashSet<>(); |
| hashtags.add("tag1"); |
| hashtags.add("tag2"); |
| update.setHashtags(hashtags); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getHashtags()).isEqualTo(hashtags); |
| } |
| |
| @Test |
| public void customKeyedValuesCommit() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.addCustomKeyedValue("key1", "value1"); |
| update.addCustomKeyedValue("key2", "value2"); |
| update.commit(); |
| try (RevWalk walk = new RevWalk(repo)) { |
| RevCommit commit = walk.parseCommit(update.getResult()); |
| walk.parseBody(commit); |
| assertThat(commit.getFullMessage()).contains("Custom-Keyed-Value: key1=value1\n"); |
| assertThat(commit.getFullMessage()).contains("Custom-Keyed-Value: key2=value2\n"); |
| } |
| } |
| |
| @Test |
| public void customKeyedValuesChangeNotes() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.addCustomKeyedValue("key1", "value\n1"); |
| update.addCustomKeyedValue("key2", "value2=value3"); |
| update.addCustomKeyedValue("key3", "value3: value4"); |
| update.commit(); |
| |
| TreeMap<String, String> customKeyedValues = new TreeMap<>(); |
| customKeyedValues.put("key1", "value 1"); |
| customKeyedValues.put("key2", "value2=value3"); |
| customKeyedValues.put("key3", "value3: value4"); |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getCustomKeyedValues()) |
| .isEqualTo(ImmutableSortedMap.copyOfSorted(customKeyedValues)); |
| } |
| |
| @Test |
| public void customKeyedValuesChangeNotes_Override() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.addCustomKeyedValue("key1", "value1"); |
| update.addCustomKeyedValue("key2", "value2"); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.addCustomKeyedValue("key1", "value3"); |
| update.commit(); |
| |
| TreeMap<String, String> customKeyedValues = new TreeMap<>(); |
| customKeyedValues.put("key1", "value3"); |
| customKeyedValues.put("key2", "value2"); |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getCustomKeyedValues()) |
| .isEqualTo(ImmutableSortedMap.copyOfSorted(customKeyedValues)); |
| } |
| |
| @Test |
| public void customKeyedValuesChangeNotes_Deletion() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.addCustomKeyedValue("key1", "value1"); |
| update.addCustomKeyedValue("key2", "value2"); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.deleteCustomKeyedValue("key1"); |
| update.commit(); |
| |
| TreeMap<String, String> customKeyedValues = new TreeMap<>(); |
| customKeyedValues.put("key2", "value2"); |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getCustomKeyedValues()) |
| .isEqualTo(ImmutableSortedMap.copyOfSorted(customKeyedValues)); |
| } |
| |
| @Test |
| public void topicChangeNotes() throws Exception { |
| Change c = newChange(); |
| |
| // initially topic is not set |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getChange().getTopic()).isNull(); |
| |
| // set topic |
| String topic = "myTopic"; |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setTopic(topic, topicValidator); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getChange().getTopic()).isEqualTo(topic); |
| |
| // clear topic by setting empty string |
| update = newUpdate(c, changeOwner); |
| update.setTopic("", topicValidator); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getChange().getTopic()).isNull(); |
| |
| // set other topic |
| topic = "otherTopic"; |
| update = newUpdate(c, changeOwner); |
| update.setTopic(topic, topicValidator); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getChange().getTopic()).isEqualTo(topic); |
| |
| // clear topic by setting null |
| update = newUpdate(c, changeOwner); |
| update.setTopic(null, topicValidator); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getChange().getTopic()).isNull(); |
| |
| // check invalid topic |
| ChangeUpdate failingUpdate = newUpdate(c, changeOwner); |
| assertThrows(ValidationException.class, () -> failingUpdate.setTopic("\"", topicValidator)); |
| } |
| |
| @Test |
| public void changeIdChangeNotes() throws Exception { |
| Change c = newChange(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getChange().getKey()).isEqualTo(c.getKey()); |
| |
| // An update doesn't affect the Change-Id |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setTopic("topic", topicValidator); // Change something to get a new commit. |
| update.commit(); |
| assertThat(notes.getChange().getKey()).isEqualTo(c.getKey()); |
| |
| // Trying to set another Change-Id fails |
| String otherChangeId = "I577fb248e474018276351785930358ec0450e9f7"; |
| ChangeUpdate failingUpdate = newUpdate(c, changeOwner); |
| IllegalArgumentException thrown = |
| assertThrows( |
| IllegalArgumentException.class, () -> failingUpdate.setChangeId(otherChangeId)); |
| assertThat(thrown) |
| .hasMessageThat() |
| .contains( |
| "The Change-Id was already set to " |
| + c.getKey() |
| + ", so we cannot set this Change-Id: " |
| + otherChangeId); |
| } |
| |
| @Test |
| public void branchChangeNotes() throws Exception { |
| Change c = newChange(); |
| |
| ChangeNotes notes = newNotes(c); |
| BranchNameKey expectedBranch = BranchNameKey.create(project, "refs/heads/master"); |
| assertThat(notes.getChange().getDest()).isEqualTo(expectedBranch); |
| |
| // An update doesn't affect the branch |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setTopic("topic", topicValidator); // Change something to get a new commit. |
| update.commit(); |
| assertThat(newNotes(c).getChange().getDest()).isEqualTo(expectedBranch); |
| |
| // Set another branch |
| String otherBranch = "refs/heads/stable"; |
| update = newUpdate(c, changeOwner); |
| update.setBranch(otherBranch); |
| update.commit(); |
| assertThat(newNotes(c).getChange().getDest()) |
| .isEqualTo(BranchNameKey.create(project, otherBranch)); |
| } |
| |
| @Test |
| public void ownerChangeNotes() throws Exception { |
| Change c = newChange(); |
| |
| assertThat(newNotes(c).getChange().getOwner()).isEqualTo(changeOwner.getAccountId()); |
| |
| // An update doesn't affect the owner |
| ChangeUpdate update = newUpdate(c, otherUser); |
| update.setTopic("topic", topicValidator); // Change something to get a new commit. |
| update.commit(); |
| assertThat(newNotes(c).getChange().getOwner()).isEqualTo(changeOwner.getAccountId()); |
| } |
| |
| @Test |
| public void createdOnChangeNotes() throws Exception { |
| Change c = newChange(); |
| |
| Instant createdOn = newNotes(c).getChange().getCreatedOn(); |
| assertThat(createdOn).isNotNull(); |
| |
| // An update doesn't affect the createdOn timestamp. |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setTopic("topic", topicValidator); // Change something to get a new commit. |
| update.commit(); |
| assertThat(newNotes(c).getChange().getCreatedOn()).isEqualTo(createdOn); |
| } |
| |
| @Test |
| public void lastUpdatedOnChangeNotes() throws Exception { |
| Change c = newChange(); |
| |
| ChangeNotes notes = newNotes(c); |
| Instant ts1 = notes.getChange().getLastUpdatedOn(); |
| assertThat(ts1).isEqualTo(notes.getChange().getCreatedOn()); |
| |
| // Various kinds of updates that update the timestamp. |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setTopic("topic", topicValidator); // Change something to get a new commit. |
| update.commit(); |
| Instant ts2 = newNotes(c).getChange().getLastUpdatedOn(); |
| assertThat(ts2).isGreaterThan(ts1); |
| |
| update = newUpdate(c, changeOwner); |
| update.setChangeMessage("Some message"); |
| update.commit(); |
| Instant ts3 = newNotes(c).getChange().getLastUpdatedOn(); |
| assertThat(ts3).isGreaterThan(ts2); |
| |
| update = newUpdate(c, changeOwner); |
| update.setHashtags(ImmutableSet.of("foo")); |
| update.commit(); |
| Instant ts4 = newNotes(c).getChange().getLastUpdatedOn(); |
| assertThat(ts4).isGreaterThan(ts3); |
| |
| incrementPatchSet(c); |
| Instant ts5 = newNotes(c).getChange().getLastUpdatedOn(); |
| assertThat(ts5).isGreaterThan(ts4); |
| |
| update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 1); |
| update.commit(); |
| Instant ts6 = newNotes(c).getChange().getLastUpdatedOn(); |
| assertThat(ts6).isGreaterThan(ts5); |
| |
| update = newUpdate(c, changeOwner); |
| update.setStatus(Change.Status.ABANDONED); |
| update.commit(); |
| Instant ts7 = newNotes(c).getChange().getLastUpdatedOn(); |
| assertThat(ts7).isGreaterThan(ts6); |
| |
| update = newUpdate(c, changeOwner); |
| update.putReviewer(otherUser.getAccountId(), ReviewerStateInternal.REVIEWER); |
| update.commit(); |
| Instant ts8 = newNotes(c).getChange().getLastUpdatedOn(); |
| assertThat(ts8).isGreaterThan(ts7); |
| |
| update = newUpdate(c, changeOwner); |
| update.setGroups(ImmutableList.of("a", "b")); |
| update.commit(); |
| Instant ts9 = newNotes(c).getChange().getLastUpdatedOn(); |
| assertThat(ts9).isGreaterThan(ts8); |
| |
| // Finish off by merging the change. |
| update = newUpdate(c, changeOwner); |
| update.merge( |
| new SubmissionId(c), |
| ImmutableList.of( |
| submitRecord( |
| "NOT_READY", |
| null, |
| submitLabel(LabelId.VERIFIED, "OK", changeOwner.getAccountId()), |
| submitLabel("Alternative-Code-Review", "NEED", null)))); |
| update.commit(); |
| Instant ts10 = newNotes(c).getChange().getLastUpdatedOn(); |
| assertThat(ts10).isGreaterThan(ts9); |
| } |
| |
| @Test |
| public void subjectLeadingWhitespaceChangeNotes() throws Exception { |
| Change c = TestChanges.newChange(project, changeOwner.getAccountId()); |
| String trimmedSubj = c.getSubject(); |
| c.setCurrentPatchSet(c.currentPatchSetId(), " " + trimmedSubj, c.getOriginalSubject()); |
| ChangeUpdate update = newUpdateForNewChange(c, changeOwner); |
| update.setChangeId(c.getKey().get()); |
| update.setBranch(c.getDest().branch()); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getChange().getSubject()).isEqualTo(trimmedSubj); |
| |
| String tabSubj = "\t\t" + trimmedSubj; |
| |
| c = TestChanges.newChange(project, changeOwner.getAccountId()); |
| c.setCurrentPatchSet(c.currentPatchSetId(), tabSubj, c.getOriginalSubject()); |
| update = newUpdateForNewChange(c, changeOwner); |
| update.setChangeId(c.getKey().get()); |
| update.setBranch(c.getDest().branch()); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getChange().getSubject()).isEqualTo(tabSubj); |
| } |
| |
| @Test |
| public void commitChangeNotesUnique() throws Exception { |
| // PatchSetId -> ObjectId must be a one to one mapping |
| Change c = newChange(); |
| |
| ChangeNotes notes = newNotes(c); |
| PatchSet ps = notes.getCurrentPatchSet(); |
| assertThat(ps).isNotNull(); |
| |
| // new revId for the same patch set, ps1 |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| RevCommit commit = tr.commit().message("PS1 again").create(); |
| update.setCommit(rw, commit); |
| update.commit(); |
| |
| StorageException e = assertThrows(StorageException.class, () -> newNotes(c)); |
| assertCause( |
| e, |
| ConfigInvalidException.class, |
| "Multiple revisions parsed for patch set 1:" |
| + " " |
| + commit.name() |
| + " and " |
| + ps.commitId().name()); |
| } |
| |
| @Test |
| public void patchSetChangeNotes() throws Exception { |
| Change c = newChange(); |
| |
| // ps1 created by newChange() |
| ChangeNotes notes = newNotes(c); |
| PatchSet ps1 = notes.getCurrentPatchSet(); |
| assertThat(notes.getChange().currentPatchSetId()).isEqualTo(ps1.id()); |
| assertThat(notes.getChange().getSubject()).isEqualTo("Change subject"); |
| assertThat(notes.getChange().getOriginalSubject()).isEqualTo("Change subject"); |
| assertThat(ps1.id()).isEqualTo(PatchSet.id(c.getId(), 1)); |
| assertThat(ps1.uploader()).isEqualTo(changeOwner.getAccountId()); |
| |
| // ps2 by other user |
| RevCommit commit = incrementPatchSet(c, otherUser); |
| notes = newNotes(c); |
| PatchSet ps2 = notes.getCurrentPatchSet(); |
| assertThat(ps2.id()).isEqualTo(PatchSet.id(c.getId(), 2)); |
| assertThat(notes.getChange().getSubject()).isEqualTo("PS2"); |
| assertThat(notes.getChange().getOriginalSubject()).isEqualTo("Change subject"); |
| assertThat(notes.getChange().currentPatchSetId()).isEqualTo(ps2.id()); |
| assertThat(ps2.commitId()).isNotEqualTo(ps1.commitId()); |
| assertThat(ps2.commitId()).isEqualTo(commit); |
| assertThat(ps2.uploader()).isEqualTo(otherUser.getAccountId()); |
| assertThat(ps2.createdOn()).isEqualTo(notes.getChange().getLastUpdatedOn()); |
| |
| // comment on ps1, current patch set is still ps2 |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setPatchSetId(ps1.id()); |
| update.setChangeMessage("Comment on old patch set."); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getChange().currentPatchSetId()).isEqualTo(ps2.id()); |
| } |
| |
| @Test |
| public void patchSetStates() throws Exception { |
| Change c = newChange(); |
| PatchSet.Id psId1 = c.currentPatchSetId(); |
| |
| incrementCurrentPatchSetFieldOnly(c); |
| PatchSet.Id psId2 = c.currentPatchSetId(); |
| RevCommit commit = tr.commit().message("PS2").create(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setCommit(rw, commit); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 1); |
| update.setChangeMessage("This is a message"); |
| update.putComment( |
| HumanComment.Status.PUBLISHED, |
| newComment( |
| c.currentPatchSetId(), |
| "a.txt", |
| "uuid1", |
| new CommentRange(1, 2, 3, 4), |
| 1, |
| changeOwner, |
| null, |
| TimeUtil.now(), |
| "Comment", |
| (short) 1, |
| commit, |
| false)); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getPatchSets().keySet()).containsExactly(psId1, psId2); |
| assertThat(notes.getApprovals().all()).isNotEmpty(); |
| assertThat(notes.getChangeMessages()).isNotEmpty(); |
| assertThat(notes.getHumanComments()).isNotEmpty(); |
| |
| // publish ps2 |
| update = newUpdate(c, changeOwner); |
| update.setPatchSetState(PatchSetState.PUBLISHED); |
| update.commit(); |
| |
| // delete ps2 |
| update = newUpdate(c, changeOwner); |
| update.setPatchSetState(PatchSetState.DELETED); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getPatchSets().keySet()).containsExactly(psId1); |
| assertThat(notes.getApprovals().all()).isEmpty(); |
| assertThat(notes.getChangeMessages()).isEmpty(); |
| assertThat(notes.getHumanComments()).isEmpty(); |
| } |
| |
| @Test |
| public void patchSetGroups() throws Exception { |
| Change c = newChange(); |
| PatchSet.Id psId1 = c.currentPatchSetId(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getPatchSets().get(psId1).groups()).isEmpty(); |
| |
| // ps1 |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setGroups(ImmutableList.of("a", "b")); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getPatchSets().get(psId1).groups()).containsExactly("a", "b").inOrder(); |
| |
| incrementCurrentPatchSetFieldOnly(c); |
| PatchSet.Id psId2 = c.currentPatchSetId(); |
| update = newUpdate(c, changeOwner); |
| update.setCommit(rw, tr.commit().message("PS2").create()); |
| update.setGroups(ImmutableList.of("d")); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getPatchSets().get(psId2).groups()).containsExactly("d"); |
| assertThat(notes.getPatchSets().get(psId1).groups()).containsExactly("a", "b").inOrder(); |
| } |
| |
| @Test |
| public void pushCertificate() throws Exception { |
| String pushCert = |
| "certificate version 0.1\n" |
| + "pusher This is not a real push cert\n" |
| + "-----BEGIN PGP SIGNATURE-----\n" |
| + "Version: GnuPG v1\n" |
| + "\n" |
| + "Nor is this a real signature.\n" |
| + "-----END PGP SIGNATURE-----\n"; |
| |
| // ps2 with push cert |
| Change c = newChange(); |
| PatchSet.Id psId1 = c.currentPatchSetId(); |
| incrementCurrentPatchSetFieldOnly(c); |
| PatchSet.Id psId2 = c.currentPatchSetId(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setPatchSetId(psId2); |
| RevCommit commit = tr.commit().message("PS2").create(); |
| update.setCommit(rw, commit, pushCert); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| readNote(notes, commit); |
| |
| Map<PatchSet.Id, PatchSet> patchSets = notes.getPatchSets(); |
| assertThat(patchSets.get(psId1).pushCertificate()).isEmpty(); |
| assertThat(patchSets.get(psId2).pushCertificate()).hasValue(pushCert); |
| assertThat(notes.getHumanComments()).isEmpty(); |
| |
| // comment on ps2 |
| update = newUpdate(c, changeOwner); |
| update.setPatchSetId(psId2); |
| Instant ts = TimeUtil.now(); |
| update.putComment( |
| HumanComment.Status.PUBLISHED, |
| newComment( |
| psId2, |
| "a.txt", |
| "uuid1", |
| new CommentRange(1, 2, 3, 4), |
| 1, |
| changeOwner, |
| null, |
| ts, |
| "Comment", |
| (short) 1, |
| commit, |
| false)); |
| update.commit(); |
| |
| notes = newNotes(c); |
| |
| patchSets = notes.getPatchSets(); |
| assertThat(patchSets.get(psId1).pushCertificate()).isEmpty(); |
| assertThat(patchSets.get(psId2).pushCertificate()).hasValue(pushCert); |
| assertThat(notes.getHumanComments()).isNotEmpty(); |
| } |
| |
| @Test |
| public void emptyExceptSubject() throws Exception { |
| ChangeUpdate update = newUpdate(newChange(), changeOwner); |
| update.setSubjectForCommit("Create change"); |
| assertThat(update.commit()).isNotNull(); |
| } |
| |
| @Test |
| public void multipleUpdatesInManager() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update1 = newUpdate(c, changeOwner); |
| update1.putApproval(LabelId.VERIFIED, (short) 1); |
| |
| ChangeUpdate update2 = newUpdate(c, otherUser); |
| update2.putApproval(LabelId.CODE_REVIEW, (short) 2); |
| |
| try (NoteDbUpdateManager updateManager = updateManagerFactory.create(project, otherUser)) { |
| updateManager.add(update1); |
| updateManager.add(update2); |
| testRefAction(() -> updateManager.execute()); |
| } |
| |
| ChangeNotes notes = newNotes(c); |
| List<PatchSetApproval> psas = notes.getApprovals().all().get(c.currentPatchSetId()); |
| assertThat(psas).hasSize(2); |
| |
| assertThat(psas.get(0).accountId()).isEqualTo(changeOwner.getAccount().id()); |
| assertThat(psas.get(0).label()).isEqualTo(LabelId.VERIFIED); |
| assertThat(psas.get(0).value()).isEqualTo((short) 1); |
| |
| assertThat(psas.get(1).accountId()).isEqualTo(otherUser.getAccount().id()); |
| assertThat(psas.get(1).label()).isEqualTo(LabelId.CODE_REVIEW); |
| assertThat(psas.get(1).value()).isEqualTo((short) 2); |
| } |
| |
| @Test |
| public void multipleUpdatesIncludingComments() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update1 = newUpdate(c, otherUser); |
| String uuid1 = "uuid1"; |
| String message1 = "comment 1"; |
| CommentRange range1 = new CommentRange(1, 1, 2, 1); |
| Instant time1 = TimeUtil.now(); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| RevCommit tipCommit; |
| try (NoteDbUpdateManager updateManager = updateManagerFactory.create(project, otherUser)) { |
| HumanComment comment1 = |
| newComment( |
| psId, |
| "file1", |
| uuid1, |
| range1, |
| range1.getEndLine(), |
| otherUser, |
| null, |
| time1, |
| message1, |
| (short) 0, |
| ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"), |
| false); |
| update1.setPatchSetId(psId); |
| update1.putComment(HumanComment.Status.PUBLISHED, comment1); |
| updateManager.add(update1); |
| |
| ChangeUpdate update2 = newUpdate(c, otherUser); |
| update2.putApproval(LabelId.CODE_REVIEW, (short) 2); |
| updateManager.add(update2); |
| |
| testRefAction(() -> updateManager.execute()); |
| } |
| |
| ChangeNotes notes = newNotes(c); |
| ObjectId tip = notes.getRevision(); |
| tipCommit = rw.parseCommit(tip); |
| |
| RevCommit commitWithApprovals = tipCommit; |
| assertThat(commitWithApprovals).isNotNull(); |
| RevCommit commitWithComments = commitWithApprovals.getParent(0); |
| assertThat(commitWithComments).isNotNull(); |
| |
| try (ChangeNotesRevWalk rw = ChangeNotesCommit.newRevWalk(repo)) { |
| ChangeNotesParser notesWithComments = |
| new ChangeNotesParser( |
| c.getId(), |
| commitWithComments.copy(), |
| rw, |
| changeNoteJson, |
| args.metrics, |
| new NoteDbUtil(serverId, externalIdCache)); |
| ChangeNotesState state = notesWithComments.parseAll(); |
| assertThat(state.approvals()).isEmpty(); |
| assertThat(state.publishedComments()).hasSize(1); |
| } |
| |
| try (ChangeNotesRevWalk rw = ChangeNotesCommit.newRevWalk(repo)) { |
| ChangeNotesParser notesWithApprovals = |
| new ChangeNotesParser( |
| c.getId(), |
| commitWithApprovals.copy(), |
| rw, |
| changeNoteJson, |
| args.metrics, |
| new NoteDbUtil(serverId, externalIdCache)); |
| |
| ChangeNotesState state = notesWithApprovals.parseAll(); |
| assertThat(state.approvals()).hasSize(1); |
| assertThat(state.publishedComments()).hasSize(1); |
| } |
| } |
| |
| @Test |
| public void multipleUpdatesAcrossRefs() throws Exception { |
| Change c1 = newChange(); |
| ChangeUpdate update1 = newUpdate(c1, changeOwner); |
| update1.putApproval(LabelId.VERIFIED, (short) 1); |
| |
| Change c2 = newChange(); |
| ChangeUpdate update2 = newUpdate(c2, otherUser); |
| update2.putApproval(LabelId.CODE_REVIEW, (short) 2); |
| |
| Ref initial1 = repo.exactRef(update1.getRefName()); |
| assertThat(initial1).isNotNull(); |
| Ref initial2 = repo.exactRef(update2.getRefName()); |
| assertThat(initial2).isNotNull(); |
| |
| try (NoteDbUpdateManager updateManager = updateManagerFactory.create(project, otherUser)) { |
| updateManager.add(update1); |
| updateManager.add(update2); |
| testRefAction(() -> updateManager.execute()); |
| } |
| |
| Ref ref1 = repo.exactRef(update1.getRefName()); |
| assertThat(ref1.getObjectId()).isEqualTo(update1.getResult()); |
| assertThat(ref1.getObjectId()).isNotEqualTo(initial1.getObjectId()); |
| Ref ref2 = repo.exactRef(update2.getRefName()); |
| assertThat(ref2.getObjectId()).isEqualTo(update2.getResult()); |
| assertThat(ref2.getObjectId()).isNotEqualTo(initial2.getObjectId()); |
| |
| PatchSetApproval approval1 = |
| newNotes(c1).getApprovals().all().get(c1.currentPatchSetId()).iterator().next(); |
| assertThat(approval1.label()).isEqualTo(LabelId.VERIFIED); |
| |
| PatchSetApproval approval2 = |
| newNotes(c2).getApprovals().all().get(c2.currentPatchSetId()).iterator().next(); |
| assertThat(approval2.label()).isEqualTo(LabelId.CODE_REVIEW); |
| } |
| |
| @Test |
| public void changeMessageOnePatchSet() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewer(changeOwner.getAccount().id(), REVIEWER); |
| update.setChangeMessage("Just a little code change.\n"); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| ChangeMessage cm = Iterables.getOnlyElement(notes.getChangeMessages()); |
| assertThat(cm.getMessage()).isEqualTo("Just a little code change.\n"); |
| assertThat(cm.getAuthor()).isEqualTo(changeOwner.getAccount().id()); |
| assertThat(cm.getPatchSetId()).isEqualTo(c.currentPatchSetId()); |
| } |
| |
| @Test |
| public void noChangeMessage() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewer(changeOwner.getAccount().id(), REVIEWER); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getChangeMessages()).isEmpty(); |
| } |
| |
| @Test |
| public void changeMessageWithTrailingDoubleNewline() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setChangeMessage("Testing trailing double newline\n\n"); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| ChangeMessage cm1 = Iterables.getOnlyElement(notes.getChangeMessages()); |
| assertThat(cm1.getMessage()).isEqualTo("Testing trailing double newline\n\n"); |
| |
| assertThat(cm1.getAuthor()).isEqualTo(changeOwner.getAccount().id()); |
| } |
| |
| @Test |
| public void changeMessageWithMultipleParagraphs() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setChangeMessage("Testing paragraph 1\n\nTesting paragraph 2\n\nTesting paragraph 3"); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| ChangeMessage cm1 = Iterables.getOnlyElement(notes.getChangeMessages()); |
| assertThat(cm1.getMessage()) |
| .isEqualTo( |
| "Testing paragraph 1\n" |
| + "\n" |
| + "Testing paragraph 2\n" |
| + "\n" |
| + "Testing paragraph 3"); |
| |
| assertThat(cm1.getAuthor()).isEqualTo(changeOwner.getAccount().id()); |
| } |
| |
| @Test |
| public void changeMessageWithTemplate() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewer(changeOwner.getAccount().id(), REVIEWER); |
| String messageTemplate = |
| String.format( |
| "Change update by %s, also includes %s", |
| AccountTemplateUtil.getAccountTemplate(changeOwner.getAccountId()), |
| AccountTemplateUtil.getAccountTemplate(otherUser.getAccountId())); |
| update.setChangeMessage(messageTemplate); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| ChangeMessage cm = Iterables.getOnlyElement(notes.getChangeMessages()); |
| assertThat(cm.getMessage()).isEqualTo(messageTemplate); |
| } |
| |
| @Test |
| public void changeMessagesMultiplePatchSets() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewer(changeOwner.getAccount().id(), REVIEWER); |
| update.setChangeMessage("This is the change message for the first PS."); |
| update.commit(); |
| PatchSet.Id ps1 = c.currentPatchSetId(); |
| |
| incrementPatchSet(c); |
| update = newUpdate(c, changeOwner); |
| |
| update.setChangeMessage("This is the change message for the second PS."); |
| update.commit(); |
| PatchSet.Id ps2 = c.currentPatchSetId(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getChangeMessages()).hasSize(2); |
| |
| ChangeMessage cm1 = notes.getChangeMessages().get(0); |
| assertThat(cm1.getPatchSetId()).isEqualTo(ps1); |
| assertThat(cm1.getMessage()).isEqualTo("This is the change message for the first PS."); |
| |
| assertThat(cm1.getAuthor()).isEqualTo(changeOwner.getAccount().id()); |
| |
| ChangeMessage cm2 = notes.getChangeMessages().get(1); |
| assertThat(cm2.getPatchSetId()).isEqualTo(ps2); |
| assertThat(cm2.getMessage()).isEqualTo("This is the change message for the second PS."); |
| |
| assertThat(cm2.getAuthor()).isEqualTo(changeOwner.getAccount().id()); |
| assertThat(cm2.getPatchSetId()).isEqualTo(ps2); |
| } |
| |
| @Test |
| public void changeMessageMultipleInOnePatchSet() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewer(changeOwner.getAccount().id(), REVIEWER); |
| update.setChangeMessage("First change message.\n"); |
| update.commit(); |
| |
| PatchSet.Id ps1 = c.currentPatchSetId(); |
| |
| update = newUpdate(c, changeOwner); |
| update.putReviewer(changeOwner.getAccount().id(), REVIEWER); |
| update.setChangeMessage("Second change message.\n"); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| |
| List<ChangeMessage> cm = notes.getChangeMessages(); |
| assertThat(cm).hasSize(2); |
| assertThat(cm.get(0).getMessage()).isEqualTo("First change message.\n"); |
| assertThat(cm.get(0).getAuthor()).isEqualTo(changeOwner.getAccount().id()); |
| assertThat(cm.get(0).getPatchSetId()).isEqualTo(ps1); |
| assertThat(cm.get(1).getMessage()).isEqualTo("Second change message.\n"); |
| assertThat(cm.get(1).getAuthor()).isEqualTo(changeOwner.getAccount().id()); |
| assertThat(cm.get(1).getPatchSetId()).isEqualTo(ps1); |
| } |
| |
| @Test |
| public void patchLineCommentsFileComment() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, otherUser); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| |
| HumanComment comment = |
| newComment( |
| psId, |
| "file1", |
| "uuid", |
| null, |
| 0, |
| otherUser, |
| null, |
| TimeUtil.now(), |
| "message", |
| (short) 1, |
| commitId, |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, comment); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getHumanComments()).isEqualTo(ImmutableListMultimap.of(commitId, comment)); |
| } |
| |
| @Test |
| public void patchLineCommentsZeroColumns() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, otherUser); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| CommentRange range = new CommentRange(1, 0, 2, 0); |
| |
| HumanComment comment = |
| newComment( |
| psId, |
| "file1", |
| "uuid", |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| TimeUtil.now(), |
| "message", |
| (short) 1, |
| commitId, |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, comment); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getHumanComments()).isEqualTo(ImmutableListMultimap.of(commitId, comment)); |
| } |
| |
| @Test |
| public void patchLineCommentZeroRange() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, otherUser); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| CommentRange range = new CommentRange(0, 0, 0, 0); |
| |
| HumanComment comment = |
| newComment( |
| psId, |
| "file", |
| "uuid", |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| TimeUtil.now(), |
| "message", |
| (short) 1, |
| commitId, |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, comment); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getHumanComments()).isEqualTo(ImmutableListMultimap.of(commitId, comment)); |
| } |
| |
| @Test |
| public void patchLineCommentEmptyFilename() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, otherUser); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| CommentRange range = new CommentRange(1, 2, 3, 4); |
| |
| HumanComment comment = |
| newComment( |
| psId, |
| "", |
| "uuid", |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| TimeUtil.now(), |
| "message", |
| (short) 1, |
| commitId, |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, comment); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getHumanComments()).isEqualTo(ImmutableListMultimap.of(commitId, comment)); |
| } |
| |
| @Test |
| public void patchLineCommentNotesFormatMultiplePatchSetsSameCommitId() throws Exception { |
| Change c = newChange(); |
| PatchSet.Id psId1 = c.currentPatchSetId(); |
| incrementPatchSet(c); |
| PatchSet.Id psId2 = c.currentPatchSetId(); |
| String uuid1 = "uuid1"; |
| String uuid2 = "uuid2"; |
| String uuid3 = "uuid3"; |
| String message1 = "comment 1"; |
| String message2 = "comment 2"; |
| String message3 = "comment 3"; |
| CommentRange range1 = new CommentRange(1, 1, 2, 1); |
| CommentRange range2 = new CommentRange(2, 1, 3, 1); |
| Instant time = TimeUtil.now(); |
| ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| |
| HumanComment comment1 = |
| newComment( |
| psId1, |
| "file1", |
| uuid1, |
| range1, |
| range1.getEndLine(), |
| otherUser, |
| null, |
| time, |
| message1, |
| (short) 0, |
| commitId, |
| false); |
| HumanComment comment2 = |
| newComment( |
| psId1, |
| "file1", |
| uuid2, |
| range2, |
| range2.getEndLine(), |
| otherUser, |
| null, |
| time, |
| message2, |
| (short) 0, |
| commitId, |
| false); |
| HumanComment comment3 = |
| newComment( |
| psId2, |
| "file1", |
| uuid3, |
| range1, |
| range1.getEndLine(), |
| otherUser, |
| null, |
| time, |
| message3, |
| (short) 0, |
| commitId, |
| false); |
| |
| ChangeUpdate update = newUpdate(c, otherUser); |
| update.setPatchSetId(psId2); |
| update.putComment(HumanComment.Status.PUBLISHED, comment3); |
| update.putComment(HumanComment.Status.PUBLISHED, comment2); |
| update.putComment(HumanComment.Status.PUBLISHED, comment1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getHumanComments()) |
| .isEqualTo( |
| ImmutableListMultimap.of( |
| commitId, comment1, |
| commitId, comment2, |
| commitId, comment3)); |
| } |
| |
| @Test |
| public void patchLineCommentNotesFormatRealAuthor() throws Exception { |
| Change c = newChange(); |
| CurrentUser ownerAsOtherUser = userFactory.runAs(null, otherUserId, changeOwner); |
| ChangeUpdate update = newUpdate(c, ownerAsOtherUser); |
| String uuid = "uuid"; |
| String message = "comment"; |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| Instant time = TimeUtil.now(); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| |
| HumanComment comment = |
| newComment( |
| psId, |
| "file", |
| uuid, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| time, |
| message, |
| (short) 1, |
| commitId, |
| false); |
| comment.setRealAuthor(changeOwner.getAccountId()); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, comment); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| |
| assertThat(notes.getHumanComments()).isEqualTo(ImmutableListMultimap.of(commitId, comment)); |
| } |
| |
| @Test |
| public void patchLineCommentNotesFormatWeirdUser() throws Exception { |
| Account.Builder account = Account.builder(Account.id(3), TimeUtil.now()); |
| account.setFullName("Weird\n\u0002<User>\n"); |
| account.setPreferredEmail(" we\r\nird@ex>ample<.com"); |
| accountCache.put(account.build()); |
| IdentifiedUser user = userFactory.create(Account.id(3)); |
| |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, user); |
| String uuid = "uuid"; |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| Instant time = TimeUtil.now(); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| |
| HumanComment comment = |
| newComment( |
| psId, |
| "file1", |
| uuid, |
| range, |
| range.getEndLine(), |
| user, |
| null, |
| time, |
| "comment", |
| (short) 1, |
| ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"), |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, comment); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| |
| assertThat(notes.getHumanComments()) |
| .isEqualTo(ImmutableListMultimap.of(comment.getCommitId(), comment)); |
| } |
| |
| @Test |
| public void patchLineCommentMultipleOnePatchsetOneFileBothSides() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, otherUser); |
| String uuid1 = "uuid1"; |
| String uuid2 = "uuid2"; |
| ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| ObjectId commitId2 = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567"); |
| String messageForBase = "comment for base"; |
| String messageForPS = "comment for ps"; |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| Instant now = TimeUtil.now(); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| |
| HumanComment commentForBase = |
| newComment( |
| psId, |
| "filename", |
| uuid1, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| messageForBase, |
| (short) 0, |
| commitId1, |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, commentForBase); |
| update.commit(); |
| |
| update = newUpdate(c, otherUser); |
| HumanComment commentForPS = |
| newComment( |
| psId, |
| "filename", |
| uuid2, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| messageForPS, |
| (short) 1, |
| commitId2, |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, commentForPS); |
| update.commit(); |
| |
| assertThat(newNotes(c).getHumanComments()) |
| .containsExactlyEntriesIn( |
| ImmutableListMultimap.of( |
| commitId1, commentForBase, |
| commitId2, commentForPS)); |
| } |
| |
| @Test |
| public void patchLineCommentMultipleOnePatchsetOneFile() throws Exception { |
| Change c = newChange(); |
| String uuid1 = "uuid1"; |
| String uuid2 = "uuid2"; |
| ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| String filename = "filename"; |
| short side = (short) 1; |
| |
| ChangeUpdate update = newUpdate(c, otherUser); |
| Instant timeForComment1 = TimeUtil.now(); |
| Instant timeForComment2 = TimeUtil.now(); |
| HumanComment comment1 = |
| newComment( |
| psId, |
| filename, |
| uuid1, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| timeForComment1, |
| "comment 1", |
| side, |
| commitId, |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, comment1); |
| update.commit(); |
| |
| update = newUpdate(c, otherUser); |
| HumanComment comment2 = |
| newComment( |
| psId, |
| filename, |
| uuid2, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| timeForComment2, |
| "comment 2", |
| side, |
| commitId, |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, comment2); |
| update.commit(); |
| |
| assertThat(newNotes(c).getHumanComments()) |
| .containsExactlyEntriesIn( |
| ImmutableListMultimap.of( |
| commitId, comment1, |
| commitId, comment2)) |
| .inOrder(); |
| } |
| |
| @Test |
| public void patchLineCommentMultipleOnePatchsetMultipleFiles() throws Exception { |
| Change c = newChange(); |
| String uuid = "uuid"; |
| ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| String filename1 = "filename1"; |
| String filename2 = "filename2"; |
| short side = (short) 1; |
| |
| ChangeUpdate update = newUpdate(c, otherUser); |
| Instant now = TimeUtil.now(); |
| HumanComment comment1 = |
| newComment( |
| psId, |
| filename1, |
| uuid, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment 1", |
| side, |
| commitId, |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, comment1); |
| update.commit(); |
| |
| update = newUpdate(c, otherUser); |
| HumanComment comment2 = |
| newComment( |
| psId, |
| filename2, |
| uuid, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment 2", |
| side, |
| commitId, |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, comment2); |
| update.commit(); |
| |
| assertThat(newNotes(c).getHumanComments()) |
| .containsExactlyEntriesIn( |
| ImmutableListMultimap.of( |
| commitId, comment1, |
| commitId, comment2)) |
| .inOrder(); |
| } |
| |
| @Test |
| public void patchLineCommentMultiplePatchsets() throws Exception { |
| Change c = newChange(); |
| String uuid = "uuid"; |
| ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| ObjectId commitId2 = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567"); |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| PatchSet.Id ps1 = c.currentPatchSetId(); |
| String filename = "filename1"; |
| short side = (short) 1; |
| |
| ChangeUpdate update = newUpdate(c, otherUser); |
| Instant now = TimeUtil.now(); |
| HumanComment comment1 = |
| newComment( |
| ps1, |
| filename, |
| uuid, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on ps1", |
| side, |
| commitId1, |
| false); |
| update.setPatchSetId(ps1); |
| update.putComment(HumanComment.Status.PUBLISHED, comment1); |
| update.commit(); |
| |
| incrementPatchSet(c); |
| PatchSet.Id ps2 = c.currentPatchSetId(); |
| |
| update = newUpdate(c, otherUser); |
| now = TimeUtil.now(); |
| HumanComment comment2 = |
| newComment( |
| ps2, |
| filename, |
| uuid, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on ps2", |
| side, |
| commitId2, |
| false); |
| update.setPatchSetId(ps2); |
| update.putComment(HumanComment.Status.PUBLISHED, comment2); |
| update.commit(); |
| |
| assertThat(newNotes(c).getHumanComments()) |
| .containsExactlyEntriesIn( |
| ImmutableListMultimap.of( |
| commitId1, comment1, |
| commitId2, comment2)); |
| } |
| |
| @Test |
| public void patchLineCommentSingleDraftToPublished() throws Exception { |
| Change c = newChange(); |
| String uuid = "uuid"; |
| ObjectId commitId = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567"); |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| PatchSet.Id ps1 = c.currentPatchSetId(); |
| String filename = "filename1"; |
| short side = (short) 1; |
| |
| ChangeUpdate update = newUpdate(c, otherUser); |
| Instant now = TimeUtil.now(); |
| HumanComment comment1 = |
| newComment( |
| ps1, |
| filename, |
| uuid, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on ps1", |
| side, |
| commitId, |
| false); |
| update.setPatchSetId(ps1); |
| update.putComment(HumanComment.Status.DRAFT, comment1); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).containsExactly(comment1); |
| assertThat(notes.getHumanComments()).isEmpty(); |
| |
| update = newUpdate(c, otherUser); |
| update.setPatchSetId(ps1); |
| update.putComment(HumanComment.Status.PUBLISHED, comment1); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).isEmpty(); |
| assertThat(notes.getHumanComments()) |
| .containsExactlyEntriesIn(ImmutableListMultimap.of(commitId, comment1)); |
| } |
| |
| @Test |
| public void patchLineCommentMultipleDraftsSameSidePublishOne() throws Exception { |
| Change c = newChange(); |
| String uuid1 = "uuid1"; |
| String uuid2 = "uuid2"; |
| ObjectId commitId = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567"); |
| CommentRange range1 = new CommentRange(1, 1, 2, 2); |
| CommentRange range2 = new CommentRange(2, 2, 3, 3); |
| String filename = "filename1"; |
| short side = (short) 1; |
| Instant now = TimeUtil.now(); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| |
| // Write two drafts on the same side of one patch set. |
| ChangeUpdate update = newUpdate(c, otherUser); |
| update.setPatchSetId(psId); |
| HumanComment comment1 = |
| newComment( |
| psId, |
| filename, |
| uuid1, |
| range1, |
| range1.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on ps1", |
| side, |
| commitId, |
| false); |
| HumanComment comment2 = |
| newComment( |
| psId, |
| filename, |
| uuid2, |
| range2, |
| range2.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "other on ps1", |
| side, |
| commitId, |
| false); |
| update.putComment(HumanComment.Status.DRAFT, comment1); |
| update.putComment(HumanComment.Status.DRAFT, comment2); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).containsExactly(comment1, comment2).inOrder(); |
| assertThat(notes.getHumanComments()).isEmpty(); |
| |
| // Publish first draft. |
| update = newUpdate(c, otherUser); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, comment1); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).containsExactly(comment2); |
| assertThat(notes.getHumanComments()) |
| .containsExactlyEntriesIn(ImmutableListMultimap.of(commitId, comment1)); |
| } |
| |
| @Test |
| public void patchLineCommentsMultipleDraftsBothSidesPublishAll() throws Exception { |
| Change c = newChange(); |
| String uuid1 = "uuid1"; |
| String uuid2 = "uuid2"; |
| ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| ObjectId commitId2 = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567"); |
| CommentRange range1 = new CommentRange(1, 1, 2, 2); |
| CommentRange range2 = new CommentRange(2, 2, 3, 3); |
| String filename = "filename1"; |
| Instant now = TimeUtil.now(); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| |
| // Write two drafts, one on each side of the patchset. |
| ChangeUpdate update = newUpdate(c, otherUser); |
| update.setPatchSetId(psId); |
| HumanComment baseComment = |
| newComment( |
| psId, |
| filename, |
| uuid1, |
| range1, |
| range1.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on base", |
| (short) 0, |
| commitId1, |
| false); |
| HumanComment psComment = |
| newComment( |
| psId, |
| filename, |
| uuid2, |
| range2, |
| range2.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on ps", |
| (short) 1, |
| commitId2, |
| false); |
| |
| update.putComment(HumanComment.Status.DRAFT, baseComment); |
| update.putComment(HumanComment.Status.DRAFT, psComment); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).containsExactly(baseComment, psComment); |
| assertThat(notes.getHumanComments()).isEmpty(); |
| |
| // Publish both comments. |
| update = newUpdate(c, otherUser); |
| update.setPatchSetId(psId); |
| |
| update.putComment(HumanComment.Status.PUBLISHED, baseComment); |
| update.putComment(HumanComment.Status.PUBLISHED, psComment); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).isEmpty(); |
| assertThat(notes.getHumanComments()) |
| .containsExactlyEntriesIn( |
| ImmutableListMultimap.of( |
| commitId1, baseComment, |
| commitId2, psComment)); |
| } |
| |
| @Test |
| public void patchLineCommentsDeleteAllDrafts() throws Exception { |
| Change c = newChange(); |
| String uuid = "uuid"; |
| ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| String filename = "filename"; |
| short side = (short) 1; |
| |
| ChangeUpdate update = newUpdate(c, otherUser); |
| Instant now = TimeUtil.now(); |
| HumanComment comment = |
| newComment( |
| psId, |
| filename, |
| uuid, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on ps1", |
| side, |
| commitId, |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.DRAFT, comment); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).hasSize(1); |
| assertThat(notes.getDraftCommentNotes().getNoteMap().contains(commitId)).isTrue(); |
| |
| update = newUpdate(c, otherUser); |
| update.setPatchSetId(psId); |
| update.deleteComment(comment); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).isEmpty(); |
| assertThat(notes.getDraftCommentNotes().getNoteMap()).isNull(); |
| } |
| |
| @Test |
| public void patchLineCommentsDeleteAllDraftsForOneRevision() throws Exception { |
| Change c = newChange(); |
| String uuid = "uuid"; |
| ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| ObjectId commitId2 = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567"); |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| PatchSet.Id ps1 = c.currentPatchSetId(); |
| String filename = "filename1"; |
| short side = (short) 1; |
| |
| ChangeUpdate update = newUpdate(c, otherUser); |
| Instant now = TimeUtil.now(); |
| HumanComment comment1 = |
| newComment( |
| ps1, |
| filename, |
| uuid, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on ps1", |
| side, |
| commitId1, |
| false); |
| update.setPatchSetId(ps1); |
| update.putComment(HumanComment.Status.DRAFT, comment1); |
| update.commit(); |
| |
| incrementPatchSet(c); |
| PatchSet.Id ps2 = c.currentPatchSetId(); |
| |
| update = newUpdate(c, otherUser); |
| now = TimeUtil.now(); |
| HumanComment comment2 = |
| newComment( |
| ps2, |
| filename, |
| uuid, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on ps2", |
| side, |
| commitId2, |
| false); |
| update.setPatchSetId(ps2); |
| update.putComment(HumanComment.Status.DRAFT, comment2); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).hasSize(2); |
| |
| update = newUpdate(c, otherUser); |
| update.setPatchSetId(ps2); |
| update.deleteComment(comment2); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).hasSize(1); |
| NoteMap noteMap = notes.getDraftCommentNotes().getNoteMap(); |
| assertThat(noteMap.contains(commitId1)).isTrue(); |
| assertThat(noteMap.contains(commitId2)).isFalse(); |
| } |
| |
| @Test |
| public void addingPublishedCommentDoesNotCreateNoOpCommitOnEmptyDraftRef() throws Exception { |
| Change c = newChange(); |
| String uuid = "uuid"; |
| ObjectId commitId = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567"); |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| PatchSet.Id ps1 = c.currentPatchSetId(); |
| String filename = "filename1"; |
| short side = (short) 1; |
| |
| ChangeUpdate update = newUpdate(c, otherUser); |
| Instant now = TimeUtil.now(); |
| HumanComment comment = |
| newComment( |
| ps1, |
| filename, |
| uuid, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on ps1", |
| side, |
| commitId, |
| false); |
| update.putComment(HumanComment.Status.PUBLISHED, comment); |
| update.commit(); |
| |
| assertThat(repo.exactRef(changeMetaRef(c.getId()))).isNotNull(); |
| String draftRef = refsDraftComments(c.getId(), otherUser.getAccountId()); |
| assertThat(exactRefAllUsers(draftRef)).isNull(); |
| } |
| |
| @Test |
| public void addingPublishedCommentDoesNotCreateNoOpCommitOnNonEmptyDraftRef() throws Exception { |
| Change c = newChange(); |
| ObjectId commitId = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567"); |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| PatchSet.Id ps1 = c.currentPatchSetId(); |
| String filename = "filename1"; |
| short side = (short) 1; |
| |
| ChangeUpdate update = newUpdate(c, otherUser); |
| Instant now = TimeUtil.now(); |
| HumanComment draft = |
| newComment( |
| ps1, |
| filename, |
| "uuid1", |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "draft comment on ps1", |
| side, |
| commitId, |
| false); |
| update.putComment(HumanComment.Status.DRAFT, draft); |
| update.commit(); |
| |
| String draftRef = refsDraftComments(c.getId(), otherUser.getAccountId()); |
| ObjectId old = exactRefAllUsers(draftRef); |
| assertThat(old).isNotNull(); |
| |
| update = newUpdate(c, otherUser); |
| HumanComment pub = |
| newComment( |
| ps1, |
| filename, |
| "uuid2", |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on ps1", |
| side, |
| commitId, |
| false); |
| update.putComment(HumanComment.Status.PUBLISHED, pub); |
| update.commit(); |
| |
| assertThat(exactRefAllUsers(draftRef)).isEqualTo(old); |
| } |
| |
| @Test |
| public void fileComment() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, otherUser); |
| String uuid = "uuid"; |
| ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| String messageForBase = "comment for base"; |
| Instant now = TimeUtil.now(); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| |
| HumanComment comment = |
| newComment( |
| psId, |
| "filename", |
| uuid, |
| null, |
| 0, |
| otherUser, |
| null, |
| now, |
| messageForBase, |
| (short) 0, |
| commitId, |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, comment); |
| update.commit(); |
| |
| assertThat(newNotes(c).getHumanComments()) |
| .containsExactlyEntriesIn(ImmutableListMultimap.of(commitId, comment)); |
| } |
| |
| @Test |
| public void patchLineCommentNoRange() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, otherUser); |
| String uuid = "uuid"; |
| ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| String messageForBase = "comment for base"; |
| Instant now = TimeUtil.now(); |
| PatchSet.Id psId = c.currentPatchSetId(); |
| |
| HumanComment comment = |
| newComment( |
| psId, |
| "filename", |
| uuid, |
| null, |
| 1, |
| otherUser, |
| null, |
| now, |
| messageForBase, |
| (short) 0, |
| commitId, |
| false); |
| update.setPatchSetId(psId); |
| update.putComment(HumanComment.Status.PUBLISHED, comment); |
| update.commit(); |
| |
| assertThat(newNotes(c).getHumanComments()) |
| .containsExactlyEntriesIn(ImmutableListMultimap.of(commitId, comment)); |
| } |
| |
| @Test |
| public void putCommentsForMultipleRevisions() throws Exception { |
| Change c = newChange(); |
| String uuid = "uuid"; |
| ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| ObjectId commitId2 = ObjectId.fromString("abcd4567abcd4567abcd4567abcd4567abcd4567"); |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| PatchSet.Id ps1 = c.currentPatchSetId(); |
| String filename = "filename1"; |
| short side = (short) 1; |
| |
| incrementPatchSet(c); |
| PatchSet.Id ps2 = c.currentPatchSetId(); |
| |
| ChangeUpdate update = newUpdate(c, otherUser); |
| update.setPatchSetId(ps2); |
| Instant now = TimeUtil.now(); |
| HumanComment comment1 = |
| newComment( |
| ps1, |
| filename, |
| uuid, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on ps1", |
| side, |
| commitId1, |
| false); |
| HumanComment comment2 = |
| newComment( |
| ps2, |
| filename, |
| uuid, |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on ps2", |
| side, |
| commitId2, |
| false); |
| update.putComment(HumanComment.Status.DRAFT, comment1); |
| update.putComment(HumanComment.Status.DRAFT, comment2); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).hasSize(2); |
| assertThat(notes.getHumanComments()).isEmpty(); |
| |
| update = newUpdate(c, otherUser); |
| update.setPatchSetId(ps2); |
| update.putComment(HumanComment.Status.PUBLISHED, comment1); |
| update.putComment(HumanComment.Status.PUBLISHED, comment2); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).isEmpty(); |
| assertThat(notes.getHumanComments()).hasSize(2); |
| } |
| |
| @Test |
| public void publishSubsetOfCommentsOnRevision() throws Exception { |
| Change c = newChange(); |
| ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| PatchSet.Id ps1 = c.currentPatchSetId(); |
| short side = (short) 1; |
| |
| ChangeUpdate update = newUpdate(c, otherUser); |
| update.setPatchSetId(ps1); |
| Instant now = TimeUtil.now(); |
| HumanComment comment1 = |
| newComment( |
| ps1, |
| "file1", |
| "uuid1", |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment1", |
| side, |
| commitId1, |
| false); |
| HumanComment comment2 = |
| newComment( |
| ps1, |
| "file2", |
| "uuid2", |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment2", |
| side, |
| commitId1, |
| false); |
| update.putComment(HumanComment.Status.DRAFT, comment1); |
| update.putComment(HumanComment.Status.DRAFT, comment2); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).containsExactly(comment1, comment2); |
| assertThat(notes.getHumanComments()).isEmpty(); |
| |
| update = newUpdate(c, otherUser); |
| update.setPatchSetId(ps1); |
| update.putComment(HumanComment.Status.PUBLISHED, comment2); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).containsExactly(comment1); |
| assertThat(notes.getHumanComments().get(commitId1)).containsExactly(comment2); |
| } |
| |
| @Test |
| public void updateWithServerIdent() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, internalUser); |
| update.setChangeMessage("A message."); |
| update.commit(); |
| |
| ChangeMessage msg = Iterables.getLast(newNotes(c).getChangeMessages()); |
| assertThat(msg.getMessage()).isEqualTo("A message."); |
| assertThat(msg.getAuthor()).isNull(); |
| |
| ChangeUpdate failingUpdate = newUpdate(c, internalUser); |
| assertThrows( |
| IllegalStateException.class, |
| () -> failingUpdate.putApproval(LabelId.CODE_REVIEW, (short) 1)); |
| } |
| |
| @Test |
| public void filterOutAndFixUpZombieDraftComments() throws Exception { |
| Change c = newChange(); |
| ObjectId commitId1 = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| PatchSet.Id ps1 = c.currentPatchSetId(); |
| short side = (short) 1; |
| |
| ChangeUpdate update = newUpdate(c, otherUser); |
| Instant now = TimeUtil.now(); |
| HumanComment comment1 = |
| newComment( |
| ps1, |
| "file1", |
| "uuid1", |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "comment on ps1", |
| side, |
| commitId1, |
| false); |
| HumanComment comment2 = |
| newComment( |
| ps1, |
| "file2", |
| "uuid2", |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| now, |
| "another comment", |
| side, |
| commitId1, |
| false); |
| update.putComment(HumanComment.Status.DRAFT, comment1); |
| update.putComment(HumanComment.Status.DRAFT, comment2); |
| update.commit(); |
| |
| String refName = refsDraftComments(c.getId(), otherUserId); |
| ObjectId oldDraftId = exactRefAllUsers(refName); |
| |
| update = newUpdate(c, otherUser); |
| update.setPatchSetId(ps1); |
| update.putComment(HumanComment.Status.PUBLISHED, comment2); |
| update.commit(); |
| assertThat(exactRefAllUsers(refName)).isNotNull(); |
| assertThat(exactRefAllUsers(refName)).isNotEqualTo(oldDraftId); |
| |
| // Re-add draft version of comment2 back to draft ref without updating |
| // change ref. Simulates the case where deleting the draft failed |
| // non-atomically after adding the published comment succeeded. |
| Optional<ChangeDraftNotesUpdate> draftUpdate = |
| ChangeDraftNotesUpdate.asChangeDraftNotesUpdate( |
| newUpdate(c, otherUser).createDraftUpdateIfNull()); |
| if (draftUpdate.isPresent()) { |
| draftUpdate.get().putDraftComment(comment2); |
| try (NoteDbUpdateManager manager = updateManagerFactory.create(c.getProject(), otherUser)) { |
| manager.add(draftUpdate.get()); |
| testRefAction(() -> manager.execute()); |
| } |
| } |
| |
| // Looking at drafts directly shows the zombie comment. |
| assertThat(draftCommentsReader.getDraftsByChangeAndDraftAuthor(c.getId(), otherUserId)) |
| .containsExactly(comment1, comment2); |
| |
| // Zombie comment is filtered out of drafts via ChangeNotes. |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getDraftComments(otherUserId)).containsExactly(comment1); |
| assertThat(notes.getHumanComments().get(commitId1)).containsExactly(comment2); |
| |
| update = newUpdate(c, otherUser); |
| update.setPatchSetId(ps1); |
| update.putComment(HumanComment.Status.PUBLISHED, comment1); |
| update.commit(); |
| |
| // Updating an unrelated comment causes the zombie comment to get fixed up. |
| assertThat(exactRefAllUsers(refName)).isNull(); |
| } |
| |
| @Test |
| public void updateCommentsInSequentialUpdates() throws Exception { |
| Change c = newChange(); |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| ObjectId commitId = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"); |
| |
| ChangeUpdate update1 = newUpdate(c, otherUser); |
| HumanComment comment1 = |
| newComment( |
| c.currentPatchSetId(), |
| "filename", |
| "uuid1", |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| update1.getWhen(), |
| "comment 1", |
| (short) 1, |
| commitId, |
| false); |
| update1.putComment(HumanComment.Status.PUBLISHED, comment1); |
| |
| ChangeUpdate update2 = newUpdate(c, otherUser); |
| HumanComment comment2 = |
| newComment( |
| c.currentPatchSetId(), |
| "filename", |
| "uuid2", |
| range, |
| range.getEndLine(), |
| otherUser, |
| null, |
| update2.getWhen(), |
| "comment 2", |
| (short) 1, |
| commitId, |
| false); |
| update2.putComment(HumanComment.Status.PUBLISHED, comment2); |
| |
| try (NoteDbUpdateManager manager = updateManagerFactory.create(project, otherUser)) { |
| manager.add(update1); |
| manager.add(update2); |
| testRefAction(() -> manager.execute()); |
| } |
| |
| ChangeNotes notes = newNotes(c); |
| List<HumanComment> comments = notes.getHumanComments().get(commitId); |
| assertThat(comments).hasSize(2); |
| assertThat(comments.get(0).message).isEqualTo("comment 1"); |
| assertThat(comments.get(1).message).isEqualTo("comment 2"); |
| } |
| |
| @Test |
| public void realUser() throws Exception { |
| Change c = newChange(); |
| CurrentUser ownerAsOtherUser = userFactory.runAs(null, otherUserId, changeOwner); |
| ChangeUpdate update = newUpdate(c, ownerAsOtherUser); |
| update.setChangeMessage("Message on behalf of other user"); |
| update.commit(); |
| |
| ChangeMessage msg = Iterables.getLast(newNotes(c).getChangeMessages()); |
| assertThat(msg.getMessage()).isEqualTo("Message on behalf of other user"); |
| assertThat(msg.getAuthor()).isEqualTo(otherUserId); |
| assertThat(msg.getRealAuthor()).isEqualTo(changeOwner.getAccountId()); |
| } |
| |
| @Test |
| public void ignoreEntitiesBeyondCurrentPatchSet() throws Exception { |
| Change c = newChange(); |
| ChangeNotes notes = newNotes(c); |
| int numMessages = notes.getChangeMessages().size(); |
| int numPatchSets = notes.getPatchSets().size(); |
| int numApprovals = notes.getApprovals().all().size(); |
| int numComments = notes.getHumanComments().size(); |
| |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setPatchSetId(PatchSet.id(c.getId(), c.currentPatchSetId().get() + 1)); |
| update.setChangeMessage("Should be ignored"); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 2); |
| CommentRange range = new CommentRange(1, 1, 2, 1); |
| HumanComment comment = |
| newComment( |
| update.getPatchSetId(), |
| "filename", |
| "uuid", |
| range, |
| range.getEndLine(), |
| changeOwner, |
| null, |
| update.getWhen(), |
| "comment", |
| (short) 1, |
| ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234"), |
| false); |
| update.putComment(HumanComment.Status.PUBLISHED, comment); |
| update.commit(); |
| |
| notes = newNotes(c); |
| assertThat(notes.getChangeMessages()).hasSize(numMessages); |
| assertThat(notes.getPatchSets()).hasSize(numPatchSets); |
| assertThat(notes.getApprovals().all()).hasSize(numApprovals); |
| assertThat(notes.getHumanComments()).hasSize(numComments); |
| } |
| |
| @Test |
| public void currentPatchSet() throws Exception { |
| Change c = newChange(); |
| assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(1); |
| |
| incrementPatchSet(c); |
| assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(2); |
| |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setPatchSetId(PatchSet.id(c.getId(), 1)); |
| update.setCurrentPatchSet(); |
| update.commit(); |
| assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(1); |
| |
| incrementPatchSet(c); |
| assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(3); |
| |
| // Delete PS3, PS1 becomes current, as the most recent event explicitly set |
| // it to current. |
| update = newUpdate(c, changeOwner); |
| update.setPatchSetState(PatchSetState.DELETED); |
| update.commit(); |
| assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(1); |
| |
| // Delete PS1, PS2 becomes current. |
| update = newUpdate(c, changeOwner); |
| update.setPatchSetId(PatchSet.id(c.getId(), 1)); |
| update.setPatchSetState(PatchSetState.DELETED); |
| update.commit(); |
| assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(2); |
| } |
| |
| @Test |
| public void privateDefault() throws Exception { |
| Change c = newChange(); |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getChange().isPrivate()).isFalse(); |
| } |
| |
| @Test |
| public void privateSetPrivate() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setPrivate(true); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getChange().isPrivate()).isTrue(); |
| } |
| |
| @Test |
| public void privateSetPrivateMultipleTimes() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setPrivate(true); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.setPrivate(false); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getChange().isPrivate()).isFalse(); |
| } |
| |
| @Test |
| public void defaultReviewersByEmailIsEmpty() throws Exception { |
| Change c = newChange(); |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getReviewersByEmail().all()).isEmpty(); |
| } |
| |
| @Test |
| public void putReviewerByEmail() throws Exception { |
| Address adr = Address.create("Foo Bar", "foo.bar@gerritcodereview.com"); |
| |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewerByEmail(adr, ReviewerStateInternal.REVIEWER); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getReviewersByEmail().all()).containsExactly(adr); |
| } |
| |
| @Test |
| public void putAndRemoveReviewerByEmail() throws Exception { |
| Address adr = Address.create("Foo Bar", "foo.bar@gerritcodereview.com"); |
| |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewerByEmail(adr, ReviewerStateInternal.REVIEWER); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.removeReviewerByEmail(adr); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getReviewersByEmail().all()).isEmpty(); |
| } |
| |
| @Test |
| public void putRemoveAndAddBackReviewerByEmail() throws Exception { |
| Address adr = Address.create("Foo Bar", "foo.bar@gerritcodereview.com"); |
| |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewerByEmail(adr, ReviewerStateInternal.REVIEWER); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.removeReviewerByEmail(adr); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.putReviewerByEmail(adr, ReviewerStateInternal.CC); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getReviewersByEmail().all()).containsExactly(adr); |
| } |
| |
| @Test |
| public void putReviewerByEmailAndCcByEmail() throws Exception { |
| Address adrReviewer = Address.create("Foo Bar", "foo.bar@gerritcodereview.com"); |
| Address adrCc = Address.create("Foo Bor", "foo.bar.2@gerritcodereview.com"); |
| |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewerByEmail(adrReviewer, ReviewerStateInternal.REVIEWER); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.putReviewerByEmail(adrCc, ReviewerStateInternal.CC); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getReviewersByEmail().byState(ReviewerStateInternal.REVIEWER)) |
| .containsExactly(adrReviewer); |
| assertThat(notes.getReviewersByEmail().byState(ReviewerStateInternal.CC)) |
| .containsExactly(adrCc); |
| assertThat(notes.getReviewersByEmail().all()).containsExactly(adrReviewer, adrCc); |
| } |
| |
| @Test |
| public void putReviewerByEmailAndChangeToCc() throws Exception { |
| Address adr = Address.create("Foo Bar", "foo.bar@gerritcodereview.com"); |
| |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewerByEmail(adr, ReviewerStateInternal.REVIEWER); |
| update.commit(); |
| |
| update = newUpdate(c, changeOwner); |
| update.putReviewerByEmail(adr, ReviewerStateInternal.CC); |
| update.commit(); |
| |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getReviewersByEmail().byState(ReviewerStateInternal.REVIEWER)).isEmpty(); |
| assertThat(notes.getReviewersByEmail().byState(ReviewerStateInternal.CC)).containsExactly(adr); |
| assertThat(notes.getReviewersByEmail().all()).containsExactly(adr); |
| } |
| |
| @Test |
| public void hasReviewStarted() throws Exception { |
| ChangeNotes notes = newNotes(newChange()); |
| assertThat(notes.getChange().hasReviewStarted()).isTrue(); |
| |
| notes = newNotes(newWorkInProgressChange()); |
| assertThat(notes.getChange().hasReviewStarted()).isFalse(); |
| |
| Change c = newWorkInProgressChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getChange().hasReviewStarted()).isFalse(); |
| |
| update = newUpdate(c, changeOwner); |
| update.setWorkInProgress(true); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getChange().hasReviewStarted()).isFalse(); |
| |
| update = newUpdate(c, changeOwner); |
| update.setWorkInProgress(false); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getChange().hasReviewStarted()).isTrue(); |
| |
| // Once review is started, setting WIP should have no impact. |
| c = newChange(); |
| notes = newNotes(c); |
| assertThat(notes.getChange().hasReviewStarted()).isTrue(); |
| update = newUpdate(c, changeOwner); |
| update.setWorkInProgress(true); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getChange().hasReviewStarted()).isTrue(); |
| } |
| |
| @Test |
| public void pendingReviewers() throws Exception { |
| Address adr1 = Address.create("Foo Bar1", "foo.bar1@gerritcodereview.com"); |
| Address adr2 = Address.create("Foo Bar2", "foo.bar2@gerritcodereview.com"); |
| Account.Id ownerId = changeOwner.getAccount().id(); |
| Account.Id otherUserId = otherUser.getAccount().id(); |
| |
| ChangeNotes notes = newNotes(newChange()); |
| assertThat(notes.getPendingReviewers().asTable()).isEmpty(); |
| assertThat(notes.getPendingReviewersByEmail().asTable()).isEmpty(); |
| |
| Change c = newWorkInProgressChange(); |
| notes = newNotes(c); |
| assertThat(notes.getPendingReviewers().asTable()).isEmpty(); |
| assertThat(notes.getPendingReviewersByEmail().asTable()).isEmpty(); |
| |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putReviewer(ownerId, REVIEWER); |
| update.putReviewer(otherUserId, CC); |
| update.putReviewerByEmail(adr1, REVIEWER); |
| update.putReviewerByEmail(adr2, CC); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getPendingReviewers().byState(REVIEWER)).containsExactly(ownerId); |
| assertThat(notes.getPendingReviewers().byState(CC)).containsExactly(otherUserId); |
| assertThat(notes.getPendingReviewers().byState(REMOVED)).isEmpty(); |
| assertThat(notes.getPendingReviewersByEmail().byState(REVIEWER)).containsExactly(adr1); |
| assertThat(notes.getPendingReviewersByEmail().byState(CC)).containsExactly(adr2); |
| assertThat(notes.getPendingReviewersByEmail().byState(REMOVED)).isEmpty(); |
| |
| update = newUpdate(c, changeOwner); |
| update.removeReviewer(ownerId); |
| update.removeReviewerByEmail(adr1); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getPendingReviewers().byState(REVIEWER)).isEmpty(); |
| assertThat(notes.getPendingReviewers().byState(CC)).containsExactly(otherUserId); |
| assertThat(notes.getPendingReviewers().byState(REMOVED)).containsExactly(ownerId); |
| assertThat(notes.getPendingReviewersByEmail().byState(REVIEWER)).isEmpty(); |
| assertThat(notes.getPendingReviewersByEmail().byState(CC)).containsExactly(adr2); |
| assertThat(notes.getPendingReviewersByEmail().byState(REMOVED)).containsExactly(adr1); |
| |
| update = newUpdate(c, changeOwner); |
| update.setWorkInProgress(false); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getPendingReviewers().asTable()).isEmpty(); |
| assertThat(notes.getPendingReviewersByEmail().asTable()).isEmpty(); |
| |
| update = newUpdate(c, changeOwner); |
| update.putReviewer(ownerId, REVIEWER); |
| update.putReviewerByEmail(adr1, REVIEWER); |
| update.commit(); |
| notes = newNotes(c); |
| assertThat(notes.getPendingReviewers().asTable()).isEmpty(); |
| assertThat(notes.getPendingReviewersByEmail().asTable()).isEmpty(); |
| } |
| |
| @Test |
| public void revertOfIsNullByDefault() throws Exception { |
| Change c = newChange(); |
| ChangeNotes notes = newNotes(c); |
| assertThat(notes.getChange().getRevertOf()).isNull(); |
| } |
| |
| @Test |
| public void setRevertOfPersistsValue() throws Exception { |
| Change changeToRevert = newChange(); |
| Change c = TestChanges.newChange(project, changeOwner.getAccountId()); |
| ChangeUpdate update = newUpdateForNewChange(c, changeOwner); |
| update.setChangeId(c.getKey().get()); |
| update.setRevertOf(changeToRevert.getId().get()); |
| update.commit(); |
| assertThat(newNotes(c).getChange().getRevertOf()).isEqualTo(changeToRevert.getId()); |
| } |
| |
| @Test |
| public void setRevertOfToCurrentChangeFails() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| IllegalArgumentException thrown = |
| assertThrows(IllegalArgumentException.class, () -> update.setRevertOf(c.getId().get())); |
| assertThat(thrown).hasMessageThat().contains("A change cannot revert itself"); |
| } |
| |
| @Test |
| public void setRevertOfOnChildCommitFails() throws Exception { |
| Change c = newChange(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setRevertOf(newChange().getId().get()); |
| StorageException thrown = assertThrows(StorageException.class, () -> update.commit()); |
| assertThat(thrown) |
| .hasMessageThat() |
| .contains("Given ChangeUpdate is only allowed on initial commit"); |
| } |
| |
| @Test |
| public void resetCherryPickOf() throws Exception { |
| Change destinationChange = newChange(); |
| Change cherryPickChange = newChange(); |
| assertThat(newNotes(destinationChange).getChange().getCherryPickOf()).isNull(); |
| |
| ChangeUpdate update = newUpdate(destinationChange, changeOwner); |
| update.setCherryPickOf( |
| cherryPickChange.currentPatchSetId().getCommaSeparatedChangeAndPatchSetId()); |
| update.commit(); |
| assertThat(newNotes(destinationChange).getChange().getCherryPickOf()) |
| .isEqualTo(cherryPickChange.currentPatchSetId()); |
| |
| update = newUpdate(destinationChange, changeOwner); |
| update.resetCherryPickOf(); |
| update.commit(); |
| assertThat(newNotes(destinationChange).getChange().getCherryPickOf()).isNull(); |
| |
| // Can set again after reset. |
| cherryPickChange = newChange(); |
| update = newUpdate(destinationChange, changeOwner); |
| update.setCherryPickOf( |
| cherryPickChange.currentPatchSetId().getCommaSeparatedChangeAndPatchSetId()); |
| update.commit(); |
| assertThat(newNotes(destinationChange).getChange().getCherryPickOf()) |
| .isEqualTo(cherryPickChange.currentPatchSetId()); |
| } |
| |
| @Test |
| public void updateCount() throws Exception { |
| Change c = newChange(); |
| assertThat(newNotes(c).getUpdateCount()).isEqualTo(1); |
| |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) -1); |
| update.commit(); |
| assertThat(newNotes(c).getUpdateCount()).isEqualTo(2); |
| |
| update = newUpdate(c, changeOwner); |
| update.putApproval(LabelId.CODE_REVIEW, (short) 1); |
| update.commit(); |
| assertThat(newNotes(c).getUpdateCount()).isEqualTo(3); |
| } |
| |
| @Test |
| public void createPatchSetAfterPatchSetDeletion() throws Exception { |
| Change c = newChange(); |
| assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(1); |
| |
| // Create PS2. |
| incrementCurrentPatchSetFieldOnly(c); |
| RevCommit commit = tr.commit().message("PS" + c.currentPatchSetId().get()).create(); |
| ChangeUpdate update = newUpdate(c, changeOwner); |
| update.setCommit(rw, commit); |
| update.setGroups(ImmutableList.of(commit.name())); |
| update.commit(); |
| assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(2); |
| |
| // Delete PS2. |
| update = newUpdate(c, changeOwner); |
| update.setPatchSetState(PatchSetState.DELETED); |
| update.commit(); |
| c = newNotes(c).getChange(); |
| assertThat(c.currentPatchSetId().get()).isEqualTo(1); |
| |
| // Create another PS2 |
| incrementCurrentPatchSetFieldOnly(c); |
| commit = tr.commit().message("PS" + c.currentPatchSetId().get()).create(); |
| update = newUpdate(c, changeOwner); |
| update.setPatchSetState(PatchSetState.PUBLISHED); |
| update.setCommit(rw, commit); |
| update.setGroups(ImmutableList.of(commit.name())); |
| update.commit(); |
| assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(2); |
| } |
| |
| private String readNote(ChangeNotes notes, ObjectId noteId) throws Exception { |
| ObjectId dataId = notes.revisionNoteMap.noteMap.getNote(noteId).getData(); |
| return new String(rw.getObjectReader().open(dataId, OBJ_BLOB).getCachedBytes(), UTF_8); |
| } |
| |
| @Nullable |
| private ObjectId exactRefAllUsers(String refName) throws Exception { |
| try (Repository allUsersRepo = repoManager.openRepository(allUsers)) { |
| Ref ref = allUsersRepo.exactRef(refName); |
| return ref != null ? ref.getObjectId() : null; |
| } |
| } |
| |
| private void assertCause( |
| Throwable e, Class<? extends Throwable> expectedClass, String expectedMsg) { |
| Throwable cause = null; |
| for (Throwable t : Throwables.getCausalChain(e)) { |
| if (expectedClass.isAssignableFrom(t.getClass())) { |
| cause = t; |
| break; |
| } |
| } |
| assertWithMessage( |
| expectedClass.getSimpleName() |
| + " in causal chain of:\n" |
| + Throwables.getStackTraceAsString(e)) |
| .that(cause) |
| .isNotNull(); |
| assertThat(cause.getMessage()).isEqualTo(expectedMsg); |
| } |
| |
| private void incrementCurrentPatchSetFieldOnly(Change c) { |
| TestChanges.incrementPatchSet(c); |
| } |
| |
| private RevCommit incrementPatchSet(Change c) throws Exception { |
| return incrementPatchSet(c, userFactory.create(c.getOwner())); |
| } |
| |
| private RevCommit incrementPatchSet(Change c, IdentifiedUser user) throws Exception { |
| incrementCurrentPatchSetFieldOnly(c); |
| RevCommit commit = tr.commit().message("PS" + c.currentPatchSetId().get()).create(); |
| ChangeUpdate update = newUpdate(c, user); |
| update.setCommit(rw, commit); |
| update.commit(); |
| return tr.parseBody(commit); |
| } |
| |
| private AttentionSetUpdate addTimestamp(AttentionSetUpdate attentionSetUpdate, Change c) { |
| Instant timestamp = newNotes(c).getChange().getLastUpdatedOn(); |
| return AttentionSetUpdate.createFromRead( |
| timestamp, |
| attentionSetUpdate.account(), |
| attentionSetUpdate.operation(), |
| attentionSetUpdate.reason()); |
| } |
| |
| /** |
| * Assert UUID was parsed as generated by {@link |
| * com.google.gerrit.server.approval.testing.TestPatchSetApprovalUuidGenerator}. |
| */ |
| private void assertParsedUuid(PatchSetApproval patchSetApproval) { |
| assertThat(patchSetApproval.uuid().get().get()).matches("^[0-9a-z_]+$"); |
| } |
| |
| private void assertCopiedApproval(PatchSetApproval originalPsa, PatchSetApproval copiedPsa) { |
| assertThat(copiedPsa.label()).isEqualTo(originalPsa.label()); |
| assertThat(copiedPsa.value()).isEqualTo(originalPsa.value()); |
| assertThat(copiedPsa.tag()).isEqualTo(originalPsa.tag()); |
| assertThat(copiedPsa.accountId()).isEqualTo(originalPsa.accountId()); |
| assertThat(copiedPsa.realAccountId()).isEqualTo(originalPsa.realAccountId()); |
| assertThat(copiedPsa.uuid()).isEqualTo(originalPsa.uuid()); |
| assertThat(copiedPsa.copied()).isTrue(); |
| } |
| |
| private void addCopiedApproval(Change c, CurrentUser user, PatchSetApproval originalPsa) |
| throws Exception { |
| ChangeUpdate update = newUpdate(c, user); |
| update.putCopiedApproval( |
| PatchSetApproval.builder() |
| .key(originalPsa.key()) |
| .value(originalPsa.value()) |
| .copied(true) |
| .granted(TimeUtil.now()) |
| .tag(originalPsa.tag()) |
| .uuid(originalPsa.uuid()) |
| .realAccountId(originalPsa.realAccountId()) |
| .build()); |
| update.commit(); |
| } |
| } |