Merge "Use a fake in-memory implementation for AccountPatchReviewStore in tests"
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index a149f29..36bc3c4 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -67,6 +67,7 @@
 import com.google.gerrit.server.util.ReplicaUtil;
 import com.google.gerrit.server.util.SocketUtil;
 import com.google.gerrit.server.util.SystemLog;
+import com.google.gerrit.testing.FakeAccountPatchReviewStore.FakeAccountPatchReviewStoreModule;
 import com.google.gerrit.testing.FakeEmailSender.FakeEmailSenderModule;
 import com.google.gerrit.testing.InMemoryRepositoryManager;
 import com.google.gerrit.testing.SshMode;
@@ -414,6 +415,7 @@
               }
             },
             site);
+    daemon.setAccountPatchReviewStoreModuleForTesting(new FakeAccountPatchReviewStoreModule());
     daemon.setEmailModuleForTesting(new FakeEmailSenderModule());
     daemon.setAuditEventModuleForTesting(
         MoreObjects.firstNonNull(testAuditModule, new FakeGroupAuditServiceModule()));
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 845cc9a..744f91b 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -215,6 +215,7 @@
   private Path runFile;
   private boolean inMemoryTest;
   private AbstractModule indexModule;
+  private Module accountPatchReviewStoreModule;
   private Module emailModule;
   private List<Module> testSysModules = new ArrayList<>();
   private List<Module> testSshModules = new ArrayList<>();
@@ -333,6 +334,11 @@
   }
 
   @VisibleForTesting
+  public void setAccountPatchReviewStoreModuleForTesting(Module module) {
+    accountPatchReviewStoreModule = module;
+  }
+
+  @VisibleForTesting
   public void setEmailModuleForTesting(Module module) {
     emailModule = module;
   }
@@ -442,7 +448,11 @@
     modules.add(new WorkQueueModule());
     modules.add(new StreamEventsApiListenerModule());
     modules.add(new EventBrokerModule());
-    modules.add(new JdbcAccountPatchReviewStoreModule(config));
+    if (accountPatchReviewStoreModule != null) {
+      modules.add(accountPatchReviewStoreModule);
+    } else {
+      modules.add(new JdbcAccountPatchReviewStoreModule(config));
+    }
     modules.add(new SysExecutorModule());
     modules.add(new DiffExecutorModule());
     modules.add(new MimeUtil2Module());
diff --git a/java/com/google/gerrit/testing/FakeAccountPatchReviewStore.java b/java/com/google/gerrit/testing/FakeAccountPatchReviewStore.java
new file mode 100644
index 0000000..186abe5
--- /dev/null
+++ b/java/com/google/gerrit/testing/FakeAccountPatchReviewStore.java
@@ -0,0 +1,139 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.testing;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.change.AccountPatchReviewStore;
+import com.google.inject.Singleton;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * An implementation of the {@link AccountPatchReviewStore} that's only used in tests. This
+ * implementation stores reviewed files in memory.
+ */
+@Singleton
+public class FakeAccountPatchReviewStore implements AccountPatchReviewStore, LifecycleListener {
+
+  private final Set<Entity> store = new HashSet<>();
+
+  @Override
+  public void start() {}
+
+  @Override
+  public void stop() {}
+
+  public static class FakeAccountPatchReviewStoreModule extends LifecycleModule {
+    @Override
+    protected void configure() {
+      DynamicItem.bind(binder(), AccountPatchReviewStore.class)
+          .to(FakeAccountPatchReviewStore.class);
+      listener().to(FakeAccountPatchReviewStore.class);
+    }
+  }
+
+  @AutoValue
+  abstract static class Entity {
+    abstract PatchSet.Id psId();
+
+    abstract Account.Id accountId();
+
+    abstract String path();
+
+    static Entity create(PatchSet.Id psId, Account.Id accountId, String path) {
+      return new AutoValue_FakeAccountPatchReviewStore_Entity(psId, accountId, path);
+    }
+  }
+
+  @Override
+  public boolean markReviewed(PatchSet.Id psId, Account.Id accountId, String path) {
+    synchronized (store) {
+      Entity entity = Entity.create(psId, accountId, path);
+      return store.add(entity);
+    }
+  }
+
+  @Override
+  public void markReviewed(PatchSet.Id psId, Account.Id accountId, Collection<String> paths) {
+    paths.forEach(path -> markReviewed(psId, accountId, path));
+  }
+
+  @Override
+  public void clearReviewed(PatchSet.Id psId, Account.Id accountId, String path) {
+    synchronized (store) {
+      store.remove(Entity.create(psId, accountId, path));
+    }
+  }
+
+  @Override
+  public void clearReviewed(PatchSet.Id psId) {
+    synchronized (store) {
+      List<Entity> toRemove = new ArrayList<>();
+      for (Entity entity : store) {
+        if (entity.psId().equals(psId)) {
+          toRemove.add(entity);
+        }
+      }
+      store.removeAll(toRemove);
+    }
+  }
+
+  @Override
+  public void clearReviewed(Change.Id changeId) {
+    synchronized (store) {
+      List<Entity> toRemove = new ArrayList<>();
+      for (Entity entity : store) {
+        if (entity.psId().changeId().equals(changeId)) {
+          toRemove.add(entity);
+        }
+      }
+      store.removeAll(toRemove);
+    }
+  }
+
+  @Override
+  public Optional<PatchSetWithReviewedFiles> findReviewed(PatchSet.Id psId, Account.Id accountId) {
+    synchronized (store) {
+      int matchedPsNumber = -1;
+      Optional<PatchSetWithReviewedFiles> result = Optional.empty();
+      for (Entity entity : store) {
+        if (entity.accountId() != accountId || !entity.psId().changeId().equals(psId.changeId())) {
+          continue;
+        }
+        int entityPsNumber = Integer.parseInt(entity.psId().getId());
+        if (entityPsNumber <= psId.get() && entityPsNumber > matchedPsNumber) {
+          matchedPsNumber = entityPsNumber;
+          result =
+              Optional.of(
+                  PatchSetWithReviewedFiles.create(
+                      PatchSet.id(psId.changeId(), matchedPsNumber),
+                      ImmutableSet.of(entity.path())));
+        }
+      }
+      return result;
+    }
+  }
+}