Allow to set a verbosity level on check code owner config REST endpoints

The check code owner config REST endpoints accept a verbosity level as
input now that controls which kind of issues should be reported.

For example, by setting the verbosity level to fatal one can use these
REST endpoint to only check for non-parseable code owner config files,
without needing to filter out other results manually. E.g. checking for
non-parseable code owner config files should be done before turning on
the code owners functionality for a project.

Change-Id: I29f301afd7a21815f00a1191bacd385caeb25631
Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInRevisionInput.java b/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInRevisionInput.java
index 45c4156..daad0df 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInRevisionInput.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInRevisionInput.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.plugins.codeowners.api;
 
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
+
 /**
  * The input for the {@link
  * com.google.gerrit.plugins.codeowners.restapi.CheckCodeOwnerConfigFilesInRevision} REST endpoint.
@@ -26,4 +28,19 @@
    * <p>By default unset, which means that all code owner config files should be validated.
    */
   public String path;
+
+  /**
+   * Level that controls which code owner config file issues are returned.
+   *
+   * <p>The following values are supported:
+   *
+   * <ul>
+   *   <li>{@code FATAL}: only fatal issues are returned
+   *   <li>{@code ERROR}: only fatal and error issues are returned
+   *   <li>{@code WARNING}: all issues (warning, error and fatal) are returned
+   * </ul>
+   *
+   * <p>If unset, {@code WARNING} is used.
+   */
+  public ConsistencyProblemInfo.Status verbosity;
 }
diff --git a/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInput.java b/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInput.java
index fd865b8..37f6757 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInput.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/CheckCodeOwnerConfigFilesInput.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.plugins.codeowners.api;
 
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
 import java.util.List;
 
 /**
@@ -46,4 +47,19 @@
    * <p>By default unset, which means that all code owner config files should be validated.
    */
   public String path;
+
+  /**
+   * Level that controls which code owner config file issues are returned.
+   *
+   * <p>The following values are supported:
+   *
+   * <ul>
+   *   <li>{@code FATAL}: only fatal issues are returned
+   *   <li>{@code ERROR}: only fatal and error issues are returned
+   *   <li>{@code WARNING}: all issues (warning, error and fatal) are returned
+   * </ul>
+   *
+   * <p>If unset, {@code WARNING} is used.
+   */
+  public ConsistencyProblemInfo.Status verbosity;
 }
diff --git a/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwners.java
index 4d5159f..ccce1ed 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwners.java
@@ -39,6 +39,7 @@
     private boolean validateDisabledBranches;
     private ImmutableList<String> branches;
     private String path;
+    private ConsistencyProblemInfo.Status verbosity;
 
     /**
      * Includes code owner config files in branches for which the code owners functionality is
@@ -101,6 +102,30 @@
     }
 
     /**
+     * Sets the verbosity level that controls which kind of issues should be returned.
+     *
+     * <p>The following values are supported:
+     *
+     * <ul>
+     *   <li>{@code FATAL}: only fatal issues are returned
+     *   <li>{@code ERROR}: only fatal and error issues are returned
+     *   <li>{@code WARNING}: all issues (warning, error and fatal) are returned
+     * </ul>
+     *
+     * <p>If unset, {@code WARNING} is used.
+     */
+    public CheckCodeOwnerConfigFilesRequest setVerbosity(
+        @Nullable ConsistencyProblemInfo.Status verbosity) {
+      this.verbosity = verbosity;
+      return this;
+    }
+
+    /** Gets the verbosity level that controls which kind of issues should be returned. */
+    public ConsistencyProblemInfo.Status getVerbosity() {
+      return verbosity;
+    }
+
+    /**
      * Executes the request to check the code owner config files and retrieves the result of the
      * validation.
      */
diff --git a/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwnersImpl.java b/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwnersImpl.java
index 6011572..54b37ec 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwnersImpl.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/ProjectCodeOwnersImpl.java
@@ -86,6 +86,7 @@
           input.validateDisabledBranches = isValidateDisabledBranches();
           input.branches = getBranches();
           input.path = getPath();
