blob: 3c1abbd70f9367ed7d4c671cb9b6be477fc4541b [file] [log] [blame]
// 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.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.util.Comparator.comparing;
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.ImmutableMap;
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.Lists;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
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.ChangeDraftUpdate;
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.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());
ImmutableList<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);
ImmutableListMultimap<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());
ImmutableList<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());
ImmutableList<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());
ImmutableList<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);
ImmutableList<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);
ImmutableList<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);
ImmutableMap<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);
ImmutableList<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);
ImmutableList<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.
ChangeDraftUpdate draftUpdate = newUpdate(c, otherUser).createDraftUpdateIfNull();
if (draftUpdate != null) {
draftUpdate.putDraftComment(comment2);
try (NoteDbUpdateManager manager = updateManagerFactory.create(c.getProject(), otherUser)) {
manager.add(draftUpdate);
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);
ImmutableList<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@example.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@example.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@example.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@example.com");
Address adrCc = Address.create("Foo Bor", "foo.bar.2@example.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@example.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@example.com");
Address adr2 = Address.create("Foo Bar2", "foo.bar2@example.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);
}
@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);
}
@CanIgnoreReturnValue
private RevCommit incrementPatchSet(Change c) throws Exception {
return incrementPatchSet(c, userFactory.create(c.getOwner()));
}
@CanIgnoreReturnValue
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();
}
}