Refactor ReviewerConfig and ReviewerFilterSection

* Extract abstract class ReviewerFilter.
  - Abstracts the Config from REST and reviewer-adding logic in
    Reviewers.
  - Makes it more straight-forward to create ReviewerFilters in tests
    for comparison.
* Introduce ReviewerFilterCollection class.
  - Maintains the collection of ReviewerFilters found in the Config
    object provided to the ReviewerFilterCollection.
  - Handles adding and removing reviewers from a Config object through
    it's own implementation of ReviewerFilter.
* Create new config package.
  - Move ReviewerFilterCollection, ReviewersConfig and
    ReviewersConfigIT into this new package for separation.
* Remove ReviewerConfig#forProject(Project.NameKey) creator.
  - This created a ForProject with a Config "with inheritance" to
    enable getting all ReviewerFilterSections with inheritance
    from a ForProject.
    Since ForProject is also a VersionedMetaData this Config was later
    replaced if the ForProject was loaded. This meant that ForProjects
    had different responsibilities and behavior depending on if they
    were loaded or not.
  - Replace this creator with new method:
    ReviewersConfig#filtersWithInheritance(Project.NameKey)
    with the sole responsibility of returning all inherited
    ReviewerFilters.

In preparation of supporting CC.
https://bugs.chromium.org/p/gerrit/issues/detail?id=11676

Feature: Issue 12903
Change-Id: Ie0593297bd5e52749993966458aa13dd42f28bfd
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/GetReviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/GetReviewers.java
index 4aa8602..212b544 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/GetReviewers.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/GetReviewers.java
@@ -20,11 +20,12 @@
 import com.google.gerrit.server.project.ProjectResource;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig;
 import java.util.List;
 
 /**
- * GET REST end-point for getting all configured {@link ReviewerFilterSection}s of a project, local
- * and inherited.
+ * GET REST end-point for getting all configured {@link ReviewerFilter}s of a project, local and
+ * inherited.
  */
 @Singleton
 class GetReviewers implements RestReadView<ProjectResource> {
@@ -36,8 +37,7 @@
   }
 
   @Override
