| // Copyright (C) 2022 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.allowLabel; |
| 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 com.google.common.collect.Iterables; |
| import com.google.gerrit.acceptance.AbstractDaemonTest; |
| import com.google.gerrit.acceptance.GitUtil; |
| import com.google.gerrit.acceptance.PushOneCommit; |
| import com.google.gerrit.acceptance.TestAccount; |
| import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; |
| import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; |
| import com.google.gerrit.entities.LabelId; |
| import com.google.gerrit.entities.LabelType; |
| import com.google.gerrit.entities.RefNames; |
| import com.google.gerrit.extensions.api.changes.ReviewInput; |
| import com.google.gerrit.server.project.testing.TestLabels; |
| import com.google.gerrit.testing.FakeEmailSender.Message; |
| import com.google.inject.Inject; |
| import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; |
| import org.eclipse.jgit.junit.TestRepository; |
| import org.junit.Test; |
| |
| /** |
| * Integration test to verify that change-no-longer-submittable emails are sent when a change |
| * becomes not submittable, and that they are sent only in this case (and not when the change |
| * becomes submittable or stays submittable/unsubmittable). |
| */ |
| public class ChangeNoLongerSubmittableIT extends AbstractDaemonTest { |
| @Inject private ProjectOperations projectOperations; |
| @Inject private RequestScopeOperations requestScopeOperations; |
| |
| @Test |
| public void postReview_changeBecomesUnsubmittable_approvalRemoved() throws Exception { |
| // Allow all users to approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Approve the change. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| approve(r.getChangeId()); |
| requestScopeOperations.setApiUser(admin.id()); |
| |
| // Revoke the approval |
| requestScopeOperations.setApiUser(approver.id()); |
| sender.clear(); |
| gApi.changes().id(r.getChangeId()).current().review(ReviewInput.noScore()); |
| |
| // Verify the email notifications that have been sent for removing the approval. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s.\n" |
| + "\n" |
| + "%s has posted comments on this change.", |
| admin.fullName(), user.fullName(), approver.fullName())); |
| assertThat(message.body()) |
| .contains("The change is no longer submittable: Code-Review is unsatisfied now.\n"); |
| assertThat(message.htmlBody()) |
| .contains("<p>The change is no longer submittable: Code-Review is unsatisfied now.</p>"); |
| } |
| |
| @Test |
| public void postReview_changeBecomesUnsubmittable_approvalDowngraded() throws Exception { |
| // Allow all users to approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Approve the change. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| approve(r.getChangeId()); |
| requestScopeOperations.setApiUser(admin.id()); |
| |
| // Downgrade the approval |
| requestScopeOperations.setApiUser(approver.id()); |
| sender.clear(); |
| recommend(r.getChangeId()); |
| |
| // Verify the email notification that has been sent for downgrading the approval. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s.\n" |
| + "\n" |
| + "%s has posted comments on this change.", |
| admin.fullName(), user.fullName(), approver.fullName())); |
| assertThat(message.body()) |
| .contains("The change is no longer submittable: Code-Review is unsatisfied now.\n"); |
| assertThat(message.htmlBody()) |
| .contains("<p>The change is no longer submittable: Code-Review is unsatisfied now.</p>"); |
| } |
| |
| @Test |
| public void postReview_changeBecomesUnsubmittable_vetoApplied() throws Exception { |
| // Allow all users to approve and veto. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-2, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Approve the change. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| approve(r.getChangeId()); |
| requestScopeOperations.setApiUser(admin.id()); |
| |
| // Apply veto by another user. |
| TestAccount approver2 = accountCreator.user2(); |
| sender.clear(); |
| requestScopeOperations.setApiUser(approver2.id()); |
| gApi.changes().id(r.getChangeId()).current().review(ReviewInput.reject()); |
| |
| // Verify the email notification that has been sent for adding the veto. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s.\n" |
| + "\n" |
| + "%s has posted comments on this change.", |
| admin.fullName(), user.fullName(), approver.fullName())); |
| assertThat(message.body()) |
| .contains("The change is no longer submittable: Code-Review is unsatisfied now.\n"); |
| assertThat(message.htmlBody()) |
| .contains("<p>The change is no longer submittable: Code-Review is unsatisfied now.</p>"); |
| } |
| |
| @Test |
| public void postReview_changeBecomesUnsubmittable_multipleSubmitRequirementsNoLongerSatisfied() |
| throws Exception { |
| // Create a Verify, a Foo-Bar and a Bar-Baz label and allow voting on it. |
| 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()); |
| |
| LabelType.Builder fooBar = |
| labelBuilder("Foo-Bar", value(1, "Passes"), value(0, "No score"), value(-1, "Failed")); |
| u.getConfig().upsertLabelType(fooBar.build()); |
| |
| LabelType.Builder barBaz = |
| labelBuilder("Bar-Baz", value(1, "Passes"), value(0, "No score"), value(-1, "Failed")); |
| u.getConfig().upsertLabelType(barBaz.build()); |
| |
| u.save(); |
| } |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(LabelId.VERIFIED) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 1)) |
| .add( |
| allowLabel("Foo-Bar") |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 1)) |
| .add( |
| allowLabel("Bar-Baz") |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 1)) |
| .update(); |
| |
| // Allow all users to approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Approve all labels. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| approve(r.getChangeId()); |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label(LabelId.VERIFIED, 1)); |
| gApi.changes().id(r.getChangeId()).current().review(new ReviewInput().label("Foo-Bar", 1)); |
| gApi.changes().id(r.getChangeId()).current().review(new ReviewInput().label("Bar-Baz", 1)); |
| requestScopeOperations.setApiUser(admin.id()); |
| |
| // Revoke several approval |
| requestScopeOperations.setApiUser(approver.id()); |
| sender.clear(); |
| gApi.changes() |
| .id(r.getChangeId()) |
| .current() |
| .review(new ReviewInput().label("Code-Review", 0).label("Foo-Bar", 0).label("Verified", 0)); |
| |
| // Verify the email notification that have been sent for removing the approval. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s.\n" |
| + "\n" |
| + "%s has posted comments on this change.", |
| admin.fullName(), user.fullName(), approver.fullName())); |
| assertThat(message.body()) |
| .contains( |
| "The change is no longer submittable:" |
| + " Code-Review, Foo-Bar and Verified are unsatisfied now.\n"); |
| assertThat(message.htmlBody()) |
| .contains( |
| "<p>The change is no longer submittable:" |
| + " Code-Review, Foo-Bar and Verified are unsatisfied now.</p>"); |
| } |
| |
| @Test |
| public void postReview_changeStaysSubmittable_approvalRemoved() throws Exception { |
| // Allow all users to approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Approve the change by 2 users. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| approve(r.getChangeId()); |
| TestAccount approver2 = |
| accountCreator.create("user3", "user3@email.com", "User3", /* displayName= */ null); |
| requestScopeOperations.setApiUser(approver2.id()); |
| approve(r.getChangeId()); |
| requestScopeOperations.setApiUser(admin.id()); |
| |
| // Revoke one approval. |
| requestScopeOperations.setApiUser(approver.id()); |
| sender.clear(); |
| gApi.changes().id(r.getChangeId()).current().review(ReviewInput.noScore()); |
| |
| // Verify the email notification. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s.\n" |
| + "\n" |
| + "%s has posted comments on this change.", |
| admin.fullName(), user.fullName(), approver.fullName())); |
| assertThat(message.body()).doesNotContain("The change is no longer submittable"); |
| assertThat(message.htmlBody()).doesNotContain("The change is no longer submittable"); |
| } |
| |
| @Test |
| public void postReview_changeStaysSubmittable_approvalDowngraded() throws Exception { |
| // Allow all users to approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Approve the change by 2 users. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| approve(r.getChangeId()); |
| TestAccount approver2 = |
| accountCreator.create("user3", "user3@email.com", "User3", /* displayName= */ null); |
| requestScopeOperations.setApiUser(approver2.id()); |
| approve(r.getChangeId()); |
| requestScopeOperations.setApiUser(admin.id()); |
| |
| // Downgrade one approval |
| requestScopeOperations.setApiUser(approver.id()); |
| sender.clear(); |
| recommend(r.getChangeId()); |
| |
| // Verify the email notification. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s.\n" |
| + "\n" |
| + "%s has posted comments on this change.", |
| admin.fullName(), user.fullName(), approver.fullName())); |
| assertThat(message.body()).doesNotContain("The change is no longer submittable"); |
| assertThat(message.htmlBody()).doesNotContain("The change is no longer submittable"); |
| } |
| |
| @Test |
| public void postReview_changeStaysUnsubmittable_approvalAdded() throws Exception { |
| // Allow all users to approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Add a vote that doesn't make the change submittable. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| sender.clear(); |
| recommend(r.getChangeId()); |
| |
| // Verify the email notification. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s.\n" |
| + "\n" |
| + "%s has posted comments on this change.", |
| admin.fullName(), user.fullName(), approver.fullName())); |
| assertThat(message.body()).doesNotContain("The change is no longer submittable"); |
| assertThat(message.htmlBody()).doesNotContain("The change is no longer submittable"); |
| } |
| |
| @Test |
| public void postReview_changeBecomesSubmittable_approvalAdded() throws Exception { |
| // Allow all users to approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Add a vote that makes the change submittable. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| sender.clear(); |
| approve(r.getChangeId()); |
| |
| // Verify the email notification. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s.\n" |
| + "\n" |
| + "%s has posted comments on this change.", |
| admin.fullName(), user.fullName(), approver.fullName())); |
| assertThat(message.body()).doesNotContain("The change is no longer submittable"); |
| assertThat(message.htmlBody()).doesNotContain("The change is no longer submittable"); |
| } |
| |
| @Test |
| public void pushNewPatchSet_changeBecomesUnsubmittable_approvalNotCopied() throws Exception { |
| // Allow all users to approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Approve the change. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| approve(r.getChangeId()); |
| requestScopeOperations.setApiUser(admin.id()); |
| |
| // Upload a new patch set that removes the approval. |
| TestAccount uploaderPs3 = |
| accountCreator.create("user3", "user3@email.com", "User3", /* displayName= */ null); |
| sender.clear(); |
| r = amendChangeWithUploader(r, project, uploaderPs3); |
| r.assertOkStatus(); |
| |
| r.assertMessage( |
| "The following approvals got outdated and were removed:\n* Code-Review+2 by user2\n"); |
| |
| // Verify the email notification that has been sent for uploading the new patch set. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s, %s.\n" |
| + "\n" |
| + "%s has uploaded a new patch set (#3) to the change originally created by %s.", |
| admin.fullName(), |
| user.fullName(), |
| approver.fullName(), |
| uploaderPs3.fullName(), |
| admin.fullName())); |
| assertThat(message.body()) |
| .contains("The change is no longer submittable: Code-Review is unsatisfied now.\n"); |
| assertThat(message.htmlBody()) |
| .contains("<p>The change is no longer submittable: Code-Review is unsatisfied now.</p>"); |
| } |
| |
| @Test |
| public void pushNewPatchSet_changeBecomesUnsubmittable_approvalCopiedAndRevoked() |
| throws Exception { |
| // Make Code-Review approvals sticky. |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| LabelType.Builder codeReview = |
| labelBuilder( |
| LabelId.CODE_REVIEW, |
| value(2, "Looks good to me, approved"), |
| value(1, "Looks good to me, but someone else must approve"), |
| value(0, "No score"), |
| value(-1, "I would prefer this is not submitted as is"), |
| value(-2, "This shall not be submitted")); |
| codeReview.setCopyCondition("is:MAX"); |
| u.getConfig().upsertLabelType(codeReview.build()); |
| u.save(); |
| } |
| |
| // Allow all users to veto and approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-2, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Approve the change. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| approve(r.getChangeId()); |
| requestScopeOperations.setApiUser(admin.id()); |
| |
| // Upload a new patch set that copies the approval, but revoke it on push. |
| sender.clear(); |
| TestRepository<InMemoryRepository> repo = cloneProject(project, approver); |
| GitUtil.fetch(repo, "refs/*:refs/*"); |
| repo.reset(r.getCommit()); |
| r = |
| amendChange( |
| r.getChangeId(), |
| "refs/for/master%l=-Code-Review", |
| approver, |
| repo, |
| "new subject", |
| "new file", |
| "new content"); |
| r.assertOkStatus(); |
| |
| // Verify the email notification that has been sent for uploading the new patch set. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s.\n" |
| + "\n" |
| + "%s has uploaded a new patch set (#3) to the change originally created by %s.", |
| admin.fullName(), user.fullName(), approver.fullName(), admin.fullName())); |
| assertThat(message.body()) |
| .contains("The change is no longer submittable: Code-Review is unsatisfied now.\n"); |
| assertThat(message.htmlBody()) |
| .contains("<p>The change is no longer submittable: Code-Review is unsatisfied now.</p>"); |
| } |
| |
| @Test |
| public void pushNewPatchSet_changeBecomesUnsubmittable_approvalCopiedAndDowngraded() |
| throws Exception { |
| // Make Code-Review approvals sticky. |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| LabelType.Builder codeReview = |
| labelBuilder( |
| LabelId.CODE_REVIEW, |
| value(2, "Looks good to me, approved"), |
| value(1, "Looks good to me, but someone else must approve"), |
| value(0, "No score"), |
| value(-1, "I would prefer this is not submitted as is"), |
| value(-2, "This shall not be submitted")); |
| codeReview.setCopyCondition("is:MAX"); |
| u.getConfig().upsertLabelType(codeReview.build()); |
| u.save(); |
| } |
| |
| // Allow all users to veto and approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-2, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Approve the change. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| approve(r.getChangeId()); |
| requestScopeOperations.setApiUser(admin.id()); |
| |
| // Upload a new patch set that copies the approval, but downgrade it on push. |
| sender.clear(); |
| TestRepository<InMemoryRepository> repo = cloneProject(project, approver); |
| GitUtil.fetch(repo, "refs/*:refs/*"); |
| repo.reset(r.getCommit()); |
| r = |
| amendChange( |
| r.getChangeId(), |
| "refs/for/master%l=Code-Review+1", |
| approver, |
| repo, |
| "new subject", |
| "new file", |
| "new content"); |
| r.assertOkStatus(); |
| |
| // Verify the email notification that has been sent for uploading the new patch set. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s.\n" |
| + "\n" |
| + "%s has uploaded a new patch set (#3) to the change originally created by %s.", |
| admin.fullName(), user.fullName(), approver.fullName(), admin.fullName())); |
| assertThat(message.body()) |
| .contains("The change is no longer submittable: Code-Review is unsatisfied now.\n"); |
| assertThat(message.htmlBody()) |
| .contains("<p>The change is no longer submittable: Code-Review is unsatisfied now.</p>"); |
| } |
| |
| @Test |
| public void pushNewPatchSet_changeBecomesUnsubmittable_approvalCopiedVetoApplied() |
| throws Exception { |
| // Make Code-Review approvals sticky. |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| LabelType.Builder codeReview = |
| labelBuilder( |
| LabelId.CODE_REVIEW, |
| value(2, "Looks good to me, approved"), |
| value(1, "Looks good to me, but someone else must approve"), |
| value(0, "No score"), |
| value(-1, "I would prefer this is not submitted as is"), |
| value(-2, "This shall not be submitted")); |
| codeReview.setCopyCondition("is:MAX"); |
| u.getConfig().upsertLabelType(codeReview.build()); |
| u.save(); |
| } |
| |
| // Allow all users to veto and approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-2, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Approve the change. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| approve(r.getChangeId()); |
| requestScopeOperations.setApiUser(admin.id()); |
| |
| // Upload a new patch set that copies the approval, but apply a new veto on push. |
| TestAccount uploaderPs3 = |
| accountCreator.create("user3", "user3@email.com", "User3", /* displayName= */ null); |
| sender.clear(); |
| TestRepository<InMemoryRepository> repo = cloneProject(project, uploaderPs3); |
| GitUtil.fetch(repo, "refs/*:refs/*"); |
| repo.reset(r.getCommit()); |
| r = |
| amendChange( |
| r.getChangeId(), |
| "refs/for/master%l=Code-Review-2", |
| uploaderPs3, |
| repo, |
| "new subject", |
| "new file", |
| "new content"); |
| r.assertOkStatus(); |
| |
| // Verify the email notification that has been sent for uploading the new patch set. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s, %s.\n" |
| + "\n" |
| + "%s has uploaded a new patch set (#3) to the change originally created by %s.", |
| admin.fullName(), |
| user.fullName(), |
| uploaderPs3.fullName(), |
| uploaderPs3.fullName(), |
| admin.fullName())); |
| assertThat(message.body()) |
| .contains("The change is no longer submittable: Code-Review is unsatisfied now.\n"); |
| assertThat(message.htmlBody()) |
| .contains("<p>The change is no longer submittable: Code-Review is unsatisfied now.</p>"); |
| } |
| |
| @Test |
| public void pushNewPatchSet_changeStaysSubmittable_approvalCopied() throws Exception { |
| // Make Code-Review approvals sticky. |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| LabelType.Builder codeReview = |
| labelBuilder( |
| LabelId.CODE_REVIEW, |
| value(2, "Looks good to me, approved"), |
| value(1, "Looks good to me, but someone else must approve"), |
| value(0, "No score"), |
| value(-1, "I would prefer this is not submitted as is"), |
| value(-2, "This shall not be submitted")); |
| codeReview.setCopyCondition("is:MAX"); |
| u.getConfig().upsertLabelType(codeReview.build()); |
| u.save(); |
| } |
| |
| // Allow all users to approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Approve the change. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| approve(r.getChangeId()); |
| requestScopeOperations.setApiUser(admin.id()); |
| |
| // Upload a new patch set, the approval is copied. |
| TestAccount uploaderPs3 = |
| accountCreator.create("user3", "user3@email.com", "User3", /* displayName= */ null); |
| sender.clear(); |
| r = amendChangeWithUploader(r, project, uploaderPs3); |
| r.assertOkStatus(); |
| |
| // Verify the email notification that has been sent for uploading the new patch set. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s.\n" |
| + "\n" |
| + "%s has uploaded a new patch set (#3) to the change originally created by %s.", |
| admin.fullName(), user.fullName(), uploaderPs3.fullName(), admin.fullName())); |
| assertThat(message.body()).doesNotContain("The change is no longer submittable"); |
| assertThat(message.htmlBody()).doesNotContain("The change is no longer submittable"); |
| } |
| |
| @Test |
| public void pushNewPatchSet_changeStaysSubmittable_approvalReapplied() throws Exception { |
| // Allow all users to approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Approve the change. |
| TestAccount approver = accountCreator.user2(); |
| requestScopeOperations.setApiUser(approver.id()); |
| approve(r.getChangeId()); |
| requestScopeOperations.setApiUser(admin.id()); |
| |
| // Upload a new patch set that removes the approval, but re-apply a new approval on push. |
| TestAccount uploaderPs3 = |
| accountCreator.create("user3", "user3@email.com", "User3", /* displayName= */ null); |
| sender.clear(); |
| TestRepository<InMemoryRepository> repo = cloneProject(project, uploaderPs3); |
| GitUtil.fetch(repo, "refs/*:refs/*"); |
| repo.reset(r.getCommit()); |
| r = |
| amendChange( |
| r.getChangeId(), |
| "refs/for/master%l=Code-Review+2", |
| uploaderPs3, |
| repo, |
| "new subject", |
| "new file", |
| "new content"); |
| r.assertOkStatus(); |
| |
| // Verify the email notification that has been sent for uploading the new patch set. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| // uploaderPs3 gets added to the attention set because this user is a new reviewer on the change |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s, %s, %s, %s.\n" |
| + "\n" |
| + "%s has uploaded a new patch set (#3) to the change originally created by %s.", |
| admin.fullName(), |
| user.fullName(), |
| approver.fullName(), |
| uploaderPs3.fullName(), |
| uploaderPs3.fullName(), |
| admin.fullName())); |
| assertThat(message.body()).doesNotContain("The change is no longer submittable"); |
| assertThat(message.htmlBody()).doesNotContain("The change is no longer submittable"); |
| } |
| |
| @Test |
| public void pushNewPatchSet_changeStaysUnsubmittable() throws Exception { |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Upload a new patch set. |
| TestAccount uploaderPs3 = |
| accountCreator.create("user3", "user3@email.com", "User3", /* displayName= */ null); |
| sender.clear(); |
| r = amendChangeWithUploader(r, project, uploaderPs3); |
| r.assertOkStatus(); |
| |
| // Verify the email notification that has been sent for uploading the new patch set. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "%s has uploaded a new patch set (#3) to the change originally created by %s.", |
| uploaderPs3.fullName(), admin.fullName())); |
| assertThat(message.body()).doesNotContain("The change is no longer submittable"); |
| assertThat(message.htmlBody()).doesNotContain("The change is no longer submittable"); |
| } |
| |
| @Test |
| public void pushNewPatchSet_changeBecomesSubmittable() throws Exception { |
| // Allow all users to approve. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 2)) |
| .update(); |
| |
| // Create change with admin as the owner and upload a new patch set with user as uploader. |
| PushOneCommit.Result r = createChange(); |
| r = amendChangeWithUploader(r, project, user); |
| r.assertOkStatus(); |
| |
| // Upload a new patch set and approve it. |
| TestAccount uploaderPs3 = |
| accountCreator.create("user3", "user3@email.com", "User3", /* displayName= */ null); |
| sender.clear(); |
| TestRepository<InMemoryRepository> repo = cloneProject(project, uploaderPs3); |
| GitUtil.fetch(repo, "refs/*:refs/*"); |
| repo.reset(r.getCommit()); |
| r = |
| amendChange( |
| r.getChangeId(), |
| "refs/for/master%l=Code-Review+2", |
| uploaderPs3, |
| repo, |
| "new subject", |
| "new file", |
| "new content"); |
| r.assertOkStatus(); |
| |
| // Verify the email notification that has been sent for uploading the new patch set. |
| Message message = Iterables.getOnlyElement(sender.getMessages()); |
| // uploaderPs3 gets added to the attention set because this user is a new reviewer on the change |
| assertThat(message.body()) |
| .contains( |
| String.format( |
| "Attention is currently required from: %s.\n" |
| + "\n" |
| + "%s has uploaded a new patch set (#3) to the change originally created by %s.", |
| uploaderPs3.fullName(), uploaderPs3.fullName(), admin.fullName())); |
| assertThat(message.body()).doesNotContain("The change is no longer submittable"); |
| assertThat(message.htmlBody()).doesNotContain("The change is no longer submittable"); |
| } |
| } |