Merge branch 'stable-2.13'

* stable-2.13:
  Forward account indexing events to the other master
  Refactor RestForwarderTest for readability

Change-Id: I5ee0bf41ff996c4b02a4c137a3eedebe2bf91c51
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
index 0d1a0bb..1608042 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
@@ -22,6 +22,14 @@
 public interface Forwarder {
 
   /**
+   * Forward a account indexing event to the other master.
+   *
+   * @param accountId the account to index.
+   * @return true if successful, otherwise false.
+   */
+  boolean indexAccount(int accountId);
+
+  /**
    * Forward a change indexing event to the other master.
    *
    * @param changeId the change to index.
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServlet.java
new file mode 100644
index 0000000..a96b1c4
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServlet.java
@@ -0,0 +1,113 @@
+// Copyright (C) 2017 Ericsson
+//
+// 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.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.index.account.AccountIndexer;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Singleton
+class IndexAccountRestApiServlet extends HttpServlet {
+  private static final long serialVersionUID = -1L;
+  private static final Logger logger =
+      LoggerFactory.getLogger(IndexAccountRestApiServlet.class);
+  private static final Map<Account.Id, AtomicInteger> accountIdLocks =
+      new HashMap<>();
+
+  private final AccountIndexer indexer;
+
+  @Inject
+  IndexAccountRestApiServlet(AccountIndexer indexer) {
+    this.indexer = indexer;
+  }
+
+  @Override
+  protected void doPost(HttpServletRequest req, HttpServletResponse rsp)
+      throws IOException, ServletException {
+    rsp.setContentType("text/plain");
+    rsp.setCharacterEncoding("UTF-8");
+    String path = req.getPathInfo();
+    String accountId = path.substring(path.lastIndexOf('/') + 1);
+    Account.Id id = Account.Id.parse(accountId);
+    try {
+      Context.setForwardedEvent(true);
+      index(id);
+      rsp.setStatus(SC_NO_CONTENT);
+    } catch (IOException e) {
+      sendError(rsp, SC_CONFLICT, e.getMessage());
+      logger.error("Unable to update account index", e);
+    } finally {
+      Context.unsetForwardedEvent();
+    }
+  }
+
+  private static void sendError(HttpServletResponse rsp, int statusCode,
+      String message) {
+    try {
+      rsp.sendError(statusCode, message);
+    } catch (IOException e) {
+      logger.error("Failed to send error messsage: " + e.getMessage(), e);
+    }
+  }
+
+  private void index(Account.Id id) throws IOException {
+    AtomicInteger accountIdLock = getAndIncrementAccountIdLock(id);
+    synchronized (accountIdLock) {
+      indexer.index(id);;
+      logger.debug("Account {} successfully indexed", id);
+    }
+    if (accountIdLock.decrementAndGet() == 0) {
+      removeAccountIdLock(id);
+    }
+  }
+
+  private AtomicInteger getAndIncrementAccountIdLock(Account.Id id) {
+    synchronized (accountIdLocks) {
+      AtomicInteger accountIdLock = accountIdLocks.get(id);
+      if (accountIdLock == null) {
+        accountIdLock = new AtomicInteger(1);
+        accountIdLocks.put(id, accountIdLock);
+      } else {
+        accountIdLock.incrementAndGet();
+      }
+      return accountIdLock;
+    }
+  }
+
+  private void removeAccountIdLock(Account.Id id) {
+    synchronized (accountIdLocks) {
+      accountIdLocks.remove(id);
+    }
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServlet.java
index 0ed9bcc..ad4aa5e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServlet.java
@@ -84,7 +84,7 @@
       rsp.setStatus(SC_NO_CONTENT);
     } catch (IOException e) {
       sendError(rsp,SC_CONFLICT, e.getMessage());
-      logger.error("Unable to update index", e);
+      logger.error("Unable to update change index", e);
     } catch (OrmException e) {
       String msg = "Error trying to find a change \n";
       sendError(rsp,SC_NOT_FOUND, msg);
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
index cc58e3c..467ae53 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
@@ -51,6 +51,17 @@
   }
 
   @Override
+  public boolean indexAccount(final int accountId) {
+    return new Request("index account " + accountId) {
+      @Override
+      HttpResult send() throws IOException {
+        return httpSession.post(Joiner.on("/").join(pluginRelativePath,
+            "index/account", accountId));
+      }
+    }.execute();
+  }
+
+  @Override
   public boolean indexChange(final int changeId) {
     return new Request("index change " + changeId) {
       @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java
index 4b0157d..bd093ae 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java
@@ -19,6 +19,7 @@
 public class RestForwarderServletModule extends HttpPluginModule {
   @Override
   protected void configureServlets() {
+    serveRegex("/index/account/\\d+$").with(IndexAccountRestApiServlet.class);
     serveRegex("/index/change/\\d+$").with(IndexChangeRestApiServlet.class);
     serve("/event").with(EventRestApiServlet.class);
     serve("/cache/*").with(CacheRestApiServlet.class);
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
index 36a5c82..99fffa6 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
@@ -16,6 +16,7 @@
 
 import com.google.common.base.Objects;
 import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.events.AccountIndexedListener;
 import com.google.gerrit.extensions.events.ChangeIndexedListener;
 import com.google.inject.Inject;
 
@@ -27,7 +28,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 
-class IndexEventHandler implements ChangeIndexedListener {
+class IndexEventHandler implements ChangeIndexedListener, AccountIndexedListener {
   private final Executor executor;
   private final Forwarder forwarder;
   private final String pluginName;
@@ -44,61 +45,117 @@
   }
 
   @Override
-  public void onChangeIndexed(int id) {
-    executeIndexTask(id, false);
-  }
-
-  @Override
-  public void onChangeDeleted(int id) {
-    executeIndexTask(id, true);
-  }
-
-  private void executeIndexTask(int id, boolean deleted) {
+  public void onAccountIndexed(int id) {
     if (!Context.isForwardedEvent()) {
-      IndexTask indexTask = new IndexTask(id, deleted);
-      if (queuedTasks.add(indexTask)) {
-        executor.execute(indexTask);
+      IndexAccountTask task = new IndexAccountTask(id);
+      if (queuedTasks.add(task)) {
+        executor.execute(task);
       }
     }
   }
 
-  class IndexTask implements Runnable {
-    private int changeId;
-    private boolean deleted;
+  @Override
+  public void onChangeIndexed(int id) {
+    executeIndexChangeTask(id, false);
+  }
 
-    IndexTask(int changeId, boolean deleted) {
-      this.changeId = changeId;
-      this.deleted = deleted;
+  @Override
+  public void onChangeDeleted(int id) {
+    executeIndexChangeTask(id, true);
+  }
+
+  private void executeIndexChangeTask(int id, boolean deleted) {
+    if (!Context.isForwardedEvent()) {
+      IndexChangeTask task = new IndexChangeTask(id, deleted);
+      if (queuedTasks.add(task)) {
+        executor.execute(task);
+      }
+    }
+  }
+
+  abstract class IndexTask implements Runnable {
+    protected int id;
+
+    IndexTask(int id) {
+      this.id = id;
     }
 
     @Override
     public void run() {
       queuedTasks.remove(this);
+      execute();
+    }
+
+    abstract void execute();
+  }
+
+  class IndexChangeTask extends IndexTask {
+    private boolean deleted;
+
+    IndexChangeTask(int changeId, boolean deleted) {
+      super(changeId);
+      this.deleted = deleted;
+    }
+
+    @Override
+    public void execute() {
       if (deleted) {
-        forwarder.deleteChangeFromIndex(changeId);
+        forwarder.deleteChangeFromIndex(id);
       } else {
-        forwarder.indexChange(changeId);
+        forwarder.indexChange(id);
       }
     }
 
     @Override
     public int hashCode() {
-      return Objects.hashCode(changeId, deleted);
+      return Objects.hashCode(IndexChangeTask.class, id, deleted);
     }
 
     @Override
     public boolean equals(Object obj) {
-      if (!(obj instanceof IndexTask)) {
+      if (!(obj instanceof IndexChangeTask)) {
         return false;
       }
-      IndexTask other = (IndexTask) obj;
-      return changeId == other.changeId && deleted == other.deleted;
+      IndexChangeTask other = (IndexChangeTask) obj;
+      return id == other.id && deleted == other.deleted;
     }
 
     @Override
     public String toString() {
       return String.format("[%s] Index change %s in target instance",
-          pluginName, changeId);
+          pluginName, id);
+    }
+  }
+
+  class IndexAccountTask extends IndexTask {
+
+    IndexAccountTask(int accountId) {
+      super(accountId);
+    }
+
+    @Override
+    public void execute() {
+      forwarder.indexAccount(id);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(IndexAccountTask.class, id);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (!(obj instanceof IndexAccountTask)) {
+        return false;
+      }
+      IndexAccountTask other = (IndexAccountTask) obj;
+      return id == other.id;
+    }
+
+    @Override
+    public String toString() {
+      return String.format("[%s] Index account %s in target instance",
+          pluginName, id);
     }
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
index 7e8b116..cd9839c 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
@@ -14,6 +14,7 @@
 
 package com.ericsson.gerrit.plugins.highavailability.index;
 
+import com.google.gerrit.extensions.events.AccountIndexedListener;
 import com.google.gerrit.extensions.events.ChangeIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleModule;
@@ -30,5 +31,7 @@
     listener().to(IndexExecutorProvider.class);
     DynamicSet.bind(binder(), ChangeIndexedListener.class).to(
         IndexEventHandler.class);
+    DynamicSet.bind(binder(), AccountIndexedListener.class).to(
+        IndexEventHandler.class);
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServletTest.java
new file mode 100644
index 0000000..8c81817
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServletTest.java
@@ -0,0 +1,88 @@
+// Copyright (C) 2017 Ericsson
+//
+// 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.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.index.account.AccountIndexer;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.StandardKeyEncoder;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@RunWith(MockitoJUnitRunner.class)
+public class IndexAccountRestApiServletTest {
+  private static final String ACCOUNT_NUMBER = "1";
+
+  @Mock
+  private AccountIndexer indexer;
+  @Mock
+  private HttpServletRequest req;
+  @Mock
+  private HttpServletResponse rsp;
+
+  private Account.Id id;
+  private IndexAccountRestApiServlet servlet;
+
+  @BeforeClass
+  public static void setup() {
+    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+  }
+
+  @Before
+  public void setUpMocks() {
+    servlet = new IndexAccountRestApiServlet(indexer);
+    id = Account.Id.parse(ACCOUNT_NUMBER);
+    when(req.getPathInfo()).thenReturn("/index/account/" + ACCOUNT_NUMBER);
+  }
+
+  @Test
+  public void accountIsIndexed() throws Exception {
+    servlet.doPost(req, rsp);
+    verify(indexer, times(1)).index(id);
+    verify(rsp).setStatus(SC_NO_CONTENT);
+  }
+
+  @Test
+  public void indexerThrowsIOExceptionTryingToIndexAccount() throws Exception {
+    doThrow(new IOException("io-error")).when(indexer).index(id);
+    servlet.doPost(req, rsp);
+    verify(rsp).sendError(SC_CONFLICT, "io-error");
+  }
+
+  @Test
+  public void sendErrorThrowsIOException() throws Exception {
+    doThrow(new IOException("io-error")).when(indexer).index(id);
+    doThrow(new IOException("someError")).when(rsp).sendError(SC_CONFLICT, "io-error");
+    servlet.doPost(req, rsp);
+    verify(rsp).sendError(SC_CONFLICT, "io-error");
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
index ecc8fbf..3475e47 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
@@ -15,7 +15,7 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.anyString;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -25,11 +25,15 @@
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.events.Event;
 import com.google.gson.GsonBuilder;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.StandardKeyEncoder;
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.ericsson.gerrit.plugins.highavailability.cache.Constants;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
 
+import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -39,277 +43,240 @@
 public class RestForwarderTest {
   private static final String PLUGIN_NAME = "high-availability";
   private static final String EMPTY_MSG = "";
-  private static final String ERROR_MSG = "Error";
-  private static final String EXCEPTION_MSG = "Exception";
   private static final boolean SUCCESSFUL = true;
   private static final boolean FAILED = false;
-  private static final boolean DO_NOT_THROW_EXCEPTION = false;
-  private static final boolean THROW_EXCEPTION = true;
-
-  private static final int MAX_TRIES = 3;
-  private static final int RETRY_INTERVAL = 250;
 
   //Index
   private static final int CHANGE_NUMBER = 1;
-  private static final String DELETE_OP = "delete";
-  private static final String INDEX_OP = "index/change";
+  private static final String INDEX_CHANGE_ENDPOINT = Joiner.on("/")
+      .join("/plugins", PLUGIN_NAME, "index/change", CHANGE_NUMBER);
+  private static final int ACCOUNT_NUMBER = 2;
+  private static final String INDEX_ACCOUNT_ENDPOINT = Joiner.on("/")
+      .join("/plugins", PLUGIN_NAME, "index/account", ACCOUNT_NUMBER);
 
-  //Evict cache
-  private static final String EMPTY_JSON = "{}";
-  private static final String EMPTY_JSON2 = "\"{}\"";
-  private static final String ID_JSON = "{\"id\":0}";
+  //Event
+  private static final String EVENT_ENDPOINT =
+      Joiner.on("/").join("/plugins", PLUGIN_NAME, "event");
+  private static Event event = new Event("test-event") {};
+  private static String eventJson = new GsonBuilder().create().toJson(event);
 
-  private RestForwarder restForwarder;
+  private RestForwarder forwarder;
+  private HttpSession httpSessionMock;
+  private Configuration configurationMock;
+
+  @BeforeClass
+  public static void setup() {
+    KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+  }
+
+  @Before
+  public void setUp() {
+    httpSessionMock = mock(HttpSession.class);
+    configurationMock = mock(Configuration.class);
+    when(configurationMock.getMaxTries()).thenReturn(3);
+    when(configurationMock.getRetryInterval()).thenReturn(10);
+    forwarder =
+        new RestForwarder(httpSessionMock, PLUGIN_NAME, configurationMock);
+  }
+
+  @Test
+  public void testIndexAccountOK() throws Exception {
+    when(httpSessionMock.post(INDEX_ACCOUNT_ENDPOINT))
+        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
+    assertThat(forwarder.indexAccount(ACCOUNT_NUMBER)).isTrue();
+  }
+
+  @Test
+  public void testIndexAccountFailed() throws Exception {
+    when(httpSessionMock.post(INDEX_ACCOUNT_ENDPOINT))
+        .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
+    assertThat(forwarder.indexAccount(ACCOUNT_NUMBER)).isFalse();
+  }
+
+  @Test
+  public void testIndexAccountThrowsException() throws Exception {
+    doThrow(new IOException()).when(httpSessionMock)
+        .post(INDEX_ACCOUNT_ENDPOINT);
+    assertThat(forwarder.indexAccount(ACCOUNT_NUMBER)).isFalse();
+  }
 
   @Test
   public void testIndexChangeOK() throws Exception {
-    setUpMocksForIndex(INDEX_OP, SUCCESSFUL, EMPTY_MSG, DO_NOT_THROW_EXCEPTION);
-    assertThat(restForwarder.indexChange(CHANGE_NUMBER)).isTrue();
+    when(httpSessionMock.post(INDEX_CHANGE_ENDPOINT))
+        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
+    assertThat(forwarder.indexChange(CHANGE_NUMBER)).isTrue();
   }
 
   @Test
   public void testIndexChangeFailed() throws Exception {
-    setUpMocksForIndex(INDEX_OP, FAILED, ERROR_MSG, DO_NOT_THROW_EXCEPTION);
-    assertThat(restForwarder.indexChange(CHANGE_NUMBER)).isFalse();
+    when(httpSessionMock.post(INDEX_CHANGE_ENDPOINT))
+        .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
+    assertThat(forwarder.indexChange(CHANGE_NUMBER)).isFalse();
   }
 
   @Test
   public void testIndexChangeThrowsException() throws Exception {
-    setUpMocksForIndex(INDEX_OP, FAILED, EXCEPTION_MSG, THROW_EXCEPTION);
-    assertThat(restForwarder.indexChange(CHANGE_NUMBER)).isFalse();
+    doThrow(new IOException()).when(httpSessionMock)
+        .post(INDEX_CHANGE_ENDPOINT);
+    assertThat(forwarder.indexChange(CHANGE_NUMBER)).isFalse();
   }
 
   @Test
   public void testChangeDeletedFromIndexOK() throws Exception {
-    setUpMocksForIndex(DELETE_OP, SUCCESSFUL, EMPTY_MSG,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(restForwarder.deleteChangeFromIndex(CHANGE_NUMBER)).isTrue();
+    when(httpSessionMock.delete(INDEX_CHANGE_ENDPOINT))
+        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
+    assertThat(forwarder.deleteChangeFromIndex(CHANGE_NUMBER)).isTrue();
   }
 
   @Test
   public void testChangeDeletedFromIndexFailed() throws Exception {
-    setUpMocksForIndex(DELETE_OP, FAILED, ERROR_MSG, DO_NOT_THROW_EXCEPTION);
-    assertThat(restForwarder.deleteChangeFromIndex(CHANGE_NUMBER)).isFalse();
+    when(httpSessionMock.delete(INDEX_CHANGE_ENDPOINT))
+        .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
+    assertThat(forwarder.deleteChangeFromIndex(CHANGE_NUMBER)).isFalse();
   }
 
   @Test
   public void testChangeDeletedFromThrowsException() throws Exception {
-    setUpMocksForIndex(DELETE_OP, FAILED, EXCEPTION_MSG, THROW_EXCEPTION);
-    assertThat(restForwarder.deleteChangeFromIndex(CHANGE_NUMBER)).isFalse();
-  }
-
-  private void setUpMocksForIndex(String operation,
-      boolean isOperationSuccessful, String msg, boolean exception)
-      throws Exception {
-    String request =
-        Joiner.on("/").join("/plugins", PLUGIN_NAME, INDEX_OP, CHANGE_NUMBER);
-    HttpSession httpSession = mock(HttpSession.class);
-    if (exception) {
-      if (operation.equals(INDEX_OP)) {
-        doThrow(new IOException()).when(httpSession).post(request);
-      } else {
-        doThrow(new IOException()).when(httpSession).delete(request);
-      }
-    } else {
-      HttpResult result = new HttpResult(isOperationSuccessful, msg);
-      if (operation.equals(INDEX_OP)) {
-        when(httpSession.post(request)).thenReturn(result);
-      } else {
-        when(httpSession.delete(request)).thenReturn(result);
-      }
-    }
-    Configuration cfg = mock(Configuration.class);
-    when(cfg.getMaxTries()).thenReturn(MAX_TRIES);
-    when(cfg.getRetryInterval()).thenReturn(RETRY_INTERVAL);
-    restForwarder = new RestForwarder(httpSession, PLUGIN_NAME, cfg);
+    doThrow(new IOException()).when(httpSessionMock)
+        .delete(INDEX_CHANGE_ENDPOINT);
+    assertThat(forwarder.deleteChangeFromIndex(CHANGE_NUMBER)).isFalse();
   }
 
   @Test
   public void testEventSentOK() throws Exception {
-    Event event = setUpMocksForEvent(SUCCESSFUL, EMPTY_MSG, DO_NOT_THROW_EXCEPTION);
-    assertThat(restForwarder.send(event)).isTrue();
+    when(httpSessionMock.post(EVENT_ENDPOINT, eventJson))
+        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
+    assertThat(forwarder.send(event)).isTrue();
   }
 
   @Test
   public void testEventSentFailed() throws Exception {
-    Event event = setUpMocksForEvent(FAILED, ERROR_MSG, DO_NOT_THROW_EXCEPTION);
-    assertThat(restForwarder.send(event)).isFalse();
+    when(httpSessionMock.post(EVENT_ENDPOINT, eventJson))
+        .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
+    assertThat(forwarder.send(event)).isFalse();
   }
 
   @Test
   public void testEventSentThrowsException() throws Exception {
-    Event event = setUpMocksForEvent(FAILED, EXCEPTION_MSG, THROW_EXCEPTION);
-    assertThat(restForwarder.send(event)).isFalse();
-  }
-
-  private Event setUpMocksForEvent(boolean isOperationSuccessful, String msg,
-      boolean exception) throws Exception {
-    Event event = new EventTest();
-    String content = new GsonBuilder().create().toJson(event);
-    HttpSession httpSession = mock(HttpSession.class);
-    String request = Joiner.on("/").join("/plugins", PLUGIN_NAME, "event");
-    if (exception) {
-      doThrow(new IOException()).when(httpSession).post(request, content);
-    } else {
-      HttpResult result = new HttpResult(isOperationSuccessful, msg);
-      when(httpSession.post(request, content)).thenReturn(result);
-    }
-    Configuration cfg = mock(Configuration.class);
-    when(cfg.getMaxTries()).thenReturn(MAX_TRIES);
-    when(cfg.getRetryInterval()).thenReturn(RETRY_INTERVAL);
-    restForwarder = new RestForwarder(httpSession, PLUGIN_NAME, cfg);
-    return event;
-  }
-
-  private class EventTest extends Event {
-    public EventTest() {
-      super("test-event");
-    }
+    doThrow(new IOException()).when(httpSessionMock).post(EVENT_ENDPOINT,
+        eventJson);
+    assertThat(forwarder.send(event)).isFalse();
   }
 
   @Test
-  public void testEvictCacheOK() throws Exception {
-    setupMocksForCache(Constants.PROJECTS, EMPTY_JSON2, SUCCESSFUL,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(restForwarder.evict(Constants.PROJECTS, EMPTY_JSON)).isTrue();
+  public void testEvictProjectOK() throws Exception {
+    String key = "projectName";
+    String keyJson = new GsonBuilder().create().toJson(key);
+    when(httpSessionMock.post(buildCacheEndpoint(Constants.PROJECTS), keyJson))
+        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
+    assertThat(forwarder.evict(Constants.PROJECTS, key)).isTrue();
   }
 
   @Test
   public void testEvictAccountsOK() throws Exception {
-    setupMocksForCache(Constants.ACCOUNTS, ID_JSON, SUCCESSFUL,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(restForwarder.evict(Constants.ACCOUNTS, mock(Account.Id.class)))
-        .isTrue();
+    Account.Id key = Account.Id.parse("123");
+    String keyJson = new GsonBuilder().create().toJson(key);
+    when(httpSessionMock.post(buildCacheEndpoint(Constants.ACCOUNTS), keyJson))
+        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
+    assertThat(forwarder.evict(Constants.ACCOUNTS, key)).isTrue();
   }
 
   @Test
   public void testEvictGroupsOK() throws Exception {
-    setupMocksForCache(Constants.GROUPS, ID_JSON, SUCCESSFUL,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(
-        restForwarder.evict(Constants.GROUPS, mock(AccountGroup.Id.class)))
-            .isTrue();
+    AccountGroup.Id key = AccountGroup.Id.parse("123");
+    String keyJson = new GsonBuilder().create().toJson(key);
+    when(httpSessionMock.post(buildCacheEndpoint(Constants.GROUPS), keyJson))
+        .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
+    assertThat(forwarder.evict(Constants.GROUPS, key)).isTrue();
   }
 
   @Test
   public void testEvictGroupsByIncludeOK() throws Exception {
-    setupMocksForCache(Constants.GROUPS_BYINCLUDE, EMPTY_JSON, SUCCESSFUL,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(restForwarder.evict(Constants.GROUPS_BYINCLUDE,
-        mock(AccountGroup.UUID.class))).isTrue();
+    AccountGroup.UUID key =
+        AccountGroup.UUID.parse("90b3042d9094a37985f3f9281391dbbe9a5addad");
+    String keyJson = new GsonBuilder().create().toJson(key);
+    when(httpSessionMock.post(buildCacheEndpoint(Constants.GROUPS_BYINCLUDE),
+        keyJson)).thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
+    assertThat(forwarder.evict(Constants.GROUPS_BYINCLUDE, key)).isTrue();
   }
 
   @Test
   public void testEvictGroupsMembersOK() throws Exception {
-    setupMocksForCache(Constants.GROUPS_MEMBERS, EMPTY_JSON, SUCCESSFUL,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(restForwarder.evict(Constants.GROUPS_MEMBERS,
-        mock(AccountGroup.UUID.class))).isTrue();
+    AccountGroup.UUID key =
+        AccountGroup.UUID.parse("90b3042d9094a37985f3f9281391dbbe9a5addad");
+    String keyJson = new GsonBuilder().create().toJson(key);
+    when(httpSessionMock.post(buildCacheEndpoint(Constants.GROUPS_MEMBERS),
+        keyJson)).thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
+    assertThat(forwarder.evict(Constants.GROUPS_MEMBERS, key)).isTrue();
   }
 
   @Test
   public void testEvictProjectListOK() throws Exception {
-    setupMocksForCache(Constants.PROJECT_LIST, EMPTY_JSON, SUCCESSFUL,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(restForwarder.evict(Constants.PROJECT_LIST, new Object()))
-        .isTrue();
+    String key = "all";
+    String keyJson = new GsonBuilder().create().toJson(key);
+    when(httpSessionMock.post(buildCacheEndpoint(Constants.PROJECT_LIST),
+        keyJson)).thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
+    assertThat(forwarder.evict(Constants.PROJECT_LIST, key)).isTrue();
   }
 
   @Test
   public void testEvictCacheFailed() throws Exception {
-    setupMocksForCache(Constants.PROJECTS, EMPTY_JSON2, FAILED,
-        DO_NOT_THROW_EXCEPTION);
-    assertThat(restForwarder.evict(Constants.PROJECTS, EMPTY_JSON)).isFalse();
+    String key = "projectName";
+    String keyJson = new GsonBuilder().create().toJson(key);
+    when(httpSessionMock.post(buildCacheEndpoint(Constants.PROJECTS), keyJson))
+        .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
+    assertThat(forwarder.evict(Constants.PROJECTS, key)).isFalse();
   }
 
   @Test
   public void testEvictCacheThrowsException() throws Exception {
-    setupMocksForCache(Constants.PROJECTS, EMPTY_JSON2, FAILED,
-        THROW_EXCEPTION);
-    assertThat(restForwarder.evict(Constants.PROJECTS, EMPTY_JSON)).isFalse();
+    String key = "projectName";
+    String keyJson = new GsonBuilder().create().toJson(key);
+    doThrow(new IOException()).when(httpSessionMock)
+        .post(buildCacheEndpoint(Constants.PROJECTS), keyJson);
+    assertThat(forwarder.evict(Constants.PROJECTS, key)).isFalse();
   }
 
-  private void setupMocksForCache(String cacheName, String json,
-      boolean isOperationSuccessful, boolean exception) throws IOException {
-    String request =
-        Joiner.on("/").join("/plugins", PLUGIN_NAME, "cache", cacheName);
-    HttpSession httpSession = mock(HttpSession.class);
-    if (exception) {
-      doThrow(new IOException()).when(httpSession).post(request, json);
-    } else {
-      HttpResult result = new HttpResult(isOperationSuccessful, "Error");
-      when(httpSession.post(request, json)).thenReturn(result);
-    }
-    Configuration cfg = mock(Configuration.class);
-    when(cfg.getMaxTries()).thenReturn(MAX_TRIES);
-    when(cfg.getRetryInterval()).thenReturn(RETRY_INTERVAL);
-    restForwarder = new RestForwarder(httpSession, PLUGIN_NAME, cfg);
+  private String buildCacheEndpoint(String name) {
+    return Joiner.on("/").join("/plugins", PLUGIN_NAME, "cache", name);
   }
 
   @Test
   public void testRetryOnErrorThenSuccess() throws IOException {
-    Configuration cfg = mock(Configuration.class);
-    when(cfg.getMaxTries()).thenReturn(3);
-    when(cfg.getRetryInterval()).thenReturn(10);
-
-    HttpSession httpSession = mock(HttpSession.class);
-    when(httpSession.post(anyString(), anyString()))
+    when(httpSessionMock.post(anyString(), anyString()))
         .thenReturn(new HttpResult(false, "Error"))
         .thenReturn(new HttpResult(false, "Error"))
         .thenReturn(new HttpResult(true, "Success"));
 
-    RestForwarder forwarder = new RestForwarder(httpSession, PLUGIN_NAME, cfg);
-    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object()))
-        .isTrue();
+    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isTrue();
   }
 
   @Test
   public void testRetryOnIoExceptionThenSuccess() throws IOException {
-    Configuration cfg = mock(Configuration.class);
-    when(cfg.getMaxTries()).thenReturn(3);
-    when(cfg.getRetryInterval()).thenReturn(10);
-
-    HttpSession httpSession = mock(HttpSession.class);
-    when(httpSession.post(anyString(), anyString()))
+    when(httpSessionMock.post(anyString(), anyString()))
         .thenThrow(new IOException())
         .thenThrow(new IOException())
         .thenReturn(new HttpResult(true, "Success"));
 
-    RestForwarder forwarder = new RestForwarder(httpSession, PLUGIN_NAME, cfg);
-    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object()))
-        .isTrue();
+    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isTrue();
   }
 
   @Test
   public void testNoRetryAfterNonRecoverableException() throws IOException {
-    Configuration cfg = mock(Configuration.class);
-    when(cfg.getMaxTries()).thenReturn(3);
-    when(cfg.getRetryInterval()).thenReturn(10);
-
-    HttpSession httpSession = mock(HttpSession.class);
-    when(httpSession.post(anyString(), anyString()))
+    when(httpSessionMock.post(anyString(), anyString()))
         .thenThrow(new SSLException("Non Recoverable"))
         .thenReturn(new HttpResult(true, "Success"));
 
-    RestForwarder forwarder = new RestForwarder(httpSession, PLUGIN_NAME, cfg);
-    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object()))
-        .isFalse();
+    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isFalse();
   }
 
   @Test
   public void testFailureAfterMaxTries() throws IOException {
-    Configuration cfg = mock(Configuration.class);
-    when(cfg.getMaxTries()).thenReturn(3);
-    when(cfg.getRetryInterval()).thenReturn(10);
-
-    HttpSession httpSession = mock(HttpSession.class);
-    when(httpSession.post(anyString(), anyString()))
+    when(httpSessionMock.post(anyString(), anyString()))
         .thenReturn(new HttpResult(false, "Error"))
         .thenReturn(new HttpResult(false, "Error"))
         .thenReturn(new HttpResult(false, "Error"));
 
-    RestForwarder forwarder = new RestForwarder(httpSession, PLUGIN_NAME, cfg);
-    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object()))
-        .isFalse();
+    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isFalse();
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
index aa4c365..626af8b 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 
 import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.git.WorkQueue.Executor;
 import com.google.gwtorm.client.KeyUtil;
@@ -28,7 +29,8 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
-import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexTask;
+import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexAccountTask;
+import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexChangeTask;
 
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -41,11 +43,13 @@
 public class IndexEventHandlerTest {
   private static final String PLUGIN_NAME = "high-availability";
   private static final int CHANGE_ID = 1;
+  private static final int ACCOUNT_ID = 2;
 
   private IndexEventHandler indexEventHandler;
   @Mock
   private Forwarder forwarder;
-  private Change.Id id;
+  private Change.Id changeId;
+  private Account.Id accountId;
 
   @BeforeClass
   public static void setUp() {
@@ -54,61 +58,94 @@
 
   @Before
   public void setUpMocks() {
-    id = Change.Id.parse(Integer.toString(CHANGE_ID));
+    changeId = Change.Id.parse(Integer.toString(CHANGE_ID));
+    accountId = Account.Id.parse(Integer.toString(ACCOUNT_ID));
     indexEventHandler = new IndexEventHandler(MoreExecutors.directExecutor(),
         PLUGIN_NAME, forwarder);
   }
 
   @Test
   public void shouldIndexInRemoteOnChangeIndexedEvent() throws Exception {
-    indexEventHandler.onChangeIndexed(id.get());
+    indexEventHandler.onChangeIndexed(changeId.get());
     verify(forwarder).indexChange(CHANGE_ID);
   }
 
   @Test
+  public void shouldIndexInRemoteOnAccountIndexedEvent() throws Exception {
+    indexEventHandler.onAccountIndexed(accountId.get());
+    verify(forwarder).indexAccount(ACCOUNT_ID);
+  }
+
+  @Test
   public void shouldDeleteFromIndexInRemoteOnChangeDeletedEvent()
       throws Exception {
-    indexEventHandler.onChangeDeleted(id.get());
+    indexEventHandler.onChangeDeleted(changeId.get());
     verify(forwarder).deleteChangeFromIndex(CHANGE_ID);
   }
 
   @Test
-  public void shouldNotCallRemoteWhenEventIsForwarded() throws Exception {
+  public void shouldNotCallRemoteWhenChangeEventIsForwarded() throws Exception {
     Context.setForwardedEvent(true);
-    indexEventHandler.onChangeIndexed(id.get());
-    indexEventHandler.onChangeDeleted(id.get());
+    indexEventHandler.onChangeIndexed(changeId.get());
+    indexEventHandler.onChangeDeleted(changeId.get());
     Context.unsetForwardedEvent();
     verifyZeroInteractions(forwarder);
   }
 
   @Test
-  public void duplicateEventOfAQueuedEventShouldGetDiscarded() {
-    Executor poolMock = mock(Executor.class);
-    indexEventHandler =
-        new IndexEventHandler(poolMock, PLUGIN_NAME, forwarder);
-    indexEventHandler.onChangeIndexed(id.get());
-    indexEventHandler.onChangeIndexed(id.get());
-    verify(poolMock, times(1))
-        .execute(indexEventHandler.new IndexTask(CHANGE_ID, false));
+  public void shouldNotCallRemoteWhenAccountEventIsForwarded() throws Exception {
+    Context.setForwardedEvent(true);
+    indexEventHandler.onAccountIndexed(accountId.get());
+    indexEventHandler.onAccountIndexed(accountId.get());
+    Context.unsetForwardedEvent();
+    verifyZeroInteractions(forwarder);
   }
 
   @Test
-  public void testIndexTaskToString() throws Exception {
-    IndexTask indexTask =
-        indexEventHandler.new IndexTask(CHANGE_ID, false);
-    assertThat(indexTask.toString()).isEqualTo(String.format(
+  public void duplicateChangeEventOfAQueuedEventShouldGetDiscarded() {
+    Executor poolMock = mock(Executor.class);
+    indexEventHandler = new IndexEventHandler(poolMock, PLUGIN_NAME, forwarder);
+    indexEventHandler.onChangeIndexed(changeId.get());
+    indexEventHandler.onChangeIndexed(changeId.get());
+    verify(poolMock, times(1))
+        .execute(indexEventHandler.new IndexChangeTask(CHANGE_ID, false));
+  }
+
+  @Test
+  public void duplicateAccountEventOfAQueuedEventShouldGetDiscarded() {
+    Executor poolMock = mock(Executor.class);
+    indexEventHandler = new IndexEventHandler(poolMock, PLUGIN_NAME, forwarder);
+    indexEventHandler.onAccountIndexed(accountId.get());
+    indexEventHandler.onAccountIndexed(accountId.get());
+    verify(poolMock, times(1))
+        .execute(indexEventHandler.new IndexAccountTask(ACCOUNT_ID));
+  }
+
+  @Test
+  public void testIndexChangeTaskToString() throws Exception {
+    IndexChangeTask task =
+        indexEventHandler.new IndexChangeTask(CHANGE_ID, false);
+    assertThat(task.toString()).isEqualTo(String.format(
         "[%s] Index change %s in target instance", PLUGIN_NAME, CHANGE_ID));
   }
 
   @Test
-  public void testIndexTaskHashCodeAndEquals() {
-    IndexTask task = indexEventHandler.new IndexTask(CHANGE_ID, false);
+  public void testIndexAccountTaskToString() throws Exception {
+    IndexAccountTask task = indexEventHandler.new IndexAccountTask(ACCOUNT_ID);
+    assertThat(task.toString()).isEqualTo(String.format(
+        "[%s] Index account %s in target instance", PLUGIN_NAME, ACCOUNT_ID));
+  }
+
+  @Test
+  public void testIndexChangeTaskHashCodeAndEquals() {
+    IndexChangeTask task =
+        indexEventHandler.new IndexChangeTask(CHANGE_ID, false);
 
     assertThat(task.equals(task)).isTrue();
     assertThat(task.hashCode()).isEqualTo(task.hashCode());
 
-    IndexTask identicalTask =
-        indexEventHandler.new IndexTask(CHANGE_ID, false);
+    IndexChangeTask identicalTask =
+        indexEventHandler.new IndexChangeTask(CHANGE_ID, false);
     assertThat(task.equals(identicalTask)).isTrue();
     assertThat(task.hashCode()).isEqualTo(identicalTask.hashCode());
 
@@ -116,14 +153,37 @@
     assertThat(task.equals("test")).isFalse();
     assertThat(task.hashCode()).isNotEqualTo("test".hashCode());
 
-    IndexTask differentChangeIdTask =
-        indexEventHandler.new IndexTask(123, false);
+    IndexChangeTask differentChangeIdTask =
+        indexEventHandler.new IndexChangeTask(123, false);
     assertThat(task.equals(differentChangeIdTask)).isFalse();
     assertThat(task.hashCode()).isNotEqualTo(differentChangeIdTask.hashCode());
 
-    IndexTask removeTask =
-        indexEventHandler.new IndexTask(CHANGE_ID, true);
+    IndexChangeTask removeTask =
+        indexEventHandler.new IndexChangeTask(CHANGE_ID, true);
     assertThat(task.equals(removeTask)).isFalse();
     assertThat(task.hashCode()).isNotEqualTo(removeTask.hashCode());
   }
+
+  @Test
+  public void testIndexAccountTaskHashCodeAndEquals() {
+    IndexAccountTask task = indexEventHandler.new IndexAccountTask(ACCOUNT_ID);
+
+    assertThat(task.equals(task)).isTrue();
+    assertThat(task.hashCode()).isEqualTo(task.hashCode());
+
+    IndexAccountTask identicalTask =
+        indexEventHandler.new IndexAccountTask(ACCOUNT_ID);
+    assertThat(task.equals(identicalTask)).isTrue();
+    assertThat(task.hashCode()).isEqualTo(identicalTask.hashCode());
+
+    assertThat(task.equals(null)).isFalse();
+    assertThat(task.equals("test")).isFalse();
+    assertThat(task.hashCode()).isNotEqualTo("test".hashCode());
+
+    IndexAccountTask differentAccountIdTask =
+        indexEventHandler.new IndexAccountTask(123);
+    assertThat(task.equals(differentAccountIdTask)).isFalse();
+    assertThat(task.hashCode()).isNotEqualTo(differentAccountIdTask.hashCode());
+
+  }
 }