Merge "Get project list for guessing groups from in memory project cache"
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index a8ba052..22eb32c 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -379,7 +379,8 @@
       // Add the review ops.
       logger.atFine().log("posting review");
       PostReviewOp postReviewOp =
-          postReviewOpFactory.create(projectState, revision.getPatchSet().id(), input);
+          postReviewOpFactory.create(
+              projectState, revision.getPatchSet().id(), input, revision.getAccountId());
       bu.addOp(revision.getChange().getId(), postReviewOp);
 
       // Adjust the attention set based on the input
diff --git a/java/com/google/gerrit/server/restapi/change/PostReviewOp.java b/java/com/google/gerrit/server/restapi/change/PostReviewOp.java
index b7d17f2..29e453b 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReviewOp.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReviewOp.java
@@ -97,7 +97,8 @@
 
 public class PostReviewOp implements BatchUpdateOp {
   interface Factory {
-    PostReviewOp create(ProjectState projectState, PatchSet.Id psId, ReviewInput in);
+    PostReviewOp create(
+        ProjectState projectState, PatchSet.Id psId, ReviewInput in, Account.Id reviewerId);
   }
 
   /**
@@ -192,6 +193,7 @@
   private final ProjectState projectState;
   private final PatchSet.Id psId;
   private final ReviewInput in;
+  private final Account.Id reviewerId;
   private final boolean publishPatchSetLevelComment;
 
   private IdentifiedUser user;
@@ -220,7 +222,8 @@
       PluginSetContext<OnPostReview> onPostReviews,
       @Assisted ProjectState projectState,
       @Assisted PatchSet.Id psId,
-      @Assisted ReviewInput in) {
+      @Assisted ReviewInput in,
+      @Assisted Account.Id reviewerId) {
     this.approvalCopier = approvalCopier;
     this.approvalsUtil = approvalsUtil;
     this.publishCommentUtil = publishCommentUtil;
@@ -237,6 +240,7 @@
     this.projectState = projectState;
     this.psId = psId;
     this.in = in;
+    this.reviewerId = reviewerId;
   }
 
   @Override
@@ -645,10 +649,11 @@
           del.add(c);
           update.putApproval(normName, (short) 0);
         }
-        // Only allow voting again if the vote is copied over from a past patch-set, or the
-        // values are different.
+        // Only allow voting again the values are different, if the real account differs or if the
+        // vote is copied over from a past patch-set.
       } else if (c != null
           && (c.value() != ent.getValue()
+              || !c.realAccountId().equals(reviewerId)
               || (inLabels.containsKey(c.label()) && isApprovalCopiedOver(c, ctx.getNotes())))) {
         PatchSetApproval.Builder b =
             c.toBuilder()
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
index 804723b..3531d1c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
@@ -166,6 +166,202 @@
   }
 
   @Test
+  public void overrideImpersonatedVoteWithOtherImpersonatedVote_sameValue() throws Exception {
+    allowCodeReviewOnBehalfOf();
+    TestAccount realUser = admin;
+    TestAccount realUser2 = admin2;
+    TestAccount impersonatedUser = user;
+    PushOneCommit.Result r = createChange();
+    RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+
+    // realUser votes Code-Review+1 on behalf of impersonatedUser
+    ReviewInput in = ReviewInput.recommend();
+    in.onBehalfOf = impersonatedUser.id().toString();
+    in.message = "Message on behalf of";
+    revision.review(in);
+
+    PatchSetApproval psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+    assertThat(psa.patchSetId().get()).isEqualTo(1);
+    assertThat(psa.label()).isEqualTo("Code-Review");
+    assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+    assertThat(psa.value()).isEqualTo(1);
+    assertThat(psa.realAccountId()).isEqualTo(realUser.id());
+
+    ChangeData cd = r.getChange();
+    ChangeMessage m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+    assertThat(m.getMessage()).endsWith(in.message);
+    assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+    assertThat(m.getRealAuthor()).isEqualTo(realUser.id());
+
+    // realUser2 votes Code-Review+1 on behalf of impersonatedUser, this should override the
+    // impersonated Code-Review+1 of realUser with an impersonated Code-Review+1 of realUser2
+    requestScopeOperations.setApiUser(realUser2.id());
+    in = ReviewInput.recommend();
+    in.onBehalfOf = impersonatedUser.id().toString();
+    in.message = "Another message on behalf of";
+    gApi.changes().id(r.getChangeId()).current().review(in);
+
+    psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+    assertThat(psa.patchSetId().get()).isEqualTo(1);
+    assertThat(psa.label()).isEqualTo("Code-Review");
+    assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+    assertThat(psa.value()).isEqualTo(1);
+    assertThat(psa.realAccountId()).isEqualTo(realUser2.id());
+
+    cd = r.getChange();
+    m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+    assertThat(m.getMessage()).endsWith(in.message);
+    assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+    assertThat(m.getRealAuthor()).isEqualTo(realUser2.id());
+  }
+
+  @Test
+  public void overrideImpersonatedVoteWithOtherImpersonatedVote_differentValue() throws Exception {
+    allowCodeReviewOnBehalfOf();
+    TestAccount realUser = admin;
+    TestAccount realUser2 = admin2;
+    TestAccount impersonatedUser = user;
+    PushOneCommit.Result r = createChange();
+    RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+
+    // realUser votes Code-Review+1 on behalf of impersonatedUser
+    ReviewInput in = ReviewInput.recommend();
+    in.onBehalfOf = impersonatedUser.id().toString();
+    in.message = "Message on behalf of";
+    revision.review(in);
+
+    PatchSetApproval psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+    assertThat(psa.patchSetId().get()).isEqualTo(1);
+    assertThat(psa.label()).isEqualTo("Code-Review");
+    assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+    assertThat(psa.value()).isEqualTo(1);
+    assertThat(psa.realAccountId()).isEqualTo(realUser.id());
+
+    ChangeData cd = r.getChange();
+    ChangeMessage m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+    assertThat(m.getMessage()).endsWith(in.message);
+    assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+    assertThat(m.getRealAuthor()).isEqualTo(realUser.id());
+
+    // realUser2 votes Code-Review-1 on behalf of impersonatedUser, this should override the
+    // impersonated Code-Review+1 of realUser with an impersonated Code-Review-1 of realUser2
+    requestScopeOperations.setApiUser(realUser2.id());
+    in = ReviewInput.dislike();
+    in.onBehalfOf = impersonatedUser.id().toString();
+    in.message = "Another message on behalf of";
+    gApi.changes().id(r.getChangeId()).current().review(in);
+
+    psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+    assertThat(psa.patchSetId().get()).isEqualTo(1);
+    assertThat(psa.label()).isEqualTo("Code-Review");
+    assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+    assertThat(psa.value()).isEqualTo(-1);
+    assertThat(psa.realAccountId()).isEqualTo(realUser2.id());
+
+    cd = r.getChange();
+    m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+    assertThat(m.getMessage()).endsWith(in.message);
+    assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+    assertThat(m.getRealAuthor()).isEqualTo(realUser2.id());
+  }
+
+  @Test
+  public void overrideImpersonatedVoteWithNonImpersonatedVote_sameValue() throws Exception {
+    allowCodeReviewOnBehalfOf();
+    TestAccount realUser = admin;
+    TestAccount impersonatedUser = user;
+    PushOneCommit.Result r = createChange();
+    RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+
+    // realUser votes Code-Review+1 on behalf of impersonatedUser
+    ReviewInput in = ReviewInput.recommend();
+    in.onBehalfOf = impersonatedUser.id().toString();
+    in.message = "Message on behalf of";
+    revision.review(in);
+
+    PatchSetApproval psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+    assertThat(psa.patchSetId().get()).isEqualTo(1);
+    assertThat(psa.label()).isEqualTo("Code-Review");
+    assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+    assertThat(psa.value()).isEqualTo(1);
+    assertThat(psa.realAccountId()).isEqualTo(realUser.id());
+
+    ChangeData cd = r.getChange();
+    ChangeMessage m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+    assertThat(m.getMessage()).endsWith(in.message);
+    assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+    assertThat(m.getRealAuthor()).isEqualTo(realUser.id());
+
+    // impersonatedUser votes Code-Review+1 themselves, this should override the impersonated
+    // Code-Review+1 with a non-impersonated Code-Review+1
+    requestScopeOperations.setApiUser(impersonatedUser.id());
+    in = ReviewInput.recommend();
+    in.message = "Message";
+    gApi.changes().id(r.getChangeId()).current().review(in);
+
+    psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+    assertThat(psa.patchSetId().get()).isEqualTo(1);
+    assertThat(psa.label()).isEqualTo("Code-Review");
+    assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+    assertThat(psa.value()).isEqualTo(1);
+    assertThat(psa.realAccountId()).isEqualTo(impersonatedUser.id());
+
+    cd = r.getChange();
+    m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+    assertThat(m.getMessage()).endsWith(in.message);
+    assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+    assertThat(m.getRealAuthor()).isEqualTo(impersonatedUser.id());
+  }
+
+  @Test
+  public void overrideImpersonatedVoteWithNonImpersonatedVote_differentValue() throws Exception {
+    allowCodeReviewOnBehalfOf();
+    TestAccount realUser = admin;
+    TestAccount impersonatedUser = user;
+    PushOneCommit.Result r = createChange();
+    RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+
+    // realUser votes Code-Review+1 on behalf of impersonatedUser
+    ReviewInput in = ReviewInput.recommend();
+    in.onBehalfOf = impersonatedUser.id().toString();
+    in.message = "Message on behalf of";
+    revision.review(in);
+
+    PatchSetApproval psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+    assertThat(psa.patchSetId().get()).isEqualTo(1);
+    assertThat(psa.label()).isEqualTo("Code-Review");
+    assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+    assertThat(psa.value()).isEqualTo(1);
+    assertThat(psa.realAccountId()).isEqualTo(realUser.id());
+
+    ChangeData cd = r.getChange();
+    ChangeMessage m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+    assertThat(m.getMessage()).endsWith(in.message);
+    assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+    assertThat(m.getRealAuthor()).isEqualTo(realUser.id());
+
+    // impersonatedUser votes Code-Review-1 themselves, this should override the impersonated
+    // Code-Review+1 with a non-impersonated Code-Review-1
+    requestScopeOperations.setApiUser(impersonatedUser.id());
+    in = ReviewInput.dislike();
+    in.message = "Message";
+    gApi.changes().id(r.getChangeId()).current().review(in);
+
+    psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+    assertThat(psa.patchSetId().get()).isEqualTo(1);
+    assertThat(psa.label()).isEqualTo("Code-Review");
+    assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+    assertThat(psa.value()).isEqualTo(-1);
+    assertThat(psa.realAccountId()).isEqualTo(impersonatedUser.id());
+
+    cd = r.getChange();
+    m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+    assertThat(m.getMessage()).endsWith(in.message);
+    assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+    assertThat(m.getRealAuthor()).isEqualTo(impersonatedUser.id());
+  }
+
+  @Test
   public void voteOnBehalfOfRequiresLabel() throws Exception {
     allowCodeReviewOnBehalfOf();
     PushOneCommit.Result r = createChange();