// Copyright (C) 2016 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.gerrit.acceptance.rest.change; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS; import static com.google.gerrit.extensions.client.ReviewerState.CC; import static com.google.gerrit.extensions.client.ReviewerState.REMOVED; import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_OK; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.acceptance.RestResponse; import com.google.gerrit.acceptance.TestAccount; import com.google.gerrit.extensions.api.changes.AddReviewerInput; import com.google.gerrit.extensions.api.changes.AddReviewerResult; import com.google.gerrit.extensions.api.changes.NotifyHandling; import com.google.gerrit.extensions.api.changes.NotifyInfo; import com.google.gerrit.extensions.api.changes.RecipientType; import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.api.changes.ReviewResult; import com.google.gerrit.extensions.client.ReviewerState; import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.common.ApprovalInfo; import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.LabelInfo; import com.google.gerrit.extensions.common.ReviewerUpdateInfo; import com.google.gerrit.server.change.PostReviewers; import com.google.gerrit.server.mail.Address; import com.google.gerrit.testutil.FakeEmailSender.Message; import com.google.gson.stream.JsonReader; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.junit.Test; public class ChangeReviewersIT extends AbstractDaemonTest { @Test public void addGroupAsReviewer() throws Exception { // Set up two groups, one that is too large too add as reviewer, and one // that is too large to add without confirmation. String largeGroup = createGroup("largeGroup"); String mediumGroup = createGroup("mediumGroup"); int largeGroupSize = PostReviewers.DEFAULT_MAX_REVIEWERS + 1; int mediumGroupSize = PostReviewers.DEFAULT_MAX_REVIEWERS_WITHOUT_CHECK + 1; List<TestAccount> users = createAccounts(largeGroupSize, "addGroupAsReviewer"); List<String> largeGroupUsernames = new ArrayList<>(mediumGroupSize); for (TestAccount u : users) { largeGroupUsernames.add(u.username); } List<String> mediumGroupUsernames = largeGroupUsernames.subList(0, mediumGroupSize); gApi.groups() .id(largeGroup) .addMembers(largeGroupUsernames.toArray(new String[largeGroupSize])); gApi.groups() .id(mediumGroup) .addMembers(mediumGroupUsernames.toArray(new String[mediumGroupSize])); // Attempt to add overly large group as reviewers. PushOneCommit.Result r = createChange(); String changeId = r.getChangeId(); AddReviewerResult result = addReviewer(changeId, largeGroup); assertThat(result.input).isEqualTo(largeGroup); assertThat(result.confirm).isNull(); assertThat(result.error).contains("has too many members to add them all as reviewers"); assertThat(result.reviewers).isNull(); // Attempt to add medium group without confirmation. result = addReviewer(changeId, mediumGroup); assertThat(result.input).isEqualTo(mediumGroup); assertThat(result.confirm).isTrue(); assertThat(result.error) .contains("has " + mediumGroupSize + " members. Do you want to add them all as reviewers?"); assertThat(result.reviewers).isNull(); // Add medium group with confirmation. AddReviewerInput in = new AddReviewerInput(); in.reviewer = mediumGroup; in.confirmed = true; result = addReviewer(changeId, in); assertThat(result.input).isEqualTo(mediumGroup); assertThat(result.confirm).isNull(); assertThat(result.error).isNull(); assertThat(result.reviewers).hasSize(mediumGroupSize); // Verify that group members were added as reviewers. ChangeInfo c = gApi.changes().id(r.getChangeId()).get(); assertReviewers(c, REVIEWER, users.subList(0, mediumGroupSize)); } @Test public void addCcAccount() throws Exception { PushOneCommit.Result r = createChange(); String changeId = r.getChangeId(); AddReviewerInput in = new AddReviewerInput(); in.reviewer = user.email; in.state = CC; AddReviewerResult result = addReviewer(changeId, in); assertThat(result.input).isEqualTo(user.email); assertThat(result.confirm).isNull(); assertThat(result.error).isNull(); ChangeInfo c = gApi.changes().id(r.getChangeId()).get(); if (notesMigration.readChanges()) { assertThat(result.reviewers).isNull(); assertThat(result.ccs).hasSize(1); AccountInfo ai = result.ccs.get(0); assertThat(ai._accountId).isEqualTo(user.id.get()); assertReviewers(c, CC, user); } else { assertThat(result.ccs).isNull(); assertThat(result.reviewers).hasSize(1); AccountInfo ai = result.reviewers.get(0); assertThat(ai._accountId).isEqualTo(user.id.get()); assertReviewers(c, REVIEWER, user); } // Verify email was sent to CCed account. List<Message> messages = sender.getMessages(); assertThat(messages).hasSize(1); Message m = messages.get(0); assertThat(m.rcpt()).containsExactly(user.emailAddress); if (notesMigration.readChanges()) { assertThat(m.body()).contains(admin.fullName + " has uploaded this change for review."); } else { assertThat(m.body()).contains("Hello " + user.fullName + ",\n"); assertThat(m.body()).contains("I'd like you to do a code review."); } } @Test public void addCcGroup() throws Exception { List<TestAccount> users = createAccounts(6, "addCcGroup"); List<String> usernames = new ArrayList<>(6); for (TestAccount u : users) { usernames.add(u.username); } List<TestAccount> firstUsers = users.subList(0, 3); List<String> firstUsernames = usernames.subList(0, 3); PushOneCommit.Result r = createChange(); String changeId = r.getChangeId(); AddReviewerInput in = new AddReviewerInput(); in.reviewer = createGroup("cc1"); in.state = CC; gApi.groups() .id(in.reviewer) .addMembers(firstUsernames.toArray(new String[firstUsernames.size()])); AddReviewerResult result = addReviewer(changeId, in); assertThat(result.input).isEqualTo(in.reviewer); assertThat(result.confirm).isNull(); assertThat(result.error).isNull(); if (notesMigration.readChanges()) { assertThat(result.reviewers).isNull(); } else { assertThat(result.ccs).isNull(); } ChangeInfo c = gApi.changes().id(r.getChangeId()).get(); if (notesMigration.readChanges()) { assertReviewers(c, CC, firstUsers); } else { assertReviewers(c, REVIEWER, firstUsers); assertReviewers(c, CC); } // Verify emails were sent to each of the group's accounts. List<Message> messages = sender.getMessages(); assertThat(messages).hasSize(1); Message m = messages.get(0); List<Address> expectedAddresses = new ArrayList<>(firstUsers.size()); for (TestAccount u : firstUsers) { expectedAddresses.add(u.emailAddress); } assertThat(m.rcpt()).containsExactlyElementsIn(expectedAddresses); // CC a group that overlaps with some existing reviewers and CCed accounts. TestAccount reviewer = accounts.create(name("reviewer"), "addCcGroup-reviewer@example.com", "Reviewer"); result = addReviewer(changeId, reviewer.username); assertThat(result.error).isNull(); sender.clear(); in.reviewer = createGroup("cc2"); gApi.groups().id(in.reviewer).addMembers(usernames.toArray(new String[usernames.size()])); gApi.groups().id(in.reviewer).addMembers(reviewer.username); result = addReviewer(changeId, in); assertThat(result.input).isEqualTo(in.reviewer); assertThat(result.confirm).isNull(); assertThat(result.error).isNull(); c = gApi.changes().id(r.getChangeId()).get(); if (notesMigration.readChanges()) { assertThat(result.ccs).hasSize(3); assertThat(result.reviewers).isNull(); assertReviewers(c, REVIEWER, reviewer); assertReviewers(c, CC, users); } else { assertThat(result.ccs).isNull(); assertThat(result.reviewers).hasSize(3); List<TestAccount> expectedUsers = new ArrayList<>(users.size() + 2); expectedUsers.addAll(users); expectedUsers.add(reviewer); assertReviewers(c, REVIEWER, expectedUsers); } messages = sender.getMessages(); assertThat(messages).hasSize(1); m = messages.get(0); expectedAddresses = new ArrayList<>(4); for (int i = 0; i < 3; i++) { expectedAddresses.add(users.get(users.size() - i - 1).emailAddress); } if (!notesMigration.readChanges()) { for (int i = 0; i < 3; i++) { expectedAddresses.add(users.get(i).emailAddress); } } expectedAddresses.add(reviewer.emailAddress); assertThat(m.rcpt()).containsExactlyElementsIn(expectedAddresses); } @Test public void transitionCcToReviewer() throws Exception { PushOneCommit.Result r = createChange(); String changeId = r.getChangeId(); AddReviewerInput in = new AddReviewerInput(); in.reviewer = user.email; in.state = CC; addReviewer(changeId, in); ChangeInfo c = gApi.changes().id(r.getChangeId()).get(); if (notesMigration.readChanges()) { assertReviewers(c, REVIEWER); assertReviewers(c, CC, user); } else { assertReviewers(c, REVIEWER, user); assertReviewers(c, CC); } in.state = REVIEWER; addReviewer(changeId, in); c = gApi.changes().id(r.getChangeId()).get(); assertReviewers(c, REVIEWER, user); assertReviewers(c, CC); } @Test public void driveByComment() throws Exception { // Create change owned by admin. PushOneCommit.Result r = createChange(); // Post drive-by message as user. ReviewInput input = new ReviewInput().message("hello"); RestResponse resp = userRestSession.post( "/changes/" + r.getChangeId() + "/revisions/" + r.getCommit().getName() + "/review", input); ReviewResult result = readContentFromJson(resp, 200, ReviewResult.class); assertThat(result.labels).isNull(); assertThat(result.reviewers).isNull(); // Verify user is added to CC list. ChangeInfo c = gApi.changes().id(r.getChangeId()).get(); if (notesMigration.readChanges()) { assertReviewers(c, REVIEWER); assertReviewers(c, CC, user); } else { // If we aren't reading from NoteDb, the user will appear as a // reviewer. assertReviewers(c, REVIEWER, user); assertReviewers(c, CC); } } @Test public void addSelfAsReviewer() throws Exception { // Create change owned by admin. PushOneCommit.Result r = createChange(); // user adds self as REVIEWER. ReviewInput input = new ReviewInput().reviewer(user.username); RestResponse resp = userRestSession.post( "/changes/" + r.getChangeId() + "/revisions/" + r.getCommit().getName() + "/review", input); ReviewResult result = readContentFromJson(resp, 200, ReviewResult.class); assertThat(result.labels).isNull(); assertThat(result.reviewers).isNotNull(); assertThat(result.reviewers).hasSize(1); // Verify reviewer state. ChangeInfo c = gApi.changes().id(r.getChangeId()).get(); assertReviewers(c, REVIEWER, user); assertReviewers(c, CC); LabelInfo label = c.labels.get("Code-Review"); assertThat(label).isNotNull(); assertThat(label.all).isNotNull(); assertThat(label.all).hasSize(1); ApprovalInfo approval = label.all.get(0); assertThat(approval._accountId).isEqualTo(user.getId().get()); } @Test public void addSelfAsCc() throws Exception { // Create change owned by admin. PushOneCommit.Result r = createChange(); // user adds self as CC. ReviewInput input = new ReviewInput().reviewer(user.username, CC, false); RestResponse resp = userRestSession.post( "/changes/" + r.getChangeId() + "/revisions/" + r.getCommit().getName() + "/review", input); ReviewResult result = readContentFromJson(resp, 200, ReviewResult.class); assertThat(result.labels).isNull(); assertThat(result.reviewers).isNotNull(); assertThat(result.reviewers).hasSize(1); // Verify reviewer state. ChangeInfo c = gApi.changes().id(r.getChangeId()).get(); if (notesMigration.readChanges()) { assertReviewers(c, REVIEWER); assertReviewers(c, CC, user); // Verify no approvals were added. assertThat(c.labels).isNotNull(); LabelInfo label = c.labels.get("Code-Review"); assertThat(label).isNotNull(); assertThat(label.all).isNull(); } else { // When approvals are stored in ReviewDb, we still create a label for // the reviewing user, and force them into the REVIEWER state. assertReviewers(c, REVIEWER, user); assertReviewers(c, CC); LabelInfo label = c.labels.get("Code-Review"); assertThat(label).isNotNull(); assertThat(label.all).isNotNull(); assertThat(label.all).hasSize(1); ApprovalInfo approval = label.all.get(0); assertThat(approval._accountId).isEqualTo(user.getId().get()); } } @Test public void reviewerReplyWithoutVote() throws Exception { // Create change owned by admin. PushOneCommit.Result r = createChange(); // Verify reviewer state. ChangeInfo c = gApi.changes().id(r.getChangeId()).get(); assertReviewers(c, REVIEWER); assertReviewers(c, CC); LabelInfo label = c.labels.get("Code-Review"); assertThat(label).isNotNull(); assertThat(label.all).isNull(); // Add user as REVIEWER. ReviewInput input = new ReviewInput().reviewer(user.username); ReviewResult result = review(r.getChangeId(), r.getCommit().name(), input); assertThat(result.labels).isNull(); assertThat(result.reviewers).isNotNull(); assertThat(result.reviewers).hasSize(1); // Verify reviewer state. Both admin and user should be REVIEWERs now, // because admin gets forced into REVIEWER state by virtue of being owner. c = gApi.changes().id(r.getChangeId()).get(); assertReviewers(c, REVIEWER, admin, user); assertReviewers(c, CC); label = c.labels.get("Code-Review"); assertThat(label).isNotNull(); assertThat(label.all).isNotNull(); assertThat(label.all).hasSize(2); Map<Integer, Integer> approvals = new HashMap<>(); for (ApprovalInfo approval : label.all) { approvals.put(approval._accountId, approval.value); } assertThat(approvals).containsEntry(admin.getId().get(), 0); assertThat(approvals).containsEntry(user.getId().get(), 0); // Comment as user without voting. This should delete the approval and // then replace it with the default value. input = new ReviewInput().message("hello"); RestResponse resp = userRestSession.post( "/changes/" + r.getChangeId() + "/revisions/" + r.getCommit().getName() + "/review", input); result = readContentFromJson(resp, 200, ReviewResult.class); assertThat(result.labels).isNull(); // Verify reviewer state. c = gApi.changes().id(r.getChangeId()).get(); assertReviewers(c, REVIEWER, admin, user); assertReviewers(c, CC); label = c.labels.get("Code-Review"); assertThat(label).isNotNull(); assertThat(label.all).isNotNull(); assertThat(label.all).hasSize(2); approvals.clear(); for (ApprovalInfo approval : label.all) { approvals.put(approval._accountId, approval.value); } assertThat(approvals).containsEntry(admin.getId().get(), 0); assertThat(approvals).containsEntry(user.getId().get(), 0); } @Test public void reviewAndAddReviewers() throws Exception { TestAccount observer = accounts.user2(); PushOneCommit.Result r = createChange(); ReviewInput input = ReviewInput.approve().reviewer(user.email).reviewer(observer.email, CC, false); ReviewResult result = review(r.getChangeId(), r.getCommit().name(), input); assertThat(result.labels).isNotNull(); assertThat(result.reviewers).isNotNull(); assertThat(result.reviewers).hasSize(2); // Verify reviewer and CC were added. If not in NoteDb read mode, both // parties will be returned as CCed. ChangeInfo c = gApi.changes().id(r.getChangeId()).get(); if (notesMigration.readChanges()) { assertReviewers(c, REVIEWER, admin, user); assertReviewers(c, CC, observer); } else { // In legacy mode, everyone should be a reviewer. assertReviewers(c, REVIEWER, admin, user, observer); assertReviewers(c, CC); } // Verify emails were sent to added reviewers. List<Message> messages = sender.getMessages(); assertThat(messages).hasSize(2); Message m = messages.get(0); assertThat(m.rcpt()).containsExactly(user.emailAddress, observer.emailAddress); assertThat(m.body()).contains(admin.fullName + " has posted comments on this change."); assertThat(m.body()).contains("Change subject: " + PushOneCommit.SUBJECT + "\n"); assertThat(m.body()).contains("Patch Set 1: Code-Review+2"); m = messages.get(1); assertThat(m.rcpt()).containsExactly(user.emailAddress, observer.emailAddress); assertThat(m.body()).contains("Hello " + user.fullName + ",\n"); assertThat(m.body()).contains("I'd like you to do a code review."); } @Test public void reviewAndAddGroupReviewers() throws Exception { int largeGroupSize = PostReviewers.DEFAULT_MAX_REVIEWERS + 1; int mediumGroupSize = PostReviewers.DEFAULT_MAX_REVIEWERS_WITHOUT_CHECK + 1; List<TestAccount> users = createAccounts(largeGroupSize, "reviewAndAddGroupReviewers"); List<String> usernames = new ArrayList<>(largeGroupSize); for (TestAccount u : users) { usernames.add(u.username); } String largeGroup = createGroup("largeGroup"); String mediumGroup = createGroup("mediumGroup"); gApi.groups().id(largeGroup).addMembers(usernames.toArray(new String[largeGroupSize])); gApi.groups() .id(mediumGroup) .addMembers(usernames.subList(0, mediumGroupSize).toArray(new String[mediumGroupSize])); TestAccount observer = accounts.user2(); PushOneCommit.Result r = createChange(); // Attempt to add overly large group as reviewers. ReviewInput input = ReviewInput.approve() .reviewer(user.email) .reviewer(observer.email, CC, false) .reviewer(largeGroup); ReviewResult result = review(r.getChangeId(), r.getCommit().name(), input, SC_BAD_REQUEST); assertThat(result.labels).isNull(); assertThat(result.reviewers).isNotNull(); assertThat(result.reviewers).hasSize(3); AddReviewerResult reviewerResult = result.reviewers.get(largeGroup); assertThat(reviewerResult).isNotNull(); assertThat(reviewerResult.confirm).isNull(); assertThat(reviewerResult.error).isNotNull(); assertThat(reviewerResult.error).contains("has too many members to add them all as reviewers"); // No labels should have changed, and no reviewers/CCs should have been added. ChangeInfo c = gApi.changes().id(r.getChangeId()).get(); assertThat(c.messages).hasSize(1); assertThat(c.reviewers.get(REVIEWER)).isNull(); assertThat(c.reviewers.get(CC)).isNull(); // Attempt to add group large enough to require confirmation, without // confirmation, as reviewers. input = ReviewInput.approve() .reviewer(user.email) .reviewer(observer.email, CC, false) .reviewer(mediumGroup); result = review(r.getChangeId(), r.getCommit().name(), input, SC_BAD_REQUEST); assertThat(result.labels).isNull(); assertThat(result.reviewers).isNotNull(); assertThat(result.reviewers).hasSize(3); reviewerResult = result.reviewers.get(mediumGroup); assertThat(reviewerResult).isNotNull(); assertThat(reviewerResult.confirm).isTrue(); assertThat(reviewerResult.error) .contains("has " + mediumGroupSize + " members. Do you want to add them all as reviewers?"); // No labels should have changed, and no reviewers/CCs should have been added. c = gApi.changes().id(r.getChangeId()).get(); assertThat(c.messages).hasSize(1); assertThat(c.reviewers.get(REVIEWER)).isNull(); assertThat(c.reviewers.get(CC)).isNull(); // Retrying with confirmation should successfully approve and add reviewers/CCs. input = ReviewInput.approve().reviewer(user.email).reviewer(mediumGroup, CC, true); result = review(r.getChangeId(), r.getCommit().name(), input); assertThat(result.labels).isNotNull(); assertThat(result.reviewers).isNotNull(); assertThat(result.reviewers).hasSize(2); c = gApi.changes().id(r.getChangeId()).get(); assertThat(c.messages).hasSize(2); if (notesMigration.readChanges()) { assertReviewers(c, REVIEWER, admin, user); assertReviewers(c, CC, users.subList(0, mediumGroupSize)); } else { // If not in NoteDb mode, then everyone is a REVIEWER. List<TestAccount> expected = users.subList(0, mediumGroupSize); expected.add(admin); expected.add(user); assertReviewers(c, REVIEWER, expected); assertReviewers(c, CC); } } @Test public void noteDbAddReviewerToReviewerChangeInfo() throws Exception { assume().that(notesMigration.readChanges()).isTrue(); PushOneCommit.Result r = createChange(); String changeId = r.getChangeId(); AddReviewerInput in = new AddReviewerInput(); in.reviewer = user.email; in.state = CC; addReviewer(changeId, in); in.state = REVIEWER; addReviewer(changeId, in); gApi.changes().id(changeId).current().review(ReviewInput.dislike()); setApiUser(user); // NoteDb adds reviewer to a change on every review. gApi.changes().id(changeId).current().review(ReviewInput.dislike()); deleteReviewer(changeId, user).assertNoContent(); ChangeInfo c = gApi.changes().id(changeId).get(); assertThat(c.reviewerUpdates).isNotNull(); assertThat(c.reviewerUpdates).hasSize(3); Iterator<ReviewerUpdateInfo> it = c.reviewerUpdates.iterator(); ReviewerUpdateInfo reviewerChange = it.next(); assertThat(reviewerChange.state).isEqualTo(CC); assertThat(reviewerChange.reviewer._accountId).isEqualTo(user.getId().get()); assertThat(reviewerChange.updatedBy._accountId).isEqualTo(admin.getId().get()); reviewerChange = it.next(); assertThat(reviewerChange.state).isEqualTo(REVIEWER); assertThat(reviewerChange.reviewer._accountId).isEqualTo(user.getId().get()); assertThat(reviewerChange.updatedBy._accountId).isEqualTo(admin.getId().get()); reviewerChange = it.next(); assertThat(reviewerChange.state).isEqualTo(REMOVED); assertThat(reviewerChange.reviewer._accountId).isEqualTo(user.getId().get()); assertThat(reviewerChange.updatedBy._accountId).isEqualTo(admin.getId().get()); } @Test public void addDuplicateReviewers() throws Exception { PushOneCommit.Result r = createChange(); ReviewInput input = ReviewInput.approve().reviewer(user.email).reviewer(user.email); ReviewResult result = review(r.getChangeId(), r.getCommit().name(), input); assertThat(result.reviewers).isNotNull(); assertThat(result.reviewers).hasSize(1); AddReviewerResult reviewerResult = result.reviewers.get(user.email); assertThat(reviewerResult).isNotNull(); assertThat(reviewerResult.confirm).isNull(); assertThat(reviewerResult.error).isNull(); } @Test public void addOverlappingGroups() throws Exception { String emailPrefix = "addOverlappingGroups-"; TestAccount user1 = accounts.create(name("user1"), emailPrefix + "user1@example.com", "User1"); TestAccount user2 = accounts.create(name("user2"), emailPrefix + "user2@example.com", "User2"); TestAccount user3 = accounts.create(name("user3"), emailPrefix + "user3@example.com", "User3"); String group1 = createGroup("group1"); String group2 = createGroup("group2"); gApi.groups().id(group1).addMembers(user1.username, user2.username); gApi.groups().id(group2).addMembers(user2.username, user3.username); PushOneCommit.Result r = createChange(); ReviewInput input = ReviewInput.approve().reviewer(group1).reviewer(group2); ReviewResult result = review(r.getChangeId(), r.getCommit().name(), input); assertThat(result.reviewers).isNotNull(); assertThat(result.reviewers).hasSize(2); AddReviewerResult reviewerResult = result.reviewers.get(group1); assertThat(reviewerResult.error).isNull(); assertThat(reviewerResult.reviewers).hasSize(2); reviewerResult = result.reviewers.get(group2); assertThat(reviewerResult.error).isNull(); assertThat(reviewerResult.reviewers).hasSize(1); // Repeat the above for CCs if (!notesMigration.readChanges()) { return; } r = createChange(); input = ReviewInput.approve().reviewer(group1, CC, false).reviewer(group2, CC, false); result = review(r.getChangeId(), r.getCommit().name(), input); assertThat(result.reviewers).isNotNull(); assertThat(result.reviewers).hasSize(2); reviewerResult = result.reviewers.get(group1); assertThat(reviewerResult.error).isNull(); assertThat(reviewerResult.ccs).hasSize(2); reviewerResult = result.reviewers.get(group2); assertThat(reviewerResult.error).isNull(); assertThat(reviewerResult.ccs).hasSize(1); // Repeat again with one group REVIEWER, the other CC. The overlapping // member should end up as a REVIEWER. r = createChange(); input = ReviewInput.approve().reviewer(group1, REVIEWER, false).reviewer(group2, CC, false); result = review(r.getChangeId(), r.getCommit().name(), input); assertThat(result.reviewers).isNotNull(); assertThat(result.reviewers).hasSize(2); reviewerResult = result.reviewers.get(group1); assertThat(reviewerResult.error).isNull(); assertThat(reviewerResult.reviewers).hasSize(2); reviewerResult = result.reviewers.get(group2); assertThat(reviewerResult.error).isNull(); assertThat(reviewerResult.reviewers).isNull(); assertThat(reviewerResult.ccs).hasSize(1); } @Test public void removingReviewerRemovesTheirVote() throws Exception { String crLabel = "Code-Review"; PushOneCommit.Result r = createChange(); ReviewInput input = ReviewInput.approve().reviewer(admin.email); ReviewResult addResult = review(r.getChangeId(), r.getCommit().name(), input); assertThat(addResult.reviewers).isNotNull(); assertThat(addResult.reviewers).hasSize(1); Map<String, LabelInfo> changeLabels = getChangeLabels(r.getChangeId()); assertThat(changeLabels.get(crLabel).all).hasSize(1); RestResponse deleteResult = deleteReviewer(r.getChangeId(), admin); deleteResult.assertNoContent(); changeLabels = getChangeLabels(r.getChangeId()); assertThat(changeLabels.get(crLabel).all).isNull(); // Check that the vote is gone even after the reviewer is added back addReviewer(r.getChangeId(), admin.email); changeLabels = getChangeLabels(r.getChangeId()); assertThat(changeLabels.get(crLabel).all).isNull(); } @Test public void notifyDetailsWorkOnPostReview() throws Exception { PushOneCommit.Result r = createChange(); TestAccount userToNotify = createAccounts(1, "notify-details-post-review").get(0); ReviewInput reviewInput = new ReviewInput(); reviewInput.reviewer(user.email, ReviewerState.REVIEWER, true); reviewInput.notify = NotifyHandling.NONE; reviewInput.notifyDetails = ImmutableMap.of(RecipientType.TO, new NotifyInfo(ImmutableList.of(userToNotify.email))); sender.clear(); gApi.changes().id(r.getChangeId()).current().review(reviewInput); assertThat(sender.getMessages()).hasSize(1); assertThat(sender.getMessages().get(0).rcpt()).containsExactly(userToNotify.emailAddress); } @Test public void notifyDetailsWorkOnPostReviewers() throws Exception { PushOneCommit.Result r = createChange(); TestAccount userToNotify = createAccounts(1, "notify-details-post-reviewers").get(0); AddReviewerInput addReviewer = new AddReviewerInput(); addReviewer.reviewer = user.email; addReviewer.notify = NotifyHandling.NONE; addReviewer.notifyDetails = ImmutableMap.of(RecipientType.TO, new NotifyInfo(ImmutableList.of(userToNotify.email))); sender.clear(); gApi.changes().id(r.getChangeId()).addReviewer(addReviewer); assertThat(sender.getMessages()).hasSize(1); assertThat(sender.getMessages().get(0).rcpt()).containsExactly(userToNotify.emailAddress); } private AddReviewerResult addReviewer(String changeId, String reviewer) throws Exception { return addReviewer(changeId, reviewer, SC_OK); } private AddReviewerResult addReviewer(String changeId, String reviewer, int expectedStatus) throws Exception { AddReviewerInput in = new AddReviewerInput(); in.reviewer = reviewer; return addReviewer(changeId, in, expectedStatus); } private AddReviewerResult addReviewer(String changeId, AddReviewerInput in) throws Exception { return addReviewer(changeId, in, SC_OK); } private AddReviewerResult addReviewer(String changeId, AddReviewerInput in, int expectedStatus) throws Exception { RestResponse resp = adminRestSession.post("/changes/" + changeId + "/reviewers", in); return readContentFromJson(resp, expectedStatus, AddReviewerResult.class); } private RestResponse deleteReviewer(String changeId, TestAccount account) throws Exception { return adminRestSession.delete("/changes/" + changeId + "/reviewers/" + account.getId().get()); } private ReviewResult review(String changeId, String revisionId, ReviewInput in) throws Exception { return review(changeId, revisionId, in, SC_OK); } private ReviewResult review( String changeId, String revisionId, ReviewInput in, int expectedStatus) throws Exception { RestResponse resp = adminRestSession.post("/changes/" + changeId + "/revisions/" + revisionId + "/review", in); return readContentFromJson(resp, expectedStatus, ReviewResult.class); } private static <T> T readContentFromJson(RestResponse r, int expectedStatus, Class<T> clazz) throws Exception { r.assertStatus(expectedStatus); JsonReader jsonReader = new JsonReader(r.getReader()); jsonReader.setLenient(true); return newGson().fromJson(jsonReader, clazz); } private static void assertReviewers( ChangeInfo c, ReviewerState reviewerState, TestAccount... accounts) throws Exception { List<TestAccount> accountList = new ArrayList<>(accounts.length); for (TestAccount a : accounts) { accountList.add(a); } assertReviewers(c, reviewerState, accountList); } private static void assertReviewers( ChangeInfo c, ReviewerState reviewerState, Iterable<TestAccount> accounts) throws Exception { Collection<AccountInfo> actualAccounts = c.reviewers.get(reviewerState); if (actualAccounts == null) { assertThat(accounts.iterator().hasNext()).isFalse(); return; } assertThat(actualAccounts).isNotNull(); List<Integer> actualAccountIds = new ArrayList<>(actualAccounts.size()); for (AccountInfo account : actualAccounts) { actualAccountIds.add(account._accountId); } List<Integer> expectedAccountIds = new ArrayList<>(); for (TestAccount account : accounts) { expectedAccountIds.add(account.getId().get()); } assertThat(actualAccountIds).containsExactlyElementsIn(expectedAccountIds); } private List<TestAccount> createAccounts(int n, String emailPrefix) throws Exception { List<TestAccount> result = new ArrayList<>(n); for (int i = 0; i < n; i++) { result.add( accounts.create(name("u" + i), emailPrefix + "-" + i + "@example.com", "Full Name " + i)); } return result; } private Map<String, LabelInfo> getChangeLabels(String changeId) throws Exception { return gApi.changes().id(changeId).get(EnumSet.of(DETAILED_LABELS)).labels; } }