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(); + } +}