Return code-owners~skip-validation option in GetValidationOptions response
Return the code-owners~skip-validation option only if the code owners
functionality is enabled for the change and if the user is allowed to
skip the validation.
Bug: Google b/279897514
Change-Id: Ic50cc6ec1b3ab50649c3966ce8e865f438b80983
Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/java/com/google/gerrit/plugins/codeowners/validation/SkipCodeOwnerConfigValidationPushOption.java b/java/com/google/gerrit/plugins/codeowners/validation/SkipCodeOwnerConfigValidationPushOption.java
index 2443ef5..b11bd61 100644
--- a/java/com/google/gerrit/plugins/codeowners/validation/SkipCodeOwnerConfigValidationPushOption.java
+++ b/java/com/google/gerrit/plugins/codeowners/validation/SkipCodeOwnerConfigValidationPushOption.java
@@ -18,8 +18,10 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
+import com.google.gerrit.entities.Change;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
import com.google.gerrit.server.git.receive.PluginPushOption;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -30,21 +32,23 @@
@Singleton
public class SkipCodeOwnerConfigValidationPushOption implements PluginPushOption {
public static final String NAME = "skip-validation";
-
- private static final String DESCRIPTION = "skips the code owner config validation";
+ public static final String DESCRIPTION = "skips the code owner config validation";
private final String pluginName;
private final PermissionBackend permissionBackend;
private final SkipCodeOwnerConfigValidationCapability skipCodeOwnerConfigValidationCapability;
+ private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration;
@Inject
SkipCodeOwnerConfigValidationPushOption(
@PluginName String pluginName,
PermissionBackend permissionBackend,
- SkipCodeOwnerConfigValidationCapability skipCodeOwnerConfigValidationCapability) {
+ SkipCodeOwnerConfigValidationCapability skipCodeOwnerConfigValidationCapability,
+ CodeOwnersPluginConfiguration codeOwnersPluginConfiguration) {
this.pluginName = pluginName;
this.permissionBackend = permissionBackend;
this.skipCodeOwnerConfigValidationCapability = skipCodeOwnerConfigValidationCapability;
+ this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration;
}
@Override
@@ -57,6 +61,14 @@
return DESCRIPTION;
}
+ @Override
+ public boolean isOptionEnabled(Change change) {
+ return !codeOwnersPluginConfiguration
+ .getProjectConfig(change.getProject())
+ .isDisabled(change.getDest().branch())
+ && canSkipCodeOwnerConfigValidation();
+ }
+
/**
* Whether the code owner config validation should be skipped.
*
@@ -86,7 +98,7 @@
String value = values.get(0);
if (Boolean.parseBoolean(value) || value.isEmpty()) {
- canSkipCodeOwnerConfigValidation();
+ checkCanSkipCodeOwnerConfigValidation();
return true;
}
@@ -98,7 +110,7 @@
throw new InvalidValueException(values);
}
- private void canSkipCodeOwnerConfigValidation() throws AuthException {
+ private void checkCanSkipCodeOwnerConfigValidation() throws AuthException {
try {
permissionBackend
.currentUser()
@@ -112,6 +124,20 @@
}
}
+ private boolean canSkipCodeOwnerConfigValidation() {
+ try {
+ return permissionBackend
+ .currentUser()
+ .test(skipCodeOwnerConfigValidationCapability.getPermission());
+ } catch (PermissionBackendException e) {
+ throw newInternalServerError(
+ String.format(
+ "Failed to check %s~%s capability",
+ pluginName, SkipCodeOwnerConfigValidationCapability.ID),
+ e);
+ }
+ }
+
public class InvalidValueException extends Exception {
private static final long serialVersionUID = 1L;
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/SkipCodeOwnerConfigValidationPushOptionIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/SkipCodeOwnerConfigValidationPushOptionIT.java
new file mode 100644
index 0000000..3abee10
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/SkipCodeOwnerConfigValidationPushOptionIT.java
@@ -0,0 +1,115 @@
+// Copyright (C) 2025 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.testsuite.project.TestProjectUpdate.allowCapability;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.extensions.common.ValidationOptionInfo;
+import com.google.gerrit.extensions.common.ValidationOptionInfos;
+import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT;
+import com.google.gerrit.plugins.codeowners.validation.SkipCodeOwnerConfigValidationCapability;
+import com.google.gerrit.plugins.codeowners.validation.SkipCodeOwnerConfigValidationPushOption;
+import com.google.inject.Inject;
+import org.junit.Test;
+
+public class SkipCodeOwnerConfigValidationPushOptionIT extends AbstractCodeOwnersIT {
+ @Inject private ChangeOperations changeOperations;
+ @Inject private ProjectOperations projectOperations;
+ @Inject private RequestScopeOperations requestScopeOperations;
+
+ @Test
+ public void getCodeOwnersSkipOptionAsAdmin() throws Exception {
+ // Use the admin user that has the SkipCodeOwnerConfigValidationCapability global capability
+ // implicitly assigned.
+ requestScopeOperations.setApiUser(admin.id());
+
+ Change.Id changeId = changeOperations.newChange().project(project).create();
+
+ ValidationOptionInfos validationOptionsInfos =
+ gApi.changes().id(project.get(), changeId.get()).getValidationOptions();
+ assertThat(validationOptionsInfos.validationOptions)
+ .isEqualTo(
+ ImmutableList.of(
+ new ValidationOptionInfo(
+ "code-owners~" + SkipCodeOwnerConfigValidationPushOption.NAME,
+ SkipCodeOwnerConfigValidationPushOption.DESCRIPTION)));
+ }
+
+ @Test
+ public void getCodeOwnersSkipOptionAsUserWithTheSkipCodeOwnerConfigValidationCapability()
+ throws Exception {
+ projectOperations
+ .allProjectsForUpdate()
+ .add(
+ allowCapability("code-owners-" + SkipCodeOwnerConfigValidationCapability.ID)
+ .group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+
+ Change.Id changeId = changeOperations.newChange().project(project).create();
+
+ ValidationOptionInfos validationOptionsInfos =
+ gApi.changes().id(project.get(), changeId.get()).getValidationOptions();
+ assertThat(validationOptionsInfos.validationOptions)
+ .isEqualTo(
+ ImmutableList.of(
+ new ValidationOptionInfo(
+ "code-owners~" + SkipCodeOwnerConfigValidationPushOption.NAME,
+ SkipCodeOwnerConfigValidationPushOption.DESCRIPTION)));
+ }
+
+ @GerritConfig(name = "plugin.code-owners.disabledBranch", value = "refs/heads/master")
+ @Test
+ public void codeOwnersSkipOptionIsOmittedIfUserCannotSkipTheCodeOwnersValidation()
+ throws Exception {
+ // Use non-admin user that doesn't have the SkipCodeOwnerConfigValidationCapability global
+ // capability.
+ requestScopeOperations.setApiUser(user.id());
+
+ Change.Id changeId = changeOperations.newChange().project(project).branch("master").create();
+ ValidationOptionInfos validationOptionsInfos =
+ gApi.changes().id(project.get(), changeId.get()).getValidationOptions();
+ assertThat(validationOptionsInfos.validationOptions).isEmpty();
+ }
+
+ @GerritConfig(name = "plugin.code-owners.disabled", value = "true")
+ @Test
+ public void codeOwnersSkipOptionIsOmittedIfCodeOwnersFunctionalityIsDisabledForProject()
+ throws Exception {
+ Change.Id changeId = changeOperations.newChange().project(project).create();
+ ValidationOptionInfos validationOptionsInfos =
+ gApi.changes().id(project.get(), changeId.get()).getValidationOptions();
+ assertThat(validationOptionsInfos.validationOptions).isEmpty();
+ }
+
+ @GerritConfig(name = "plugin.code-owners.disabledBranch", value = "refs/heads/master")
+ @Test
+ public void codeOwnersSkipOptionIsOmittedIfCodeOwnersFunctionalityIsDisabledForBranch()
+ throws Exception {
+ Change.Id changeId = changeOperations.newChange().project(project).branch("master").create();
+ ValidationOptionInfos validationOptionsInfos =
+ gApi.changes().id(project.get(), changeId.get()).getValidationOptions();
+ assertThat(validationOptionsInfos.validationOptions).isEmpty();
+ }
+}