+          input.verbosity = getVerbosity();
           return checkCodeOwnerConfigFiles.apply(projectResource, input).value();
         } catch (Exception e) {
           throw asRestApiException("Cannot check code owner config files", e);
diff --git a/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwners.java b/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwners.java
index 1471280..df9ed04 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwners.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwners.java
@@ -37,6 +37,7 @@
   /** Request to check code owner config files. */
   abstract class CheckCodeOwnerConfigFilesRequest {
     private String path;
+    private ConsistencyProblemInfo.Status verbosity;
 
     /**
      * Sets a glob that limits the validation to code owner config files that have a path that
@@ -57,6 +58,30 @@
     }
 
     /**
+     * Sets the verbosity level that controls which kind of issues should be returned.
+     *
+     * <p>The following values are supported:
+     *
+     * <ul>
+     *   <li>{@code FATAL}: only fatal issues are returned
+     *   <li>{@code ERROR}: only fatal and error issues are returned
+     *   <li>{@code WARNING}: all issues (warning, error and fatal) are returned
+     * </ul>
+     *
+     * <p>If unset, {@code WARNING} is used.
+     */
+    public CheckCodeOwnerConfigFilesRequest setVerbosity(
+        @Nullable ConsistencyProblemInfo.Status verbosity) {
+      this.verbosity = verbosity;
+      return this;
+    }
+
+    /** Gets the verbosity level that controls which kind of issues should be returned. */
+    public ConsistencyProblemInfo.Status getVerbosity() {
+      return verbosity;
+    }
+
+    /**
      * Executes the request to check the code owner config files and retrieves the result of the
      * validation.
      */
diff --git a/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwnersImpl.java b/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwnersImpl.java
index e05b281..817639f 100644
--- a/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwnersImpl.java
+++ b/java/com/google/gerrit/plugins/codeowners/api/RevisionCodeOwnersImpl.java
@@ -51,6 +51,7 @@
           CheckCodeOwnerConfigFilesInRevisionInput input =
               new CheckCodeOwnerConfigFilesInRevisionInput();
           input.path = getPath();
+          input.verbosity = getVerbosity();
           return checkCodeOwnerConfigFilesInRevision.apply(revisionResource, input).value();
         } catch (Exception e) {
           throw asRestApiException("Cannot check code owner config files", e);
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
index 07788ca..6829041 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFiles.java
@@ -24,6 +24,7 @@
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Multimaps;
 import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
@@ -126,7 +127,8 @@
         .forEach(
             branchNameKey ->
                 resultsByBranchBuilder.put(
-                    branchNameKey.branch(), checkBranch(input.path, branchNameKey)));
+                    branchNameKey.branch(),
+                    checkBranch(input.path, branchNameKey, input.verbosity)));
     return Response.ok(resultsByBranchBuilder.build());
   }
 
