blob: d8aa78994138e4c2679218c533541cf9f931d53a [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.acceptance.server.project;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.GitUtil.fetch;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.project.ProjectConfig;
import java.util.Locale;
import java.util.function.Consumer;
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;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.RawParseUtils;
import org.junit.Test;
/**
* Tests validating submit requirements on upload of {@code project.config} to {@code
* refs/meta/config}.
*/
public class SubmitRequirementsValidationIT extends AbstractDaemonTest {
@Test
public void validSubmitRequirementIsAccepted_optionalParametersNotSet() throws Exception {
fetchRefsMetaConfig();
String submitRequirementName = "Code-Review";
updateProjectConfig(
projectConfig ->
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
/* value= */ "label:\"Code-Review=+2\""));
PushResult r = pushRefsMetaConfig();
assertOkStatus(r);
}
@Test
public void validSubmitRequirementIsAccepted_allParametersSet() throws Exception {
fetchRefsMetaConfig();
String submitRequirementName = "Code-Review";
updateProjectConfig(
projectConfig -> {
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_DESCRIPTION,
/* value= */ "foo bar description");
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_APPLICABILITY_EXPRESSION,
/* value= */ "branch:refs/heads/master");
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
/* value= */ "label:\"Code-Review=+2\"");
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_OVERRIDE_EXPRESSION,
/* value= */ "label:\"override=+1\"");
projectConfig.setBoolean(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_OVERRIDE_IN_CHILD_PROJECTS,
/* value= */ false);
});
PushResult r = pushRefsMetaConfig();
assertOkStatus(r);
}
@Test
public void parametersDirectlyInSubmitRequirementsSectionAreRejected() throws Exception {
fetchRefsMetaConfig();
updateProjectConfig(
projectConfig -> {
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ null,
/* name= */ ProjectConfig.KEY_SR_DESCRIPTION,
/* value= */ "foo bar description");
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ null,
/* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
/* value= */ "label:\"Code-Review=+2\"");
});
PushResult r = pushRefsMetaConfig();
assertErrorStatus(
r,
"Invalid project configuration",
String.format(
"project.config: Submit requirements must be defined in submit-requirement.<name>"
+ " subsections. Setting parameters directly in the submit-requirement section is"
+ " not allowed: [%s, %s]",
ProjectConfig.KEY_SR_DESCRIPTION, ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION));
}
@Test
public void unsupportedParameterDirectlyInSubmitRequirementsSectionIsRejected() throws Exception {
fetchRefsMetaConfig();
updateProjectConfig(
projectConfig ->
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ null,
/* name= */ "unknown",
/* value= */ "value"));
PushResult r = pushRefsMetaConfig();
assertErrorStatus(
r,
"Invalid project configuration",
"project.config: Submit requirements must be defined in submit-requirement.<name>"
+ " subsections. Setting parameters directly in the submit-requirement section is"
+ " not allowed: [unknown]");
}
@Test
public void unsupportedParameterForSubmitRequirementIsRejected() throws Exception {
fetchRefsMetaConfig();
String submitRequirementName = "Code-Review";
updateProjectConfig(
projectConfig ->
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ "unknown",
/* value= */ "value"));
PushResult r = pushRefsMetaConfig();
assertErrorStatus(
r,
"Invalid project configuration",
String.format(
"project.config: Unsupported parameters for submit requirement '%s': [unknown]",
submitRequirementName));
}
@Test
public void conflictingSubmitRequirementsAreRejected() throws Exception {
fetchRefsMetaConfig();
String submitRequirementName = "Code-Review";
updateProjectConfig(
projectConfig -> {
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
/* value= */ "label:\"Code-Review=+2\"");
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName.toLowerCase(Locale.US),
/* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
/* value= */ "label:\"Code-Review=+2\"");
});
PushResult r = pushRefsMetaConfig();
assertErrorStatus(
r,
"Invalid project configuration",
String.format(
"project.config: Submit requirement '%s' conflicts with '%s'.",
submitRequirementName.toLowerCase(Locale.US), submitRequirementName));
}
@Test
public void conflictingSubmitRequirementIsRejected() throws Exception {
fetchRefsMetaConfig();
String submitRequirementName = "Code-Review";
updateProjectConfig(
projectConfig ->
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
/* value= */ "label:\"Code-Review=+2\""));
PushResult r = pushRefsMetaConfig();
assertOkStatus(r);
updateProjectConfig(
projectConfig ->
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName.toLowerCase(Locale.US),
/* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
/* value= */ "label:\"Code-Review=+2\""));
r = pushRefsMetaConfig();
assertErrorStatus(
r,
"Invalid project configuration",
String.format(
"project.config: Submit requirement '%s' conflicts with '%s'.",
submitRequirementName.toLowerCase(Locale.US), submitRequirementName));
}
@Test
public void submitRequirementWithoutSubmittabilityExpressionIsRejected() throws Exception {
fetchRefsMetaConfig();
String submitRequirementName = "Code-Review";
updateProjectConfig(
projectConfig ->
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_DESCRIPTION,
/* value= */ "foo bar description"));
PushResult r = pushRefsMetaConfig();
assertErrorStatus(
r,
"Invalid project configuration",
String.format(
"project.config: Setting a submittability expression for submit requirement '%s' is"
+ " required: Missing %s.%s.%s",
submitRequirementName,
ProjectConfig.SUBMIT_REQUIREMENT,
submitRequirementName,
ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION));
}
@Test
public void submitRequirementWithInvalidSubmittabilityExpressionIsRejected() throws Exception {
fetchRefsMetaConfig();
String submitRequirementName = "Code-Review";
String invalidExpression = "invalid_field:invalid_value";
updateProjectConfig(
projectConfig ->
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
/* value= */ invalidExpression));
PushResult r = pushRefsMetaConfig();
assertErrorStatus(
r,
"Invalid project configuration",
String.format(
"project.config: Expression '%s' of submit requirement '%s' (parameter %s.%s.%s) is"
+ " invalid: Unsupported operator %s",
invalidExpression,
submitRequirementName,
ProjectConfig.SUBMIT_REQUIREMENT,
submitRequirementName,
ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
invalidExpression));
}
@Test
public void submitRequirementWithInvalidApplicabilityExpressionIsRejected() throws Exception {
fetchRefsMetaConfig();
String submitRequirementName = "Code-Review";
String invalidExpression = "invalid_field:invalid_value";
updateProjectConfig(
projectConfig -> {
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
/* value= */ "label:\"Code-Review=+2\"");
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_APPLICABILITY_EXPRESSION,
/* value= */ invalidExpression);
});
PushResult r = pushRefsMetaConfig();
assertErrorStatus(
r,
"Invalid project configuration",
String.format(
"project.config: Expression '%s' of submit requirement '%s' (parameter %s.%s.%s) is"
+ " invalid: Unsupported operator %s",
invalidExpression,
submitRequirementName,
ProjectConfig.SUBMIT_REQUIREMENT,
submitRequirementName,
ProjectConfig.KEY_SR_APPLICABILITY_EXPRESSION,
invalidExpression));
}
@Test
public void submitRequirementWithInvalidOverrideExpressionIsRejected() throws Exception {
fetchRefsMetaConfig();
String submitRequirementName = "Code-Review";
String invalidExpression = "invalid_field:invalid_value";
updateProjectConfig(
projectConfig -> {
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_SUBMITTABILITY_EXPRESSION,
/* value= */ "label:\"Code-Review=+2\"");
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_OVERRIDE_EXPRESSION,
/* value= */ invalidExpression);
});
PushResult r = pushRefsMetaConfig();
assertErrorStatus(
r,
"Invalid project configuration",
String.format(
"project.config: Expression '%s' of submit requirement '%s' (parameter %s.%s.%s) is"
+ " invalid: Unsupported operator %s",
invalidExpression,
submitRequirementName,
ProjectConfig.SUBMIT_REQUIREMENT,
submitRequirementName,
ProjectConfig.KEY_SR_OVERRIDE_EXPRESSION,
invalidExpression));
}
@Test
public void submitRequirementWithInvalidAllowOverrideInChildProjectsIsRejected()
throws Exception {
fetchRefsMetaConfig();
String submitRequirementName = "Code-Review";
String invalidValue = "invalid";
updateProjectConfig(
projectConfig ->
projectConfig.setString(
ProjectConfig.SUBMIT_REQUIREMENT,
/* subsection= */ submitRequirementName,
/* name= */ ProjectConfig.KEY_SR_OVERRIDE_IN_CHILD_PROJECTS,
/* value= */ invalidValue));
PushResult r = pushRefsMetaConfig();
assertErrorStatus(
r,
"Invalid project configuration",
String.format(
"project.config: Invalid value %s.%s.%s for submit requirement '%s': %s",
ProjectConfig.SUBMIT_REQUIREMENT,
submitRequirementName,
ProjectConfig.KEY_SR_OVERRIDE_IN_CHILD_PROJECTS,
submitRequirementName,
invalidValue));
}
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 updateProjectConfig(Consumer<Config> configUpdater) throws Exception {
RevCommit head = getHead(testRepo.getRepository(), RefNames.REFS_CONFIG);
Config projectConfig = readProjectConfig(head);
configUpdater.accept(projectConfig);
RevCommit commit =
testRepo.update(
RefNames.REFS_CONFIG,
testRepo
.commit()
.parent(head)
.message("Update project config")
.author(admin.newIdent())
.committer(admin.newIdent())
.add(ProjectConfig.PROJECT_CONFIG, projectConfig.toText()));
testRepo.reset(commit);
}
private Config readProjectConfig(RevCommit commit) throws Exception {
try (TreeWalk tw =
TreeWalk.forPath(
testRepo.getRevWalk().getObjectReader(),
ProjectConfig.PROJECT_CONFIG,
commit.getTree())) {
if (tw == null) {
throw new IllegalStateException(
String.format("%s does not exist", ProjectConfig.PROJECT_CONFIG));
}
}
RevObject blob = testRepo.get(commit.getTree(), ProjectConfig.PROJECT_CONFIG);
byte[] data = testRepo.getRepository().open(blob).getCachedBytes(Integer.MAX_VALUE);
String content = RawParseUtils.decode(data);
Config projectConfig = new Config();
projectConfig.fromText(content);
return projectConfig;
}
public void assertOkStatus(PushResult result) {
RemoteRefUpdate refUpdate = result.getRemoteUpdate(RefNames.REFS_CONFIG);
assertThat(refUpdate).isNotNull();
assertWithMessage(getMessage(result, refUpdate))
.that(refUpdate.getStatus())
.isEqualTo(Status.OK);
}
public void assertErrorStatus(PushResult result, String... expectedMessages) {
RemoteRefUpdate refUpdate = result.getRemoteUpdate(RefNames.REFS_CONFIG);
assertThat(refUpdate).isNotNull();
assertWithMessage(getMessage(result, refUpdate))
.that(refUpdate.getStatus())
.isEqualTo(Status.REJECTED_OTHER_REASON);
for (String expectedMessage : expectedMessages) {
assertThat(result.getMessages()).contains(expectedMessage);
}
}
private String getMessage(PushResult result, RemoteRefUpdate refUpdate) {
StringBuilder b = new StringBuilder();
if (refUpdate.getMessage() != null) {
b.append(refUpdate.getMessage());
b.append("\n");
}
b.append(result.getMessages());
return b.toString();
}
}