CheckCodeOwner: Return relevant code owner config files To make debugging of code owner config files easier return all code owner config files that are relevant for computing the code ownership when checking a code owner, i.e. all code owner config files which have been inspected to compute the code ownership. Note, there is already a codeOwnerConfigFilePaths field. This field contains the paths of the code owner config files that assign code ownership to the given email for the specified path. This is different from all inspected owners files. In a follow-up change we may drop the codeOwnerConfigFilePaths field and instead include the information of whether a code owner config file assigns code ownership to the given email for the specified path into a new field inside CodeOwnerConfigFileInfo. Bug: Google b/345161989 Change-Id: Ib3a57feac6e9ea293eaba97640c4251fd8d558e7 Signed-off-by: Edwin Kempin <ekempin@google.com> Reviewed-on: https://gerrit-review.googlesource.com/c/plugins/code-owners/+/434758 Tested-by: Zuul <zuul-63@gerritcodereview-ci.iam.gserviceaccount.com> Reviewed-by: Kamil Musin <kamilm@google.com>
diff --git a/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java b/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java index 1283932..14257b7 100644 --- a/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java +++ b/java/com/google/gerrit/plugins/codeowners/acceptance/AbstractCodeOwnersTest.java
@@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.gerrit.acceptance.GitUtil; import com.google.gerrit.acceptance.LightweightPluginDaemonTest; import com.google.gerrit.acceptance.PushOneCommit; @@ -287,8 +288,9 @@ * * @param testAccounts the accounts of the users that should be code owners */ - protected void setAsDefaultCodeOwners(TestAccount... testAccounts) { - setAsCodeOwners(RefNames.REFS_CONFIG, "/", testAccounts); + @CanIgnoreReturnValue + protected CodeOwnerConfig.Key setAsDefaultCodeOwners(TestAccount... testAccounts) { + return setAsCodeOwners(RefNames.REFS_CONFIG, "/", testAccounts); } /** @@ -296,8 +298,9 @@ * * @param testAccounts the accounts of the users that should be code owners */ - protected void setAsRootCodeOwners(TestAccount... testAccounts) { - setAsCodeOwners("/", testAccounts); + @CanIgnoreReturnValue + protected CodeOwnerConfig.Key setAsRootCodeOwners(TestAccount... testAccounts) { + return setAsCodeOwners("/", testAccounts); } /** @@ -306,8 +309,9 @@ * @param path the path of the code owner config file * @param testAccounts the accounts of the users that should be code owners */ - protected void setAsCodeOwners(String path, TestAccount... testAccounts) { - setAsCodeOwners("master", path, testAccounts); + @CanIgnoreReturnValue + protected CodeOwnerConfig.Key setAsCodeOwners(String path, TestAccount... testAccounts) { + return setAsCodeOwners("master", path, testAccounts); } /** @@ -317,7 +321,9 @@ * @param path the path of the code owner config file * @param testAccounts the accounts of the users that should be code owners */ - private void setAsCodeOwners(String branchName, String path, TestAccount... testAccounts) { + @CanIgnoreReturnValue + private CodeOwnerConfig.Key setAsCodeOwners( + String branchName, String path, TestAccount... testAccounts) { Builder newCodeOwnerConfigBuilder = codeOwnerConfigOperations .newCodeOwnerConfig() @@ -327,7 +333,7 @@ for (TestAccount testAccount : testAccounts) { newCodeOwnerConfigBuilder.addCodeOwnerEmail(testAccount.email()); } - newCodeOwnerConfigBuilder.create(); + return newCodeOwnerConfigBuilder.create(); } /**
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerCheckInfo.java b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerCheckInfo.java index 19b02a0..15c8558 100644 --- a/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerCheckInfo.java +++ b/java/com/google/gerrit/plugins/codeowners/api/CodeOwnerCheckInfo.java
@@ -46,6 +46,12 @@ public boolean isResolvable; /** + * The code owner config files that are relevant for computing the code ownership, i.e. all code + * owner config files which have been inspected to compute the code ownership. + */ + public List<CodeOwnerConfigFileInfo> codeOwnerConfigs; + + /** * Whether the user to which the given email was resolved has read permissions on the branch. * * <p>Not set if:
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwner.java b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwner.java index ad8b388..58b072f 100644 --- a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwner.java +++ b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwner.java
@@ -30,6 +30,7 @@ import com.google.gerrit.extensions.restapi.RestReadView; import com.google.gerrit.extensions.restapi.TopLevelResource; import com.google.gerrit.plugins.codeowners.api.CodeOwnerCheckInfo; +import com.google.gerrit.plugins.codeowners.api.CodeOwnerConfigFileInfo; import com.google.gerrit.plugins.codeowners.backend.CodeOwner; import com.google.gerrit.plugins.codeowners.backend.CodeOwnerAnnotations; import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigHierarchy; @@ -85,6 +86,7 @@ private final AccountsCollection accountsCollection; private final UnresolvedImportFormatter unresolvedImportFormatter; private final ChangeFinder changeFinder; + private final CodeOwnerConfigFileJson codeOwnerConfigFileJson; private String email; private String path; @@ -104,7 +106,8 @@ CodeOwners codeOwners, AccountsCollection accountsCollection, UnresolvedImportFormatter unresolvedImportFormatter, - ChangeFinder changeFinder) { + ChangeFinder changeFinder, + CodeOwnerConfigFileJson codeOwnerConfigFileJson) { this.checkCodeOwnerCapability = checkCodeOwnerCapability; this.permissionBackend = permissionBackend; this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration; @@ -115,6 +118,7 @@ this.accountsCollection = accountsCollection; this.unresolvedImportFormatter = unresolvedImportFormatter; this.changeFinder = changeFinder; + this.codeOwnerConfigFileJson = codeOwnerConfigFileJson; } @Option(name = "--email", usage = "email for which the code ownership should be checked") @@ -154,6 +158,8 @@ validateInput(branchResource); Path absolutePath = JgitPath.of(path).getAsAbsolutePath(); + ImmutableList.Builder<CodeOwnerConfigFileInfo> codeOwnerConfigFileInfosBuilder = + ImmutableList.builder(); List<String> messages = new ArrayList<>(); List<Path> codeOwnerConfigFilePaths = new ArrayList<>(); AtomicBoolean isCodeOwnershipAssignedToEmail = new AtomicBoolean(false); @@ -174,6 +180,13 @@ pathCodeOwnersFactory .createWithoutCache(codeOwnerConfig, absolutePath) .resolveCodeOwnerConfig(); + + codeOwnerConfigFileInfosBuilder.add( + codeOwnerConfigFileJson.format( + codeOwnerConfig, + pathCodeOwnersResult.get().resolvedImports(), + pathCodeOwnersResult.get().unresolvedImports())); + messages.addAll(pathCodeOwnersResult.messages()); pathCodeOwnersResult .get() @@ -332,6 +345,7 @@ || isFallbackCodeOwner) && isResolvable; codeOwnerCheckInfo.isResolvable = isResolvable; + codeOwnerCheckInfo.codeOwnerConfigs = codeOwnerConfigFileInfosBuilder.build(); codeOwnerCheckInfo.canReadRef = canReadRef; codeOwnerCheckInfo.canSeeChange = canSeeChange; codeOwnerCheckInfo.canApproveChange = canApproveChange;
diff --git a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerCheckInfoSubject.java b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerCheckInfoSubject.java index 3128b7f..532fdd7 100644 --- a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerCheckInfoSubject.java +++ b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerCheckInfoSubject.java
@@ -15,11 +15,15 @@ package com.google.gerrit.plugins.codeowners.testing; import static com.google.common.truth.Truth.assertAbout; +import static com.google.gerrit.plugins.codeowners.testing.CodeOwnerConfigFileInfoSubject.codeOwnerConfigFileInfos; +import static com.google.gerrit.truth.ListSubject.elements; import com.google.common.truth.FailureMetadata; import com.google.common.truth.IterableSubject; import com.google.common.truth.Subject; import com.google.gerrit.plugins.codeowners.api.CodeOwnerCheckInfo; +import com.google.gerrit.plugins.codeowners.api.CodeOwnerConfigFileInfo; +import com.google.gerrit.truth.ListSubject; /** {@link Subject} for doing assertions on {@link CodeOwnerCheckInfo}s. */ public class CodeOwnerCheckInfoSubject extends Subject { @@ -69,6 +73,14 @@ check("isResolvable").that(codeOwnerCheckInfo().isResolvable).isFalse(); } + /** Returns a {@link ListSubject} for the code owner config file infos. */ + public ListSubject<CodeOwnerConfigFileInfoSubject, CodeOwnerConfigFileInfo> + hasCodeOwnerConfigsThat() { + return check("codeOwnerConfigs") + .about(elements()) + .thatCustom(codeOwnerCheckInfo().codeOwnerConfigs, codeOwnerConfigFileInfos()); + } + public void canReadRef() { check("canReadRef").that(codeOwnerCheckInfo().canReadRef).isTrue(); }
diff --git a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerConfigFileInfoSubject.java b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerConfigFileInfoSubject.java index 4d09a10..67ddce7 100644 --- a/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerConfigFileInfoSubject.java +++ b/java/com/google/gerrit/plugins/codeowners/testing/CodeOwnerConfigFileInfoSubject.java
@@ -18,6 +18,7 @@ import static com.google.gerrit.truth.ListSubject.elements; import com.google.common.truth.FailureMetadata; +import com.google.common.truth.IterableSubject; import com.google.common.truth.StringSubject; import com.google.common.truth.Subject; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -69,6 +70,16 @@ return check("path()").that(codeOwnerConfigFileInfo().path); } + public IterableSubject hasWebLinksThat() { + return check("webLinks()").that(codeOwnerConfigFileInfo().webLinks); + } + + @CanIgnoreReturnValue + public CodeOwnerConfigFileInfoSubject assertNoWebLinks() { + hasWebLinksThat().isNull(); + return this; + } + /** * Returns a {@link ListSubject} for the (resolved) imports of the code owner config file info. */
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerIT.java index 2166eb4..3e455c4 100644 --- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerIT.java +++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerIT.java
@@ -25,6 +25,7 @@ import static com.google.gerrit.testing.TestActionRefUpdateContext.testRefAction; import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.gerrit.acceptance.RestResponse; import com.google.gerrit.acceptance.TestAccount; import com.google.gerrit.acceptance.config.GerritConfig; @@ -47,20 +48,25 @@ 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.api.CodeOwnerConfigFileInfo; import com.google.gerrit.plugins.codeowners.backend.CodeOwnerAnnotation; import com.google.gerrit.plugins.codeowners.backend.CodeOwnerAnnotations; +import com.google.gerrit.plugins.codeowners.backend.CodeOwnerBackend; 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.backend.config.BackendConfig; import com.google.gerrit.plugins.codeowners.restapi.CheckCodeOwnerCapability; +import com.google.gerrit.plugins.codeowners.testing.CodeOwnerConfigFileInfoSubject; 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.storage.notedb.ExternalIdNotes; import com.google.gerrit.server.git.meta.MetaDataUpdate; +import com.google.gerrit.truth.ListSubject; import com.google.inject.Inject; import com.google.inject.Provider; import java.util.Arrays; @@ -84,10 +90,12 @@ @Inject private ExternalIdFactory externalIdFactory; private TestPathExpressions testPathExpressions; + private CodeOwnerBackend backend; @Before public void setUpCodeOwnersPlugin() throws Exception { testPathExpressions = plugin.getSysInjector().getInstance(TestPathExpressions.class); + backend = plugin.getSysInjector().getInstance(BackendConfig.class).getDefaultBackend(); } @Test @@ -155,12 +163,20 @@ TestAccount codeOwner = accountCreator.create( "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); - setAsCodeOwners("/foo/", codeOwner); + CodeOwnerConfig.Key codeOwnerConfigKey = setAsCodeOwners("/foo/", codeOwner); String path = "/foo/bar/baz.md"; CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email()); assertThat(checkCodeOwnerInfo).isCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); + assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); assertThat(checkCodeOwnerInfo).canReadRef(); assertThat(checkCodeOwnerInfo).canSeeChangeNotSet(); assertThat(checkCodeOwnerInfo).canApproveChangeNotSet(); @@ -187,14 +203,41 @@ TestAccount codeOwner = accountCreator.create( "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); - setAsRootCodeOwners(codeOwner); - setAsCodeOwners("/foo/", codeOwner); - setAsCodeOwners("/foo/bar/", codeOwner); + CodeOwnerConfig.Key rootCodeOwnerConfigKey = setAsRootCodeOwners(codeOwner); + CodeOwnerConfig.Key fooCodeOwnerConfigKey = setAsCodeOwners("/foo/", codeOwner); + CodeOwnerConfig.Key fooBarCodeOwnerConfigKey = setAsCodeOwners("/foo/bar/", codeOwner); String path = "/foo/bar/baz.md"; CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email()); assertThat(checkCodeOwnerInfo).isCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); + + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().hasSize(3); + assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .element(0) + .assertKey(backend, fooBarCodeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .element(1) + .assertKey(backend, fooCodeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .element(2) + .assertKey(backend, rootCodeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly( @@ -226,21 +269,41 @@ "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); setAsRootCodeOwners(codeOwner); - codeOwnerConfigOperations - .newCodeOwnerConfig() - .project(project) - .branch("master") - .folderPath("/foo/") - .ignoreParentCodeOwners() - .addCodeOwnerEmail(codeOwner.email()) - .create(); + CodeOwnerConfig.Key fooCodeOwnerConfigKey = + codeOwnerConfigOperations + .newCodeOwnerConfig() + .project(project) + .branch("master") + .folderPath("/foo/") + .ignoreParentCodeOwners() + .addCodeOwnerEmail(codeOwner.email()) + .create(); - setAsCodeOwners("/foo/bar/", codeOwner); + CodeOwnerConfig.Key fooBarCodeOwnerConfigKey = setAsCodeOwners("/foo/bar/", codeOwner); String path = "/foo/bar/baz.md"; CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email()); assertThat(checkCodeOwnerInfo).isCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); + + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().hasSize(2); + assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .element(0) + .assertKey(backend, fooBarCodeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .element(1) + .assertKey(backend, fooCodeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly( @@ -273,12 +336,20 @@ .addSecondaryEmail(secondaryEmail) .update(); - setAsRootCodeOwners(secondaryEmail); + CodeOwnerConfig.Key codeOwnerConfigKey = setAsRootCodeOwners(secondaryEmail); CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, secondaryEmail); assertThat(checkCodeOwnerInfo).isCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); @@ -298,12 +369,21 @@ accountCreator.create( "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); - setAsRootCodeOwners(CodeOwnerResolver.ALL_USERS_WILDCARD); + CodeOwnerConfig.Key codeOwnerConfigKey = + setAsRootCodeOwners(CodeOwnerResolver.ALL_USERS_WILDCARD); CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, codeOwner.email()); assertThat(checkCodeOwnerInfo).isCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); @@ -325,12 +405,21 @@ accountCreator.create( "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); - setAsRootCodeOwners(codeOwner.email(), CodeOwnerResolver.ALL_USERS_WILDCARD); + CodeOwnerConfig.Key codeOwnerConfigKey = + setAsRootCodeOwners(codeOwner.email(), CodeOwnerResolver.ALL_USERS_WILDCARD); CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, codeOwner.email()); assertThat(checkCodeOwnerInfo).isCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); @@ -354,6 +443,7 @@ CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, user.email()); assertThat(checkCodeOwnerInfo).isNotCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().isEmpty(); assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); @@ -367,12 +457,20 @@ public void checkNonExistingEmail() throws Exception { String nonExistingEmail = "non-exiting@example.com"; - setAsRootCodeOwners(nonExistingEmail); + CodeOwnerConfig.Key codeOwnerConfigKey = setAsRootCodeOwners(nonExistingEmail); CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, nonExistingEmail); assertThat(checkCodeOwnerInfo).isNotCodeOwner(); assertThat(checkCodeOwnerInfo).isNotResolvable(); assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); @@ -392,7 +490,7 @@ public void checkAmbiguousExistingEmail() throws Exception { String ambiguousEmail = "ambiguous@example.com"; - setAsRootCodeOwners(ambiguousEmail); + CodeOwnerConfig.Key codeOwnerConfigKey = setAsRootCodeOwners(ambiguousEmail); // Add the email to 2 accounts to make it ambiguous. addEmail(user.id(), ambiguousEmail); @@ -402,6 +500,14 @@ assertThat(checkCodeOwnerInfo).isNotCodeOwner(); assertThat(checkCodeOwnerInfo).isNotResolvable(); assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); @@ -428,12 +534,20 @@ extIdNotes.commit(md); } - setAsRootCodeOwners(orphanedEmail); + CodeOwnerConfig.Key codeOwnerConfigKey = setAsRootCodeOwners(orphanedEmail); CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, orphanedEmail); assertThat(checkCodeOwnerInfo).isNotCodeOwner(); assertThat(checkCodeOwnerInfo).isNotResolvable(); assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); @@ -459,12 +573,20 @@ "inactiveUser", "inactiveUser@example.com", "Inactive User", /* displayName= */ null); accountOperations.account(inactiveUser.id()).forUpdate().inactive().update(); - setAsRootCodeOwners(inactiveUser); + CodeOwnerConfig.Key codeOwnerConfigKey = setAsRootCodeOwners(inactiveUser); CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, inactiveUser.email()); assertThat(checkCodeOwnerInfo).isNotCodeOwner(); assertThat(checkCodeOwnerInfo).isNotResolvable(); assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); @@ -491,12 +613,20 @@ "User with allowed emil", /* displayName= */ null); - setAsRootCodeOwners(userWithAllowedEmail); + CodeOwnerConfig.Key codeOwnerConfigKey = setAsRootCodeOwners(userWithAllowedEmail); CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, emailWithAllowedEmailDomain); assertThat(checkCodeOwnerInfo).isCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); @@ -517,20 +647,28 @@ @GerritConfig(name = "plugin.code-owners.allowedEmailDomain", value = "example.net") public void checkEmailWithNonAllowedDomain() throws Exception { String emailWithNonAllowedEmailDomain = "foo@example.com"; - TestAccount userWithAllowedEmail = + TestAccount userWithNonAllowedEmail = accountCreator.create( "userWithNonAllowedEmail", emailWithNonAllowedEmailDomain, "User with non-allowed emil", /* displayName= */ null); - setAsRootCodeOwners(userWithAllowedEmail); + CodeOwnerConfig.Key codeOwnerConfigKey = setAsRootCodeOwners(userWithNonAllowedEmail); CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, emailWithNonAllowedEmailDomain); assertThat(checkCodeOwnerInfo).isNotCodeOwner(); assertThat(checkCodeOwnerInfo).isNotResolvable(); assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); @@ -554,6 +692,7 @@ checkCodeOwner(ROOT_PATH, CodeOwnerResolver.ALL_USERS_WILDCARD); assertThat(checkCodeOwnerInfo).isNotCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().isEmpty(); assertThat(checkCodeOwnerInfo).canReadRefNotSet(); assertThat(checkCodeOwnerInfo).canSeeChangeNotSet(); assertThat(checkCodeOwnerInfo).canApproveChangeNotSet(); @@ -565,13 +704,22 @@ @Test public void checkAllUsersWildcard_ownedByAllUsers() throws Exception { - setAsRootCodeOwners(CodeOwnerResolver.ALL_USERS_WILDCARD); + CodeOwnerConfig.Key codeOwnerConfigKey = + setAsRootCodeOwners(CodeOwnerResolver.ALL_USERS_WILDCARD); CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, CodeOwnerResolver.ALL_USERS_WILDCARD); assertThat(checkCodeOwnerInfo).isCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); @@ -595,12 +743,20 @@ "defaultCodeOwner@example.com", "Default Code Owner", /* displayName= */ null); - setAsDefaultCodeOwners(defaultCodeOwner); + CodeOwnerConfig.Key codeOwnerConfigKey = setAsDefaultCodeOwners(defaultCodeOwner); String path = "/foo/bar/baz.md"; CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, defaultCodeOwner.email()); assertThat(checkCodeOwnerInfo).isCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); + assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); assertThat(checkCodeOwnerInfo).isDefaultCodeOwner(); assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); @@ -623,12 +779,21 @@ "defaultCodeOwner@example.com", "Default Code Owner", /* displayName= */ null); - setAsDefaultCodeOwner(CodeOwnerResolver.ALL_USERS_WILDCARD); + CodeOwnerConfig.Key codeOwnerConfigKey = + 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) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); assertThat(checkCodeOwnerInfo).isDefaultCodeOwner(); assertThat(checkCodeOwnerInfo).isNotGlobalCodeOwner(); @@ -658,6 +823,7 @@ CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, globalCodeOwner.email()); assertThat(checkCodeOwnerInfo).isCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().isEmpty(); assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); assertThat(checkCodeOwnerInfo).isGlobalCodeOwner(); @@ -685,6 +851,7 @@ CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, globalCodeOwner.email()); assertThat(checkCodeOwnerInfo).isCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().isEmpty(); assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); assertThat(checkCodeOwnerInfo).isGlobalCodeOwner(); @@ -702,13 +869,21 @@ TestAccount codeOwner = accountCreator.create( "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); - setAsCodeOwners("/foo/", codeOwner); + CodeOwnerConfig.Key codeOwnerConfigKey = 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) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath("/foo/")); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); @@ -741,13 +916,21 @@ accountCreator.create( "codeOwner", "codeOwner@example.com", "Code Owner", /* displayName= */ null); - setAsRootCodeOwners(codeOwner); + CodeOwnerConfig.Key codeOwnerConfigKey = setAsRootCodeOwners(codeOwner); CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, codeOwner.email(), user.email()); assertThat(checkCodeOwnerInfo).isNotCodeOwner(); assertThat(checkCodeOwnerInfo).isNotResolvable(); assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); @@ -775,12 +958,20 @@ .addSecondaryEmail(secondaryEmail) .update(); - setAsRootCodeOwners(secondaryEmail); + CodeOwnerConfig.Key codeOwnerConfigKey = setAsRootCodeOwners(secondaryEmail); CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, secondaryEmail, user.email()); assertThat(checkCodeOwnerInfo).isNotCodeOwner(); assertThat(checkCodeOwnerInfo).isNotResolvable(); assertThat(checkCodeOwnerInfo) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath(ROOT_PATH)); assertThat(checkCodeOwnerInfo).isNotDefaultCodeOwner(); @@ -798,16 +989,16 @@ } @Test - public void debugLogsContainUnresolvedImports() throws Exception { + public void checkWithUnresolvedImports() throws Exception { skipTestIfImportsNotSupportedByCodeOwnersBackend(); CodeOwnerConfigReference unresolvableCodeOwnerConfigReferenceCodeOwnerConfigNotFound = CodeOwnerConfigReference.create( - CodeOwnerConfigImportMode.ALL, "non-existing/" + getCodeOwnerConfigFileName()); + CodeOwnerConfigImportMode.ALL, "/non-existing/" + getCodeOwnerConfigFileName()); CodeOwnerConfigReference unresolvableCodeOwnerConfigReferenceProjectNotFound = CodeOwnerConfigReference.builder( - CodeOwnerConfigImportMode.ALL, getCodeOwnerConfigFileName()) + CodeOwnerConfigImportMode.ALL, "/" + getCodeOwnerConfigFileName()) .setProject(Project.nameKey("non-existing")) .build(); @@ -818,7 +1009,7 @@ gApi.projects().name(nonReadableProject.get()).config(configInput); CodeOwnerConfigReference unresolvableCodeOwnerConfigReferenceProjectNotReadable = CodeOwnerConfigReference.builder( - CodeOwnerConfigImportMode.ALL, getCodeOwnerConfigFileName()) + CodeOwnerConfigImportMode.ALL, "/" + getCodeOwnerConfigFileName()) .setProject(nonReadableProject) .build(); @@ -834,6 +1025,75 @@ .create(); CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, user.email()); + + CodeOwnerConfigFileInfoSubject codeOwnerConfigFileInfoSubject = + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().onlyElement(); + codeOwnerConfigFileInfoSubject.assertKey(backend, codeOwnerConfigKey); + codeOwnerConfigFileInfoSubject + .assertNoWebLinks() + .assertNoResolvedImports() + .assertNoImportMode(); + + ListSubject<CodeOwnerConfigFileInfoSubject, CodeOwnerConfigFileInfo> + unresolvedImportsListSubject = codeOwnerConfigFileInfoSubject.hasUnresolvedImportsThat(); + unresolvedImportsListSubject.hasSize(3); + + CodeOwnerConfigFileInfoSubject unresolvedImportSubject1 = + unresolvedImportsListSubject.element(0); + unresolvedImportSubject1.hasProjectThat().isEqualTo(project.get()); + unresolvedImportSubject1.hasBranchThat().isEqualTo("refs/heads/master"); + unresolvedImportSubject1 + .hasPathThat() + .isEqualTo( + unresolvableCodeOwnerConfigReferenceCodeOwnerConfigNotFound.filePath().toString()); + unresolvedImportSubject1.assertImportMode( + unresolvableCodeOwnerConfigReferenceCodeOwnerConfigNotFound.importMode()); + unresolvedImportSubject1 + .hasUnresolvedErrorMessageThat() + .isEqualTo( + String.format( + "code owner config does not exist (revision = %s)", + projectOperations.project(project).getHead("master").name())); + unresolvedImportSubject1.assertNoWebLinks().assertNoImports(); + + CodeOwnerConfigFileInfoSubject unresolvedImportSubject2 = + unresolvedImportsListSubject.element(1); + unresolvedImportSubject2 + .hasProjectThat() + .isEqualTo(unresolvableCodeOwnerConfigReferenceProjectNotFound.project().get().get()); + unresolvedImportSubject2.hasBranchThat().isEqualTo("refs/heads/master"); + unresolvedImportSubject2 + .hasPathThat() + .isEqualTo(unresolvableCodeOwnerConfigReferenceProjectNotFound.filePath().toString()); + unresolvedImportSubject2.assertImportMode( + unresolvableCodeOwnerConfigReferenceProjectNotFound.importMode()); + unresolvedImportSubject2 + .hasUnresolvedErrorMessageThat() + .isEqualTo( + String.format( + "project %s not found", + unresolvableCodeOwnerConfigReferenceProjectNotFound.project().get())); + unresolvedImportSubject2.assertNoWebLinks().assertNoImports(); + + CodeOwnerConfigFileInfoSubject unresolvedImportSubject3 = + unresolvedImportsListSubject.element(2); + unresolvedImportSubject3 + .hasProjectThat() + .isEqualTo(unresolvableCodeOwnerConfigReferenceProjectNotReadable.project().get().get()); + unresolvedImportSubject3.hasBranchThat().isEqualTo("refs/heads/master"); + unresolvedImportSubject3 + .hasPathThat() + .isEqualTo(unresolvableCodeOwnerConfigReferenceProjectNotReadable.filePath().toString()); + unresolvedImportSubject3.assertImportMode( + unresolvableCodeOwnerConfigReferenceProjectNotReadable.importMode()); + unresolvedImportSubject3 + .hasUnresolvedErrorMessageThat() + .isEqualTo( + String.format( + "state of project %s doesn't permit read", + unresolvableCodeOwnerConfigReferenceProjectNotReadable.project().get())); + unresolvedImportSubject3.assertNoWebLinks().assertNoImports(); + assertThat(checkCodeOwnerInfo) .hasDebugLogsThatContainAllOf( String.format( @@ -887,18 +1147,20 @@ } @Test - public void debugLogsContainUnresolvedTransitiveImports() throws Exception { + public void checkWithUnresolvedTransitiveImports() throws Exception { skipTestIfImportsNotSupportedByCodeOwnersBackend(); - codeOwnerConfigOperations - .newCodeOwnerConfig() - .project(project) - .branch("master") - .folderPath(ROOT_PATH) - .addImport( - CodeOwnerConfigReference.create( - CodeOwnerConfigImportMode.ALL, "/foo/" + getCodeOwnerConfigFileName())) - .create(); + CodeOwnerConfigReference codeOwnerConfigReference = + CodeOwnerConfigReference.create( + CodeOwnerConfigImportMode.ALL, "/foo/" + getCodeOwnerConfigFileName()); + CodeOwnerConfig.Key codeOwnerConfigKey = + codeOwnerConfigOperations + .newCodeOwnerConfig() + .project(project) + .branch("master") + .folderPath(ROOT_PATH) + .addImport(codeOwnerConfigReference) + .create(); CodeOwnerConfigReference unresolvableCodeOwnerConfigReference = CodeOwnerConfigReference.create( @@ -912,6 +1174,40 @@ .create(); CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(ROOT_PATH, user.email()); + + CodeOwnerConfigFileInfoSubject codeOwnerConfigFileInfoSubject = + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().onlyElement(); + codeOwnerConfigFileInfoSubject.assertKey(backend, codeOwnerConfigKey); + codeOwnerConfigFileInfoSubject + .assertNoWebLinks() + .assertNoUnresolvedImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + + CodeOwnerConfigFileInfoSubject importSubject = + codeOwnerConfigFileInfoSubject.hasImportsThat().onlyElement(); + importSubject.hasProjectThat().isEqualTo(project.get()); + importSubject.hasBranchThat().isEqualTo("refs/heads/master"); + importSubject.hasPathThat().isEqualTo(codeOwnerConfigReference.filePath().toString()); + importSubject.assertImportMode(codeOwnerConfigReference.importMode()); + importSubject.assertNoWebLinks().assertNoResolvedImports(); + + CodeOwnerConfigFileInfoSubject transitiveImportSubject = + importSubject.hasUnresolvedImportsThat().onlyElement(); + transitiveImportSubject.hasProjectThat().isEqualTo(project.get()); + transitiveImportSubject.hasBranchThat().isEqualTo("refs/heads/master"); + transitiveImportSubject + .hasPathThat() + .isEqualTo(unresolvableCodeOwnerConfigReference.filePath().toString()); + transitiveImportSubject.assertImportMode(unresolvableCodeOwnerConfigReference.importMode()); + transitiveImportSubject + .hasUnresolvedErrorMessageThat() + .isEqualTo( + String.format( + "code owner config does not exist (revision = %s)", + projectOperations.project(project).getHead("master").name())); + transitiveImportSubject.assertNoWebLinks().assertNoImports(); + assertThat(checkCodeOwnerInfo) .hasDebugLogsThatContainAllOf( String.format( @@ -945,28 +1241,37 @@ 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(); + CodeOwnerConfig.Key codeOwnerConfigKey = + 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) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath("/foo/")); assertThat(checkCodeOwnerInfo) @@ -1001,24 +1306,33 @@ "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(); + CodeOwnerConfig.Key codeOwnerConfigKey = + 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) + .hasCodeOwnerConfigsThat() + .onlyElement() + .assertKey(backend, codeOwnerConfigKey) + .assertNoWebLinks() + .assertNoImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); assertThat(checkCodeOwnerInfo) .hasDebugLogsThatContainAllOf( @@ -1060,32 +1374,58 @@ 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(); + CodeOwnerConfigReference barCodeOwnerConfigReference = + CodeOwnerConfigReference.create( + CodeOwnerConfigImportMode.ALL, "/bar/" + getCodeOwnerConfigFileName()); + CodeOwnerConfig.Key fooCodeOwnerConfigKey = + codeOwnerConfigOperations + .newCodeOwnerConfig() + .project(project) + .branch("master") + .folderPath("/foo/") + .addImport(barCodeOwnerConfigReference) + .create(); - codeOwnerConfigOperations - .newCodeOwnerConfig() - .project(project) - .branch("master") - .folderPath("/bar/") - .addImport( - CodeOwnerConfigReference.create( - CodeOwnerConfigImportMode.ALL, "/baz/" + getCodeOwnerConfigFileName())) - .create(); + CodeOwnerConfigReference bazCodeOwnerConfigReference = + CodeOwnerConfigReference.create( + CodeOwnerConfigImportMode.ALL, "/baz/" + getCodeOwnerConfigFileName()); + CodeOwnerConfig.Key barCodeOwnerConfigKey = + codeOwnerConfigOperations + .newCodeOwnerConfig() + .project(project) + .branch("master") + .folderPath("/bar/") + .addImport(bazCodeOwnerConfigReference) + .create(); - setAsCodeOwners("/baz/", codeOwner); + CodeOwnerConfig.Key bazCodeOwnerConfigKey = setAsCodeOwners("/baz/", codeOwner); String path = "/foo/bar/baz.md"; CodeOwnerCheckInfo checkCodeOwnerInfo = checkCodeOwner(path, codeOwner.email()); assertThat(checkCodeOwnerInfo).isCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); + + CodeOwnerConfigFileInfoSubject codeOwnerConfigFileInfoSubject = + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().onlyElement(); + codeOwnerConfigFileInfoSubject.assertKey(backend, fooCodeOwnerConfigKey); + codeOwnerConfigFileInfoSubject + .assertNoWebLinks() + .assertNoUnresolvedImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + + CodeOwnerConfigFileInfoSubject importSubject = + codeOwnerConfigFileInfoSubject.hasImportsThat().onlyElement(); + importSubject.assertKey(backend, barCodeOwnerConfigKey); + importSubject.assertImportMode(barCodeOwnerConfigReference.importMode()); + importSubject.assertNoWebLinks().assertNoUnresolvedImports().assertNoUnresolvedErrorMessage(); + + CodeOwnerConfigFileInfoSubject transitiveImportSubject = + importSubject.hasImportsThat().onlyElement(); + transitiveImportSubject.assertKey(backend, bazCodeOwnerConfigKey); + transitiveImportSubject.assertImportMode(bazCodeOwnerConfigReference.importMode()); + transitiveImportSubject.assertNoWebLinks().assertNoImports().assertNoUnresolvedErrorMessage(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath("/foo/")); @@ -1114,38 +1454,64 @@ 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(); + CodeOwnerConfigReference barCodeOwnerConfigReference = + CodeOwnerConfigReference.create( + CodeOwnerConfigImportMode.ALL, "/bar/" + getCodeOwnerConfigFileName()); + CodeOwnerConfig.Key fooCodeOwnerConfigKey = + codeOwnerConfigOperations + .newCodeOwnerConfig() + .project(project) + .branch("master") + .folderPath("/foo/") + .addImport(barCodeOwnerConfigReference) + .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(); + CodeOwnerConfigReference bazCodeOwnerConfigReference = + CodeOwnerConfigReference.create( + CodeOwnerConfigImportMode.GLOBAL_CODE_OWNER_SETS_ONLY, + "/baz/" + getCodeOwnerConfigFileName()); + CodeOwnerConfig.Key barCodeOwnerConfigKey = + codeOwnerConfigOperations + .newCodeOwnerConfig() + .project(project) + .branch("master") + .folderPath("/bar/") + .addCodeOwnerSet( + CodeOwnerSet.builder() + .addPathExpression(testPathExpressions.matchFileType("md")) + .addImport(bazCodeOwnerConfigReference) + .build()) + .create(); - setAsCodeOwners("/baz/", mdCodeOwner); + CodeOwnerConfig.Key bazCodeOwnerConfigKey = 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(); + + CodeOwnerConfigFileInfoSubject codeOwnerConfigFileInfoSubject = + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().onlyElement(); + codeOwnerConfigFileInfoSubject.assertKey(backend, fooCodeOwnerConfigKey); + codeOwnerConfigFileInfoSubject + .assertNoWebLinks() + .assertNoUnresolvedImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + + CodeOwnerConfigFileInfoSubject importSubject = + codeOwnerConfigFileInfoSubject.hasImportsThat().onlyElement(); + importSubject.assertKey(backend, barCodeOwnerConfigKey); + importSubject.assertImportMode(barCodeOwnerConfigReference.importMode()); + importSubject.assertNoWebLinks().assertNoUnresolvedImports().assertNoUnresolvedErrorMessage(); + + CodeOwnerConfigFileInfoSubject transitiveImportSubject = + importSubject.hasImportsThat().onlyElement(); + transitiveImportSubject.assertKey(backend, bazCodeOwnerConfigKey); + transitiveImportSubject.assertImportMode(bazCodeOwnerConfigReference.importMode()); + transitiveImportSubject.assertNoWebLinks().assertNoImports().assertNoUnresolvedErrorMessage(); + assertThat(checkCodeOwnerInfo) .hasCodeOwnerConfigFilePathsThat() .containsExactly(getCodeOwnerConfigFilePath("/foo/")); @@ -1174,6 +1540,26 @@ checkCodeOwnerInfo = checkCodeOwner(path, user.email()); assertThat(checkCodeOwnerInfo).isNotCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); + + codeOwnerConfigFileInfoSubject = + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().onlyElement(); + codeOwnerConfigFileInfoSubject.assertKey(backend, fooCodeOwnerConfigKey); + codeOwnerConfigFileInfoSubject + .assertNoWebLinks() + .assertNoUnresolvedImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + + importSubject = codeOwnerConfigFileInfoSubject.hasImportsThat().onlyElement(); + importSubject.assertKey(backend, barCodeOwnerConfigKey); + importSubject.assertImportMode(barCodeOwnerConfigReference.importMode()); + importSubject.assertNoWebLinks().assertNoUnresolvedImports().assertNoUnresolvedErrorMessage(); + + transitiveImportSubject = importSubject.hasImportsThat().onlyElement(); + transitiveImportSubject.assertKey(backend, bazCodeOwnerConfigKey); + transitiveImportSubject.assertImportMode(bazCodeOwnerConfigReference.importMode()); + transitiveImportSubject.assertNoWebLinks().assertNoImports().assertNoUnresolvedErrorMessage(); + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); assertThat(checkCodeOwnerInfo) .hasDebugLogsThatContainAllOf( @@ -1199,6 +1585,21 @@ checkCodeOwnerInfo = checkCodeOwner(path, mdCodeOwner.email()); assertThat(checkCodeOwnerInfo).isNotCodeOwner(); assertThat(checkCodeOwnerInfo).isResolvable(); + + codeOwnerConfigFileInfoSubject = + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigsThat().onlyElement(); + codeOwnerConfigFileInfoSubject.assertKey(backend, fooCodeOwnerConfigKey); + codeOwnerConfigFileInfoSubject + .assertNoWebLinks() + .assertNoUnresolvedImports() + .assertNoUnresolvedErrorMessage() + .assertNoImportMode(); + + importSubject = codeOwnerConfigFileInfoSubject.hasImportsThat().onlyElement(); + importSubject.assertKey(backend, barCodeOwnerConfigKey); + importSubject.assertImportMode(barCodeOwnerConfigReference.importMode()); + importSubject.assertNoWebLinks().assertNoImports().assertNoUnresolvedErrorMessage(); + assertThat(checkCodeOwnerInfo).hasCodeOwnerConfigFilePathsThat().isEmpty(); assertThat(checkCodeOwnerInfo) .hasDebugLogsThatContainAllOf( @@ -1571,12 +1972,17 @@ } private String getCodeOwnerConfigFilePath(String folderPath) { - assertThat(folderPath).startsWith("/"); - assertThat(folderPath).endsWith("/"); + if (!folderPath.startsWith("/")) { + folderPath = "/" + folderPath; + } + if (!folderPath.endsWith("/")) { + folderPath = folderPath + "/"; + } return folderPath + getCodeOwnerConfigFileName(); } - private void setAsRootCodeOwners(String... emails) { + @CanIgnoreReturnValue + private CodeOwnerConfig.Key setAsRootCodeOwners(String... emails) { TestCodeOwnerConfigCreation.Builder builder = codeOwnerConfigOperations .newCodeOwnerConfig() @@ -1584,11 +1990,12 @@ .branch("master") .folderPath(ROOT_PATH); Arrays.stream(emails).forEach(builder::addCodeOwnerEmail); - builder.create(); + return builder.create(); } - private void setAsDefaultCodeOwner(String email) { - codeOwnerConfigOperations + @CanIgnoreReturnValue + private CodeOwnerConfig.Key setAsDefaultCodeOwner(String email) { + return codeOwnerConfigOperations .newCodeOwnerConfig() .project(project) .branch(RefNames.REFS_CONFIG)
diff --git a/resources/Documentation/rest-api.md b/resources/Documentation/rest-api.md index fdaad90..9481c29 100644 --- a/resources/Documentation/rest-api.md +++ b/resources/Documentation/rest-api.md
@@ -301,6 +301,13 @@ { "is_code_owner": false, "is_resolvable": false, + "code_owner_configs": [ + { + "project": "foo/bar", + "branch": "master", + "path": "/OWNERS" + } + ] "can_read_ref": true, "code_owner_config_file_paths": [ "/OWNERS", @@ -941,6 +948,7 @@ | --------------- | ----------- | | `is_code_owner` | Whether the given email owns the specified path in the branch. True if: a) the given email is resolvable (see field `is_resolvable') and b) any code owner config file assigns codeownership to the email for the path (see field `code_owner_config_file_paths`) or the email is configured as default code owner (see field `is_default_code_owner` or the email is configured as global code owner (see field `is_global_code_owner`) or the user is a fallback code owner (see field `is_fallback_code_owner`). | `is_resolvable` | Whether the given email is resolvable for the specified user or the calling user if no user was specified. +| `code_owner_configs` | The code owner config files that have been inspected to check the code owner as [CodeOwnerConfigFileInfo](#code-owner-config-file-info) entities. | `can_read_ref` | Whether the user to which the given email was resolved has read permissions on the branch. Not set if the given email is not resolvable or if the given email is the all users wildcard (aka '*'). | `can_see_change`| Whether the user to which the given email was resolved can see the specified change. Not set if the given email is not resolvable, if the given email is the all users wildcard (aka '*') or if no change was specified. | `can_approve_change`| Whether the user to which the given email was resolved can code-owner approve the specified change. Being able to code-owner approve the change means that the user has permissions to vote on the label that is [required as code owner approval](config.html#pluginCodeOwnersRequiredApproval). Other permissions are not considered for computing this flag. In particular missing read permissions on the change don't have any effect on this flag. Whether the user misses read permissions on the change (and hence cannot apply the code owner approval) can be seen from the `can_see_change` flag. Not set if the given email is not resolvable, if the given email is the all users wildcard (aka '*') or if no change was specified.