@@ -139,7 +141,9 @@
   }
 
   private Map<String, List<ConsistencyProblemInfo>> checkBranch(
-      String pathGlob, BranchNameKey branchNameKey) {
+      String pathGlob,
+      BranchNameKey branchNameKey,
+      @Nullable ConsistencyProblemInfo.Status verbosity) {
     ListMultimap<String, ConsistencyProblemInfo> problemsByPath = LinkedListMultimap.create();
     CodeOwnerBackend codeOwnerBackend = codeOwnersPluginConfiguration.getBackend(branchNameKey);
     codeOwnerConfigScanner.visit(
@@ -147,7 +151,7 @@
         codeOwnerConfig -> {
           problemsByPath.putAll(
               codeOwnerBackend.getFilePath(codeOwnerConfig.key()).toString(),
-              checkCodeOwnerConfig(codeOwnerBackend, codeOwnerConfig));
+              checkCodeOwnerConfig(codeOwnerBackend, codeOwnerConfig, verbosity));
           return true;
         },
         (codeOwnerConfigFilePath, configInvalidException) -> {
@@ -162,28 +166,42 @@
   }
 
   private ImmutableList<ConsistencyProblemInfo> checkCodeOwnerConfig(
-      CodeOwnerBackend codeOwnerBackend, CodeOwnerConfig codeOwnerConfig) {
+      CodeOwnerBackend codeOwnerBackend,
+      CodeOwnerConfig codeOwnerConfig,
+      @Nullable ConsistencyProblemInfo.Status verbosity) {
     return codeOwnerConfigValidator
         .validateCodeOwnerConfig(
             currentUser.get().asIdentifiedUser(), codeOwnerBackend, codeOwnerConfig)
-        .map(CheckCodeOwnerConfigFiles::createConsistencyProblemInfo)
+        .map(
+            commitValidationMessage ->
+                createConsistencyProblemInfo(commitValidationMessage, verbosity))
         .filter(Optional::isPresent)
         .map(Optional::get)
         .collect(toImmutableList());
   }
 
   public static Optional<ConsistencyProblemInfo> createConsistencyProblemInfo(
-      CommitValidationMessage commitValidationMessage) {
+      CommitValidationMessage commitValidationMessage,
+      @Nullable ConsistencyProblemInfo.Status verbosity) {
     switch (commitValidationMessage.getType()) {
       case FATAL:
         return Optional.of(
             new ConsistencyProblemInfo(
                 ConsistencyProblemInfo.Status.FATAL, commitValidationMessage.getMessage()));
       case ERROR:
+        if (ConsistencyProblemInfo.Status.FATAL.equals(verbosity)) {
+          // errors should not be reported
+          return Optional.empty();
+        }
         return Optional.of(
             new ConsistencyProblemInfo(
                 ConsistencyProblemInfo.Status.ERROR, commitValidationMessage.getMessage()));
       case WARNING:
+        if (ConsistencyProblemInfo.Status.FATAL.equals(verbosity)
+            || ConsistencyProblemInfo.Status.ERROR.equals(verbosity)) {
+          // warnings should not be reported
+          return Optional.empty();
+        }
         return Optional.of(
             new ConsistencyProblemInfo(
                 ConsistencyProblemInfo.Status.WARNING, commitValidationMessage.getMessage()));
diff --git a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFilesInRevision.java b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFilesInRevision.java
index 737e389..b3e2494 100644
--- a/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFilesInRevision.java
+++ b/java/com/google/gerrit/plugins/codeowners/restapi/CheckCodeOwnerConfigFilesInRevision.java
@@ -122,7 +122,10 @@
                                   changedFile,
                                   rw,
                                   commit)
-                              .map(CheckCodeOwnerConfigFiles::createConsistencyProblemInfo)
+                              .map(
+                                  commitValidationMessage ->
+                                      CheckCodeOwnerConfigFiles.createConsistencyProblemInfo(
+                                          commitValidationMessage, input.verbosity))
                               .filter(Optional::isPresent)
                               .map(Optional::get)
                               .collect(toImmutableList()))));
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
index 2c7b15d..299a4c4 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesIT.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.acceptance.config.GerritConfig;
 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.BranchNameKey;
 import com.google.gerrit.entities.Permission;
 import com.google.gerrit.entities.Project;
@@ -45,6 +46,7 @@
 import com.google.gerrit.plugins.codeowners.config.BackendConfig;
 import com.google.gerrit.plugins.codeowners.config.StatusConfig;
 import com.google.inject.Inject;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import org.junit.Before;
@@ -151,7 +153,7 @@
   @Test
   public void nonParseableCodeOwnerConfigFile() throws Exception {
     String codeOwnerConfigPath = "/" + getCodeOwnerConfigFileName();
-    createInvalidCodeOwnerConfig(codeOwnerConfigPath);
+    createNonParseableCodeOwnerConfig(codeOwnerConfigPath);
 
     assertThat(checkCodeOwnerConfigFilesIn(project))
         .containsExactly(
@@ -475,6 +477,87 @@
                             pathOfInvalidConfig3)))));
   }
 
