| // 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.plugins.codeowners.acceptance.api; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.labelPermissionKey; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey; |
| import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerCheckInfoSubject.assertThat; |
| import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; |
| import static com.google.gerrit.testing.GerritJUnit.assertThrows; |
| |
| 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.account.AccountOperations; |
| import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; |
| import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; |
| import com.google.gerrit.common.Nullable; |
| import com.google.gerrit.entities.Account; |
| import com.google.gerrit.entities.Permission; |
| import com.google.gerrit.entities.Project; |
| import com.google.gerrit.entities.RefNames; |
| import com.google.gerrit.extensions.api.projects.BranchInput; |
| import com.google.gerrit.extensions.api.projects.ConfigInput; |
| import com.google.gerrit.extensions.client.ProjectState; |
| import com.google.gerrit.extensions.restapi.AuthException; |
| import com.google.gerrit.extensions.restapi.BadRequestException; |
| import com.google.gerrit.extensions.restapi.ResourceNotFoundException; |
| import com.google.gerrit.extensions.restapi.RestApiException; |
| import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT; |
| import com.google.gerrit.plugins.codeowners.acceptance.testsuite.TestCodeOwnerConfigCreation; |
| import com.google.gerrit.plugins.codeowners.acceptance.testsuite.TestPathExpressions; |
| import com.google.gerrit.plugins.codeowners.api.CodeOwnerCheckInfo; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerAnnotation; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerAnnotations; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigImportMode; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigReference; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerResolver; |
| import com.google.gerrit.plugins.codeowners.backend.CodeOwnerSet; |
| import com.google.gerrit.plugins.codeowners.restapi.CheckCodeOwnerCapability; |
| import com.google.gerrit.plugins.codeowners.util.JgitPath; |
| import com.google.gerrit.server.ServerInitiated; |
| import com.google.gerrit.server.account.AccountsUpdate; |
| import com.google.gerrit.server.account.externalids.ExternalIdFactory; |
| import com.google.gerrit.server.account.externalids.ExternalIdNotes; |
| import com.google.gerrit.server.git.meta.MetaDataUpdate; |
| import com.google.inject.Inject; |
| import com.google.inject.Provider; |
| import java.util.Arrays; |
| import org.eclipse.jgit.lib.Repository; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| /** |
| * Acceptance test for the {@link com.google.gerrit.plugins.codeowners.restapi.CheckCodeOwner} REST |
| * endpoint. |
| */ |
| public class CheckCodeOwnerIT extends AbstractCodeOwnersIT { |
| private static final String ROOT_PATH = "/"; |
| |
| @Inject private RequestScopeOperations requestScopeOperations; |
| @Inject private AccountOperations accountOperations; |
| @Inject private ProjectOperations projectOperations; |
| @Inject @ServerInitiated private Provider<AccountsUpdate> accountsUpdate; |
| @Inject private ExternalIdNotes.Factory externalIdNotesFactory; |
| @Inject private ExternalIdFactory externalIdFactory; |
| |
| private TestPathExpressions testPathExpressions; |
| |
| @Before |
| public void setUpCodeOwnersPlugin() throws Exception { |
| testPathExpressions = plugin.getSysInjector().getInstance(TestPathExpressions.class); |
| } |
| |
| @Test |
| public void requiresEmail() throws Exception { |
| BadRequestException exception = |
| assertThrows(BadRequestException.class, () -> checkCodeOwner("/", /* email= */ null)); |
| assertThat(exception).hasMessageThat().isEqualTo("email required"); |
| } |
| |
| @Test |
| public void requiresPath() throws Exception { |
| BadRequestException exception = |
| assertThrows( |
| BadRequestException.class, () -> checkCodeOwner(/* path= */ null, user.email())); |
| assertThat(exception).hasMessageThat().isEqualTo("path required"); |
| } |
| |
| @Test |
| public void requiresCallerToBeAdminOrHaveTheCheckCodeOwnerCapability() throws Exception { |
| requestScopeOperations.setApiUser(user.id()); |
| AuthException authException = |
| assertThrows(AuthException.class, () -> checkCodeOwner(ROOT_PATH, user.email())); |
| assertThat(authException) |
| .hasMessageThat() |
| .isEqualTo( |
| String.format("%s for plugin code-owners not permitted", CheckCodeOwnerCapability.ID)); |
| } |
| |
| @Test |
| public void checkCodeOwner_byAdmin() throws Exception { |
| testCheckCodeOwner(); |
| } |
| |
| @Test |
| public void checkCodeOwner_byUserThatHasTheCheckCodeOwnerCapability() throws Exception { |
| projectOperations |
| .allProjectsForUpdate() |
| .add(allowCapability("code-owners-" + CheckCodeOwnerCapability.ID).group(REGISTERED_USERS)) |
| .update(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| testCheckCodeOwner(); |
| } |
| |
| private void testCheckCodeOwner() throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| setAsCodeOwners("/foo/", codeOwner); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo).canReadRef(); |
| assertThat(checkCodeOwnerInfo).canSeeChangeNotSet(); |
| assertThat(checkCodeOwnerInfo).canApproveChangeNotSet(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath("/foo/")); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo).hasAnnotationsThat().isEmpty(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| codeOwner.email(), getCodeOwnerConfigFilePath("/foo/")), |
| String.format("resolved email %s to account %s", codeOwner.email(), codeOwner.id())); |
| } |
| |
| @Test |
| public void checkCodeOwnerThatHasCodeOwnershipThroughMultipleFiles() throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| setAsRootCodeOwners(codeOwner); |
| setAsCodeOwners("/foo/", codeOwner); |
| setAsCodeOwners("/foo/bar/", codeOwner); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly( |
| getCodeOwnerConfigFilePath("/foo/bar/"), |
| getCodeOwnerConfigFilePath("/foo/"), |
| getCodeOwnerConfigFilePath(ROOT_PATH)) |
| .inOrder(); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| codeOwner.email(), getCodeOwnerConfigFilePath("/foo/bar/")), |
| String.format( |
| "found email %s as a code owner in %s", |
| codeOwner.email(), getCodeOwnerConfigFilePath("/foo/")), |
| String.format( |
| "found email %s as a code owner in %s", |
| codeOwner.email(), getCodeOwnerConfigFilePath(ROOT_PATH)), |
| String.format("resolved email %s to account %s", codeOwner.email(), codeOwner.id())); |
| } |
| |
| @Test |
| public void checkCodeOwnerWithParentCodeOwnersIgnored() throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| setAsRootCodeOwners(codeOwner); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .ignoreParentCodeOwners() |
| .addCodeOwnerEmail(codeOwner.email()) |
| .create(); |
| |
| setAsCodeOwners("/foo/bar/", codeOwner); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly( |
| getCodeOwnerConfigFilePath("/foo/bar/"), getCodeOwnerConfigFilePath("/foo/")) |
| .inOrder(); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| codeOwner.email(), getCodeOwnerConfigFilePath("/foo/bar/")), |
| String.format( |
| "found email %s as a code owner in %s", |
| codeOwner.email(), getCodeOwnerConfigFilePath("/foo/")), |
| "parent code owners are ignored", |
| String.format("resolved email %s to account %s", codeOwner.email(), codeOwner.id())); |
| } |
| |
| @Test |
| public void checkCodeOwner_secondaryEmail() throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| String secondaryEmail = "codeOwnerSecondary@example.com"; |
| accountOperations |
| .account(codeOwner.id()) |
| .forUpdate() |
| .addSecondaryEmail(secondaryEmail) |
| .update(); |
| |
| setAsRootCodeOwners(secondaryEmail); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, secondaryEmail); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| secondaryEmail, getCodeOwnerConfigFilePath(ROOT_PATH)), |
| String.format("resolved email %s to account %s", secondaryEmail, codeOwner.id())); |
| } |
| |
| @Test |
| public void checkCodeOwner_ownedByAllUsers() throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| |
| setAsRootCodeOwners(CodeOwnerResolver.ALL_USERS_WILDCARD); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, codeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found the all users wildcard ('%s') as a code owner in %s which makes %s a code" |
| + " owner", |
| CodeOwnerResolver.ALL_USERS_WILDCARD, |
| getCodeOwnerConfigFilePath(ROOT_PATH), |
| codeOwner.email())); |
| } |
| |
| @Test |
| public void checkCodeOwner_ownedByEmailAndOwnedByAllUsers() throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| |
| setAsRootCodeOwners(codeOwner.email(), CodeOwnerResolver.ALL_USERS_WILDCARD); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, codeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| codeOwner.email(), getCodeOwnerConfigFilePath(ROOT_PATH)), |
| String.format( |
| "found the all users wildcard ('%s') as a code owner in %s which makes %s a code" |
| + " owner", |
| CodeOwnerResolver.ALL_USERS_WILDCARD, |
| getCodeOwnerConfigFilePath(ROOT_PATH), |
| codeOwner.email())); |
| } |
| |
| @Test |
| public void checkNonCodeOwner() throws Exception { |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, user.email()); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format("resolved email %s to account %s", user.email(), user.id())); |
| } |
| |
| @Test |
| public void checkNonExistingEmail() throws Exception { |
| String nonExistingEmail = "non-exiting@example.com"; |
| |
| setAsRootCodeOwners(nonExistingEmail); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, nonExistingEmail); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| nonExistingEmail, getCodeOwnerConfigFilePath(ROOT_PATH)), |
| String.format( |
| "cannot resolve code owner email %s: no account with this email exists", |
| nonExistingEmail)); |
| } |
| |
| @Test |
| public void checkAmbiguousExistingEmail() throws Exception { |
| String ambiguousEmail = "ambiguous@example.com"; |
| |
| setAsRootCodeOwners(ambiguousEmail); |
| |
| // Add the email to 2 accounts to make it ambiguous. |
| addEmail(user.id(), ambiguousEmail); |
| addEmail(admin.id(), ambiguousEmail); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, ambiguousEmail); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| ambiguousEmail, getCodeOwnerConfigFilePath(ROOT_PATH)), |
| String.format( |
| "cannot resolve code owner email %s: email is ambiguous", ambiguousEmail)); |
| } |
| |
| @Test |
| public void checkOrphanedEmail() throws Exception { |
| // Create an external ID with an email for a non-existing account. |
| String orphanedEmail = "orphaned@example.com"; |
| Account.Id accountId = Account.id(999999); |
| try (Repository allUsersRepo = repoManager.openRepository(allUsers); |
| MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) { |
| ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo); |
| extIdNotes.upsert(externalIdFactory.createEmail(accountId, orphanedEmail)); |
| extIdNotes.commit(md); |
| } |
| |
| setAsRootCodeOwners(orphanedEmail); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, orphanedEmail); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| orphanedEmail, getCodeOwnerConfigFilePath(ROOT_PATH)), |
| String.format( |
| "cannot resolve account %s for email %s: account does not exists", |
| accountId, orphanedEmail), |
| String.format( |
| "cannot resolve code owner email %s: no active account with this email found", |
| orphanedEmail)); |
| } |
| |
| @Test |
| public void checkInactiveAccount() throws Exception { |
| TestAccount inactiveUser = |
| accountCreator.create( |
| "inactiveUser", "inactiveUser@example.com", "Inactive User", /* displayName= */ null); |
| accountOperations.account(inactiveUser.id()).forUpdate().inactive().update(); |
| |
| setAsRootCodeOwners(inactiveUser); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, inactiveUser.email()); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| inactiveUser.email(), getCodeOwnerConfigFilePath(ROOT_PATH)), |
| String.format( |
| "ignoring inactive account %s for email %s", |
| inactiveUser.id(), inactiveUser.email())); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.allowedEmailDomain", value = "example.net") |
| public void checkEmailWithAllowedDomain() throws Exception { |
| String emailWithAllowedEmailDomain = "foo@example.net"; |
| TestAccount userWithAllowedEmail = |
| accountCreator.create( |
| "userWithAllowedEmail", |
| emailWithAllowedEmailDomain, |
| "User with allowed emil", |
| /* displayName= */ null); |
| |
| setAsRootCodeOwners(userWithAllowedEmail); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, emailWithAllowedEmailDomain); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| emailWithAllowedEmailDomain, getCodeOwnerConfigFilePath(ROOT_PATH)), |
| String.format( |
| "domain %s of email %s is allowed", |
| emailWithAllowedEmailDomain.substring(emailWithAllowedEmailDomain.indexOf('@') + 1), |
| emailWithAllowedEmailDomain)); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.allowedEmailDomain", value = "example.net") |
| public void checkEmailWithNonAllowedDomain() throws Exception { |
| String emailWithNonAllowedEmailDomain = "foo@example.com"; |
| TestAccount userWithAllowedEmail = |
| accountCreator.create( |
| "userWithNonAllowedEmail", |
| emailWithNonAllowedEmailDomain, |
| "User with non-allowed emil", |
| /* displayName= */ null); |
| |
| setAsRootCodeOwners(userWithAllowedEmail); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = |
| checkCodeOwner(ROOT_PATH, emailWithNonAllowedEmailDomain); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| emailWithNonAllowedEmailDomain, getCodeOwnerConfigFilePath(ROOT_PATH)), |
| String.format( |
| "domain %s of email %s is not allowed", |
| emailWithNonAllowedEmailDomain.substring( |
| emailWithNonAllowedEmailDomain.indexOf('@') + 1), |
| emailWithNonAllowedEmailDomain)); |
| } |
| |
| @Test |
| public void checkAllUsersWildcard() throws Exception { |
| CodeOwnerCheckInfo checkCodeOwnerInfo = |
| checkCodeOwner(ROOT_PATH, CodeOwnerResolver.ALL_USERS_WILDCARD); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo).canReadRefNotSet(); |
| assertThat(checkCodeOwnerInfo).canSeeChangeNotSet(); |
| assertThat(checkCodeOwnerInfo).canApproveChangeNotSet(); |
| assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| } |
| |
| @Test |
| public void checkAllUsersWildcard_ownedByAllUsers() throws Exception { |
| setAsRootCodeOwners(CodeOwnerResolver.ALL_USERS_WILDCARD); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = |
| checkCodeOwner(ROOT_PATH, CodeOwnerResolver.ALL_USERS_WILDCARD); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found the all users wildcard ('%s') as a code owner in %s which makes %s a code owner", |
| CodeOwnerResolver.ALL_USERS_WILDCARD, |
| getCodeOwnerConfigFilePath(ROOT_PATH), |
| CodeOwnerResolver.ALL_USERS_WILDCARD)); |
| } |
| |
| @Test |
| public void checkDefaultCodeOwner() throws Exception { |
| TestAccount defaultCodeOwner = |
| accountCreator.create( |
| "defaultCodeOwner", |
| "defaultCodeOwner@example.com", |
| "Default Code Owner", |
| /* displayName= */ null); |
| setAsDefaultCodeOwners(defaultCodeOwner); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, defaultCodeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); |
| assertThat(checkCodeOwnerInfo).isDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in the default code owner config", |
| defaultCodeOwner.email()), |
| String.format( |
| "resolved email %s to account %s", |
| defaultCodeOwner.email(), defaultCodeOwner.id())); |
| } |
| |
| @Test |
| public void checkDefaultCodeOwner_ownedByAllUsers() throws Exception { |
| TestAccount defaultCodeOwner = |
| accountCreator.create( |
| "defaultCodeOwner", |
| "defaultCodeOwner@example.com", |
| "Default Code Owner", |
| /* displayName= */ null); |
| setAsDefaultCodeOwner(CodeOwnerResolver.ALL_USERS_WILDCARD); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, defaultCodeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); |
| assertThat(checkCodeOwnerInfo).isDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found the all users wildcard ('%s') as a code owner in the default code owner" |
| + " config which makes %s a code owner", |
| CodeOwnerResolver.ALL_USERS_WILDCARD, defaultCodeOwner.email()), |
| String.format( |
| "resolved email %s to account %s", |
| defaultCodeOwner.email(), defaultCodeOwner.id())); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "globalCodeOwner@example.com") |
| public void checkGlobalCodeOwner() throws Exception { |
| TestAccount globalCodeOwner = |
| accountCreator.create( |
| "globalCodeOwner", |
| "globalCodeOwner@example.com", |
| "Global Code Owner", |
| /* displayName= */ null); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, globalCodeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format("found email %s as global code owner", globalCodeOwner.email()), |
| String.format( |
| "resolved email %s to account %s", globalCodeOwner.email(), globalCodeOwner.id())); |
| } |
| |
| @Test |
| @GerritConfig( |
| name = "plugin.code-owners.globalCodeOwner", |
| value = CodeOwnerResolver.ALL_USERS_WILDCARD) |
| public void checkGlobalCodeOwner_ownedByAllUsers() throws Exception { |
| TestAccount globalCodeOwner = |
| accountCreator.create( |
| "globalCodeOwner", |
| "globalCodeOwner@example.com", |
| "Global Code Owner", |
| /* displayName= */ null); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, globalCodeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as global code owner", CodeOwnerResolver.ALL_USERS_WILDCARD), |
| String.format( |
| "resolved email %s to account %s", globalCodeOwner.email(), globalCodeOwner.id())); |
| } |
| |
| @Test |
| public void checkCodeOwnerForOtherUser() throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| setAsCodeOwners("/foo/", codeOwner); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email(), user.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath("/foo/")); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| codeOwner.email(), getCodeOwnerConfigFilePath("/foo/")), |
| String.format("account %s is visible to user %s", codeOwner.id(), user.username()), |
| String.format("resolved email %s to account %s", codeOwner.email(), codeOwner.id())); |
| } |
| |
| @Test |
| public void cannotCheckForNonExistingUser() throws Exception { |
| String nonExistingEmail = "non-existing@example.com"; |
| BadRequestException exception = |
| assertThrows( |
| BadRequestException.class, () -> checkCodeOwner("/", user.email(), nonExistingEmail)); |
| assertThat(exception) |
| .hasMessageThat() |
| .isEqualTo(String.format("user %s not found", nonExistingEmail)); |
| } |
| |
| @Test |
| @GerritConfig(name = "accounts.visibility", value = "SAME_GROUP") |
| public void checkNonVisibleCodeOwnerForOtherUser() throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| |
| setAsRootCodeOwners(codeOwner); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = |
| checkCodeOwner(ROOT_PATH, codeOwner.email(), user.email()); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| codeOwner.email(), getCodeOwnerConfigFilePath(ROOT_PATH)), |
| String.format( |
| "cannot resolve code owner email %s: account %s is not visible to user %s", |
| codeOwner.email(), codeOwner.id(), user.username())); |
| } |
| |
| @Test |
| public void checkNonVisibleCodeOwnerForOtherUser_secondaryEmail() throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| String secondaryEmail = "codeOwnerSecondary@example.com"; |
| accountOperations |
| .account(codeOwner.id()) |
| .forUpdate() |
| .addSecondaryEmail(secondaryEmail) |
| .update(); |
| |
| setAsRootCodeOwners(secondaryEmail); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, secondaryEmail, user.email()); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); |
| assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotOwnedByAllUsers(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| secondaryEmail, getCodeOwnerConfigFilePath(ROOT_PATH)), |
| String.format( |
| "cannot resolve code owner email %s: account %s is referenced by secondary email" |
| + " but user %s cannot see secondary emails", |
| secondaryEmail, codeOwner.id(), user.username())); |
| } |
| |
| @Test |
| public void debugLogsContainUnresolvedImports() throws Exception { |
| skipTestIfImportsNotSupportedByCodeOwnersBackend(); |
| |
| CodeOwnerConfigReference unresolvableCodeOwnerConfigReferenceCodeOwnerConfigNotFound = |
| CodeOwnerConfigReference.create( |
| CodeOwnerConfigImportMode.ALL, "non-existing/" + getCodeOwnerConfigFileName()); |
| |
| CodeOwnerConfigReference unresolvableCodeOwnerConfigReferenceProjectNotFound = |
| CodeOwnerConfigReference.builder( |
| CodeOwnerConfigImportMode.ALL, getCodeOwnerConfigFileName()) |
| .setProject(Project.nameKey("non-existing")) |
| .build(); |
| |
| Project.NameKey nonReadableProject = |
| projectOperations.newProject().name("non-readable").create(); |
| ConfigInput configInput = new ConfigInput(); |
| configInput.state = ProjectState.HIDDEN; |
| gApi.projects().name(nonReadableProject.get()).config(configInput); |
| CodeOwnerConfigReference unresolvableCodeOwnerConfigReferenceProjectNotReadable = |
| CodeOwnerConfigReference.builder( |
| CodeOwnerConfigImportMode.ALL, getCodeOwnerConfigFileName()) |
| .setProject(nonReadableProject) |
| .build(); |
| |
| CodeOwnerConfig.Key codeOwnerConfigKey = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath(ROOT_PATH) |
| .addImport(unresolvableCodeOwnerConfigReferenceCodeOwnerConfigNotFound) |
| .addImport(unresolvableCodeOwnerConfigReferenceProjectNotFound) |
| .addImport(unresolvableCodeOwnerConfigReferenceProjectNotReadable) |
| .create(); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, user.email()); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "Code owner config %s:%s:%s imports:\n" |
| + "* %s (global import, import mode = ALL)\n" |
| + " * failed to resolve (code owner config not found)\n" |
| + "* %s:%s (global import, import mode = ALL)\n" |
| + " * failed to resolve (project not found)\n" |
| + "* %s:%s (global import, import mode = ALL)\n" |
| + " * failed to resolve (project state doesn't allow read)", |
| project, |
| "master", |
| getCodeOwnerConfigFilePath(codeOwnerConfigKey.folderPath().toString()), |
| unresolvableCodeOwnerConfigReferenceCodeOwnerConfigNotFound.filePath(), |
| unresolvableCodeOwnerConfigReferenceProjectNotFound.project().get(), |
| unresolvableCodeOwnerConfigReferenceProjectNotFound.filePath(), |
| unresolvableCodeOwnerConfigReferenceProjectNotReadable.project().get(), |
| unresolvableCodeOwnerConfigReferenceProjectNotReadable.filePath()), |
| String.format( |
| "The import of %s:%s:%s in %s:%s:%s cannot be resolved:" |
| + " code owner config does not exist (revision = %s)", |
| project, |
| "master", |
| JgitPath.of(unresolvableCodeOwnerConfigReferenceCodeOwnerConfigNotFound.filePath()) |
| .getAsAbsolutePath(), |
| project, |
| "master", |
| getCodeOwnerConfigFilePath(codeOwnerConfigKey.folderPath().toString()), |
| projectOperations.project(project).getHead("master").name()), |
| String.format( |
| "The import of %s:%s:%s in %s:%s:%s cannot be resolved: project %s not found", |
| unresolvableCodeOwnerConfigReferenceProjectNotFound.project().get(), |
| "master", |
| JgitPath.of(unresolvableCodeOwnerConfigReferenceProjectNotFound.filePath()) |
| .getAsAbsolutePath(), |
| project, |
| "master", |
| getCodeOwnerConfigFilePath(codeOwnerConfigKey.folderPath().toString()), |
| unresolvableCodeOwnerConfigReferenceProjectNotFound.project().get()), |
| String.format( |
| "The import of %s:%s:%s in %s:%s:%s cannot be resolved:" |
| + " state of project %s doesn't permit read", |
| unresolvableCodeOwnerConfigReferenceProjectNotReadable.project().get(), |
| "master", |
| JgitPath.of(unresolvableCodeOwnerConfigReferenceProjectNotReadable.filePath()) |
| .getAsAbsolutePath(), |
| project, |
| "master", |
| getCodeOwnerConfigFilePath(codeOwnerConfigKey.folderPath().toString()), |
| unresolvableCodeOwnerConfigReferenceProjectNotReadable.project().get())); |
| } |
| |
| @Test |
| public void debugLogsContainUnresolvedTransitiveImports() throws Exception { |
| skipTestIfImportsNotSupportedByCodeOwnersBackend(); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath(ROOT_PATH) |
| .addImport( |
| CodeOwnerConfigReference.create( |
| CodeOwnerConfigImportMode.ALL, "/foo/" + getCodeOwnerConfigFileName())) |
| .create(); |
| |
| CodeOwnerConfigReference unresolvableCodeOwnerConfigReference = |
| CodeOwnerConfigReference.create( |
| CodeOwnerConfigImportMode.ALL, "/non-existing/" + getCodeOwnerConfigFileName()); |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .addImport(unresolvableCodeOwnerConfigReference) |
| .create(); |
| |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, user.email()); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "Code owner config %s:%s:%s imports:\n" |
| + "* %s (global import, import mode = ALL)\n" |
| + " * %s (global import, import mode = ALL)\n" |
| + " * failed to resolve (code owner config not found)", |
| project, |
| "master", |
| getCodeOwnerConfigFilePath("/"), |
| getCodeOwnerConfigFilePath("/foo/"), |
| unresolvableCodeOwnerConfigReference.filePath()), |
| String.format( |
| "The import of %s:%s:%s in %s:%s:%s cannot be resolved:" |
| + " code owner config does not exist (revision = %s)", |
| project, |
| "master", |
| JgitPath.of(unresolvableCodeOwnerConfigReference.filePath()).getAsAbsolutePath(), |
| project, |
| "master", |
| getCodeOwnerConfigFilePath("/foo/"), |
| projectOperations.project(project).getHead("master").name())); |
| } |
| |
| @Test |
| public void checkPerFileCodeOwner() throws Exception { |
| TestAccount txtOwner = |
| accountCreator.create( |
| "txtCodeOwner", "txtCodeOwner@example.com", "Txt Code Owner", /* displayName= */ null); |
| TestAccount mdOwner = |
| accountCreator.create( |
| "mdCodeOwner", "mdCodeOwner@example.com", "Md Code Owner", /* displayName= */ null); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .addCodeOwnerSet( |
| CodeOwnerSet.builder() |
| .addPathExpression(testPathExpressions.matchFileType("txt")) |
| .addCodeOwnerEmail(txtOwner.email()) |
| .build()) |
| .addCodeOwnerSet( |
| CodeOwnerSet.builder() |
| .addPathExpression(testPathExpressions.matchFileType("md")) |
| .addCodeOwnerEmail(mdOwner.email()) |
| .build()) |
| .create(); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, mdOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath("/foo/")); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "per-file code owner set with path expressions [%s] matches", |
| testPathExpressions.matchFileType("md")), |
| String.format( |
| "found email %s as a code owner in %s", |
| mdOwner.email(), getCodeOwnerConfigFilePath("/foo/")), |
| String.format("resolved email %s to account %s", mdOwner.email(), mdOwner.id())); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatDoNotContainAnyOf( |
| String.format( |
| "path expressions [%s] matches", testPathExpressions.matchFileType("txt"))); |
| } |
| |
| @Test |
| public void checkPerFileCodeOwnerWhenParentCodeOwnersAreIgnored() throws Exception { |
| skipTestIfIgnoreParentCodeOwnersNotSupportedByCodeOwnersBackend(); |
| |
| TestAccount fileCodeOwner = |
| accountCreator.create( |
| "fileCodeOwner", |
| "fileCodeOwner@example.com", |
| "File Code Owner", |
| /* displayName= */ null); |
| TestAccount folderCodeOwner = |
| accountCreator.create( |
| "folderCodeOwner", |
| "folderCodeOwner@example.com", |
| "Folder Code Owner", |
| /* displayName= */ null); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(folderCodeOwner.email()) |
| .addCodeOwnerSet( |
| CodeOwnerSet.builder() |
| .addPathExpression(testPathExpressions.matchFileType("md")) |
| .setIgnoreGlobalAndParentCodeOwners() |
| .addCodeOwnerEmail(fileCodeOwner.email()) |
| .build()) |
| .create(); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, folderCodeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "per-file code owner set with path expressions [%s] matches", |
| testPathExpressions.matchFileType("md")), |
| String.format( |
| "found matching per-file code owner set (with path expressions = [%s]) that ignores" |
| + " parent code owners, hence ignoring the folder code owners", |
| testPathExpressions.matchFileType("md"))); |
| |
| checkCodeOwnerInfo = checkCodeOwner(path, fileCodeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath("/foo/")); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "per-file code owner set with path expressions [%s] matches", |
| testPathExpressions.matchFileType("md")), |
| String.format( |
| "found matching per-file code owner set (with path expressions = [%s]) that ignores" |
| + " parent code owners, hence ignoring the folder code owners", |
| testPathExpressions.matchFileType("md")), |
| String.format( |
| "found email %s as a code owner in %s", |
| fileCodeOwner.email(), getCodeOwnerConfigFilePath("/foo/")), |
| String.format( |
| "resolved email %s to account %s", fileCodeOwner.email(), fileCodeOwner.id())); |
| } |
| |
| @Test |
| public void checkCodeOwnerFromImportedConfig() throws Exception { |
| skipTestIfImportsNotSupportedByCodeOwnersBackend(); |
| |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .addImport( |
| CodeOwnerConfigReference.create( |
| CodeOwnerConfigImportMode.ALL, "/bar/" + getCodeOwnerConfigFileName())) |
| .create(); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/bar/") |
| .addImport( |
| CodeOwnerConfigReference.create( |
| CodeOwnerConfigImportMode.ALL, "/baz/" + getCodeOwnerConfigFileName())) |
| .create(); |
| |
| setAsCodeOwners("/baz/", codeOwner); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath("/foo/")); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "Code owner config %s:%s:/foo/%s imports:\n" |
| + "* /bar/%s (global import, import mode = ALL)\n" |
| + " * /baz/%s (global import, import mode = ALL)", |
| project, |
| "master", |
| getCodeOwnerConfigFileName(), |
| getCodeOwnerConfigFileName(), |
| getCodeOwnerConfigFileName()), |
| String.format( |
| "found email %s as a code owner in %s", |
| codeOwner.email(), getCodeOwnerConfigFilePath("/foo/")), |
| String.format("resolved email %s to account %s", codeOwner.email(), codeOwner.id())); |
| } |
| |
| @Test |
| public void checkCodeOwnerFromImportedPerFileConfig() throws Exception { |
| skipTestIfImportsNotSupportedByCodeOwnersBackend(); |
| |
| TestAccount mdCodeOwner = |
| accountCreator.create( |
| "mdCodeOwner", "mdCodeOwner@example.com", "Md Code Owner", /* displayName= */ null); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .addImport( |
| CodeOwnerConfigReference.create( |
| CodeOwnerConfigImportMode.ALL, "/bar/" + getCodeOwnerConfigFileName())) |
| .create(); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/bar/") |
| .addCodeOwnerSet( |
| CodeOwnerSet.builder() |
| .addPathExpression(testPathExpressions.matchFileType("md")) |
| .addImport( |
| CodeOwnerConfigReference.create( |
| CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, |
| "/baz/" + getCodeOwnerConfigFileName())) |
| .build()) |
| .create(); |
| |
| setAsCodeOwners("/baz/", mdCodeOwner); |
| |
| // 1. check for mdCodeOwner and path of an md file |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, mdCodeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo) |
| .hasCodeOwnerConfigFilePathsThat() |
| .containsExactly(getCodeOwnerConfigFilePath("/foo/")); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "Code owner config %s:%s:/foo/%s imports:\n" |
| + "* /bar/%s (global import, import mode = ALL)\n" |
| + " * per-file code owner set with path expressions [%s] matches\n" |
| + " * /baz/%s (per-file import, import mode = GLOBAL_CODE_OWNER_SETS_ONLY," |
| + " path expressions = [%s])", |
| project, |
| "master", |
| getCodeOwnerConfigFileName(), |
| getCodeOwnerConfigFileName(), |
| testPathExpressions.matchFileType("md"), |
| getCodeOwnerConfigFileName(), |
| testPathExpressions.matchFileType("md")), |
| String.format( |
| "found email %s as a code owner in %s", |
| mdCodeOwner.email(), getCodeOwnerConfigFilePath("/foo/")), |
| String.format( |
| "resolved email %s to account %s", mdCodeOwner.email(), mdCodeOwner.id())); |
| |
| // 2. check for user and path of an md file |
| checkCodeOwnerInfo = checkCodeOwner(path, user.email()); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "Code owner config %s:%s:/foo/%s imports:\n" |
| + "* /bar/%s (global import, import mode = ALL)\n" |
| + " * per-file code owner set with path expressions [%s] matches\n" |
| + " * /baz/%s (per-file import, import mode = GLOBAL_CODE_OWNER_SETS_ONLY," |
| + " path expressions = [%s])", |
| project, |
| "master", |
| getCodeOwnerConfigFileName(), |
| getCodeOwnerConfigFileName(), |
| testPathExpressions.matchFileType("md"), |
| getCodeOwnerConfigFileName(), |
| testPathExpressions.matchFileType("md")), |
| String.format("resolved email %s to account %s", user.email(), user.id())); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatDoNotContainAnyOf(String.format("email %s", user.email())); |
| |
| // 3. check for mdCodeOwner and path of an txt file |
| path = "/foo/bar/baz.txt"; |
| checkCodeOwnerInfo = checkCodeOwner(path, mdCodeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "Code owner config %s:%s:/foo/%s imports:\n" |
| + "* /bar/%s (global import, import mode = ALL)", |
| project, "master", getCodeOwnerConfigFileName(), getCodeOwnerConfigFileName()), |
| String.format( |
| "resolved email %s to account %s", mdCodeOwner.email(), mdCodeOwner.id())); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatDoNotContainAnyOf(String.format("email %s", mdCodeOwner.email())); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "ALL_USERS") |
| public void checkFallbackCodeOwner_AllUsers() throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| setAsCodeOwners("/foo/", codeOwner); |
| |
| // 1. Check for a file to which fallback code owners do not apply because code owners are |
| // defined |
| String path = "/foo/bar/baz.md"; |
| |
| // 1a. by a code owner |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotFallbackCodeOwner(); |
| |
| // 1b. by a non code owner |
| checkCodeOwnerInfo = checkCodeOwner(path, user.email()); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotFallbackCodeOwner(); |
| |
| // 2. Check for a file to which fallback code owners apply because no code owners are defined |
| path = "/other/bar/baz.md"; |
| checkCodeOwnerInfo = checkCodeOwner(path, user.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isFallbackCodeOwner(); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "PROJECT_OWNERS") |
| public void checkFallbackCodeOwner_ProjectOwners() throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| setAsCodeOwners("/foo/", codeOwner); |
| |
| // 1. Check for a file to which fallback code owners do not apply because code owners are |
| // defined |
| String path = "/foo/bar/baz.md"; |
| |
| // 1a. by a code owner |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotFallbackCodeOwner(); |
| |
| // 1b. by a project owner |
| checkCodeOwnerInfo = checkCodeOwner(path, admin.email()); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotFallbackCodeOwner(); |
| |
| // 1c. by a non code owner |
| checkCodeOwnerInfo = checkCodeOwner(path, user.email()); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotFallbackCodeOwner(); |
| |
| // 2. Check for a file to which fallback code owners apply because no code owners are defined |
| path = "/other/bar/baz.md"; |
| |
| // 2b. by a project owner |
| checkCodeOwnerInfo = checkCodeOwner(path, admin.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isFallbackCodeOwner(); |
| |
| // 2b. by a non project owner |
| checkCodeOwnerInfo = checkCodeOwner(path, user.email()); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotFallbackCodeOwner(); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "ALL_USERS") |
| public void noFallbackCodeOwnerIfParentCodeOwnersIgnored() throws Exception { |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .ignoreParentCodeOwners() |
| .create(); |
| |
| // 1. Check for a file to which parent code owners are ignored |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, user.email()); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotFallbackCodeOwner(); |
| |
| // 2. Check for a file to which parent code owners are not ignored |
| path = "/other/bar/baz.md"; |
| checkCodeOwnerInfo = checkCodeOwner(path, user.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isFallbackCodeOwner(); |
| } |
| |
| @Test |
| @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "ALL_USERS") |
| @GerritConfig(name = "accounts.visibility", value = "SAME_GROUP") |
| public void noFallbackCodeOwnerIfNonVisibleRelevantCodeOwnerExists() throws Exception { |
| TestAccount nonVisibleCodeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .addCodeOwnerEmail(nonVisibleCodeOwner.email()) |
| .create(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| |
| // verify that the account is not visible |
| assertThrows( |
| ResourceNotFoundException.class, () -> gApi.accounts().id(nonVisibleCodeOwner.id().get())); |
| |
| // allow user to call the check code owner REST endpoint |
| projectOperations |
| .allProjectsForUpdate() |
| .add(allowCapability("code-owners-" + CheckCodeOwnerCapability.ID).group(REGISTERED_USERS)) |
| .update(); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, admin.email()); |
| assertThat(checkCodeOwnerInfo).isNotCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isNotFallbackCodeOwner(); |
| } |
| |
| @Test |
| public void noEmptyDebugLogs() throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| setAsCodeOwners("/foo/", codeOwner); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email()); |
| assertThat(checkCodeOwnerInfo).hasDebugLogsThatDoNotContainAnyOf(""); |
| } |
| |
| @Test |
| public void checkCodeOwnerThatCannotReadRef() throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| setAsCodeOwners("/foo/", codeOwner); |
| |
| // Make read permission on master branch exclusive for admins, so that the code owner cannot |
| // read master. |
| projectOperations |
| .allProjectsForUpdate() |
| .add(allow(Permission.READ).ref("refs/heads/master").group(adminGroupUuid())) |
| .setExclusiveGroup(permissionKey(Permission.READ).ref("refs/heads/master"), true) |
| .update(); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo).cannotReadRef(); |
| assertThat(checkCodeOwnerInfo).canSeeChangeNotSet(); |
| assertThat(checkCodeOwnerInfo).canApproveChangeNotSet(); |
| } |
| |
| @Test |
| public void cannotCheckForNonExistingChange() throws Exception { |
| String nonExistingChange = "non-existing"; |
| BadRequestException exception = |
| assertThrows( |
| BadRequestException.class, |
| () -> checkCodeOwnerForChange("/", user.email(), nonExistingChange)); |
| assertThat(exception) |
| .hasMessageThat() |
| .isEqualTo(String.format("change %s not found", nonExistingChange)); |
| } |
| |
| @Test |
| public void cannotCheckForNonVisibleChange() throws Exception { |
| String changeId = createChange().getChangeId(); |
| gApi.changes().id(changeId).setPrivate(true); |
| |
| projectOperations |
| .allProjectsForUpdate() |
| .add(allowCapability("code-owners-" + CheckCodeOwnerCapability.ID).group(REGISTERED_USERS)) |
| .update(); |
| |
| requestScopeOperations.setApiUser(user.id()); |
| BadRequestException exception = |
| assertThrows( |
| BadRequestException.class, () -> checkCodeOwnerForChange("/", user.email(), changeId)); |
| assertThat(exception) |
| .hasMessageThat() |
| .isEqualTo(String.format("change %s not found", changeId)); |
| } |
| |
| @Test |
| public void cannotCheckForChangeOfOtherBranch() throws Exception { |
| // Create another branch |
| String branchName = "foo"; |
| BranchInput branchInput = new BranchInput(); |
| branchInput.ref = branchName; |
| branchInput.revision = projectOperations.project(project).getHead("master").name(); |
| gApi.projects().name(project.get()).branch(branchInput.ref).create(branchInput); |
| |
| String changeId = createChange("refs/for/" + branchName).getChangeId(); |
| BadRequestException exception = |
| assertThrows( |
| BadRequestException.class, () -> checkCodeOwnerForChange("/", user.email(), changeId)); |
| assertThat(exception) |
| .hasMessageThat() |
| .isEqualTo("target branch of specified change must match branch from the request URL"); |
| } |
| |
| @Test |
| public void checkCodeOwnerThatCannotSeeChange_privateChange() throws Exception { |
| String changeId = createChange().getChangeId(); |
| gApi.changes().id(changeId).setPrivate(true); |
| |
| testCheckCodeOwnerThatCannotSeeChange(changeId, /* canReadRef= */ true); |
| } |
| |
| @Test |
| public void checkCodeOwnerThatCannotSeeChange_cannotReadRef() throws Exception { |
| String changeId = createChange().getChangeId(); |
| |
| // Make read permission on master branch exclusive for admins, so that the code owner cannot |
| // read master. |
| projectOperations |
| .allProjectsForUpdate() |
| .add(allow(Permission.READ).ref("refs/heads/master").group(adminGroupUuid())) |
| .setExclusiveGroup(permissionKey(Permission.READ).ref("refs/heads/master"), true) |
| .update(); |
| |
| testCheckCodeOwnerThatCannotSeeChange(changeId, /* canReadRef= */ false); |
| } |
| |
| private void testCheckCodeOwnerThatCannotSeeChange(String changeId, boolean canReadRef) |
| throws Exception { |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| setAsCodeOwners("/foo/", codeOwner); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = |
| checkCodeOwnerForChange(path, codeOwner.email(), changeId); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| if (canReadRef) { |
| assertThat(checkCodeOwnerInfo).canReadRef(); |
| } else { |
| assertThat(checkCodeOwnerInfo).cannotReadRef(); |
| } |
| assertThat(checkCodeOwnerInfo).cannotSeeChange(); |
| assertThat(checkCodeOwnerInfo).canApproveChange(); |
| } |
| |
| @Test |
| public void checkCodeOwnerThatCannotApproveChange() throws Exception { |
| String changeId = createChange().getChangeId(); |
| |
| // Remove permission to vote on the Code-Review label. |
| projectOperations |
| .allProjectsForUpdate() |
| .remove(labelPermissionKey("Code-Review").ref("refs/heads/*")) |
| .update(); |
| |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| setAsCodeOwners("/foo/", codeOwner); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = |
| checkCodeOwnerForChange(path, codeOwner.email(), changeId); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo).isResolvable(); |
| assertThat(checkCodeOwnerInfo).canReadRef(); |
| assertThat(checkCodeOwnerInfo).canSeeChange(); |
| assertThat(checkCodeOwnerInfo).cannotApproveChange(); |
| } |
| |
| @Test |
| public void checkCodeOwnerWithAnnotations() throws Exception { |
| skipTestIfAnnotationsNotSupportedByCodeOwnersBackend(); |
| |
| TestAccount codeOwner = |
| accountCreator.create( |
| "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/") |
| .addCodeOwnerSet( |
| CodeOwnerSet.builder() |
| .addCodeOwnerEmail(codeOwner.email()) |
| .addAnnotation( |
| codeOwner.email(), CodeOwnerAnnotations.LAST_RESORT_SUGGESTION_ANNOTATION) |
| .build()) |
| .create(); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/") |
| .addCodeOwnerSet( |
| CodeOwnerSet.builder() |
| .addCodeOwnerEmail(CodeOwnerResolver.ALL_USERS_WILDCARD) |
| .addAnnotation( |
| CodeOwnerResolver.ALL_USERS_WILDCARD, CodeOwnerAnnotation.create("ANNOTATION")) |
| .build()) |
| .create(); |
| |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath("/foo/bar/") |
| .addCodeOwnerSet( |
| CodeOwnerSet.builder() |
| .addCodeOwnerEmail(codeOwner.email()) |
| .addAnnotation( |
| codeOwner.email(), CodeOwnerAnnotations.LAST_RESORT_SUGGESTION_ANNOTATION) |
| .addAnnotation(codeOwner.email(), CodeOwnerAnnotation.create("OTHER_ANNOTATION")) |
| .build()) |
| .create(); |
| |
| String path = "/foo/bar/baz.md"; |
| CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email()); |
| assertThat(checkCodeOwnerInfo).isCodeOwner(); |
| assertThat(checkCodeOwnerInfo) |
| .hasAnnotationsThat() |
| .containsExactly(CodeOwnerAnnotations.LAST_RESORT_SUGGESTION_ANNOTATION.key()) |
| .inOrder(); |
| assertThat(checkCodeOwnerInfo) |
| .hasDebugLogsThatContainAllOf( |
| String.format( |
| "found email %s as a code owner in %s", |
| codeOwner.email(), getCodeOwnerConfigFilePath("/foo/bar/")), |
| String.format( |
| "email %s is annotated with %s", |
| codeOwner.email(), |
| ImmutableSet.of( |
| CodeOwnerAnnotations.LAST_RESORT_SUGGESTION_ANNOTATION.key(), |
| "OTHER_ANNOTATION")), |
| String.format( |
| "found the all users wildcard ('%s') as a code owner in %s which makes %s a code" |
| + " owner", |
| CodeOwnerResolver.ALL_USERS_WILDCARD, |
| getCodeOwnerConfigFilePath("/foo/"), |
| codeOwner.email()), |
| String.format( |
| "found annotations for the all users wildcard ('%s') which apply to %s: %s", |
| CodeOwnerResolver.ALL_USERS_WILDCARD, |
| codeOwner.email(), |
| ImmutableSet.of("ANNOTATION")), |
| String.format( |
| "found email %s as a code owner in %s", |
| codeOwner.email(), getCodeOwnerConfigFilePath("/")), |
| String.format( |
| "email %s is annotated with %s", |
| codeOwner.email(), |
| ImmutableSet.of(CodeOwnerAnnotations.LAST_RESORT_SUGGESTION_ANNOTATION.key())), |
| String.format( |
| "dropping unsupported annotations for %s: %s", |
| codeOwner.email(), ImmutableSet.of("ANNOTATION", "OTHER_ANNOTATION"))); |
| } |
| |
| private CodeOwnerCheckInfo checkCodeOwner(String path, String email) throws RestApiException { |
| return checkCodeOwner(path, email, /* user= */ null); |
| } |
| |
| private CodeOwnerCheckInfo checkCodeOwner(String path, String email, @Nullable String user) |
| throws RestApiException { |
| return projectCodeOwnersApiFactory |
| .project(project) |
| .branch("master") |
| .checkCodeOwner() |
| .path(path) |
| .email(email) |
| .user(user) |
| .check(); |
| } |
| |
| private CodeOwnerCheckInfo checkCodeOwnerForChange( |
| String path, String email, @Nullable String change) throws RestApiException { |
| return projectCodeOwnersApiFactory |
| .project(project) |
| .branch("master") |
| .checkCodeOwner() |
| .path(path) |
| .email(email) |
| .change(change) |
| .check(); |
| } |
| |
| private String getCodeOwnerConfigFilePath(String folderPath) { |
| assertThat(folderPath).startsWith("/"); |
| assertThat(folderPath).endsWith("/"); |
| return folderPath + getCodeOwnerConfigFileName(); |
| } |
| |
| private void setAsRootCodeOwners(String... emails) { |
| TestCodeOwnerConfigCreation.Builder builder = |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch("master") |
| .folderPath(ROOT_PATH); |
| Arrays.stream(emails).forEach(builder::addCodeOwnerEmail); |
| builder.create(); |
| } |
| |
| private void setAsDefaultCodeOwner(String email) { |
| codeOwnerConfigOperations |
| .newCodeOwnerConfig() |
| .project(project) |
| .branch(RefNames.REFS_CONFIG) |
| .folderPath(ROOT_PATH) |
| .addCodeOwnerEmail(email) |
| .create(); |
| } |
| |
| private void addEmail(Account.Id accountId, String email) throws Exception { |
| accountsUpdate |
| .get() |
| .update( |
| "Test update", |
| accountId, |
| (a, u) -> |
| u.addExternalId( |
| externalIdFactory.create( |
| "foo", |
| "bar" + accountId.get(), |
| accountId, |
| email, |
| /* hashedPassword= */ null))); |
| } |
| } |