-  public Response<List<ReviewerFilterSection>> apply(ProjectResource resource)
-      throws RestApiException {
-    return Response.ok(config.forProject(resource.getNameKey()).getReviewerFilterSections());
+  public Response<List<ReviewerFilter>> apply(ProjectResource resource) throws RestApiException {
+    return Response.ok(config.filtersWithInheritance(resource.getNameKey()));
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Module.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/Module.java
index 4bee073..8de2cc4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/Module.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.server.change.ReviewerSuggestion;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig;
 
 public class Module extends FactoryModule {
   private final boolean enableREST;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/PutReviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/PutReviewers.java
index 5f55db2..9efc8bd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/PutReviewers.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/PutReviewers.java
@@ -41,12 +41,13 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.reviewers.PutReviewers.Input;
+import com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig;
 import java.io.IOException;
 import java.util.List;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 
-/** PUT REST end-point that removes or adds a reveiwer to a {@link ReviewerFilterSection}. */
+/** PUT REST end-point that removes or adds a reviewer to a {@link ReviewerFilter}. */
 @Singleton
 class PutReviewers implements RestModifyView<ProjectResource, Input> {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -89,13 +90,10 @@
   }
 
   @Override
-  public Response<List<ReviewerFilterSection>> apply(ProjectResource rsrc, Input input)
+  public Response<List<ReviewerFilter>> apply(ProjectResource rsrc, Input input)
       throws RestApiException, PermissionBackendException {
     Project.NameKey projectName = rsrc.getNameKey();
-    ReviewersConfig.ForProject cfg = config.forProject(projectName);
-    if (cfg == null) {
-      throw new ResourceNotFoundException("Project" + projectName.get() + " not found");
-    }
+    ReviewersConfig.ForProject forProject = new ReviewersConfig.ForProject();
 
     PermissionBackend.WithUser userPermission = permissionBackend.user(rsrc.getUser());
     if (!userPermission.project(rsrc.getNameKey()).testOrFalse(ProjectPermission.WRITE_CONFIG)
@@ -109,26 +107,26 @@
       }
       try {
         StringBuilder message = new StringBuilder(pluginName).append(" plugin: ");
-        cfg.load(md);
+        forProject.load(md);
         if (input.action == Action.ADD) {
           message
               .append("Add reviewer ")
               .append(input.reviewer)
               .append(" to filter ")
               .append(input.filter);
-          cfg.addReviewer(input.filter, input.reviewer);
+          forProject.addReviewer(input.filter, input.reviewer);
         } else {
           message
               .append("Remove reviewer ")
               .append(input.reviewer)
               .append(" from filter ")
               .append(input.filter);
-          cfg.removeReviewer(input.filter, input.reviewer);
+          forProject.removeReviewer(input.filter, input.reviewer);
         }
         message.append("\n");
         md.setMessage(message.toString());
         try {
-          cfg.commit(md);
+          forProject.commit(md);
           projectCache.evict(projectName);
         } catch (IOException e) {
           if (e.getCause() instanceof ConfigInvalidException) {
@@ -149,7 +147,7 @@
     } catch (IOException err) {
       throw new ResourceNotFoundException(projectName.get(), err);
     }
-    return Response.ok(cfg.getReviewerFilterSections());
+    return Response.ok(config.filtersWithInheritance(projectName));
   }
 
   private void validateReviewer(String reviewer) throws RestApiException {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilterSection.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilter.java
similarity index 77%
rename from src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilterSection.java
rename to src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilter.java
index a083239..99a0c59 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilterSection.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewerFilter.java
@@ -21,19 +21,14 @@
  * Representation of a filter section in reviewers.config. Example:
  *
  * <pre>
- * [filter "'"]
+ * [filter "*"]
  *   reviewer = joe
  *   reviewer = jane
  * </pre>
  */
-class ReviewerFilterSection {
-  private final String filter;
-  private final Set<String> reviewers;
-
-  ReviewerFilterSection(String filter, Set<String> reviewers) {
-    this.filter = filter;
-    this.reviewers = reviewers;
-  }
+public abstract class ReviewerFilter {
+  protected String filter;
+  protected Set<String> reviewers;
 
   String getFilter() {
     return filter;
@@ -45,8 +40,8 @@
 
   @Override
   public boolean equals(Object o) {
-    if (o instanceof ReviewerFilterSection) {
-      ReviewerFilterSection other = ((ReviewerFilterSection) o);
+    if (o instanceof ReviewerFilter) {
+      ReviewerFilter other = ((ReviewerFilter) o);
       return Objects.equals(filter, other.filter) && Objects.equals(reviewers, other.reviewers);
     }
     return false;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java
index 04e301b..75a976d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/Reviewers.java
@@ -41,6 +41,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig;
 import java.util.List;
 import java.util.Set;
 
@@ -100,14 +101,14 @@
       @Nullable Change.Id changeId,
       @Nullable String query,
       Set<Account.Id> candidates) {
-    List<ReviewerFilterSection> sections = getSections(projectName);
+    List<ReviewerFilter> filters = getFilters(projectName);
 
-    if (sections.isEmpty() || changeId == null) {
+    if (filters.isEmpty() || changeId == null) {
       return ImmutableSet.of();
     }
 
     try {
-      Set<String> reviewers = findReviewers(changeId.get(), sections);
+      Set<String> reviewers = findReviewers(changeId.get(), filters);
       if (!reviewers.isEmpty()) {
         return resolver.resolve(reviewers, projectName, changeId.get(), null).stream()
             .map(a -> suggestedReviewer(a))
@@ -126,9 +127,9 @@
     return reviewer;
   }
 
-  private List<ReviewerFilterSection> getSections(Project.NameKey projectName) {
+  private List<ReviewerFilter> getFilters(Project.NameKey projectName) {
     // TODO(davido): we have to cache per project configuration
-    return config.forProject(projectName).getReviewerFilterSections();
+    return config.filtersWithInheritance(projectName);
   }
 
   private void onEvent(ChangeEvent event) {
@@ -142,16 +143,16 @@
     }
     Project.NameKey projectName = Project.nameKey(c.project);
 
-    List<ReviewerFilterSection> sections = getSections(projectName);
+    List<ReviewerFilter> filters = getFilters(projectName);
 
-    if (sections.isEmpty()) {
+    if (filters.isEmpty()) {
       return;
     }
 
     AccountInfo uploader = event.getWho();
     int changeNumber = c._number;
     try {
-      Set<String> reviewers = findReviewers(changeNumber, sections);
+      Set<String> reviewers = findReviewers(changeNumber, filters);
       if (reviewers.isEmpty()) {
         return;
       }
@@ -168,21 +169,20 @@
     }
   }
 
-  private Set<String> findReviewers(int change, List<ReviewerFilterSection> sections)
+  private Set<String> findReviewers(int change, List<ReviewerFilter> filters)
       throws StorageException, QueryParseException {
     ImmutableSet.Builder<String> reviewers = ImmutableSet.builder();
-    List<ReviewerFilterSection> found = findReviewerSections(change, sections);
-    for (ReviewerFilterSection s : found) {
+    List<ReviewerFilter> found = findReviewerFilters(change, filters);
+    for (ReviewerFilter s : found) {
       reviewers.addAll(s.getReviewers());
     }
     return reviewers.build();
   }
 
-  private List<ReviewerFilterSection> findReviewerSections(
-      int change, List<ReviewerFilterSection> sections)
+  private List<ReviewerFilter> findReviewerFilters(int change, List<ReviewerFilter> filters)
       throws StorageException, QueryParseException {
-    ImmutableList.Builder<ReviewerFilterSection> found = ImmutableList.builder();
-    for (ReviewerFilterSection s : sections) {
+    ImmutableList.Builder<ReviewerFilter> found = ImmutableList.builder();
+    for (ReviewerFilter s : filters) {
       if (Strings.isNullOrEmpty(s.getFilter()) || s.getFilter().equals("*")) {
         found.add(s);
       } else if (filterMatch(change, s.getFilter())) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewerFilterCollection.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewerFilterCollection.java
new file mode 100644
index 0000000..dab0ca0
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewerFilterCollection.java
@@ -0,0 +1,73 @@
+// Copyright (C) 2020 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.googlesource.gerrit.plugins.reviewers.config;
+
+import static com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig.KEY_REVIEWER;
+import static com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig.SECTION_FILTER;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.googlesource.gerrit.plugins.reviewers.ReviewerFilter;
+import java.util.List;
+import org.eclipse.jgit.lib.Config;
+
+/** Representation of the collection of {@link ReviewerFilter}s in a {@link Config}. */
+class ReviewerFilterCollection {
+
+  private final Config cfg;
+
+  ReviewerFilterCollection(Config cfg) {
+    this.cfg = cfg;
+  }
+
+  List<ReviewerFilter> getAll() {
+    ImmutableList.Builder<ReviewerFilter> b = ImmutableList.builder();
+    for (String f : cfg.getSubsections(SECTION_FILTER)) {
+      b.add(new ReviewerFilterSection(f));
+    }
+    return b.build();
+  }
+
+  ReviewerFilterSection get(String filter) {
+    return new ReviewerFilterSection(filter);
+  }
+
+  class ReviewerFilterSection extends ReviewerFilter {
+
+    private ReviewerFilterSection(String filter) {
+      this.filter = filter;
+      this.reviewers = Sets.newHashSet(cfg.getStringList(SECTION_FILTER, filter, KEY_REVIEWER));
+    }
+
+    public void removeReviewer(String reviewer) {
+      reviewers.remove(reviewer);
+      save();
+    }
+
+    public void addReviewer(String reviewer) {
+      reviewers.add(reviewer);
+      save();
+    }
+
+    private void save() {
+      if (this.reviewers.isEmpty()) {
+        cfg.unsetSection(SECTION_FILTER, filter);
+      } else {
+        cfg.setStringList(SECTION_FILTER, filter, KEY_REVIEWER, Lists.newArrayList(this.reviewers));
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfig.java b/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfig.java
similarity index 61%
rename from src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfig.java
rename to src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfig.java
index 96a4f6b..e440dcd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfig.java
@@ -12,11 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.reviewers;
+package com.googlesource.gerrit.plugins.reviewers.config;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
@@ -26,9 +25,8 @@
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.reviewers.ReviewerFilter;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.CommitBuilder;
@@ -39,9 +37,9 @@
 public class ReviewersConfig {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  static final String FILENAME = "reviewers.config";
-  static final String SECTION_FILTER = "filter";
-  static final String KEY_REVIEWER = "reviewer";
+  @VisibleForTesting public static final String FILENAME = "reviewers.config";
+  @VisibleForTesting public static final String SECTION_FILTER = "filter";
+  @VisibleForTesting public static final String KEY_REVIEWER = "reviewer";
   private static final String KEY_ENABLE_REST = "enableREST";
   private static final String KEY_SUGGEST_ONLY = "suggestOnly";
   private static final String KEY_IGNORE_WIP = "ignoreWip";
@@ -63,7 +61,7 @@
     this.ignoreWip = cfg.getBoolean(pluginName, null, KEY_IGNORE_WIP, true);
   }
 
-  public ForProject forProject(Project.NameKey projectName) {
+  public List<ReviewerFilter> filtersWithInheritance(Project.NameKey projectName) {
     Config cfg;
     try {
       cfg = cfgFactory.getProjectPluginConfigWithMergedInheritance(projectName, pluginName);
@@ -71,7 +69,7 @@
       logger.atSevere().log("Unable to get config for project %s", projectName.get());
       cfg = new Config();
     }
-    return new ForProject(cfg);
+    return new ReviewerFilterCollection(cfg).getAll();
   }
 
   public boolean enableREST() {
@@ -86,49 +84,16 @@
     return ignoreWip;
   }
 
-  static class ForProject extends VersionedMetaData {
+  public static class ForProject extends VersionedMetaData {
     private Config cfg;
+    private ReviewerFilterCollection filters;
 
-    ForProject(Config cfg) {
-      this.cfg = cfg;
+    public void addReviewer(String filter, String reviewer) {
+      filters.get(filter).addReviewer(reviewer);
     }
 
-    List<ReviewerFilterSection> getReviewerFilterSections() {
-      ImmutableList.Builder<ReviewerFilterSection> b = ImmutableList.builder();
-      for (String f : cfg.getSubsections(SECTION_FILTER)) {
-        b.add(newReviewerFilterSection(f));
-      }
-      return b.build();
-    }
-
-    void addReviewer(String filter, String reviewer) {
-      if (!newReviewerFilterSection(filter).getReviewers().contains(reviewer)) {
-        List<String> values =
-            new ArrayList<>(Arrays.asList(cfg.getStringList(SECTION_FILTER, filter, KEY_REVIEWER)));
-        values.add(reviewer);
-        cfg.setStringList(SECTION_FILTER, filter, KEY_REVIEWER, values);
-      }
-    }
-
-    void removeReviewer(String filter, String reviewer) {
-      if (newReviewerFilterSection(filter).getReviewers().contains(reviewer)) {
-        List<String> values =
-            new ArrayList<>(Arrays.asList(cfg.getStringList(SECTION_FILTER, filter, KEY_REVIEWER)));
-        values.remove(reviewer);
-        if (values.isEmpty()) {
-          cfg.unsetSection(SECTION_FILTER, filter);
-        } else {
-          cfg.setStringList(SECTION_FILTER, filter, KEY_REVIEWER, values);
-        }
-      }
-    }
-
-    private ReviewerFilterSection newReviewerFilterSection(String filter) {
-      ImmutableSet.Builder<String> b = ImmutableSet.builder();
-      for (String reviewer : cfg.getStringList(SECTION_FILTER, filter, KEY_REVIEWER)) {
-        b.add(reviewer);
-      }
-      return new ReviewerFilterSection(filter, b.build());
+    public void removeReviewer(String filter, String reviewer) {
+      filters.get(filter).removeReviewer(reviewer);
     }
 
     @Override
@@ -138,7 +103,8 @@
 
     @Override
     protected void onLoad() throws IOException, ConfigInvalidException {
-      cfg = readConfig(FILENAME);
+      this.cfg = readConfig(FILENAME);
+      this.filters = new ReviewerFilterCollection(cfg);
     }
 
     @Override
diff --git a/src/main/resources/Documentation/rest-api.md b/src/main/resources/Documentation/rest-api.md
index ebd04d4..8d13cad 100644
--- a/src/main/resources/Documentation/rest-api.md
+++ b/src/main/resources/Documentation/rest-api.md
@@ -20,7 +20,7 @@
   GET /projects/myproject/@PLUGIN@ HTTP/1.0
 ```
 
-As response a List of [ReviewerFilterSection](#reviewer-filter-section) is returned
+As response a List of [ReviewerFilter](#reviewer-filter) is returned
 that describes the default reviewers for myproject.
 
 #### Response
@@ -72,7 +72,7 @@
 ```
 
 As response the default reviewers are returned as a list of
-[ReviewerFilterSection](#reviewer-filter-section).
+[ReviewerFilter](#reviewer-filter).
 
 #### Response
 
@@ -104,10 +104,9 @@
 <a id="json-entities">JSON Entities
 -----------------------------------
 
-### <a id="reviewer-filter-section"></a>ReviewerFilterSection
+### <a id="reviewer-filter"></a>ReviewerFilter
 
-The `ReviewerFilterSection` entity contains a filter section of the
-default reviewers.
+The `ReviewerFilter` entity contains a filter of the default reviewers.
 
 * _filter_: A filter that is used to assign default reviewers.
 * _reviewers_: List of usernames which are assigned as default reviewers
diff --git a/src/test/java/com/googlesource/gerrit/plugins/reviewers/AbstractReviewersPluginTest.java b/src/test/java/com/googlesource/gerrit/plugins/reviewers/AbstractReviewersPluginTest.java
index a8b308a..731602d 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/reviewers/AbstractReviewersPluginTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/AbstractReviewersPluginTest.java
@@ -15,19 +15,19 @@
 package com.googlesource.gerrit.plugins.reviewers;
 
 import static com.google.gerrit.acceptance.GitUtil.fetch;
-import static com.googlesource.gerrit.plugins.reviewers.ReviewersConfig.FILENAME;
-import static com.googlesource.gerrit.plugins.reviewers.ReviewersConfig.KEY_REVIEWER;
-import static com.googlesource.gerrit.plugins.reviewers.ReviewersConfig.SECTION_FILTER;
+import static com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig.FILENAME;
+import static com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig.KEY_REVIEWER;
+import static com.googlesource.gerrit.plugins.reviewers.config.ReviewersConfig.SECTION_FILTER;
 
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.entities.RefNames;
 import com.google.inject.Inject;
 import java.util.Arrays;
-import java.util.List;
+import java.util.Set;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Config;
 import org.junit.Ignore;
@@ -37,16 +37,19 @@
 public class AbstractReviewersPluginTest extends LightweightPluginDaemonTest {
   @Inject protected ProjectOperations projectOperations;
 
-  protected void createFilters(FilterData... filters) throws Exception {
+  protected void createFilters(TestFilter... filters) throws Exception {
     createFiltersFor(testRepo, filters);
   }
 
-  protected void createFiltersFor(TestRepository<?> repo, FilterData... filters) throws Exception {
+  protected void createFiltersFor(TestRepository<?> repo, TestFilter... filters) throws Exception {
     String previousHead = repo.getRepository().getBranch();
     checkoutRefsMetaConfig(repo);
     Config cfg = new Config();
     Arrays.stream(filters)
-        .forEach(f -> cfg.setStringList(SECTION_FILTER, f.filter, KEY_REVIEWER, f.reviewers));
+        .forEach(
+            f ->
+                cfg.setStringList(
+                    SECTION_FILTER, f.filter, KEY_REVIEWER, Lists.newArrayList(f.reviewers)));
     pushFactory
         .create(admin.newIdent(), repo, "Add reviewers", FILENAME, cfg.toText())
         .to(RefNames.REFS_CONFIG)
@@ -60,31 +63,32 @@
     return repo;
   }
 
-  protected FilterData filter(String filter) {
-    return new FilterData(filter);
+  protected TestFilter filter(String filter, Set<String> reviewers) {
+    return new TestFilter(filter, reviewers);
   }
 
-  /** Assists tests to define a filter. */
-  protected static class FilterData {
-    List<String> reviewers;
-    String filter;
+  protected TestFilter filter(String filter) {
+    return new TestFilter(filter);
+  }
 
-    FilterData(String filter) {
+  protected static class TestFilter extends ReviewerFilter {
+
+    public TestFilter(String filter, Set<String> reviewers) {
       this.filter = filter;
-      this.reviewers = Lists.newArrayList();
+      this.reviewers = reviewers;
     }
 
-    FilterData reviewer(TestAccount reviewer) {
-      return reviewer(reviewer.email());
+    public TestFilter(String filter) {
+      this(filter, Sets.newHashSet());
     }
 
-    FilterData reviewer(String reviewer) {
-      reviewers.add(reviewer);
+    public TestFilter reviewer(String reviewerId) {
+      reviewers.add(reviewerId);
       return this;
     }
 
-    public ReviewerFilterSection asSection() {
-      return new ReviewerFilterSection(filter, ImmutableSet.copyOf(reviewers));
+    public TestFilter reviewer(TestAccount reviewer) {
+      return reviewer(reviewer.email());
     }
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfigIT.java b/src/test/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfigIT.java
similarity index 87%
rename from src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfigIT.java
rename to src/test/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfigIT.java
index 19bb268..9e4c06f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/reviewers/ReviewersConfigIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/reviewers/config/ReviewersConfigIT.java
@@ -12,15 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.reviewers;
+package com.googlesource.gerrit.plugins.reviewers.config;
 
-import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.truth.Truth.assertThat;
 
+import com.google.common.collect.ImmutableList;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.TestPlugin;
 import com.google.gerrit.entities.Project;
-import java.util.Arrays;
+import com.googlesource.gerrit.plugins.reviewers.AbstractReviewersPluginTest;
 import org.eclipse.jgit.junit.TestRepository;
 import org.junit.Before;
 import org.junit.Test;
@@ -64,10 +64,9 @@
         filter(BRANCH_MAIN).reviewer(JOHN_DOE).reviewer(JANE_DOE));
   }
 
-  private void assertProjectHasFilters(Project.NameKey project, FilterData... filters) {
-    assertThat(reviewersConfig().forProject(project).getReviewerFilterSections())
-        .containsExactlyElementsIn(
-            Arrays.stream(filters).map(f -> f.asSection()).collect(toImmutableList()))
+  private void assertProjectHasFilters(Project.NameKey project, TestFilter... filters) {
+    assertThat(reviewersConfig().filtersWithInheritance(project))
+        .containsExactlyElementsIn(ImmutableList.copyOf(filters))
         .inOrder();
   }