blob: b2a6630668d5a1267605a1aa0bab72dff4436a74 [file] [log] [blame]
// Copyright (C) 2020 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 com.google.gerrit.acceptance.GitUtil.fetch;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION;
import static com.google.gerrit.plugins.codeowners.testing.RequiredApprovalSubject.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static com.google.gerrit.truth.OptionalSubject.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.RebaseInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.git.ObjectIds;
import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackendId;
import com.google.gerrit.plugins.codeowners.backend.FallbackCodeOwners;
import com.google.gerrit.plugins.codeowners.backend.PathExpressions;
import com.google.gerrit.plugins.codeowners.backend.config.BackendConfig;
import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.plugins.codeowners.backend.config.GeneralConfig;
import com.google.gerrit.plugins.codeowners.backend.config.OverrideApprovalConfig;
import com.google.gerrit.plugins.codeowners.backend.config.RequiredApproval;
import com.google.gerrit.plugins.codeowners.backend.config.RequiredApprovalConfig;
import com.google.gerrit.plugins.codeowners.backend.config.StatusConfig;
import com.google.gerrit.plugins.codeowners.backend.proto.ProtoBackend;
import com.google.gerrit.plugins.codeowners.common.MergeCommitStrategy;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.eclipse.jgit.util.RawParseUtils;
import org.junit.Before;
import org.junit.Test;
/**
* Tests for {@code com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfigValidator}.
*
* <p>Unit tests for {@code
* com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfigValidator} are contained in
* {@code com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfigValidatorTest}.
*/
public class CodeOwnersPluginConfigValidatorIT extends AbstractCodeOwnersIT {
private CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
@Before
public void setUpCodeOwnersPlugin() throws Exception {
codeOwnersPluginConfiguration =
plugin.getSysInjector().getInstance(CodeOwnersPluginConfiguration.class);
}
@Test
public void cannotUploadNonParseableConfig() throws Exception {
fetchRefsMetaConfig();
setCodeOwnersConfig("INVALID");
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
assertThat(r.getMessages())
.contains(
String.format(
"Invalid config file code-owners.config in project %s in branch %s",
project, RefNames.REFS_CONFIG));
assertThat(r.getMessages()).contains("Invalid line in config file");
}
@Test
public void setDisabledForProject() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setBoolean(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
StatusConfig.KEY_DISABLED,
/* value= */ true);
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
assertThat(codeOwnersPluginConfiguration.getProjectConfig(project).isDisabled()).isTrue();
}
@Test
public void configureDisabledBranch() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
StatusConfig.KEY_DISABLED_BRANCH,
"refs/heads/master");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
assertThat(codeOwnersPluginConfiguration.getProjectConfig(project).isDisabled("master"))
.isTrue();
}
@Test
public void cannotSetInvalidValueForDisabledForProject() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
StatusConfig.KEY_DISABLED,
"INVALID");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
assertThat(r.getMessages())
.contains(
"Disabled value 'INVALID' that is configured in code-owners.config"
+ " (parameter codeOwners.disabled) is invalid.");
}
@Test
public void cannotConfigureInvalidDisabledBranch() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
StatusConfig.KEY_DISABLED_BRANCH,
"^refs/heads/[");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
assertThat(r.getMessages())
.contains(
"Disabled branch '^refs/heads/[' that is configured in code-owners.config (parameter"
+ " codeOwners.disabledBranch) is invalid: Unclosed character class");
}
@Test
public void configureBackendForProject() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
BackendConfig.KEY_BACKEND,
CodeOwnerBackendId.PROTO.getBackendId());
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
assertThat(codeOwnersPluginConfiguration.getProjectConfig(project).getBackend("master"))
.isInstanceOf(ProtoBackend.class);
}
@Test
public void configureBackendForBranch() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
"master",
BackendConfig.KEY_BACKEND,
CodeOwnerBackendId.PROTO.getBackendId());
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
assertThat(codeOwnersPluginConfiguration.getProjectConfig(project).getBackend("master"))
.isInstanceOf(ProtoBackend.class);
}
@Test
public void cannotConfigureInvalidBackendForProject() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
BackendConfig.KEY_BACKEND,
"INVALID");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
assertThat(r.getMessages())
.contains(
"Code owner backend 'INVALID' that is configured in code-owners.config"
+ " (parameter codeOwners.backend) not found.");
}
@Test
public void cannotConfigureInvalidBackendForBranch() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
"master",
BackendConfig.KEY_BACKEND,
"INVALID");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
assertThat(r.getMessages())
.contains(
"Code owner backend 'INVALID' that is configured in code-owners.config"
+ " (parameter codeOwners.master.backend) not found.");
}
@Test
public void configurePathExpressionsForProject() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
BackendConfig.KEY_PATH_EXPRESSIONS,
PathExpressions.GLOB.name());
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
assertThat(codeOwnersPluginConfiguration.getProjectConfig(project).getPathExpressions("master"))
.value()
.isEqualTo(PathExpressions.GLOB);
}
@Test
public void configurePathExpressionsForBranch() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
"master",
BackendConfig.KEY_PATH_EXPRESSIONS,
PathExpressions.GLOB.name());
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
assertThat(codeOwnersPluginConfiguration.getProjectConfig(project).getPathExpressions("master"))
.value()
.isEqualTo(PathExpressions.GLOB);
}
@Test
public void cannotConfigureInvalidPathExpressionsForProject() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
BackendConfig.KEY_PATH_EXPRESSIONS,
"INVALID");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
assertThat(r.getMessages())
.contains(
"Path expressions 'INVALID' that are configured in code-owners.config"
+ " (parameter codeOwners.pathExpressions) not found.");
}
@Test
public void cannotConfigureInvalidPathExpressionsForBranch() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
"master",
BackendConfig.KEY_PATH_EXPRESSIONS,
"INVALID");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
assertThat(r.getMessages())
.contains(
"Path expressions 'INVALID' that are configured in code-owners.config"
+ " (parameter codeOwners.master.pathExpressions) not found.");
}
@Test
public void configureRequiredApproval() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
RequiredApprovalConfig.KEY_REQUIRED_APPROVAL,
"Code-Review+2");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
RequiredApproval requiredApproval =
codeOwnersPluginConfiguration.getProjectConfig(project).getRequiredApproval();
assertThat(requiredApproval).hasLabelNameThat().isEqualTo("Code-Review");
assertThat(requiredApproval).hasValueThat().isEqualTo(2);
}
@Test
public void cannotConfigureInvalidRequiredApproval() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
RequiredApprovalConfig.KEY_REQUIRED_APPROVAL,
"INVALID");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
assertThat(r.getMessages())
.contains(
String.format(
"Required approval 'INVALID' that is configured in code-owners.config (parameter"
+ " codeOwners.%s) is invalid: Invalid format, expected"
+ " '<label-name>+<label-value>'.",
RequiredApprovalConfig.KEY_REQUIRED_APPROVAL));
}
@Test
public void allRequiredApprovalsAreValidated() throws Exception {
fetchRefsMetaConfig();
ImmutableList<String> invalidValues = ImmutableList.of("INVALID", "ALSO_INVALID");
Config cfg = new Config();
cfg.setStringList(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
RequiredApprovalConfig.KEY_REQUIRED_APPROVAL,
invalidValues);
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
for (String invalidValue : invalidValues) {
assertThat(r.getMessages())
.contains(
String.format(
"Required approval '%s' that is configured in code-owners.config (parameter"
+ " codeOwners.%s) is invalid: Invalid format, expected"
+ " '<label-name>+<label-value>'.",
invalidValue, RequiredApprovalConfig.KEY_REQUIRED_APPROVAL));
}
}
@Test
public void configureOverrideApproval() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
"Code-Review+2");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
ImmutableSortedSet<RequiredApproval> overrideApprovals =
codeOwnersPluginConfiguration.getProjectConfig(project).getOverrideApprovals();
assertThat(overrideApprovals).hasSize(1);
assertThat(overrideApprovals).element(0).hasLabelNameThat().isEqualTo("Code-Review");
assertThat(overrideApprovals).element(0).hasValueThat().isEqualTo(2);
}
@Test
public void cannotConfigureInvalidOverrideApproval() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
"INVALID");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
assertThat(r.getMessages())
.contains(
String.format(
"Required approval 'INVALID' that is configured in code-owners.config (parameter"
+ " codeOwners.%s) is invalid: Invalid format, expected"
+ " '<label-name>+<label-value>'.",
OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL));
}
@Test
public void allOverrideApprovalsAreValidated() throws Exception {
fetchRefsMetaConfig();
ImmutableList<String> invalidValues = ImmutableList.of("INVALID", "ALSO_INVALID");
Config cfg = new Config();
cfg.setStringList(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
invalidValues);
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
for (String invalidValue : invalidValues) {
assertThat(r.getMessages())
.contains(
String.format(
"Required approval '%s' that is configured in code-owners.config (parameter"
+ " codeOwners.%s) is invalid: Invalid format, expected"
+ " '<label-name>+<label-value>'.",
invalidValue, OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL));
}
}
@Test
public void defineAndConfigureOverrideLabelInSameCommit() throws Exception {
fetchRefsMetaConfig();
RevCommit head = getHead(testRepo.getRepository(), RefNames.REFS_CONFIG);
RevObject blob = testRepo.get(head.getTree(), "project.config");
byte[] data = testRepo.getRepository().open(blob).getCachedBytes(Integer.MAX_VALUE);
String projectConfigText = RawParseUtils.decode(data);
Config projectConfig = new Config();
projectConfig.fromText(projectConfigText);
String labelName = "Owners-Override";
projectConfig.setString("label", labelName, "function", "NoOp");
projectConfig.setStringList(
"label", labelName, "value", ImmutableList.of("0 Not Override", "+1 Override"));
Config codeOwnersConfig = new Config();
codeOwnersConfig.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL,
"Owners-Override+1");
RevCommit commit =
testRepo.update(
RefNames.REFS_CONFIG,
testRepo
.commit()
.parent(head)
.message("Add test code owner config")
.author(admin.newIdent())
.committer(admin.newIdent())
.add("code-owners.config", codeOwnersConfig.toText())
.add("project.config", projectConfig.toText()));
testRepo.reset(commit);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
ImmutableSortedSet<RequiredApproval> overrideApprovals =
codeOwnersPluginConfiguration.getProjectConfig(project).getOverrideApprovals();
assertThat(overrideApprovals).hasSize(1);
assertThat(overrideApprovals).element(0).hasLabelNameThat().isEqualTo("Owners-Override");
assertThat(overrideApprovals).element(0).hasValueThat().isEqualTo(1);
}
@Test
public void configureMergeCommitStrategy() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setEnum(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
GeneralConfig.KEY_MERGE_COMMIT_STRATEGY,
MergeCommitStrategy.ALL_CHANGED_FILES);
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
assertThat(codeOwnersPluginConfiguration.getProjectConfig(project).getMergeCommitStrategy())
.isEqualTo(MergeCommitStrategy.ALL_CHANGED_FILES);
}
@Test
public void cannotSetInvalidMergeCommitStrategy() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
GeneralConfig.KEY_MERGE_COMMIT_STRATEGY,
"INVALID");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
assertThat(r.getMessages())
.contains(
"Merge commit strategy 'INVALID' that is configured in code-owners.config"
+ " (parameter codeOwners.mergeCommitStrategy) is invalid.");
}
@Test
public void configureFallbackCodeOwners() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setEnum(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
GeneralConfig.KEY_FALLBACK_CODE_OWNERS,
FallbackCodeOwners.ALL_USERS);
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
assertThat(codeOwnersPluginConfiguration.getProjectConfig(project).getFallbackCodeOwners())
.isEqualTo(FallbackCodeOwners.ALL_USERS);
}
@Test
public void cannotSetInvalidFallbackCodeOwners() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
GeneralConfig.KEY_FALLBACK_CODE_OWNERS,
"INVALID");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
assertThat(r.getMessages())
.contains(
"The value for fallback code owners 'INVALID' that is configured in code-owners.config"
+ " (parameter codeOwners.fallbackCodeOwners) is invalid.");
}
@Test
public void configureMaxPathsInChangeMessages() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setInt(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
GeneralConfig.KEY_MAX_PATHS_IN_CHANGE_MESSAGES,
50);
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
assertThat(
codeOwnersPluginConfiguration.getProjectConfig(project).getMaxPathsInChangeMessages())
.isEqualTo(50);
}
@Test
public void configureEnableAsyncMessageOnAddReviewer() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setBoolean(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
GeneralConfig.KEY_ENABLE_ASYNC_MESSAGE_ON_ADD_REVIEWER,
false);
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()).isEqualTo(Status.OK);
assertThat(
codeOwnersPluginConfiguration
.getProjectConfig(project)
.enableAsyncMessageOnAddReviewer())
.isFalse();
}
@Test
public void cannotSetInvalidMaxPathsInChangeMessages() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
GeneralConfig.KEY_MAX_PATHS_IN_CHANGE_MESSAGES,
"INVALID");
setCodeOwnersConfig(cfg);
PushResult r = pushRefsMetaConfig();
assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
assertThat(r.getMessages())
.contains(
"The value for max paths in change messages 'INVALID' that is configured in"
+ " code-owners.config (parameter codeOwners.maxPathsInChangeMessages) is invalid.");
}
@Test
@GerritConfig(name = "plugin.code-owners.disabledBranch", value = "refs/meta/config")
public void validationDoesntFailOnRebaseChange_unrelatedChange() throws Exception {
// Create two changes for refs/meta/config both with the same parent.
GitUtil.fetch(testRepo, RefNames.REFS_CONFIG + ":config");
testRepo.reset("config");
PushOneCommit push =
pushFactory.create(admin.newIdent(), testRepo, "Change 1", "a.txt", "content");
PushOneCommit.Result r = push.to("refs/for/" + RefNames.REFS_CONFIG);
r.assertOkStatus();
String changeId1 = r.getChangeId();
testRepo.reset("config");
push = pushFactory.create(admin.newIdent(), testRepo, "Change 2", "b.txt", "content");
r = push.to("refs/for/" + RefNames.REFS_CONFIG);
r.assertOkStatus();
String changeId2 = r.getChangeId();
// Approve and submit the first change
approve(changeId1);
gApi.changes().id(changeId1).current().submit();
// Rebase the second change, throws an exception if the code owner plugin config validation
// fails.
gApi.changes().id(changeId2).rebase();
// Second change should have 2 patch sets now.
ChangeInfo changeInfo = gApi.changes().id(changeId2).get(CURRENT_REVISION);
assertThat(changeInfo.revisions.get(changeInfo.currentRevision)._number).isEqualTo(2);
}
@Test
@GerritConfig(name = "plugin.code-owners.disabledBranch", value = "refs/meta/config")
public void validationDoesntFailOnRebaseChange_changeThatUpdatesTheCodeOwnersConfig()
throws Exception {
// Create two changes for refs/meta/config both with the same parent.
GitUtil.fetch(testRepo, RefNames.REFS_CONFIG + ":config");
testRepo.reset("config");
PushOneCommit push =
pushFactory.create(admin.newIdent(), testRepo, "Change 1", "a,txt", "content");
PushOneCommit.Result r = push.to("refs/for/" + RefNames.REFS_CONFIG);
r.assertOkStatus();
String changeId1 = r.getChangeId();
testRepo.reset("config");
Config cfg = new Config();
cfg.setEnum(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
GeneralConfig.KEY_FALLBACK_CODE_OWNERS,
FallbackCodeOwners.ALL_USERS);
push =
pushFactory.create(
admin.newIdent(), testRepo, "Change 2", "code-owners.config", cfg.toText());
r = push.to("refs/for/" + RefNames.REFS_CONFIG);
r.assertOkStatus();
String changeId2 = r.getChangeId();
// Approve and submit the first change
approve(changeId1);
gApi.changes().id(changeId1).current().submit();
// Rebase the second change, throws an exception if the code owner plugin config validation
// fails.
gApi.changes().id(changeId2).rebase();
// Second change should have 2 patch sets now.
ChangeInfo changeInfo = gApi.changes().id(changeId2).get(CURRENT_REVISION);
assertThat(changeInfo.revisions.get(changeInfo.currentRevision)._number).isEqualTo(2);
}
@Test
@GerritConfig(name = "plugin.code-owners.disabledBranch", value = "refs/meta/config")
public void validationFailsOnRebaseChange_changeThatCreatesInvalidCodeOwnerConfig()
throws Exception {
// Create two changes for refs/meta/config both with the same parent.
GitUtil.fetch(testRepo, RefNames.REFS_CONFIG + ":config");
testRepo.reset("config");
Config cfg = new Config();
cfg.setEnum(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
GeneralConfig.KEY_FALLBACK_CODE_OWNERS,
FallbackCodeOwners.NONE);
PushOneCommit push =
pushFactory.create(
admin.newIdent(), testRepo, "Change 1", "code-owners.config", cfg.toText());
PushOneCommit.Result r = push.to("refs/for/" + RefNames.REFS_CONFIG);
r.assertOkStatus();
String changeId1 = r.getChangeId();
testRepo.reset("config");
cfg = new Config();
cfg.setEnum(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
GeneralConfig.KEY_FALLBACK_CODE_OWNERS,
FallbackCodeOwners.ALL_USERS);
push =
pushFactory.create(
admin.newIdent(), testRepo, "Change 2", "code-owners.config", cfg.toText());
r = push.to("refs/for/" + RefNames.REFS_CONFIG);
r.assertOkStatus();
String changeId2 = r.getChangeId();
// Approve and submit the first change
approve(changeId1);
gApi.changes().id(changeId1).current().submit();
// Rebase the second change with allowing conflicts. This results in a code-owners.config that
// contains conflict markers and hence is rejected as invalid.
RebaseInput rebaseInput = new RebaseInput();
rebaseInput.allowConflicts = true;
ResourceConflictException exception =
assertThrows(
ResourceConflictException.class,
() -> gApi.changes().id(changeId2).rebase(rebaseInput));
assertThat(exception)
.hasMessageThat()
.contains(
String.format(
"Invalid config file code-owners.config in project %s in branch %s",
project, RefNames.REFS_CONFIG));
}
@Test
public void validatorForProjectConfigIsInvokedBeforeCodeOwnersConfigValidator() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setEnum(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
GeneralConfig.KEY_FALLBACK_CODE_OWNERS,
FallbackCodeOwners.ALL_USERS);
PushOneCommit push =
pushFactory.create(
admin.newIdent(),
testRepo,
"Change 1",
ImmutableMap.of("code-owners.config", cfg.toText(), "project.config", "INVALID"));
PushOneCommit.Result r = push.to("refs/for/" + RefNames.REFS_CONFIG);
// The invalid project.config is rejected by the project config validator in Gerrit core before
// CodeOwnersPluginConfigValidator is invoked (if CodeOwnersPluginConfigValidator would be
// invoked first it would fail with a ConfigInvalidException and the message would be "internal
// error").
r.assertMessage(
String.format(
"commit %s: invalid project configuration",
ObjectIds.abbreviateName(r.getCommit(), testRepo.getRevWalk().getObjectReader())));
}
@Test
public void warnIfCodeOwnersConfigurationIsDoneInProjectConfig_configWithPluginCodeOwnersSection()
throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setEnum(
/* section= */ "plugin",
/* subsection= */ "code-owners",
GeneralConfig.KEY_FALLBACK_CODE_OWNERS,
FallbackCodeOwners.ALL_USERS);
PushOneCommit push =
pushFactory.create(admin.newIdent(), testRepo, "Change", "project.config", cfg.toText());
PushOneCommit.Result r = push.to("refs/for/" + RefNames.REFS_CONFIG);
r.assertOkStatus();
r.assertMessage(
String.format(
"hint: commit %s: Section 'plugin.code-owners' in project.config is ignored and has no"
+ " effect. The configuration for the code-owners plugin must be done in"
+ " code-owners.config.",
ObjectIds.abbreviateName(r.getCommit(), testRepo.getRevWalk().getObjectReader())));
}
@Test
public void warnIfCodeOwnersConfigurationIsDoneInProjectConfig_configWithCodeOwnersSection()
throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setEnum(
CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS,
/* subsection= */ null,
GeneralConfig.KEY_FALLBACK_CODE_OWNERS,
FallbackCodeOwners.ALL_USERS);
PushOneCommit push =
pushFactory.create(admin.newIdent(), testRepo, "Change", "project.config", cfg.toText());
PushOneCommit.Result r = push.to("refs/for/" + RefNames.REFS_CONFIG);
r.assertOkStatus();
r.assertMessage(
String.format(
"hint: commit %s: Section 'codeOwners' in project.config is ignored and has no effect."
+ " The configuration for the code-owners plugin must be done in code-owners.config.",
ObjectIds.abbreviateName(r.getCommit(), testRepo.getRevWalk().getObjectReader())));
}
@Test
public void noWarningIfProjectConfigIsUpdatedWithoutCodeOwnerSettings() throws Exception {
fetchRefsMetaConfig();
Config cfg = new Config();
cfg.setString(
/* section= */ "foo", /* subsection= */ null, /* name= */ "bar", /* value= */ "baz");
PushOneCommit push =
pushFactory.create(admin.newIdent(), testRepo, "Change", "project.config", cfg.toText());
PushOneCommit.Result r = push.to("refs/for/" + RefNames.REFS_CONFIG);
r.assertOkStatus();
r.assertNotMessage(
"The configuration for the code-owners plugin must be done in code-owners.config.");
}
private void fetchRefsMetaConfig() throws Exception {
fetch(testRepo, RefNames.REFS_CONFIG + ":" + RefNames.REFS_CONFIG);
testRepo.reset(RefNames.REFS_CONFIG);
}
private PushResult pushRefsMetaConfig() throws Exception {
return pushHead(testRepo, RefNames.REFS_CONFIG);
}
private void setCodeOwnersConfig(Config codeOwnersConfig) throws Exception {
setCodeOwnersConfig(codeOwnersConfig.toText());
}
private void setCodeOwnersConfig(String codeOwnersConfig) throws Exception {
RevCommit head = getHead(testRepo.getRepository(), RefNames.REFS_CONFIG);
RevCommit commit =
testRepo.update(
RefNames.REFS_CONFIG,
testRepo
.commit()
.parent(head)
.message("Add test code owner config")
.author(admin.newIdent())
.committer(admin.newIdent())
.add("code-owners.config", codeOwnersConfig));
testRepo.reset(commit);
}
}