+  @Test
+  public void allIssuesAreReturnedIfNoLevelIsSpecified() throws Exception {
+    testIssuesAreFilteredByVerbosity(
+        /** verbosity */
+        null);
+  }
+
+  @Test
+  public void allIssuesAreReturnedIfLevelIsSetToWarning() throws Exception {
+    testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.WARNING);
+  }
+
+  @Test
+  public void onlyFatalAndErrorIssuesAreReturnedIfLevelIsSetToError() throws Exception {
+    testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.ERROR);
+  }
+
+  @Test
+  public void onlyFatalIssuesAreReturnedIfLevelIsSetToFatal() throws Exception {
+    testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.FATAL);
+  }
+
+  private void testIssuesAreFilteredByVerbosity(@Nullable ConsistencyProblemInfo.Status verbosity)
+      throws Exception {
+    // create a non-parseable code owner config, that will be reported as fatal
+    String pathOfNonParseableCodeOwnerConfig = "/" + getCodeOwnerConfigFileName();
+    createNonParseableCodeOwnerConfig(pathOfNonParseableCodeOwnerConfig);
+
+    // create an invalid code owner config, that will be reported as error
+    CodeOwnerConfig.Key keyOfInvalidConfig =
+        codeOwnerConfigOperations
+            .newCodeOwnerConfig()
+            .project(project)
+            .branch("master")
+            .folderPath("/foo/")
+            .addCodeOwnerEmail("unknown@example.com")
+            .create();
+    String pathOfInvalidConfig =
+        codeOwnerConfigOperations.codeOwnerConfig(keyOfInvalidConfig).getFilePath();
+
+    // there is currently nothing that triggers a warning
+
+    Map<String, List<ConsistencyProblemInfo>> expectedMasterIssues = new HashMap<>();
+    // the fatal issue is always expected
+    expectedMasterIssues.put(
+        pathOfNonParseableCodeOwnerConfig,
+        ImmutableList.of(
+            fatal(
+                String.format(
+                    "invalid code owner config file '%s':\n  %s",
+                    pathOfNonParseableCodeOwnerConfig,
+                    getParsingErrorMessage(
+                        ImmutableMap.of(
+                            FindOwnersBackend.class,
+                            "invalid line: INVALID",
+                            ProtoBackend.class,
+                            "1:8: Expected \"{\"."))))));
+    if (verbosity == null
+        || ConsistencyProblemInfo.Status.ERROR.equals(verbosity)
+        || ConsistencyProblemInfo.Status.WARNING.equals(verbosity)) {
+      expectedMasterIssues.put(
+          pathOfInvalidConfig,
+          ImmutableList.of(
+              error(
+                  String.format(
+                      "code owner email 'unknown@example.com' in '%s' cannot be"
+                          + " resolved for admin",
+                      pathOfInvalidConfig))));
+    }
+
+    Map<String, Map<String, List<ConsistencyProblemInfo>>> result =
+        projectCodeOwnersApiFactory
+            .project(project)
+            .checkCodeOwnerConfigFiles()
+            .setVerbosity(verbosity)
+            .check();
+    assertThat(result)
+        .containsExactly(
+            "refs/heads/master", expectedMasterIssues, "refs/meta/config", ImmutableMap.of());
+  }
+
   private ConsistencyProblemInfo fatal(String message) {
     return new ConsistencyProblemInfo(ConsistencyProblemInfo.Status.FATAL, message);
   }
@@ -498,7 +581,7 @@
     throw new IllegalStateException("unknown code owner backend: " + backend.getClass().getName());
   }
 
-  private void createInvalidCodeOwnerConfig(String path) throws Exception {
+  private void createNonParseableCodeOwnerConfig(String path) throws Exception {
     disableCodeOwnersForProject(project);
     String changeId =
         createChange("Add invalid code owners file", JgitPath.of(path).get(), "INVALID")
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesInRevisionIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesInRevisionIT.java
index 57b9506..095c4af 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesInRevisionIT.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CheckCodeOwnerConfigFilesInRevisionIT.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.plugins.codeowners.JgitPath;
@@ -33,6 +34,7 @@
 import com.google.gerrit.plugins.codeowners.backend.proto.ProtoCodeOwnerConfigParser;
 import com.google.gerrit.plugins.codeowners.config.BackendConfig;
 import com.google.gerrit.plugins.codeowners.config.StatusConfig;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import org.eclipse.jgit.lib.ObjectId;
@@ -398,6 +400,97 @@
                         unknownEmail3, codeOwnerConfigPath3))));
   }
 
