| // Copyright (C) 2014 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.server.project; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.labelPermissionKey; |
| import static com.google.gerrit.entities.LabelFunction.ANY_WITH_BLOCK; |
| import static com.google.gerrit.entities.LabelFunction.MAX_NO_BLOCK; |
| import static com.google.gerrit.entities.LabelFunction.MAX_WITH_BLOCK; |
| import static com.google.gerrit.entities.LabelFunction.NO_BLOCK; |
| import static com.google.gerrit.entities.LabelFunction.NO_OP; |
| import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS; |
| import static com.google.gerrit.extensions.client.ListChangesOption.LABELS; |
| import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE; |
| import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS; |
| import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; |
| import static com.google.gerrit.server.project.ProjectCache.illegalState; |
| import static com.google.gerrit.server.project.testing.TestLabels.label; |
| import static com.google.gerrit.server.project.testing.TestLabels.value; |
| import static com.google.gerrit.testing.GerritJUnit.assertThrows; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.gerrit.acceptance.AbstractDaemonTest; |
| import com.google.gerrit.acceptance.ExtensionRegistry; |
| import com.google.gerrit.acceptance.ExtensionRegistry.Registration; |
| import com.google.gerrit.acceptance.NoHttpd; |
| import com.google.gerrit.acceptance.PushOneCommit; |
| import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; |
| import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; |
| import com.google.gerrit.entities.CachedProjectConfig; |
| import com.google.gerrit.entities.LabelFunction; |
| import com.google.gerrit.entities.LabelType; |
| import com.google.gerrit.extensions.api.changes.ReviewInput; |
| import com.google.gerrit.extensions.api.changes.ReviewerInput; |
| import com.google.gerrit.extensions.common.ChangeInfo; |
| import com.google.gerrit.extensions.common.LabelInfo; |
| import com.google.gerrit.extensions.events.CommentAddedListener; |
| import com.google.gerrit.extensions.restapi.ResourceConflictException; |
| import com.google.inject.Inject; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| @NoHttpd |
| public class CustomLabelIT extends AbstractDaemonTest { |
| private static final String LABEL_NAME = "CustomLabel"; |
| private static final LabelType LABEL = |
| label("CustomLabel", value(1, "Positive"), value(0, "No score"), value(-1, "Negative")); |
| private static final String P_LABEL_NAME = "CustomLabel2"; |
| private static final LabelType P = |
| label("CustomLabel2", value(1, "Positive"), value(0, "No score")); |
| |
| @Inject private ProjectOperations projectOperations; |
| @Inject private ExtensionRegistry extensionRegistry; |
| @Inject private RequestScopeOperations requestScopeOperations; |
| |
| @Before |
| public void setUp() throws Exception { |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(allowLabel(LABEL_NAME).ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, 1)) |
| .add(allowLabel(P_LABEL_NAME).ref("refs/heads/*").group(ANONYMOUS_USERS).range(0, 1)) |
| .update(); |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Test |
| public void customLabelNoOp_NegativeVoteNotBlock() throws Exception { |
| saveLabelConfig(LABEL.toBuilder().setFunction(NO_OP)); |
| PushOneCommit.Result r = createChange(); |
| revision(r).review(new ReviewInput().label(LABEL_NAME, -1)); |
| ChangeInfo c = getWithLabels(r); |
| LabelInfo q = c.labels.get(LABEL_NAME); |
| assertThat(q.all).hasSize(1); |
| assertThat(q.approved).isNull(); |
| assertThat(q.recommended).isNull(); |
| assertThat(q.disliked).isNull(); |
| assertThat(q.rejected).isNotNull(); |
| assertThat(q.blocking).isNull(); |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Test |
| public void customLabelNoBlock_NegativeVoteNotBlock() throws Exception { |
| saveLabelConfig(LABEL.toBuilder().setFunction(NO_BLOCK)); |
| PushOneCommit.Result r = createChange(); |
| revision(r).review(new ReviewInput().label(LABEL_NAME, -1)); |
| ChangeInfo c = getWithLabels(r); |
| LabelInfo q = c.labels.get(LABEL_NAME); |
| assertThat(q.all).hasSize(1); |
| assertThat(q.approved).isNull(); |
| assertThat(q.recommended).isNull(); |
| assertThat(q.disliked).isNull(); |
| assertThat(q.rejected).isNotNull(); |
| assertThat(q.blocking).isNull(); |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Test |
| public void customLabelMaxNoBlock_NegativeVoteNotBlock() throws Exception { |
| saveLabelConfig(LABEL.toBuilder().setFunction(MAX_NO_BLOCK)); |
| PushOneCommit.Result r = createChange(); |
| revision(r).review(new ReviewInput().label(LABEL_NAME, -1)); |
| ChangeInfo c = getWithLabels(r); |
| LabelInfo q = c.labels.get(LABEL_NAME); |
| assertThat(q.all).hasSize(1); |
| assertThat(q.approved).isNull(); |
| assertThat(q.recommended).isNull(); |
| assertThat(q.disliked).isNull(); |
| assertThat(q.rejected).isNotNull(); |
| assertThat(q.blocking).isNull(); |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Test |
| public void customLabelMaxNoBlock_MaxVoteSubmittable() throws Exception { |
| saveLabelConfig(LABEL.toBuilder().setFunction(MAX_NO_BLOCK), P.toBuilder().setFunction(NO_OP)); |
| PushOneCommit.Result r = createChange(); |
| assertThat(info(r.getChangeId()).submittable).isNull(); |
| revision(r).review(ReviewInput.approve().label(LABEL_NAME, 1)); |
| |
| ChangeInfo c = getWithLabels(r); |
| assertThat(c.submittable).isTrue(); |
| LabelInfo q = c.labels.get(LABEL_NAME); |
| assertThat(q.all).hasSize(1); |
| assertThat(q.approved).isNotNull(); |
| assertThat(q.recommended).isNull(); |
| assertThat(q.disliked).isNull(); |
| assertThat(q.rejected).isNull(); |
| assertThat(q.blocking).isNull(); |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Test |
| public void customLabelAnyWithBlock_NegativeVoteBlock() throws Exception { |
| saveLabelConfig(LABEL.toBuilder().setFunction(ANY_WITH_BLOCK)); |
| PushOneCommit.Result r = createChange(); |
| revision(r).review(new ReviewInput().label(LABEL_NAME, -1)); |
| ChangeInfo c = getWithLabels(r); |
| LabelInfo q = c.labels.get(LABEL_NAME); |
| assertThat(q.all).hasSize(1); |
| assertThat(q.approved).isNull(); |
| assertThat(q.recommended).isNull(); |
| assertThat(q.disliked).isNull(); |
| assertThat(q.rejected).isNotNull(); |
| assertThat(q.blocking).isTrue(); |
| } |
| |
| private static class TestListener implements CommentAddedListener { |
| public CommentAddedListener.Event lastCommentAddedEvent; |
| |
| @Override |
| public void onCommentAdded(Event event) { |
| lastCommentAddedEvent = event; |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Test |
| public void customLabelAnyWithBlock_Addreviewer_ZeroVote() throws Exception { |
| TestListener testListener = new TestListener(); |
| try (Registration registration = extensionRegistry.newRegistration().add(testListener)) { |
| saveLabelConfig(P.toBuilder().setFunction(ANY_WITH_BLOCK)); |
| PushOneCommit.Result r = createChange(); |
| ReviewerInput in = new ReviewerInput(); |
| in.reviewer = user.email(); |
| gApi.changes().id(r.getChangeId()).addReviewer(in); |
| |
| ReviewInput input = new ReviewInput().label(P_LABEL_NAME, 0); |
| input.message = "foo"; |
| |
| revision(r).review(input); |
| ChangeInfo c = getWithLabels(r); |
| LabelInfo q = c.labels.get(P_LABEL_NAME); |
| assertThat(q.all).hasSize(1); |
| assertThat(q.approved).isNull(); |
| assertThat(q.recommended).isNull(); |
| assertThat(q.disliked).isNull(); |
| assertThat(q.rejected).isNull(); |
| assertThat(q.blocking).isNull(); |
| assertThat(testListener.lastCommentAddedEvent.getComment()) |
| .isEqualTo("Patch Set 1:\n\n" + input.message); |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Test |
| public void customLabelMaxWithBlock_NegativeVoteBlock() throws Exception { |
| saveLabelConfig(LABEL.toBuilder().setFunction(MAX_WITH_BLOCK)); |
| PushOneCommit.Result r = createChange(); |
| revision(r).review(new ReviewInput().label(LABEL_NAME, -1)); |
| ChangeInfo c = getWithLabels(r); |
| LabelInfo q = c.labels.get(LABEL_NAME); |
| assertThat(q.all).hasSize(1); |
| assertThat(q.approved).isNull(); |
| assertThat(q.recommended).isNull(); |
| assertThat(q.disliked).isNull(); |
| assertThat(q.rejected).isNotNull(); |
| assertThat(q.blocking).isTrue(); |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Test |
| public void customLabelMaxWithBlock_DeletedVoteDoesNotTriggerNegativeBlock() throws Exception { |
| saveLabelConfig(P.toBuilder().setFunction(MAX_WITH_BLOCK)); |
| PushOneCommit.Result r = createChange(); |
| requestScopeOperations.setApiUser(user.id()); |
| revision(r).review(new ReviewInput().label(P_LABEL_NAME, 1)); |
| requestScopeOperations.setApiUser(admin.id()); |
| revision(r).review(new ReviewInput().label(P_LABEL_NAME, 1)); |
| |
| LabelInfo labelInfo = getWithLabels(r).labels.get(P_LABEL_NAME); |
| assertThat(labelInfo.all).hasSize(2); |
| assertThat(labelInfo.approved).isNotNull(); |
| assertThat(labelInfo.blocking).isNull(); |
| |
| revision(r).reviewer(admin.email()).deleteVote(P_LABEL_NAME); |
| labelInfo = getWithLabels(r).labels.get(P_LABEL_NAME); |
| assertThat(labelInfo.all).hasSize(2); // 0 vote still delivered |
| assertThat(labelInfo.approved).isNotNull(); |
| assertThat(labelInfo.rejected).isNull(); |
| assertThat(labelInfo.blocking).isNull(); // label is not blocking the change submission |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Test |
| public void customLabelMaxWithBlock_MaxVoteSubmittable() throws Exception { |
| saveLabelConfig( |
| LABEL.toBuilder().setFunction(MAX_WITH_BLOCK), P.toBuilder().setFunction(NO_OP)); |
| PushOneCommit.Result r = createChange(); |
| assertThat(info(r.getChangeId()).submittable).isNull(); |
| revision(r).review(ReviewInput.approve().label(LABEL_NAME, 1)); |
| |
| ChangeInfo c = getWithLabels(r); |
| assertThat(c.submittable).isTrue(); |
| LabelInfo q = c.labels.get(LABEL_NAME); |
| assertThat(q.all).hasSize(1); |
| assertThat(q.approved).isNotNull(); |
| assertThat(q.recommended).isNull(); |
| assertThat(q.disliked).isNull(); |
| assertThat(q.rejected).isNull(); |
| assertThat(q.blocking).isNull(); |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Test |
| public void customLabelMaxWithBlock_MaxVoteNegativeVoteBlock() throws Exception { |
| saveLabelConfig(LABEL.toBuilder().setFunction(MAX_WITH_BLOCK)); |
| PushOneCommit.Result r = createChange(); |
| revision(r).review(new ReviewInput().label(LABEL_NAME, 1)); |
| revision(r).review(new ReviewInput().label(LABEL_NAME, -1)); |
| ChangeInfo c = getWithLabels(r); |
| LabelInfo q = c.labels.get(LABEL_NAME); |
| assertThat(q.all).hasSize(1); |
| assertThat(q.approved).isNull(); |
| assertThat(q.recommended).isNull(); |
| assertThat(q.disliked).isNull(); |
| assertThat(q.rejected).isNotNull(); |
| assertThat(q.blocking).isTrue(); |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Test |
| public void customLabel_DisallowPostSubmit() throws Exception { |
| saveLabelConfig( |
| LABEL.toBuilder().setFunction(NO_OP).setAllowPostSubmit(false), |
| P.toBuilder().setFunction(NO_OP)); |
| |
| PushOneCommit.Result r = createChange(); |
| revision(r).review(ReviewInput.approve()); |
| revision(r).submit(); |
| |
| ChangeInfo info = getWithLabels(r); |
| assertPermitted(info, "Code-Review", 2); |
| assertPermitted(info, P_LABEL_NAME, 0, 1); |
| assertPermitted(info, LABEL_NAME); |
| |
| ReviewInput postSubmitReview1 = new ReviewInput(); |
| postSubmitReview1.label(P.getName(), P.getMax().getValue()); |
| revision(r).review(postSubmitReview1); |
| |
| ReviewInput postSubmitReview2 = new ReviewInput(); |
| postSubmitReview2.label(LABEL.getName(), LABEL.getMax().getValue()); |
| ResourceConflictException thrown = |
| assertThrows(ResourceConflictException.class, () -> revision(r).review(postSubmitReview2)); |
| assertThat(thrown) |
| .hasMessageThat() |
| .contains("Voting on labels disallowed after submit: " + LABEL_NAME); |
| } |
| |
| @Test |
| public void customLabelWithUserPermissionChange() throws Exception { |
| String testLabel = "Test-Label"; |
| configLabel( |
| project, |
| testLabel, |
| LabelFunction.MAX_WITH_BLOCK, |
| 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")); |
| |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add(allowLabel(testLabel).ref("refs/heads/*").group(REGISTERED_USERS).range(-2, +2)) |
| .update(); |
| |
| PushOneCommit.Result result = createChange(); |
| String changeId = result.getChangeId(); |
| |
| // admin votes 'Test-Label +2' and 'Code-Review +2'. |
| ReviewInput input = new ReviewInput(); |
| input.label(testLabel, 2); |
| input.label("Code-Review", 2); |
| revision(result).review(input); |
| |
| // Verify the value of 'Test-Label' is +2. |
| assertLabelStatus(changeId, testLabel); |
| |
| // The change is submittable. |
| assertThat(gApi.changes().id(changeId).get().submittable).isTrue(); |
| |
| // Update admin's permitted range for 'Test-Label' to be -1...+1. |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .remove(labelPermissionKey(testLabel).ref("refs/heads/*").group(REGISTERED_USERS)) |
| .add(allowLabel(testLabel).ref("refs/heads/*").group(REGISTERED_USERS).range(-1, +1)) |
| .update(); |
| |
| // Verify admin doesn't have +2 permission any more. |
| assertPermitted(gApi.changes().id(changeId).get(), testLabel, -1, 0, 1); |
| |
| // Verify the value of 'Test-Label' is still +2. |
| assertLabelStatus(changeId, testLabel); |
| |
| // Verify the change is still submittable. |
| assertThat(gApi.changes().id(changeId).get().submittable).isTrue(); |
| gApi.changes().id(changeId).current().submit(); |
| } |
| |
| @Test |
| public void customLabel_withBranch() throws Exception { |
| saveLabelConfig(LABEL.toBuilder().setRefPatterns(ImmutableList.of("master"))); |
| CachedProjectConfig cfg = |
| projectCache.get(project).orElseThrow(illegalState(project)).getConfig(); |
| assertThat(cfg.getLabelSections().get(LABEL_NAME).getRefPatterns()).contains("master"); |
| } |
| |
| private void assertLabelStatus(String changeId, String testLabel) throws Exception { |
| ChangeInfo changeInfo = getWithLabels(changeId); |
| LabelInfo labelInfo = changeInfo.labels.get(testLabel); |
| assertThat(labelInfo.all).hasSize(1); |
| assertThat(labelInfo.approved).isNotNull(); |
| assertThat(labelInfo.recommended).isNull(); |
| assertThat(labelInfo.disliked).isNull(); |
| assertThat(labelInfo.rejected).isNull(); |
| assertThat(labelInfo.blocking).isNull(); |
| } |
| |
| private void saveLabelConfig(LabelType.Builder... builders) throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| for (LabelType.Builder b : builders) { |
| u.getConfig().upsertLabelType(b.build()); |
| } |
| u.save(); |
| } |
| } |
| |
| private ChangeInfo getWithLabels(PushOneCommit.Result r) throws Exception { |
| return getWithLabels(r.getChangeId()); |
| } |
| |
| private ChangeInfo getWithLabels(String changeId) throws Exception { |
| return get(changeId, LABELS, DETAILED_LABELS, SUBMITTABLE); |
| } |
| } |