blob: bfc028312d4f9a45a59c22199e11a8549de6b3e2 [file] [log] [blame]
// Copyright (C) 2021 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.plugins.codeowners.acceptance.api;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.DeleteBranchesInput;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT;
import com.google.gerrit.server.util.AccountTemplateUtil;
import java.time.Duration;
import java.util.Collection;
import java.util.concurrent.Callable;
import org.junit.Test;
/**
* Acceptance test for {@code com.google.gerrit.plugins.codeowners.backend.CodeOwnersOnAddReviewer}.
*
* <p>For tests the change message that is posted when a code owner is added as a reviewer, is added
* synchronously by default (see {@link AbstractCodeOwnersIT #defaultConfig()}). Tests that want to
* verify the asynchronous posting of this change message need to set {@code
* plugin.code-owners.enableAsyncMessageOnAddReviewer=true} in {@code gerrit.config} explicitly (by
* using the {@link GerritConfig} annotation).
*/
public class CodeOwnersOnAddReviewerIT extends AbstractCodeOwnersIT {
@Test
@GerritConfig(name = "plugin.code-owners.disabled", value = "true")
public void noChangeMessageAddedIfCodeOwnersFuctionalityIsDisabled() throws Exception {
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/foo/")
.addCodeOwnerEmail(user.email())
.create();
String changeId = createChange("Test Change", "foo/bar.baz", "file content").getChangeId();
gApi.changes().id(changeId).addReviewer(user.email());
Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
assertThat(Iterables.getLast(messages).message).isEqualTo("Uploaded patch set 1.");
}
@Test
public void noChangeMessageAddedIfReviewerIsNotACodeOwner() throws Exception {
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/foo/")
.addCodeOwnerEmail(admin.email())
.create();
String changeId = createChange("Test Change", "foo/bar.baz", "file content").getChangeId();
gApi.changes().id(changeId).addReviewer(user.email());
Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
assertThat(Iterables.getLast(messages).message).isEqualTo("Uploaded patch set 1.");
}
@Test
public void changeMessageListsOwnedPaths() throws Exception {
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/foo/")
.addCodeOwnerEmail(user.email())
.create();
String path = "foo/bar.baz";
String changeId = createChange("Test Change", path, "file content").getChangeId();
gApi.changes().id(changeId).addReviewer(user.email());
Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
assertThat(Iterables.getLast(messages).message)
.isEqualTo(
String.format(
"%s, who was added as reviewer owns the following files:\n* %s\n",
AccountTemplateUtil.getAccountTemplate(user.id()), path));
}
@Test
public void changeMessageListsOnlyOwnedPaths() throws Exception {
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/foo/")
.addCodeOwnerEmail(user.email())
.create();
String path1 = "foo/bar.baz";
String path2 = "foo/baz.bar";
String changeId =
createChange(
"Test Change",
ImmutableMap.of(
path1,
"file content",
path2,
"file content",
"bar/foo.baz",
"file content",
"bar/baz.foo",
"file content"))
.getChangeId();
gApi.changes().id(changeId).addReviewer(user.email());
Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
assertThat(Iterables.getLast(messages).message)
.isEqualTo(
String.format(
"%s, who was added as reviewer owns the following files:\n* %s\n* %s\n",
AccountTemplateUtil.getAccountTemplate(user.id()), path1, path2));
}
@Test
public void noChangeMessageAddedIfSameCodeOwnerIsAddedAsReviewerAgain() throws Exception {
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/foo/")
.addCodeOwnerEmail(user.email())
.create();
String path = "foo/bar.baz";
String changeId = createChange("Test Change", path, "file content").getChangeId();
gApi.changes().id(changeId).addReviewer(user.email());
int messageCount = gApi.changes().id(changeId).get().messages.size();
// Add the same code owner as reviewer again.
gApi.changes().id(changeId).addReviewer(user.email());
// Check that no new change message was added.
assertThat(gApi.changes().id(changeId).get().messages.size()).isEqualTo(messageCount);
}
@Test
@GerritConfig(name = "plugin.code-owners.maxPathsInChangeMessages", value = "4")
public void pathsInChangeMessageAreLimited_limitNotReached() throws Exception {
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(user.email())
.create();
String path1 = "foo/bar.baz";
String path2 = "foo/baz.bar";
String path3 = "bar/foo.baz";
String path4 = "bar/baz.foo";
String changeId =
createChange(
"Test Change",
ImmutableMap.of(
path1,
"file content",
path2,
"file content",
path3,
"file content",
path4,
"file content"))
.getChangeId();
gApi.changes().id(changeId).addReviewer(user.email());
Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
assertThat(Iterables.getLast(messages).message)
.isEqualTo(
String.format(
"%s, who was added as reviewer owns the following files:\n"
+ "* %s\n"
+ "* %s\n"
+ "* %s\n"
+ "* %s\n",
AccountTemplateUtil.getAccountTemplate(user.id()), path4, path3, path1, path2));
}
@Test
@GerritConfig(name = "plugin.code-owners.maxPathsInChangeMessages", value = "3")
public void pathsInChangeMessageAreLimited_limitReached() throws Exception {
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(user.email())
.create();
String path1 = "foo/bar.baz";
String path2 = "foo/baz.bar";
String path3 = "bar/foo.baz";
String path4 = "bar/baz.foo";
String path5 = "baz/foo.bar";
String changeId =
createChange(
"Test Change",
ImmutableMap.of(
path1,
"file content",
path2,
"file content",
path3,
"file content",
path4,
"file content",
path5,
"file content"))
.getChangeId();
gApi.changes().id(changeId).addReviewer(user.email());
Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
assertThat(Iterables.getLast(messages).message)
.isEqualTo(
String.format(
"%s, who was added as reviewer owns the following files:\n"
+ "* %s\n"
+ "* %s\n"
+ "* %s\n"
+ "(more files)\n",
AccountTemplateUtil.getAccountTemplate(user.id()), path4, path3, path5));
}
@Test
@GerritConfig(name = "plugin.code-owners.maxPathsInChangeMessages", value = "0")
public void pathsInChangeMessagesDisabled() throws Exception {
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(admin.email())
.create();
String changeId =
createChange(
"Test Change",
ImmutableMap.of(
"foo/bar.baz",
"file content",
"foo/baz.bar",
"file content",
"bar/foo.baz",
"file content",
"bar/baz.foo",
"file content"))
.getChangeId();
gApi.changes().id(changeId).addReviewer(user.email());
Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
assertThat(Iterables.getLast(messages).message).isEqualTo("Uploaded patch set 1.");
}
@Test
public void noChangeMessageAddedIfDestinationBranchWasDeleted() throws Exception {
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/")
.addCodeOwnerEmail(user.email())
.create();
String branchName = "tempBranch";
createBranch(BranchNameKey.create(project, branchName));
String changeId = createChange("refs/for/" + branchName).getChangeId();
DeleteBranchesInput input = new DeleteBranchesInput();
input.branches = ImmutableList.of(branchName);
gApi.projects().name(project.get()).deleteBranches(input);
gApi.changes().id(changeId).addReviewer(user.email());
// If the destination branch of the change no longer exits, the owned paths cannot be computed.
// Hence no change message is added in this case.
Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
assertThat(Iterables.getLast(messages).message).isEqualTo("Uploaded patch set 1.");
}
@Test
public void changeMessageListsOwnedPathsIfReviewerIsAddedViaPostReview() throws Exception {
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/foo/")
.addCodeOwnerEmail(user.email())
.create();
String path = "foo/bar.baz";
String changeId = createChange("Test Change", path, "file content").getChangeId();
// Add reviewer via PostReview.
gApi.changes().id(changeId).current().review(ReviewInput.create().reviewer(user.email()));
Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
assertThat(Iterables.getLast(messages).message)
.isEqualTo(
String.format(
"%s, who was added as reviewer owns the following files:\n* %s\n",
AccountTemplateUtil.getAccountTemplate(user.id()), path));
}
@Test
public void multipleCodeOwnerAddedAsReviewersAtTheSameTime() throws Exception {
TestAccount user2 = accountCreator.user2();
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/foo/")
.addCodeOwnerEmail(user.email())
.addCodeOwnerEmail(user2.email())
.create();
String path = "foo/bar.baz";
String changeId = createChange("Test Change", path, "file content").getChangeId();
// Add code owners 'user' and 'user2' as reviewers.
gApi.changes()
.id(changeId)
.current()
.review(ReviewInput.create().reviewer(user.email()).reviewer(user2.email()));
// We expect that 1 change message is added that lists the path owned for each of the new
// reviewers ('user' and 'user2').
Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
assertThat(Iterables.getLast(messages).message)
.isEqualTo(
String.format(
"%s, who was added as reviewer owns the following files:\n* %s\n\n"
+ "%s, who was added as reviewer owns the following files:\n* %s\n",
AccountTemplateUtil.getAccountTemplate(user.id()),
path,
AccountTemplateUtil.getAccountTemplate(user2.id()),
path));
}
@Test
public void reviewerAndCodeOwnerApprovalAddedAtTheSameTime() throws Exception {
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/foo/")
.addCodeOwnerEmail(admin.email())
.addCodeOwnerEmail(user.email())
.create();
String path = "foo/bar.baz";
String changeId = createChange("Test Change", path, "file content").getChangeId();
// 'admin' grants a code owner approval (Code-Review+1) and adds 'user' as reviewer.
gApi.changes().id(changeId).current().review(ReviewInput.recommend().reviewer(user.email()));
// We expect that 2 changes messages are added:
// 1. change message listing the paths that were approved by voting Code-Review+1
// 2. change message listing the paths owned by the new reviewer
Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
assertThat(Iterables.get(messages, messages.size() - 2).message)
.isEqualTo(
String.format(
"Patch Set 1: Code-Review+1\n\n"
+ "By voting Code-Review+1 the following files are now code-owner approved by"
+ " %s:\n"
+ "* %s\n",
AccountTemplateUtil.getAccountTemplate(admin.id()), path));
assertThat(Iterables.getLast(messages).message)
.isEqualTo(
String.format(
"%s, who was added as reviewer owns the following files:\n* %s\n",
AccountTemplateUtil.getAccountTemplate(user.id()), path));
}
@Test
@GerritConfig(name = "plugin.code-owners.enableAsyncMessageOnAddReviewer", value = "true")
public void asyncChangeMessageThatListsOwnedPaths() throws Exception {
codeOwnerConfigOperations
.newCodeOwnerConfig()
.project(project)
.branch("master")
.folderPath("/foo/")
.addCodeOwnerEmail(user.email())
.create();
String path = "foo/bar.baz";
String changeId = createChange("Test Change", path, "file content").getChangeId();
gApi.changes().id(changeId).addReviewer(user.email());
assertAsyncChangeMessage(
changeId,
String.format(
"%s, who was added as reviewer owns the following files:\n* %s\n",
AccountTemplateUtil.getAccountTemplate(user.id()), path));
}
private void assertAsyncChangeMessage(String changeId, String expectedChangeMessage)
throws Exception {
assertAsync(
() -> {
Collection<ChangeMessageInfo> messages = gApi.changes().id(changeId).get().messages;
assertThat(Iterables.getLast(messages).message).isEqualTo(expectedChangeMessage);
return null;
});
}
private <T> T assertAsync(Callable<T> assertion) throws Exception {
return RetryerBuilder.<T>newBuilder()
.retryIfException(t -> true)
.withStopStrategy(
StopStrategies.stopAfterDelay(Duration.ofSeconds(1).toMillis(), MILLISECONDS))
.build()
.call(() -> assertion.call());
}
}