| // 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.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel; |
| 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 com.google.gerrit.extensions.common.testing.ChangeInfoSubject.assertThat; |
| import static com.google.gerrit.extensions.common.testing.ChangeInfoSubject.vote; |
| import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; |
| import static com.google.gerrit.server.project.testing.TestLabels.labelBuilder; |
| import static com.google.gerrit.server.project.testing.TestLabels.value; |
| import static com.google.gerrit.testing.GerritJUnit.assertThrows; |
| 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.common.collect.Iterables; |
| import com.google.gerrit.acceptance.AbstractDaemonTest; |
| import com.google.gerrit.acceptance.PushOneCommit; |
| import com.google.gerrit.acceptance.RestResponse; |
| import com.google.gerrit.acceptance.Sandboxed; |
| import com.google.gerrit.acceptance.TestAccount; |
| import com.google.gerrit.acceptance.testsuite.group.GroupOperations; |
| import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; |
| import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; |
| import com.google.gerrit.entities.Address; |
| import com.google.gerrit.entities.LabelId; |
| import com.google.gerrit.entities.LabelType; |
| import com.google.gerrit.entities.Permission; |
| import com.google.gerrit.entities.RefNames; |
| 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.api.changes.ReviewerInfo; |
| import com.google.gerrit.extensions.api.changes.ReviewerInput; |
| import com.google.gerrit.extensions.api.changes.ReviewerResult; |
| 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.extensions.restapi.AuthException; |
| import com.google.gerrit.server.change.ReviewerModifier; |
| import com.google.gerrit.testing.FakeEmailSender.Message; |
| import com.google.gson.stream.JsonReader; |
| import com.google.inject.Inject; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| 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 { |
| |
| @Inject private GroupOperations groupOperations; |
| @Inject private ProjectOperations projectOperations; |
| @Inject private RequestScopeOperations requestScopeOperations; |
| |
| @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 = groupOperations.newGroup().name("largeGroup").create().get(); |
| String mediumGroup = groupOperations.newGroup().name("mediumGroup").create().get(); |
| |
| int largeGroupSize = ReviewerModifier.DEFAULT_MAX_REVIEWERS + 1; |
| int mediumGroupSize = ReviewerModifier.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(); |
| ReviewerResult result = addReviewer(changeId, largeGroup, SC_BAD_REQUEST); |
| 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, SC_BAD_REQUEST); |
| 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. |
| ReviewerInput in = new ReviewerInput(); |
| 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(); |
| ReviewerInput in = new ReviewerInput(); |
| in.reviewer = user.email(); |
| in.state = CC; |
| ReviewerResult 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(); |
| 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); |
| |
| // Verify email was sent to CCed account. |
| ImmutableList<Message> messages = sender.getMessages(); |
| assertThat(messages).hasSize(1); |
| Message m = messages.get(0); |
| assertThat(m.rcpt()).containsExactly(user.getNameEmail()); |
| assertThat(m.body()).contains(admin.fullName() + " has uploaded this change for review."); |
| } |
| |
| @Test |
| public void addCcEmailWithoutAccount() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String testEmailAddress = "email@without.account"; |
| |
| // Add a reviewer |
| ReviewerInput ri = new ReviewerInput(); |
| ri.reviewer = user.email(); |
| ri.state = REVIEWER; |
| ReviewerResult result = addReviewer(changeId, ri); |
| assertThat(result.input).isEqualTo(user.email()); |
| assertThat(result.confirm).isNull(); |
| assertThat(result.error).isNull(); |
| assertThat(result.reviewers).hasSize(1); |
| |
| // Add an email address that has no account to CC |
| ReviewerInput ccInput = new ReviewerInput(); |
| ccInput.reviewer = testEmailAddress; |
| ccInput.state = CC; |
| ReviewerResult resultCC = addReviewer(changeId, ccInput, SC_BAD_REQUEST); |
| assertThat(resultCC.error).contains("Account '" + testEmailAddress + "' not found"); |
| assertThat(resultCC.error) |
| .contains(testEmailAddress + " does not identify a registered user or group"); |
| } |
| |
| @Test |
| public void addReviewerEmailWithoutAccount() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| String testEmailAddress = "email@without.account"; |
| |
| // Add a reviewer without an account |
| ReviewerInput ri = new ReviewerInput(); |
| ri.reviewer = testEmailAddress; |
| ri.state = REVIEWER; |
| ReviewerResult result = addReviewer(changeId, ri, SC_BAD_REQUEST); |
| assertThat(result.error).contains("Account '" + testEmailAddress + "' not found"); |
| assertThat(result.error) |
| .contains(testEmailAddress + " does not identify a registered user or group"); |
| } |
| |
| @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(); |
| ReviewerInput in = new ReviewerInput(); |
| in.reviewer = groupOperations.newGroup().name("cc1").create().get(); |
| in.state = CC; |
| gApi.groups() |
| .id(in.reviewer) |
| .addMembers(firstUsernames.toArray(new String[firstUsernames.size()])); |
| ReviewerResult result = addReviewer(changeId, in); |
| |
| assertThat(result.input).isEqualTo(in.reviewer); |
| assertThat(result.confirm).isNull(); |
| assertThat(result.error).isNull(); |
| assertThat(result.reviewers).isNull(); |
| ChangeInfo c = gApi.changes().id(r.getChangeId()).get(); |
| assertReviewers(c, CC, firstUsers); |
| |
| // Verify emails were sent to each of the group's accounts. |
| ImmutableList<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.getNameEmail()); |
| } |
| assertThat(m.rcpt()).containsExactlyElementsIn(expectedAddresses); |
| |
| // CC a group that overlaps with some existing reviewers and CCed accounts. |
| TestAccount reviewer = |
| accountCreator.create( |
| name("reviewer"), "addCcGroup-reviewer@example.com", "Reviewer", null); |
| result = addReviewer(changeId, reviewer.username()); |
| assertThat(result.error).isNull(); |
| sender.clear(); |
| in.reviewer = groupOperations.newGroup().name("cc2").create().get(); |
| 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(); |
| assertThat(result.ccs).hasSize(3); |
| assertThat(result.reviewers).isNull(); |
| assertReviewers(c, REVIEWER, reviewer); |
| assertReviewers(c, CC, users); |
| |
| 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).getNameEmail()); |
| } |
| // 'reviewer' is not included in the email, since it has already been notified. |
| assertThat(m.rcpt()).containsExactlyElementsIn(expectedAddresses); |
| } |
| |
| @Test |
| public void transitionCcToReviewer() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| ReviewerInput in = new ReviewerInput(); |
| in.reviewer = user.email(); |
| in.state = CC; |
| addReviewer(changeId, in); |
| ChangeInfo c = gApi.changes().id(r.getChangeId()).get(); |
| assertReviewers(c, REVIEWER); |
| assertReviewers(c, CC, user); |
| |
| 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(); |
| assertReviewers(c, REVIEWER); |
| assertReviewers(c, CC, user); |
| } |
| |
| @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(LabelId.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.id().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(); |
| assertReviewers(c, REVIEWER); |
| assertReviewers(c, CC, user); |
| // Verify no approvals were added. |
| assertThat(c.labels).isNotNull(); |
| LabelInfo label = c.labels.get(LabelId.CODE_REVIEW); |
| assertThat(label).isNotNull(); |
| assertThat(label.all).isNull(); |
| } |
| |
| @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(LabelId.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. |
| c = gApi.changes().id(r.getChangeId()).get(); |
| assertReviewers(c, REVIEWER, user); |
| assertReviewers(c, CC); |
| label = c.labels.get(LabelId.CODE_REVIEW); |
| assertThat(label).isNotNull(); |
| assertThat(label.all).isNotNull(); |
| assertThat(label.all).hasSize(1); |
| Map<Integer, Integer> approvals = new HashMap<>(); |
| for (ApprovalInfo approval : label.all) { |
| approvals.put(approval._accountId, approval.value); |
| } |
| assertThat(approvals).containsEntry(user.id().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, user); |
| assertReviewers(c, CC); |
| label = c.labels.get(LabelId.CODE_REVIEW); |
| assertThat(label).isNotNull(); |
| assertThat(label.all).isNotNull(); |
| assertThat(label.all).hasSize(1); |
| approvals.clear(); |
| for (ApprovalInfo approval : label.all) { |
| approvals.put(approval._accountId, approval.value); |
| } |
| assertThat(approvals).containsEntry(user.id().get(), 0); |
| } |
| |
| @Test |
| public void reviewAndAddReviewers() throws Exception { |
| TestAccount observer = accountCreator.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. |
| ChangeInfo c = gApi.changes().id(r.getChangeId()).get(); |
| assertReviewers(c, REVIEWER, admin, user); |
| assertReviewers(c, CC, observer); |
| |
| // Verify emails were sent to added reviewers. |
| ImmutableList<Message> messages = sender.getMessages(); |
| assertThat(messages).hasSize(2); |
| |
| Message m = messages.get(0); |
| assertThat(m.rcpt()).containsExactly(user.getNameEmail(), observer.getNameEmail()); |
| assertThat(m.body()) |
| .contains( |
| admin.fullName() + " has posted comments on this change by " + admin.fullName() + "."); |
| 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.getNameEmail(), observer.getNameEmail()); |
| 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 = ReviewerModifier.DEFAULT_MAX_REVIEWERS + 1; |
| int mediumGroupSize = ReviewerModifier.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 = groupOperations.newGroup().name("largeGroup").create().get(); |
| String mediumGroup = groupOperations.newGroup().name("mediumGroup").create().get(); |
| 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 = accountCreator.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); |
| |
| ReviewerResult 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); |
| |
| assertReviewers(c, REVIEWER, admin, user); |
| assertReviewers(c, CC, users.subList(0, mediumGroupSize)); |
| } |
| |
| @Test |
| public void addReviewerToReviewerUpdateInfo() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| String changeId = r.getChangeId(); |
| ReviewerInput in = new ReviewerInput(); |
| in.reviewer = user.email(); |
| in.state = CC; |
| addReviewer(changeId, in); |
| |
| in.state = REVIEWER; |
| addReviewer(changeId, in); |
| |
| gApi.changes().id(changeId).current().review(ReviewInput.dislike()); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| // By posting a review the user is added as reviewer. |
| 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 reviewerUpdateInfo = it.next(); |
| assertThat(reviewerUpdateInfo.state).isEqualTo(CC); |
| assertThat(reviewerUpdateInfo.reviewer._accountId).isEqualTo(user.id().get()); |
| assertThat(reviewerUpdateInfo.updatedBy._accountId).isEqualTo(admin.id().get()); |
| |
| reviewerUpdateInfo = it.next(); |
| assertThat(reviewerUpdateInfo.state).isEqualTo(REVIEWER); |
| assertThat(reviewerUpdateInfo.reviewer._accountId).isEqualTo(user.id().get()); |
| assertThat(reviewerUpdateInfo.updatedBy._accountId).isEqualTo(admin.id().get()); |
| |
| reviewerUpdateInfo = it.next(); |
| assertThat(reviewerUpdateInfo.state).isEqualTo(REMOVED); |
| assertThat(reviewerUpdateInfo.reviewer._accountId).isEqualTo(user.id().get()); |
| assertThat(reviewerUpdateInfo.updatedBy._accountId).isEqualTo(admin.id().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); |
| |
| ReviewerResult 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 = |
| accountCreator.create(name("user1"), emailPrefix + "user1@example.com", "User1", null); |
| TestAccount user2 = |
| accountCreator.create(name("user2"), emailPrefix + "user2@example.com", "User2", null); |
| TestAccount user3 = |
| accountCreator.create(name("user3"), emailPrefix + "user3@example.com", "User3", null); |
| String group1 = groupOperations.newGroup().name("group1").create().get(); |
| String group2 = groupOperations.newGroup().name("group2").create().get(); |
| 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); |
| |
| ReviewerResult 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 |
| 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 = LabelId.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.getNameEmail()); |
| } |
| |
| @Test |
| public void notifyDetailsWorkOnPostReviewers() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| TestAccount userToNotify = createAccounts(1, "notify-details-post-reviewers").get(0); |
| |
| ReviewerInput addReviewer = new ReviewerInput(); |
| 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.getNameEmail()); |
| } |
| |
| @Test |
| public void removeReviewerWithVoteOnMergedChangeForChangeOwnerFails() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.CODE_REVIEW, 1)); |
| |
| requestScopeOperations.setApiUser(admin.id()); |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.CODE_REVIEW, 2)); |
| gApi.changes().id(r.getChangeId()).current().submit(); |
| |
| assertThat(gApi.changes().id(r.getChangeId()).get().removableReviewers).isEmpty(); |
| AuthException thrown = |
| assertThrows( |
| AuthException.class, |
| () -> gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove()); |
| assertThat(thrown).hasMessageThat().contains("remove reviewer not permitted"); |
| } |
| |
| @Test |
| public void removeReviewerWithVoteOnMergedChangeForUserFails() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.CODE_REVIEW, 1)); |
| |
| requestScopeOperations.setApiUser(admin.id()); |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.CODE_REVIEW, 2)); |
| gApi.changes().id(r.getChangeId()).current().submit(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| assertThat(gApi.changes().id(r.getChangeId()).get().removableReviewers).isEmpty(); |
| |
| AuthException thrown = |
| assertThrows( |
| AuthException.class, |
| () -> gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove()); |
| assertThat(thrown).hasMessageThat().contains("remove reviewer not permitted"); |
| } |
| |
| @Test |
| public void removeReviewerWithoutVoteOnMergedChangeForChangeOwnerSucceeds() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| gApi.changes().id(r.getChangeId()).addReviewer(user.email()); |
| |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.CODE_REVIEW, 2)); |
| gApi.changes().id(r.getChangeId()).current().submit(); |
| assertThat( |
| Iterables.getOnlyElement(gApi.changes().id(r.getChangeId()).get().removableReviewers) |
| .email) |
| .isEqualTo(user.email()); |
| |
| gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove(); |
| |
| // Admin is a "reviewer" since the admin submitted the change, this ensures user is not a |
| // reviewer. |
| assertThat( |
| Iterables.getOnlyElement( |
| gApi.changes().id(r.getChangeId()).get().reviewers.get(REVIEWER)) |
| .email) |
| .doesNotMatch(user.email()); |
| } |
| |
| @Test |
| public void removeReviewerWithoutVoteOnMergedChangeForUserSucceeds() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| gApi.changes().id(r.getChangeId()).addReviewer(user.email()); |
| |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.CODE_REVIEW, 2)); |
| gApi.changes().id(r.getChangeId()).current().submit(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| assertThat( |
| Iterables.getOnlyElement(gApi.changes().id(r.getChangeId()).get().removableReviewers) |
| .email) |
| .isEqualTo(user.email()); |
| |
| gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove(); |
| |
| // Admin is a "reviewer" since the admin submitted the change, this ensures user is not a |
| // reviewer. |
| assertThat( |
| Iterables.getOnlyElement( |
| gApi.changes().id(r.getChangeId()).get().reviewers.get(REVIEWER)) |
| .email) |
| .doesNotMatch(user.email()); |
| } |
| |
| @Test |
| public void removeReviewerWithVoteWithoutPermissionFails() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| TestAccount newUser = createAccounts(1, name("foo")).get(0); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.CODE_REVIEW, 1)); |
| requestScopeOperations.setApiUser(newUser.id()); |
| assertThat(gApi.changes().id(r.getChangeId()).get().removableReviewers).isEmpty(); |
| |
| AuthException thrown = |
| assertThrows( |
| AuthException.class, |
| () -> gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove()); |
| assertThat(thrown).hasMessageThat().contains("remove reviewer not permitted"); |
| } |
| |
| @Test |
| @Sandboxed |
| public void removeReviewerWithoutVoteWithPermissionSucceeds() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| // This test creates a new user so that it can explicitly check the REMOVE_REVIEWER permission |
| // rather than bypassing the check because of project or ref ownership. |
| TestAccount newUser = createAccounts(1, name("foo")).get(0); |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(allow(Permission.REMOVE_REVIEWER).ref(RefNames.REFS + "*").group(REGISTERED_USERS)) |
| .update(); |
| |
| gApi.changes().id(r.getChangeId()).addReviewer(user.email()); |
| assertThatUserIsOnlyReviewer(r.getChangeId()); |
| requestScopeOperations.setApiUser(newUser.id()); |
| assertThat( |
| Iterables.getOnlyElement(gApi.changes().id(r.getChangeId()).get().removableReviewers) |
| .email) |
| .isEqualTo(user.email()); |
| |
| gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove(); |
| assertThat(gApi.changes().id(r.getChangeId()).get().reviewers).isEmpty(); |
| } |
| |
| @Test |
| @Sandboxed |
| public void removeReviewerWithoutVoteOnAMergedChangeWithPermissionSucceeds() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| // This test creates a new user so that it can explicitly check the REMOVE_REVIEWER permission |
| // rather than bypassing the check because of project or ref ownership. |
| TestAccount newUser = createAccounts(1, name("foo")).get(0); |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(allow(Permission.REMOVE_REVIEWER).ref(RefNames.REFS + "*").group(REGISTERED_USERS)) |
| .update(); |
| |
| gApi.changes().id(r.getChangeId()).addReviewer(user.email()); |
| gApi.changes().id(r.getChangeId()).current().review(ReviewInput.approve()); |
| gApi.changes().id(r.getChangeId()).current().submit(); |
| |
| requestScopeOperations.setApiUser(newUser.id()); |
| |
| // Ensures user is removable. |
| assertThat( |
| gApi.changes().id(r.getChangeId()).get().removableReviewers.stream() |
| .filter(a -> user.email().equals(a.email)) |
| .findAny() |
| .isPresent()) |
| .isTrue(); |
| gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove(); |
| } |
| |
| @Test |
| public void removeReviewerWithoutVoteWithoutPermissionFails() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| TestAccount newUser = createAccounts(1, name("foo")).get(0); |
| |
| gApi.changes().id(r.getChangeId()).addReviewer(user.email()); |
| requestScopeOperations.setApiUser(newUser.id()); |
| assertThat(gApi.changes().id(r.getChangeId()).get().removableReviewers).isEmpty(); |
| |
| AuthException thrown = |
| assertThrows( |
| AuthException.class, |
| () -> gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove()); |
| assertThat(thrown).hasMessageThat().contains("remove reviewer not permitted"); |
| } |
| |
| @Test |
| public void removeCCWithoutPermissionFails() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| TestAccount newUser = createAccounts(1, name("foo")).get(0); |
| |
| ReviewerInput input = new ReviewerInput(); |
| input.reviewer = user.email(); |
| input.state = ReviewerState.CC; |
| gApi.changes().id(r.getChangeId()).addReviewer(input); |
| requestScopeOperations.setApiUser(newUser.id()); |
| assertThat(gApi.changes().id(r.getChangeId()).get().removableReviewers).isEmpty(); |
| |
| AuthException thrown = |
| assertThrows( |
| AuthException.class, |
| () -> gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove()); |
| assertThat(thrown).hasMessageThat().contains("remove reviewer not permitted"); |
| } |
| |
| @Test |
| public void removeReviewerWithVoteAndThenAddThemBackClearsVote() throws Exception { |
| // Add Verified label. |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| LabelType.Builder verified = |
| labelBuilder( |
| LabelId.VERIFIED, value(1, "Passes"), value(0, "No score"), value(-1, "Failed")); |
| u.getConfig().upsertLabelType(verified.build()); |
| u.save(); |
| } |
| |
| // Grant permissions to vote on the verified label. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(LabelId.VERIFIED) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 1)) |
| .update(); |
| |
| PushOneCommit.Result r = createChange(); |
| requestScopeOperations.setApiUser(user.id()); |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.VERIFIED, 1).label(LabelId.CODE_REVIEW, 1)); |
| |
| requestScopeOperations.setApiUser(admin.id()); |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.CODE_REVIEW, 2)); |
| |
| assertThat(gApi.changes().id(r.getChangeId()).get(DETAILED_LABELS)) |
| .hasExactlyVotes( |
| vote(LabelId.CODE_REVIEW, user.id(), 1), |
| vote(LabelId.VERIFIED, user.id(), 1), |
| vote(LabelId.CODE_REVIEW, admin.id(), 2)); |
| |
| gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove(); |
| assertThat(gApi.changes().id(r.getChangeId()).get(DETAILED_LABELS)) |
| .hasExactlyVotes(vote(LabelId.CODE_REVIEW, admin.id(), 2)); |
| |
| ReviewerInput input = new ReviewerInput(); |
| input.reviewer = user.email(); |
| input.state = ReviewerState.REVIEWER; |
| ReviewerResult result = gApi.changes().id(r.getChangeId()).addReviewer(input); |
| assertThat(result.reviewers).hasSize(1); |
| |
| assertThat(gApi.changes().id(r.getChangeId()).get(DETAILED_LABELS)) |
| .hasExactlyVotes(vote(LabelId.CODE_REVIEW, admin.id(), 2)); |
| } |
| |
| @Test |
| public void reviewerVotesAreReturnedIfReviewerIsAddedByVoting() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| requestScopeOperations.setApiUser(admin.id()); |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.CODE_REVIEW, 1)); |
| assertThat(gApi.changes().id(r.getChangeId()).get(DETAILED_LABELS)) |
| .hasExactlyVotes(vote(LabelId.CODE_REVIEW, admin.id(), 1)); |
| |
| gApi.changes().id(r.getChangeId()).reviewer(admin.email()).remove(); |
| assertThat(gApi.changes().id(r.getChangeId()).get(DETAILED_LABELS)).hasNoVotes(); |
| |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.CODE_REVIEW, 2)); |
| assertThat(gApi.changes().id(r.getChangeId()).get(DETAILED_LABELS)) |
| .hasExactlyVotes(vote(LabelId.CODE_REVIEW, admin.id(), 2)); |
| } |
| |
| @Test |
| public void reviewerVotesAreReturnedIfReviewerIsAddedAndThenVoted() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| requestScopeOperations.setApiUser(user.id()); |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.CODE_REVIEW, 1)); |
| assertThat(gApi.changes().id(r.getChangeId()).get(DETAILED_LABELS)) |
| .hasExactlyVotes(vote(LabelId.CODE_REVIEW, user.id(), 1)); |
| |
| gApi.changes().id(r.getChangeId()).reviewer(user.email()).remove(); |
| assertThat(gApi.changes().id(r.getChangeId()).get(DETAILED_LABELS)).hasNoVotes(); |
| |
| ReviewerInput input = new ReviewerInput(); |
| input.reviewer = user.email(); |
| input.state = ReviewerState.REVIEWER; |
| ReviewerResult result = gApi.changes().id(r.getChangeId()).addReviewer(input); |
| assertThat(result.reviewers).hasSize(1); |
| assertThat(gApi.changes().id(r.getChangeId()).get(DETAILED_LABELS)).hasNoVotes(); |
| |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.CODE_REVIEW, 1)); |
| assertThat(gApi.changes().id(r.getChangeId()).get(DETAILED_LABELS)) |
| .hasExactlyVotes(vote(LabelId.CODE_REVIEW, user.id(), 1)); |
| } |
| |
| @Test |
| public void addExistingReviewerShortCircuits() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| |
| ReviewerInput input = new ReviewerInput(); |
| input.reviewer = user.email(); |
| input.state = ReviewerState.REVIEWER; |
| |
| ReviewerResult result = gApi.changes().id(r.getChangeId()).addReviewer(input); |
| assertThat(result.reviewers).hasSize(1); |
| ReviewerInfo info = result.reviewers.get(0); |
| assertThat(info._accountId).isEqualTo(user.id().get()); |
| |
| assertThat(gApi.changes().id(r.getChangeId()).addReviewer(input).reviewers).isEmpty(); |
| } |
| |
| @Test |
| public void addExistingCcShortCircuits() throws Exception { |
| PushOneCommit.Result r = createChange(); |
| |
| ReviewerInput input = new ReviewerInput(); |
| input.reviewer = user.email(); |
| input.state = ReviewerState.CC; |
| |
| ReviewerResult result = gApi.changes().id(r.getChangeId()).addReviewer(input); |
| assertThat(result.ccs).hasSize(1); |
| AccountInfo info = result.ccs.get(0); |
| assertThat(info._accountId).isEqualTo(user.id().get()); |
| |
| assertThat(gApi.changes().id(r.getChangeId()).addReviewer(input).ccs).isEmpty(); |
| } |
| |
| @Test |
| public void moveCcToReviewer() throws Exception { |
| // Create a change and add 'user' as CC. |
| String changeId = createChange().getChangeId(); |
| ReviewerInput reviewerInput = new ReviewerInput(); |
| reviewerInput.reviewer = user.email(); |
| reviewerInput.state = ReviewerState.CC; |
| gApi.changes().id(changeId).addReviewer(reviewerInput); |
| |
| // Verify that 'user' is a CC on the change and that there are no reviewers. |
| ChangeInfo c = gApi.changes().id(changeId).get(); |
| Collection<AccountInfo> ccs = c.reviewers.get(CC); |
| assertThat(ccs).isNotNull(); |
| assertThat(ccs).hasSize(1); |
| assertThat(ccs.iterator().next()._accountId).isEqualTo(user.id().get()); |
| assertThat(c.reviewers.get(REVIEWER)).isNull(); |
| |
| // Move 'user' from CC to reviewer. |
| gApi.changes().id(changeId).addReviewer(user.id().toString()); |
| |
| // Verify that 'user' is a reviewer on the change now and that there are no CCs. |
| c = gApi.changes().id(changeId).get(); |
| Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER); |
| assertThat(reviewers).isNotNull(); |
| assertThat(reviewers).hasSize(1); |
| assertThat(reviewers.iterator().next()._accountId).isEqualTo(user.id().get()); |
| assertThat(c.reviewers.get(CC)).isNull(); |
| } |
| |
| @Test |
| public void moveReviewerToCc() throws Exception { |
| // Allow everyone to approve changes. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(LabelId.CODE_REVIEW) |
| .ref("refs/heads/*") |
| .group(REGISTERED_USERS) |
| .range(-2, 2)) |
| .update(); |
| |
| // Create a change and add 'user' as reviewer. |
| String changeId = createChange().getChangeId(); |
| gApi.changes().id(changeId).addReviewer(user.id().toString()); |
| |
| // Verify that 'user' is a reviewer on the change and that there are no CCs. |
| ChangeInfo c = gApi.changes().id(changeId).get(); |
| Collection<AccountInfo> reviewers = c.reviewers.get(REVIEWER); |
| assertThat(reviewers).isNotNull(); |
| assertThat(reviewers).hasSize(1); |
| assertThat(reviewers.iterator().next()._accountId).isEqualTo(user.id().get()); |
| assertThat(c.reviewers.get(CC)).isNull(); |
| |
| // Let 'user' approve the change and verify that the change has the approval. |
| requestScopeOperations.setApiUser(user.id()); |
| approve(changeId); |
| c = gApi.changes().id(changeId).get(); |
| assertThat(c.labels.get(LabelId.CODE_REVIEW).approved._accountId).isEqualTo(user.id().get()); |
| |
| // Move 'user' from reviewer to CC. |
| requestScopeOperations.setApiUser(admin.id()); |
| ReviewerInput reviewerInput = new ReviewerInput(); |
| reviewerInput.reviewer = user.id().toString(); |
| reviewerInput.state = CC; |
| gApi.changes().id(changeId).addReviewer(reviewerInput); |
| |
| // Verify that 'user' is a CC on the change now and that there are no reviewers. |
| c = gApi.changes().id(changeId).get(); |
| Collection<AccountInfo> ccs = c.reviewers.get(CC); |
| assertThat(ccs).isNotNull(); |
| assertThat(ccs).hasSize(1); |
| assertThat(ccs.iterator().next()._accountId).isEqualTo(user.id().get()); |
| assertThat(c.reviewers.get(REVIEWER)).isNull(); |
| |
| // Verify that the approval of 'user' is still there. |
| assertThat(c.labels.get(LabelId.CODE_REVIEW).approved._accountId).isEqualTo(user.id().get()); |
| } |
| |
| @Test |
| public void wipChangeDoesNotExposeReviewersInChangeSearch() throws Exception { |
| requestScopeOperations.setApiUser(admin.id()); |
| String changeId = createChange().getChangeId(); |
| gApi.changes().id(changeId).setWorkInProgress(); |
| gApi.changes().id(changeId).addReviewer(user.email()); |
| assertThat( |
| gApi.changes() |
| .query("project:" + project.get() + " AND reviewer:" + user.email() + "") |
| .get()) |
| .isEmpty(); |
| } |
| |
| private void assertThatUserIsOnlyReviewer(String changeId) throws Exception { |
| AccountInfo userInfo = new AccountInfo(user.fullName(), user.getNameEmail().email()); |
| userInfo._accountId = user.id().get(); |
| userInfo.username = user.username(); |
| assertThat(gApi.changes().id(changeId).get().reviewers) |
| .containsExactly(ReviewerState.REVIEWER, ImmutableList.of(userInfo)); |
| } |
| |
| private ReviewerResult addReviewer(String changeId, String reviewer) throws Exception { |
| return addReviewer(changeId, reviewer, SC_OK); |
| } |
| |
| private ReviewerResult addReviewer(String changeId, String reviewer, int expectedStatus) |
| throws Exception { |
| ReviewerInput in = new ReviewerInput(); |
| in.reviewer = reviewer; |
| return addReviewer(changeId, in, expectedStatus); |
| } |
| |
| private ReviewerResult addReviewer(String changeId, ReviewerInput in) throws Exception { |
| return addReviewer(changeId, in, SC_OK); |
| } |
| |
| private ReviewerResult addReviewer(String changeId, ReviewerInput in, int expectedStatus) |
| throws Exception { |
| RestResponse resp = adminRestSession.post("/changes/" + changeId + "/reviewers", in); |
| return readContentFromJson(resp, expectedStatus, ReviewerResult.class); |
| } |
| |
| private RestResponse deleteReviewer(String changeId, TestAccount account) throws Exception { |
| return adminRestSession.delete("/changes/" + changeId + "/reviewers/" + account.id().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); |
| try (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); |
| Collections.addAll(accountList, accounts); |
| 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.id().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( |
| accountCreator.create( |
| name("u" + i), emailPrefix + "-" + i + "@example.com", "Full Name " + i, null)); |
| } |
| return result; |
| } |
| |
| private Map<String, LabelInfo> getChangeLabels(String changeId) throws Exception { |
| return gApi.changes().id(changeId).get(DETAILED_LABELS).labels; |
| } |
| } |