+  @Test
+  public void allIssuesAreReturnedIfNoLevelIsSpecified() throws Exception {
+    testIssuesAreFilteredByVerbosity(
+        /** verbosity */
+        null);
+  }
+
+  @Test
+  public void allIssuesAreReturnedIfLevelIsSetToWarning() throws Exception {
+    testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.WARNING);
+  }
+
+  @Test
+  public void onlyFatalAndErrorIssuesAreReturnedIfLevelIsSetToError() throws Exception {
+    testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.ERROR);
+  }
+
+  @Test
+  public void onlyFatalIssuesAreReturnedIfLevelIsSetToFatal() throws Exception {
+    testIssuesAreFilteredByVerbosity(ConsistencyProblemInfo.Status.FATAL);
+  }
+
+  private void testIssuesAreFilteredByVerbosity(@Nullable ConsistencyProblemInfo.Status verbosity)
+      throws Exception {
+    CodeOwnerConfig.Key keyOfNonParseableCodeOwnerConfig = createCodeOwnerConfigKey("/");
+    String pathOfNonParseableCodeOwnerConfig =
+        codeOwnerConfigOperations.codeOwnerConfig(keyOfNonParseableCodeOwnerConfig).getFilePath();
+
+    CodeOwnerConfig.Key keyOfInvalidCodeOwnerConfig = createCodeOwnerConfigKey("/foo/");
+    String pathOfInvalidCodeOwnerConfig =
+        codeOwnerConfigOperations.codeOwnerConfig(keyOfInvalidCodeOwnerConfig).getFilePath();
+    String unknownEmail = "unknown@example.com";
+
+    // create a change with a) a non-parseable code owner config that will be reported as fatal and
+    // b) an invalid code owner config with an unknown email that will be reported as error
+    // (there is currently nothing that triggers a warning)
+    disableCodeOwnersForProject(project);
+    String changeId =
+        createChange(
+                "Add code owners",
+                ImmutableMap.of(
+                    JgitPath.of(pathOfNonParseableCodeOwnerConfig).get(),
+                    "INVALID",
+                    JgitPath.of(pathOfInvalidCodeOwnerConfig).get(),
+                    format(
+                        CodeOwnerConfig.builder(keyOfInvalidCodeOwnerConfig, TEST_REVISION)
+                            .addCodeOwnerSet(
+                                CodeOwnerSet.createWithoutPathExpressions(unknownEmail))
+                            .build())))
+            .getChangeId();
+    enableCodeOwnersForProject(project);
+
+    Map<String, List<ConsistencyProblemInfo>> expectedIssues = new HashMap<>();
+    // the fatal issue is always expected
+    expectedIssues.put(
+        pathOfNonParseableCodeOwnerConfig,
+        ImmutableList.of(
+            fatal(
+                String.format(
+                    "invalid code owner config file '%s':\n  %s",
+                    pathOfNonParseableCodeOwnerConfig,
+                    getParsingErrorMessage(
+                        ImmutableMap.of(
+                            FindOwnersBackend.class,
+                            "invalid line: INVALID",
+                            ProtoBackend.class,
+                            "1:8: Expected \"{\"."))))));
+    if (verbosity == null
+        || ConsistencyProblemInfo.Status.ERROR.equals(verbosity)
+        || ConsistencyProblemInfo.Status.WARNING.equals(verbosity)) {
+      expectedIssues.put(
+          pathOfInvalidCodeOwnerConfig,
+          ImmutableList.of(
+              error(
+                  String.format(
+                      "code owner email '%s' in '%s' cannot be" + " resolved for admin",
+                      unknownEmail, pathOfInvalidCodeOwnerConfig))));
+    } else {
+      expectedIssues.put(pathOfInvalidCodeOwnerConfig, ImmutableList.of());
+    }
+
+    Map<String, List<ConsistencyProblemInfo>> result =
+        changeCodeOwnersApiFactory
+            .change(changeId)
+            .current()
+            .checkCodeOwnerConfigFiles()
+            .setVerbosity(verbosity)
+            .check();
+    assertThat(result).isEqualTo(expectedIssues);
+  }
+
   private ConsistencyProblemInfo fatal(String message) {
     return new ConsistencyProblemInfo(ConsistencyProblemInfo.Status.FATAL, message);
   }