blob: 976ffc846b651b6e0619f51ffe735169f215943e [file] [log] [blame]
// Copyright (C) 2018 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.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.gerrit.proto.testing.SerializedClassSubject.assertThatSerializedClass;
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.ImmutableTable;
import com.google.common.collect.Iterables;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Address;
import com.google.gerrit.entities.AttentionSetUpdate;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.entities.Comment;
import com.google.gerrit.entities.HumanComment;
import com.google.gerrit.entities.LabelId;
import com.google.gerrit.entities.LegacySubmitRequirement;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.entities.SubmitRecord;
import com.google.gerrit.entities.SubmitRequirement;
import com.google.gerrit.entities.SubmitRequirementExpression;
import com.google.gerrit.entities.SubmitRequirementExpressionResult;
import com.google.gerrit.entities.SubmitRequirementResult;
import com.google.gerrit.entities.converter.ChangeMessageProtoConverter;
import com.google.gerrit.entities.converter.PatchSetApprovalProtoConverter;
import com.google.gerrit.entities.converter.PatchSetProtoConverter;
import com.google.gerrit.proto.Entities;
import com.google.gerrit.proto.Protos;
import com.google.gerrit.server.AssigneeStatusUpdate;
import com.google.gerrit.server.ReviewerByEmailSet;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.ReviewerStatusUpdate;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AssigneeStatusUpdateProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.AttentionSetUpdateProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ChangeColumnsProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerByEmailSetEntryProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerSetEntryProto;
import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerStatusUpdateProto;
import com.google.gerrit.server.cache.proto.Cache.SubmitRequirementExpressionResultProto;
import com.google.gerrit.server.cache.proto.Cache.SubmitRequirementProto;
import com.google.gerrit.server.cache.proto.Cache.SubmitRequirementResultProto;
import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
import com.google.gerrit.server.notedb.ChangeNotesState.ChangeColumns;
import com.google.gerrit.server.notedb.ChangeNotesState.Serializer;
import com.google.inject.TypeLiteral;
import com.google.protobuf.ByteString;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Before;
import org.junit.Test;
public class ChangeNotesStateTest {
private static final Change.Id ID = Change.id(123);
private static final ObjectId SHA =
ObjectId.fromString("1234567812345678123456781234567812345678");
private static final ByteString SHA_BYTES = ObjectIdConverter.create().toByteString(SHA);
private static final String CHANGE_KEY = "Iabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd";
private static final String DEFAULT_SERVER_ID = UUID.randomUUID().toString();
private ChangeColumns cols;
private ChangeColumnsProto colsProto;
@Before
public void setUp() throws Exception {
cols =
ChangeColumns.builder()
.changeKey(Change.key(CHANGE_KEY))
.createdOn(Instant.ofEpochMilli(123456L))
.lastUpdatedOn(Instant.ofEpochMilli(234567L))
.owner(Account.id(1000))
.branch("refs/heads/master")
.subject("Test change")
.isPrivate(false)
.workInProgress(false)
.reviewStarted(true)
.build();
colsProto = toProto(newBuilder().build()).getColumns();
}
private ChangeNotesState.Builder newBuilder() {
return ChangeNotesState.Builder.empty(ID).metaId(SHA).columns(cols);
}
private ChangeNotesStateProto.Builder newProtoBuilder() {
return ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto);
}
@Test
public void serializeChangeKey() throws Exception {
assertRoundTrip(
newBuilder()
.columns(
cols.toBuilder()
.changeKey(Change.key("Ieeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"))
.build())
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(
colsProto.toBuilder().setChangeKey("Ieeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"))
.build());
}
@Test
public void serializeCreatedOn() throws Exception {
assertRoundTrip(
newBuilder()
.columns(cols.toBuilder().createdOn(Instant.ofEpochMilli(98765L)).build())
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setCreatedOnMillis(98765L))
.build());
}
@Test
public void serializeLastUpdatedOn() throws Exception {
assertRoundTrip(
newBuilder()
.columns(cols.toBuilder().lastUpdatedOn(Instant.ofEpochMilli(98765L)).build())
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setLastUpdatedOnMillis(98765L))
.build());
}
@Test
public void serializeOwner() throws Exception {
assertRoundTrip(
newBuilder().columns(cols.toBuilder().owner(Account.id(7777)).build()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setOwner(7777))
.build());
}
@Test
public void serializeBranch() throws Exception {
assertRoundTrip(
newBuilder().columns(cols.toBuilder().branch("refs/heads/bar").build()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setBranch("refs/heads/bar"))
.build());
}
@Test
public void serializeSubject() throws Exception {
assertRoundTrip(
newBuilder().columns(cols.toBuilder().subject("A different test change").build()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setSubject("A different test change"))
.build());
}
@Test
public void serializeCurrentPatchSetId() throws Exception {
assertRoundTrip(
newBuilder()
.columns(cols.toBuilder().currentPatchSetId(PatchSet.id(ID, 2)).build())
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setCurrentPatchSetId(2).setHasCurrentPatchSetId(true))
.build());
}
@Test
public void serializeNullTopic() throws Exception {
assertRoundTrip(
newBuilder().columns(cols.toBuilder().topic(null).build()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.build());
}
@Test
public void serializeEmptyTopic() throws Exception {
ChangeNotesState state = newBuilder().columns(cols.toBuilder().topic("").build()).build();
assertRoundTrip(
state,
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setTopic("").setHasTopic(true))
.build());
}
@Test
public void serializeNonEmptyTopic() throws Exception {
ChangeNotesState state = newBuilder().columns(cols.toBuilder().topic("topic").build()).build();
assertRoundTrip(
state,
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setTopic("topic").setHasTopic(true))
.build());
}
@Test
public void serializeOriginalSubject() throws Exception {
assertRoundTrip(
newBuilder()
.columns(cols.toBuilder().originalSubject("The first patch set").build())
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(
colsProto
.toBuilder()
.setOriginalSubject("The first patch set")
.setHasOriginalSubject(true))
.build());
}
@Test
public void serializeSubmissionId() throws Exception {
assertRoundTrip(
newBuilder().columns(cols.toBuilder().submissionId("xyz").build()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setSubmissionId("xyz").setHasSubmissionId(true))
.build());
}
@Test
public void serializeStatus() throws Exception {
assertRoundTrip(
newBuilder().columns(cols.toBuilder().status(Change.Status.MERGED).build()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setStatus("MERGED").setHasStatus(true))
.build());
}
@Test
public void serializeIsPrivate() throws Exception {
assertRoundTrip(
newBuilder().columns(cols.toBuilder().isPrivate(true).build()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setIsPrivate(true))
.build());
}
@Test
public void serializeIsWorkInProgress() throws Exception {
assertRoundTrip(
newBuilder().columns(cols.toBuilder().workInProgress(true).build()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setWorkInProgress(true))
.build());
}
@Test
public void serializeHasReviewStarted() throws Exception {
assertRoundTrip(
newBuilder().columns(cols.toBuilder().reviewStarted(true).build()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setReviewStarted(true))
.build());
}
@Test
public void serializeRevertOf() throws Exception {
assertRoundTrip(
newBuilder().columns(cols.toBuilder().revertOf(Change.id(999)).build()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto.toBuilder().setRevertOf(999).setHasRevertOf(true))
.build());
}
@Test
public void serializeHashtags() throws Exception {
assertRoundTrip(
newBuilder().hashtags(ImmutableSet.of("tag2", "tag1")).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addHashtag("tag2")
.addHashtag("tag1")
.build());
}
@Test
public void serializePatchSets() throws Exception {
PatchSet ps1 =
PatchSet.builder()
.id(PatchSet.id(ID, 1))
.commitId(ObjectId.fromString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
.uploader(Account.id(2000))
.createdOn(cols.createdOn())
.build();
Entities.PatchSet ps1Proto = PatchSetProtoConverter.INSTANCE.toProto(ps1);
ByteString ps1Bytes = Protos.toByteString(ps1Proto);
assertThat(ps1Bytes.size()).isEqualTo(66);
PatchSet ps2 =
PatchSet.builder()
.id(PatchSet.id(ID, 2))
.commitId(ObjectId.fromString("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"))
.uploader(Account.id(3000))
.createdOn(cols.lastUpdatedOn())
.build();
Entities.PatchSet ps2Proto = PatchSetProtoConverter.INSTANCE.toProto(ps2);
ByteString ps2Bytes = Protos.toByteString(ps2Proto);
assertThat(ps2Bytes.size()).isEqualTo(66);
assertThat(ps2Bytes).isNotEqualTo(ps1Bytes);
assertRoundTrip(
newBuilder().patchSets(ImmutableMap.of(ps2.id(), ps2, ps1.id(), ps1).entrySet()).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addPatchSet(ps2Proto)
.addPatchSet(ps1Proto)
.build());
}
@Test
public void serializeApprovals() throws Exception {
PatchSetApproval a1 =
PatchSetApproval.builder()
.key(
PatchSetApproval.key(
PatchSet.id(ID, 1), Account.id(2001), LabelId.create(LabelId.CODE_REVIEW)))
.value(1)
.tag("tag")
.granted(Instant.ofEpochMilli(1212L))
.build();
Entities.PatchSetApproval psa1 = PatchSetApprovalProtoConverter.INSTANCE.toProto(a1);
ByteString a1Bytes = Protos.toByteString(psa1);
PatchSetApproval a2 =
PatchSetApproval.builder()
.key(
PatchSetApproval.key(
PatchSet.id(ID, 1), Account.id(2002), LabelId.create(LabelId.VERIFIED)))
.value(-1)
.tag("tag")
.copied(true)
.granted(Instant.ofEpochMilli(3434L))
.build();
Entities.PatchSetApproval psa2 = PatchSetApprovalProtoConverter.INSTANCE.toProto(a2);
ByteString a2Bytes = Protos.toByteString(psa2);
assertThat(a2Bytes.size()).isEqualTo(56);
assertThat(a2Bytes).isNotEqualTo(a1Bytes);
assertRoundTrip(
newBuilder()
.approvals(ImmutableListMultimap.of(a2.patchSetId(), a2, a1.patchSetId(), a1).entries())
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addApproval(psa2)
.addApproval(psa1)
.build());
}
@Test
public void serializeApprovalsWithUUID() throws Exception {
PatchSetApproval a1 =
PatchSetApproval.builder()
.key(
PatchSetApproval.key(
PatchSet.id(ID, 1), Account.id(2001), LabelId.create(LabelId.CODE_REVIEW)))
.uuid(Optional.of(PatchSetApproval.uuid("577fb248e474018276351785930358ec0450e9f7")))
.value(1)
.tag("tag")
.granted(Instant.ofEpochMilli(1212L))
.build();
Entities.PatchSetApproval psa1 = PatchSetApprovalProtoConverter.INSTANCE.toProto(a1);
ByteString a1Bytes = Protos.toByteString(psa1);
PatchSetApproval a2 =
PatchSetApproval.builder()
.key(
PatchSetApproval.key(
PatchSet.id(ID, 1), Account.id(2002), LabelId.create(LabelId.VERIFIED)))
.uuid(Optional.of(PatchSetApproval.uuid("577fb248e474018276351785930358ec0450e9f7")))
.value(-1)
.tag("tag")
.copied(true)
.granted(Instant.ofEpochMilli(3434L))
.build();
Entities.PatchSetApproval psa2 = PatchSetApprovalProtoConverter.INSTANCE.toProto(a2);
ByteString a2Bytes = Protos.toByteString(psa2);
assertThat(a2Bytes.size()).isEqualTo(98);
assertThat(a2Bytes).isNotEqualTo(a1Bytes);
assertRoundTrip(
newBuilder()
.approvals(ImmutableListMultimap.of(a2.patchSetId(), a2, a1.patchSetId(), a1).entries())
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addApproval(psa2)
.addApproval(psa1)
.build());
}
@Test
public void serializeReviewers() throws Exception {
assertRoundTrip(
newBuilder()
.reviewers(
ReviewerSet.fromTable(
ImmutableTable.<ReviewerStateInternal, Account.Id, Instant>builder()
.put(
ReviewerStateInternal.CC, Account.id(2001), Instant.ofEpochMilli(1212L))
.put(
ReviewerStateInternal.REVIEWER,
Account.id(2002),
Instant.ofEpochMilli(3434L))
.build()))
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addReviewer(
ReviewerSetEntryProto.newBuilder()
.setState("CC")
.setAccountId(2001)
.setTimestampMillis(1212L))
.addReviewer(
ReviewerSetEntryProto.newBuilder()
.setState("REVIEWER")
.setAccountId(2002)
.setTimestampMillis(3434L))
.build());
}
@Test
public void serializeReviewersByEmail() throws Exception {
assertRoundTrip(
newBuilder()
.reviewersByEmail(
ReviewerByEmailSet.fromTable(
ImmutableTable.<ReviewerStateInternal, Address, Instant>builder()
.put(
ReviewerStateInternal.CC,
Address.create("Name1", "email1@example.com"),
Instant.ofEpochMilli(1212L))
.put(
ReviewerStateInternal.REVIEWER,
Address.create("Name2", "email2@example.com"),
Instant.ofEpochMilli(3434L))
.build()))
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addReviewerByEmail(
ReviewerByEmailSetEntryProto.newBuilder()
.setState("CC")
.setAddress("Name1 <email1@example.com>")
.setTimestampMillis(1212L))
.addReviewerByEmail(
ReviewerByEmailSetEntryProto.newBuilder()
.setState("REVIEWER")
.setAddress("Name2 <email2@example.com>")
.setTimestampMillis(3434L))
.build());
}
@Test
public void serializeReviewersByEmailWithNullName() throws Exception {
ChangeNotesState actual =
assertRoundTrip(
newBuilder()
.reviewersByEmail(
ReviewerByEmailSet.fromTable(
ImmutableTable.of(
ReviewerStateInternal.CC,
Address.create("emailonly@example.com"),
Instant.ofEpochMilli(1212L))))
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addReviewerByEmail(
ReviewerByEmailSetEntryProto.newBuilder()
.setState("CC")
.setAddress("emailonly@example.com")
.setTimestampMillis(1212L))
.build());
// Address doesn't consider the name field in equals, so we have to check it manually.
// TODO(dborowitz): Fix Address#equals.
ImmutableSet<Address> ccs = actual.reviewersByEmail().byState(ReviewerStateInternal.CC);
assertThat(ccs).hasSize(1);
Address address = Iterables.getOnlyElement(ccs);
assertThat(address.name()).isNull();
assertThat(address.email()).isEqualTo("emailonly@example.com");
}
@Test
public void serializePendingReviewers() throws Exception {
assertRoundTrip(
newBuilder()
.pendingReviewers(
ReviewerSet.fromTable(
ImmutableTable.<ReviewerStateInternal, Account.Id, Instant>builder()
.put(
ReviewerStateInternal.CC, Account.id(2001), Instant.ofEpochMilli(1212L))
.put(
ReviewerStateInternal.REVIEWER,
Account.id(2002),
Instant.ofEpochMilli(3434L))
.build()))
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addPendingReviewer(
ReviewerSetEntryProto.newBuilder()
.setState("CC")
.setAccountId(2001)
.setTimestampMillis(1212L))
.addPendingReviewer(
ReviewerSetEntryProto.newBuilder()
.setState("REVIEWER")
.setAccountId(2002)
.setTimestampMillis(3434L))
.build());
}
@Test
public void serializePendingReviewersByEmail() throws Exception {
assertRoundTrip(
newBuilder()
.pendingReviewersByEmail(
ReviewerByEmailSet.fromTable(
ImmutableTable.<ReviewerStateInternal, Address, Instant>builder()
.put(
ReviewerStateInternal.CC,
Address.create("Name1", "email1@example.com"),
Instant.ofEpochMilli(1212L))
.put(
ReviewerStateInternal.REVIEWER,
Address.create("Name2", "email2@example.com"),
Instant.ofEpochMilli(3434L))
.build()))
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addPendingReviewerByEmail(
ReviewerByEmailSetEntryProto.newBuilder()
.setState("CC")
.setAddress("Name1 <email1@example.com>")
.setTimestampMillis(1212L))
.addPendingReviewerByEmail(
ReviewerByEmailSetEntryProto.newBuilder()
.setState("REVIEWER")
.setAddress("Name2 <email2@example.com>")
.setTimestampMillis(3434L))
.build());
}
@Test
public void serializeAllPastReviewers() throws Exception {
assertRoundTrip(
newBuilder().allPastReviewers(ImmutableList.of(Account.id(2002), Account.id(2001))).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addPastReviewer(2002)
.addPastReviewer(2001)
.build());
}
@Test
public void serializeReviewerUpdates() throws Exception {
assertRoundTrip(
newBuilder()
.reviewerUpdates(
ImmutableList.of(
ReviewerStatusUpdate.create(
Instant.ofEpochMilli(1212L),
Account.id(1000),
Account.id(2002),
ReviewerStateInternal.CC),
ReviewerStatusUpdate.create(
Instant.ofEpochMilli(3434L),
Account.id(1000),
Account.id(2001),
ReviewerStateInternal.REVIEWER)))
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addReviewerUpdate(
ReviewerStatusUpdateProto.newBuilder()
.setTimestampMillis(1212L)
.setUpdatedBy(1000)
.setReviewer(2002)
.setState("CC"))
.addReviewerUpdate(
ReviewerStatusUpdateProto.newBuilder()
.setTimestampMillis(3434L)
.setUpdatedBy(1000)
.setReviewer(2001)
.setState("REVIEWER"))
.build());
}
@Test
public void serializeAttentionSetUpdates() throws Exception {
assertRoundTrip(
newBuilder()
.attentionSet(
ImmutableSet.of(
AttentionSetUpdate.createFromRead(
Instant.EPOCH.plusSeconds(23),
Account.id(1000),
AttentionSetUpdate.Operation.ADD,
"reason 1"),
AttentionSetUpdate.createFromRead(
Instant.EPOCH.plusSeconds(42),
Account.id(2000),
AttentionSetUpdate.Operation.REMOVE,
"reason 2")))
.build(),
newProtoBuilder()
.addAttentionSetUpdate(
AttentionSetUpdateProto.newBuilder()
.setTimestampMillis(23_000) // epoch millis
.setAccount(1000)
.setOperation("ADD")
.setReason("reason 1"))
.addAttentionSetUpdate(
AttentionSetUpdateProto.newBuilder()
.setTimestampMillis(42_000) // epoch millis
.setAccount(2000)
.setOperation("REMOVE")
.setReason("reason 2"))
.build());
}
@Test
public void serializeAllAttentionSetUpdates() throws Exception {
assertRoundTrip(
newBuilder()
.allAttentionSetUpdates(
ImmutableList.of(
AttentionSetUpdate.createFromRead(
Instant.EPOCH.plusSeconds(23),
Account.id(1000),
AttentionSetUpdate.Operation.ADD,
"reason 1"),
AttentionSetUpdate.createFromRead(
Instant.EPOCH.plusSeconds(42),
Account.id(2000),
AttentionSetUpdate.Operation.REMOVE,
"reason 2")))
.build(),
newProtoBuilder()
.addAllAttentionSetUpdate(
AttentionSetUpdateProto.newBuilder()
.setTimestampMillis(23_000) // epoch millis
.setAccount(1000)
.setOperation("ADD")
.setReason("reason 1"))
.addAllAttentionSetUpdate(
AttentionSetUpdateProto.newBuilder()
.setTimestampMillis(42_000) // epoch millis
.setAccount(2000)
.setOperation("REMOVE")
.setReason("reason 2"))
.build());
}
@Test
public void serializeSubmitRequirementsResult() throws Exception {
assertRoundTrip(
newBuilder()
.submitRequirementsResult(
ImmutableList.of(
SubmitRequirementResult.builder()
.legacy(Optional.of(true))
.hidden(Optional.of(true))
.patchSetCommitId(
ObjectId.fromString("26e50c7d315a33a13e5cc00902781fa876bc36cd"))
.submitRequirement(
SubmitRequirement.builder()
.setName("Code-Review")
.setApplicabilityExpression(
SubmitRequirementExpression.of("project:foo"))
.setSubmittabilityExpression(
SubmitRequirementExpression.create("label:Code-Review=+2"))
.setAllowOverrideInChildProjects(false)
.build())
.applicabilityExpressionResult(
Optional.of(
SubmitRequirementExpressionResult.create(
SubmitRequirementExpression.create("project:foo"),
SubmitRequirementExpressionResult.Status.PASS,
ImmutableList.of("project:foo"),
ImmutableList.of())))
.submittabilityExpressionResult(
SubmitRequirementExpressionResult.create(
SubmitRequirementExpression.create("label:Code-Review=+2"),
SubmitRequirementExpressionResult.Status.FAIL,
ImmutableList.of(),
ImmutableList.of("label:Code-Review=+2")))
.build()))
.build(),
newProtoBuilder()
.addSubmitRequirementResult(
SubmitRequirementResultProto.newBuilder()
.setLegacy(true)
.setHidden(true)
.setCommit(
ObjectIdConverter.create()
.toByteString(
ObjectId.fromString("26e50c7d315a33a13e5cc00902781fa876bc36cd")))
.setSubmitRequirement(
SubmitRequirementProto.newBuilder()
.setName("Code-Review")
.setApplicabilityExpression("project:foo")
.setSubmittabilityExpression("label:Code-Review=+2")
.setAllowOverrideInChildProjects(false)
.build())
.setApplicabilityExpressionResult(
SubmitRequirementExpressionResultProto.newBuilder()
.setExpression("project:foo")
.setStatus("PASS")
.addPassingAtoms("project:foo")
.build())
.setSubmittabilityExpressionResult(
SubmitRequirementExpressionResultProto.newBuilder()
.setExpression("label:Code-Review=+2")
.setStatus("FAIL")
.addFailingAtoms("label:Code-Review=+2")
.build())
.build())
.build());
}
@Test
public void serializeAssigneeUpdates() throws Exception {
assertRoundTrip(
newBuilder()
.assigneeUpdates(
ImmutableList.of(
AssigneeStatusUpdate.create(
Instant.ofEpochMilli(1212L),
Account.id(1000),
Optional.of(Account.id(2001))),
AssigneeStatusUpdate.create(
Instant.ofEpochMilli(3434L), Account.id(1000), Optional.empty())))
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addAssigneeUpdate(
AssigneeStatusUpdateProto.newBuilder()
.setTimestampMillis(1212L)
.setUpdatedBy(1000)
.setCurrentAssignee(2001)
.setHasCurrentAssignee(true))
.addAssigneeUpdate(
AssigneeStatusUpdateProto.newBuilder()
.setTimestampMillis(3434L)
.setUpdatedBy(1000)
.setHasCurrentAssignee(false))
.build());
}
@Test
public void serializeSubmitRecords() throws Exception {
SubmitRecord sr1 = new SubmitRecord();
sr1.status = SubmitRecord.Status.OK;
SubmitRecord sr2 = new SubmitRecord();
sr2.status = SubmitRecord.Status.FORCED;
assertRoundTrip(
newBuilder().submitRecords(ImmutableList.of(sr2, sr1)).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addSubmitRecord("{\"status\":\"FORCED\"}")
.addSubmitRecord("{\"status\":\"OK\"}")
.build());
}
@Test
public void serializeChangeMessages() throws Exception {
ChangeMessage m1 =
ChangeMessage.create(
ChangeMessage.key(ID, "uuid1"),
Account.id(1000),
Instant.ofEpochMilli(1212L),
PatchSet.id(ID, 1));
Entities.ChangeMessage m1Proto = ChangeMessageProtoConverter.INSTANCE.toProto(m1);
ByteString m1Bytes = Protos.toByteString(m1Proto);
assertThat(m1Bytes.size()).isEqualTo(35);
ChangeMessage m2 =
ChangeMessage.create(
ChangeMessage.key(ID, "uuid2"),
Account.id(2000),
Instant.ofEpochMilli(3434L),
PatchSet.id(ID, 2));
Entities.ChangeMessage m2Proto = ChangeMessageProtoConverter.INSTANCE.toProto(m2);
ByteString m2Bytes = Protos.toByteString(m2Proto);
assertThat(m2Bytes.size()).isEqualTo(35);
assertThat(m2Bytes).isNotEqualTo(m1Bytes);
assertRoundTrip(
newBuilder().changeMessages(ImmutableList.of(m2, m1)).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addChangeMessage(m2Proto)
.addChangeMessage(m1Proto)
.build());
}
@Test
public void serializePublishedComments() throws Exception {
HumanComment c1 =
new HumanComment(
new Comment.Key("uuid1", "file1", 1),
Account.id(1001),
Instant.ofEpochMilli(1212L),
(short) 1,
"message 1",
"serverId",
false);
c1.setCommitId(ObjectId.fromString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
String c1Json = Serializer.GSON.toJson(c1);
HumanComment c2 =
new HumanComment(
new Comment.Key("uuid2", "file2", 2),
Account.id(1002),
Instant.ofEpochMilli(3434L),
(short) 2,
"message 2",
"serverId",
true);
c2.setCommitId(ObjectId.fromString("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"));
String c2Json = Serializer.GSON.toJson(c2);
assertRoundTrip(
newBuilder()
.publishedComments(ImmutableListMultimap.of(c2.getCommitId(), c2, c1.getCommitId(), c1))
.build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.addPublishedComment(c2Json)
.addPublishedComment(c1Json)
.build());
}
@Test
public void serializeUpdateCount() throws Exception {
assertRoundTrip(
newBuilder().updateCount(234).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setColumns(colsProto)
.setUpdateCount(234)
.build());
}
@Test
public void changeNotesStateMethods() throws Exception {
assertThatSerializedClass(ChangeNotesState.class)
.hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("metaId", ObjectId.class)
.put("changeId", Change.Id.class)
.put("serverId", String.class)
.put("columns", ChangeColumns.class)
.put("hashtags", new TypeLiteral<ImmutableSet<String>>() {}.getType())
.put(
"patchSets",
new TypeLiteral<ImmutableList<Map.Entry<PatchSet.Id, PatchSet>>>() {}.getType())
.put(
"approvals",
new TypeLiteral<
ImmutableList<Map.Entry<PatchSet.Id, PatchSetApproval>>>() {}.getType())
.put("reviewers", ReviewerSet.class)
.put("reviewersByEmail", ReviewerByEmailSet.class)
.put("pendingReviewers", ReviewerSet.class)
.put("pendingReviewersByEmail", ReviewerByEmailSet.class)
.put("allPastReviewers", new TypeLiteral<ImmutableList<Account.Id>>() {}.getType())
.put(
"reviewerUpdates",
new TypeLiteral<ImmutableList<ReviewerStatusUpdate>>() {}.getType())
.put(
"attentionSet",
new TypeLiteral<ImmutableSet<AttentionSetUpdate>>() {}.getType())
.put(
"allAttentionSetUpdates",
new TypeLiteral<ImmutableList<AttentionSetUpdate>>() {}.getType())
.put(
"assigneeUpdates",
new TypeLiteral<ImmutableList<AssigneeStatusUpdate>>() {}.getType())
.put("submitRecords", new TypeLiteral<ImmutableList<SubmitRecord>>() {}.getType())
.put("changeMessages", new TypeLiteral<ImmutableList<ChangeMessage>>() {}.getType())
.put(
"publishedComments",
new TypeLiteral<ImmutableListMultimap<ObjectId, HumanComment>>() {}.getType())
.put(
"submitRequirementsResult",
new TypeLiteral<ImmutableList<SubmitRequirementResult>>() {}.getType())
.put("updateCount", int.class)
.put("mergedOn", Instant.class)
.build());
}
@Test
public void changeColumnsMethods() throws Exception {
assertThatSerializedClass(ChangeColumns.class)
.hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("changeKey", Change.Key.class)
.put("createdOn", Instant.class)
.put("lastUpdatedOn", Instant.class)
.put("owner", Account.Id.class)
.put("branch", String.class)
.put("currentPatchSetId", PatchSet.Id.class)
.put("subject", String.class)
.put("topic", String.class)
.put("originalSubject", String.class)
.put("submissionId", String.class)
.put("status", Change.Status.class)
.put("isPrivate", boolean.class)
.put("workInProgress", boolean.class)
.put("reviewStarted", boolean.class)
.put("revertOf", Change.Id.class)
.put("cherryPickOf", PatchSet.Id.class)
.put("toBuilder", ChangeNotesState.ChangeColumns.Builder.class)
.build());
}
@Test
public void patchSetFields() throws Exception {
assertThatSerializedClass(PatchSet.class)
.hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("id", PatchSet.Id.class)
.put("commitId", ObjectId.class)
.put("uploader", Account.Id.class)
.put("createdOn", Instant.class)
.put("groups", new TypeLiteral<ImmutableList<String>>() {}.getType())
.put("pushCertificate", new TypeLiteral<Optional<String>>() {}.getType())
.put("description", new TypeLiteral<Optional<String>>() {}.getType())
.build());
}
@Test
public void patchSetApprovalFields() throws Exception {
assertThatSerializedClass(PatchSetApproval.Key.class)
.hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("patchSetId", PatchSet.Id.class)
.put("accountId", Account.Id.class)
.put("labelId", LabelId.class)
.build());
assertThatSerializedClass(PatchSetApproval.class)
.hasAutoValueMethods(
ImmutableMap.<String, Type>builder()
.put("key", PatchSetApproval.Key.class)
.put("uuid", new TypeLiteral<Optional<PatchSetApproval.UUID>>() {}.getType())
.put("value", short.class)
.put("granted", Instant.class)
.put("tag", new TypeLiteral<Optional<String>>() {}.getType())
.put("realAccountId", Account.Id.class)
.put("postSubmit", boolean.class)
.put("copied", boolean.class)
.put("toBuilder", PatchSetApproval.Builder.class)
.build());
}
@Test
public void reviewerSetFields() throws Exception {
assertThatSerializedClass(ReviewerSet.class)
.hasFields(
ImmutableMap.of(
"table",
new TypeLiteral<
ImmutableTable<ReviewerStateInternal, Account.Id, Instant>>() {}.getType(),
"accounts",
new TypeLiteral<ImmutableSet<Account.Id>>() {}.getType()));
}
@Test
public void reviewerByEmailSetFields() throws Exception {
assertThatSerializedClass(ReviewerByEmailSet.class)
.hasFields(
ImmutableMap.of(
"table",
new TypeLiteral<
ImmutableTable<ReviewerStateInternal, Address, Instant>>() {}.getType(),
"users",
new TypeLiteral<ImmutableSet<Address>>() {}.getType()));
}
@Test
public void reviewerStatusUpdateMethods() throws Exception {
assertThatSerializedClass(ReviewerStatusUpdate.class)
.hasAutoValueMethods(
ImmutableMap.of(
"date", Instant.class,
"updatedBy", Account.Id.class,
"reviewer", Account.Id.class,
"state", ReviewerStateInternal.class));
}
@Test
public void assigneeStatusUpdateMethods() throws Exception {
assertThatSerializedClass(AssigneeStatusUpdate.class)
.hasAutoValueMethods(
ImmutableMap.of(
"date",
Instant.class,
"updatedBy",
Account.Id.class,
"currentAssignee",
new TypeLiteral<Optional<Account.Id>>() {}.getType()));
}
@Test
public void submitRecordFields() throws Exception {
assertThatSerializedClass(SubmitRecord.class)
.hasFields(
ImmutableMap.of(
"ruleName",
new TypeLiteral<String>() {}.getType(),
"status",
SubmitRecord.Status.class,
"labels",
new TypeLiteral<List<SubmitRecord.Label>>() {}.getType(),
"requirements",
new TypeLiteral<List<LegacySubmitRequirement>>() {}.getType(),
"errorMessage",
String.class));
assertThatSerializedClass(SubmitRecord.Label.class)
.hasFields(
ImmutableMap.of(
"label", String.class,
"status", SubmitRecord.Label.Status.class,
"appliedBy", Account.Id.class));
assertThatSerializedClass(LegacySubmitRequirement.class)
.hasAutoValueMethods(
ImmutableMap.of(
"fallbackText", String.class,
"type", String.class));
}
@Test
public void serializeMergedOn() throws Exception {
assertRoundTrip(
newBuilder().mergedOn(Instant.ofEpochMilli(234567L)).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setMergedOnMillis(234567L)
.setHasMergedOn(true)
.setColumns(colsProto)
.build());
}
@Test
public void changeMessageFields() throws Exception {
assertThatSerializedClass(ChangeMessage.Key.class)
.hasAutoValueMethods(ImmutableMap.of("changeId", Change.Id.class, "uuid", String.class));
assertThatSerializedClass(ChangeMessage.class)
.hasFields(
ImmutableMap.<String, Type>builder()
.put("key", ChangeMessage.Key.class)
.put("author", Account.Id.class)
.put("writtenOn", Instant.class)
.put("message", String.class)
.put("patchset", PatchSet.Id.class)
.put("tag", String.class)
.put("realAuthor", Account.Id.class)
.build());
}
@Test
public void commentFields() throws Exception {
assertThatSerializedClass(Comment.Key.class)
.hasFields(
ImmutableMap.of(
"uuid", String.class, "filename", String.class, "patchSetId", int.class));
assertThatSerializedClass(Comment.Identity.class).hasFields(ImmutableMap.of("id", int.class));
assertThatSerializedClass(Comment.Range.class)
.hasFields(
ImmutableMap.of(
"startLine", int.class,
"startChar", int.class,
"endLine", int.class,
"endChar", int.class));
assertThatSerializedClass(HumanComment.class)
.hasFields(
ImmutableMap.<String, Type>builder()
.put("key", Comment.Key.class)
.put("lineNbr", int.class)
.put("author", Comment.Identity.class)
.put("realAuthor", Comment.Identity.class)
.put("writtenOn", Timestamp.class)
.put("side", short.class)
.put("message", String.class)
.put("parentUuid", String.class)
.put("range", Comment.Range.class)
.put("tag", String.class)
.put("revId", String.class)
.put("serverId", String.class)
.put("unresolved", boolean.class)
.build());
}
@Test
public void serializeServerId() throws Exception {
assertRoundTrip(
newBuilder().serverId(DEFAULT_SERVER_ID).build(),
ChangeNotesStateProto.newBuilder()
.setMetaId(SHA_BYTES)
.setChangeId(ID.get())
.setServerId(DEFAULT_SERVER_ID)
.setHasServerId(true)
.setColumns(colsProto)
.build());
}
private static ChangeNotesStateProto toProto(ChangeNotesState state) throws Exception {
return ChangeNotesStateProto.parseFrom(Serializer.INSTANCE.serialize(state));
}
private static ChangeNotesState assertRoundTrip(
ChangeNotesState state, ChangeNotesStateProto expectedProto) throws Exception {
ChangeNotesStateProto actualProto = toProto(state);
assertThat(actualProto).isEqualTo(expectedProto);
ChangeNotesState actual = Serializer.INSTANCE.deserialize(Serializer.INSTANCE.serialize(state));
assertThat(actual).isEqualTo(state);
// It's possible that ChangeNotesState contains objects which implement equals without taking
// into account all fields. Return the actual deserialized instance so that callers can perform
// additional assertions if necessary.
return actual;
}
}