| // Copyright (C) 2022 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.backend; |
| |
| import static com.google.common.collect.ImmutableSet.toImmutableSet; |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth8.assertThat; |
| |
| import com.google.common.collect.ImmutableSet; |
| import com.google.gerrit.acceptance.TestAccount; |
| import com.google.gerrit.acceptance.config.GerritConfig; |
| import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; |
| import com.google.gerrit.entities.Account; |
| import com.google.gerrit.entities.Change; |
| import com.google.gerrit.entities.PatchSetApproval; |
| import com.google.gerrit.entities.Project; |
| import com.google.gerrit.extensions.api.changes.ChangeApi; |
| import com.google.gerrit.extensions.api.changes.ReviewInput; |
| import com.google.gerrit.extensions.common.LabelDefinitionInput; |
| import com.google.gerrit.extensions.restapi.RestApiException; |
| import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest; |
| import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration; |
| import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginProjectConfigSnapshot; |
| import com.google.gerrit.server.notedb.ChangeNotes; |
| import com.google.inject.Inject; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| /** Tests for {@link CodeOwnerApprovalCheckInput}. */ |
| public class CodeOwnerApprovalCheckInputTest extends AbstractCodeOwnersTest { |
| @Inject private ChangeNotes.Factory changeNotesFactory; |
| @Inject private RequestScopeOperations requestScopeOperations; |
| |
| private CodeOwnerApprovalCheckInput.Loader.Factory inputLoaderFactory; |
| private CodeOwnerResolver codeOwnerResolver; |
| private CodeOwnersPluginProjectConfigSnapshot codeOwnersConfig; |
| private TestAccount user2; |
| private Change.Id changeId; |
| private Account.Id changeOwner; |
| |
| @Before |
| public void setUpCodeOwnersPlugin() throws Exception { |
| inputLoaderFactory = |
| plugin.getSysInjector().getInstance(CodeOwnerApprovalCheckInput.Loader.Factory.class); |
| codeOwnerResolver = plugin.getSysInjector().getInstance(CodeOwnerResolver.class); |
| CodeOwnersPluginConfiguration codeOwnersPluginConfiguration = |
| plugin.getSysInjector().getInstance(CodeOwnersPluginConfiguration.class); |
| codeOwnersConfig = codeOwnersPluginConfiguration.getProjectConfig(project); |
| } |
| |
| @Before |
| public void setUp() throws Exception { |
| user2 = accountCreator.user2(); |
| changeId = createChange().getChange().getId(); |
| changeOwner = admin.id(); |
| } |
| |
| @Test |
| public void noReviewers() { |
| assertThat(loadInput().reviewers()).isEmpty(); |
| } |
| |
| @Test |
| public void withReviewers() throws Exception { |
| changeApi().addReviewer(user.email()); |
| changeApi().addReviewer(user2.email()); |
| |
| // make the change owner a reviewer: |
| // the change owner cannot be added as a reviewer, but the change owner becomes a reviewer when |
| // they vote on the change |
| requestScopeOperations.setApiUser(changeOwner); |
| recommend(changeId.toString()); |
| |
| assertThat(loadInput().reviewers()).containsExactly(user.id(), user2.id(), changeOwner); |
| } |
| |
| @Test |
| public void withReviewers_selfApprovalsIgnored() throws Exception { |
| disableSelfCodeReviewApprovals(); |
| |
| changeApi().addReviewer(user.email()); |
| changeApi().addReviewer(user2.email()); |
| |
| // make the change owner a reviewer: |
| // the change owner cannot be added as a reviewer, but the change owner becomes a reviewer when |
| // they vote on the change |
| requestScopeOperations.setApiUser(changeOwner); |
| recommend(changeId.toString()); |
| |
| assertThat(loadInput().reviewers()).containsExactly(user.id(), user2.id()); |
| } |
| |
| @Test |
| public void noApprovers() { |
| assertThat(loadInput().approvers()).isEmpty(); |
| } |
| |
| @Test |
| public void withApprovers() throws Exception { |
| // self approve |
| requestScopeOperations.setApiUser(changeOwner); |
| recommend(changeId.toString()); |
| |
| // approve as user |
| requestScopeOperations.setApiUser(user.id()); |
| recommend(changeId.toString()); |
| |
| // approve as user2 |
| requestScopeOperations.setApiUser(user2.id()); |
| recommend(changeId.toString()); |
| |
| assertThat(loadInput().approvers()).containsExactly(changeOwner, user.id(), user2.id()); |
| } |
| |
| @Test |
| public void withApprovers_selfApprovalsIgnored() throws Exception { |
| disableSelfCodeReviewApprovals(); |
| |
| // self approve |
| requestScopeOperations.setApiUser(changeOwner); |
| recommend(changeId.toString()); |
| |
| // approve as user |
| requestScopeOperations.setApiUser(user.id()); |
| recommend(changeId.toString()); |
| |
| // approve as user2 |
| requestScopeOperations.setApiUser(user2.id()); |
| recommend(changeId.toString()); |
| |
| assertThat(loadInput().approvers()).containsExactly(user.id(), user2.id()); |
| } |
| |
| @Test |
| public void noImplicitApprover() { |
| assertThat(loadInput().implicitApprover()).isEmpty(); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true") |
| public void withImplicitApprover() { |
| // If implicit approvals are enabled, an implicit approval of the current uploader is assumed. |
| // Since the change has only 1 patch set the current uploader is the change owner. |
| assertThat(loadInput().implicitApprover()).hasValue(changeOwner); |
| } |
| |
| @Test |
| public void noOverrides() { |
| assertThat(loadInput().overrides()).isEmpty(); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1") |
| public void withOverrides() throws Exception { |
| createOwnersOverrideLabel(); |
| |
| ReviewInput reviewInput = new ReviewInput().label("Owners-Override", 1); |
| |
| // self override |
| requestScopeOperations.setApiUser(changeOwner); |
| gApi.changes().id(changeId.toString()).current().review(reviewInput); |
| |
| // override as user |
| requestScopeOperations.setApiUser(user.id()); |
| gApi.changes().id(changeId.toString()).current().review(reviewInput); |
| |
| // override as user2 |
| requestScopeOperations.setApiUser(user2.id()); |
| gApi.changes().id(changeId.toString()).current().review(reviewInput); |
| |
| assertThat( |
| loadInput().overrides().stream() |
| .map(PatchSetApproval::accountId) |
| .collect(toImmutableSet())) |
| .containsExactly(changeOwner, user.id(), user2.id()); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1") |
| public void withOverrides_selfApprovalsIgnored() throws Exception { |
| createOwnersOverrideLabel(); |
| disableSelfOwnersOverrideApprovals(); |
| |
| ReviewInput reviewInput = new ReviewInput().label("Owners-Override", 1); |
| |
| // self override |
| requestScopeOperations.setApiUser(changeOwner); |
| gApi.changes().id(changeId.toString()).current().review(reviewInput); |
| |
| // override as user |
| requestScopeOperations.setApiUser(user.id()); |
| gApi.changes().id(changeId.toString()).current().review(reviewInput); |
| |
| // override as user2 |
| requestScopeOperations.setApiUser(user2.id()); |
| gApi.changes().id(changeId.toString()).current().review(reviewInput); |
| |
| assertThat( |
| loadInput().overrides().stream() |
| .map(PatchSetApproval::accountId) |
| .collect(toImmutableSet())) |
| .containsExactly(user.id(), user2.id()); |
| } |
| |
| @Test |
| public void noGlobalCodeOwners() { |
| CodeOwnerResolverResult globalCodeOwners = loadInput().globalCodeOwners(); |
| assertThat(globalCodeOwners.codeOwnersAccountIds()).isEmpty(); |
| assertThat(globalCodeOwners.ownedByAllUsers()).isFalse(); |
| } |
| |
| @Test |
| @GerritConfig( |
| name = "plugin.code-owners.globalCodeOwner", |
| values = {"user1@example.com", "user2@example.com"}) |
| public void withGlobalCodeOwners() { |
| CodeOwnerResolverResult globalCodeOwners = loadInput().globalCodeOwners(); |
| assertThat(globalCodeOwners.codeOwnersAccountIds()).containsExactly(user.id(), user2.id()); |
| assertThat(globalCodeOwners.ownedByAllUsers()).isFalse(); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*") |
| public void withAllUsersAsGlobalCodeOwners() { |
| CodeOwnerResolverResult globalCodeOwners = loadInput().globalCodeOwners(); |
| assertThat(globalCodeOwners.codeOwnersAccountIds()).isEmpty(); |
| assertThat(globalCodeOwners.ownedByAllUsers()).isTrue(); |
| } |
| |
| @Test |
| public void withFallbackCodeOwnersNone() { |
| assertThat(loadInput().fallbackCodeOwners()).isEqualTo(FallbackCodeOwners.NONE); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "ALL_USERS") |
| public void withFallbackCodeOwnersAllUsers() { |
| assertThat(loadInput().fallbackCodeOwners()).isEqualTo(FallbackCodeOwners.ALL_USERS); |
| } |
| |
| @Test |
| public void checkAllOwnersIsFalse() { |
| assertThat(loadInput().checkAllOwners()).isFalse(); |
| } |
| |
| @Test |
| public void createInputForComputingOwnedPaths_noReviewers() throws Exception { |
| changeApi().addReviewer(user.email()); |
| changeApi().addReviewer(user2.email()); |
| |
| // CodeOwnerApprovalCheckInput#createForComputingOwnedPaths never sets reviewers |
| assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).reviewers()).isEmpty(); |
| } |
| |
| @Test |
| public void createInputForComputingOwnedPaths_noApprovers() throws Exception { |
| // self approve |
| requestScopeOperations.setApiUser(changeOwner); |
| recommend(changeId.toString()); |
| |
| // approve as user |
| requestScopeOperations.setApiUser(user.id()); |
| recommend(changeId.toString()); |
| |
| // approve as user2 |
| requestScopeOperations.setApiUser(user2.id()); |
| recommend(changeId.toString()); |
| |
| // CodeOwnerApprovalCheckInput#createForComputingOwnedPaths always sets approvers to the given |
| // accounts |
| assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).approvers()).isEmpty(); |
| } |
| |
| @Test |
| public void createInputForComputingOwnedPaths_withApprovers() { |
| // CodeOwnerApprovalCheckInput#createForComputingOwnedPaths always sets approvers to the given |
| // accounts |
| assertThat( |
| createInputForComputingOwnedPaths(ImmutableSet.of(admin.id(), user.id(), user2.id())) |
| .approvers()) |
| .containsExactly(admin.id(), user.id(), user2.id()); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.enableImplicitApprovals", value = "true") |
| public void createInputForComputingOwnedPaths_noImplicitApprover() { |
| // CodeOwnerApprovalCheckInput#createForComputingOwnedPaths never sets an implicit approver |
| assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).implicitApprover()).isEmpty(); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.overrideApproval", value = "Owners-Override+1") |
| public void createInputForComputingOwnedPaths_noOverride() throws Exception { |
| createOwnersOverrideLabel(); |
| |
| ReviewInput reviewInput = new ReviewInput().label("Owners-Override", 1); |
| |
| // self override |
| requestScopeOperations.setApiUser(changeOwner); |
| gApi.changes().id(changeId.toString()).current().review(reviewInput); |
| |
| // override as user |
| requestScopeOperations.setApiUser(user.id()); |
| gApi.changes().id(changeId.toString()).current().review(reviewInput); |
| |
| // override as user2 |
| requestScopeOperations.setApiUser(user2.id()); |
| gApi.changes().id(changeId.toString()).current().review(reviewInput); |
| |
| // CodeOwnerApprovalCheckInput#createForComputingOwnedPaths never sets overrides |
| assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).overrides()).isEmpty(); |
| } |
| |
| @Test |
| public void createInputForComputingOwnedPaths_noGlobalCodeOwners() { |
| CodeOwnerResolverResult globalCodeOwners = |
| createInputForComputingOwnedPaths(ImmutableSet.of()).globalCodeOwners(); |
| assertThat(globalCodeOwners.codeOwnersAccountIds()).isEmpty(); |
| assertThat(globalCodeOwners.ownedByAllUsers()).isFalse(); |
| } |
| |
| @Test |
| @GerritConfig( |
| name = "plugin.code-owners.globalCodeOwner", |
| values = {"user1@example.com", "user2@example.com"}) |
| public void createInputForComputingOwnedPaths_withGlobalCodeOwners() { |
| CodeOwnerResolverResult globalCodeOwners = |
| createInputForComputingOwnedPaths(ImmutableSet.of()).globalCodeOwners(); |
| assertThat(globalCodeOwners.codeOwnersAccountIds()).containsExactly(user.id(), user2.id()); |
| assertThat(globalCodeOwners.ownedByAllUsers()).isFalse(); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "*") |
| public void createInputForComputingOwnedPaths_withAllUsersAsGlobalCodeOwners() { |
| CodeOwnerResolverResult globalCodeOwners = |
| createInputForComputingOwnedPaths(ImmutableSet.of()).globalCodeOwners(); |
| assertThat(globalCodeOwners.codeOwnersAccountIds()).isEmpty(); |
| assertThat(globalCodeOwners.ownedByAllUsers()).isTrue(); |
| } |
| |
| @Test |
| public void createInputForComputingOwnedPaths_withFallbackCodeOwnersNone() { |
| assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).fallbackCodeOwners()) |
| .isEqualTo(FallbackCodeOwners.NONE); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "ALL_USERS") |
| public void createInputForComputingOwnedPaths_withFallbackCodeOwnersAllUsers() { |
| assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).fallbackCodeOwners()) |
| .isEqualTo(FallbackCodeOwners.ALL_USERS); |
| } |
| |
| @Test |
| public void createInputForComputingOwnedPaths_checkAllOwnersIsTrue() { |
| assertThat(createInputForComputingOwnedPaths(ImmutableSet.of()).checkAllOwners()).isTrue(); |
| } |
| |
| private void disableSelfCodeReviewApprovals() throws Exception { |
| disableSelfApprovals(allProjects, "Code-Review"); |
| } |
| |
| private void disableSelfOwnersOverrideApprovals() throws Exception { |
| disableSelfApprovals(project, "Owners-Override"); |
| } |
| |
| private void disableSelfApprovals(Project.NameKey project, String labelName) throws Exception { |
| LabelDefinitionInput input = new LabelDefinitionInput(); |
| input.ignoreSelfApproval = true; |
| gApi.projects().name(project.get()).label(labelName).update(input); |
| } |
| |
| private ChangeApi changeApi() throws RestApiException { |
| return gApi.changes().id(changeId.get()); |
| } |
| |
| private CodeOwnerApprovalCheckInput loadInput() { |
| ChangeNotes changeNotes = changeNotesFactory.create(project, changeId); |
| return inputLoaderFactory.create(codeOwnersConfig, codeOwnerResolver, changeNotes).load(); |
| } |
| |
| private CodeOwnerApprovalCheckInput createInputForComputingOwnedPaths( |
| ImmutableSet<Account.Id> accounts) { |
| ChangeNotes changeNotes = changeNotesFactory.create(project, changeId); |
| return CodeOwnerApprovalCheckInput.createForComputingOwnedPaths( |
| codeOwnersConfig, codeOwnerResolver, changeNotes, accounts); |
| } |
| } |