Make targeting of batch apply object endpoint configurable

Existing solution attempts to access the endpoint, and falls back to the
apply-object if it receives a 404 (ie, if the target node does not know
about the endpoint). This is fine, however this extra network call is
redundant, given we can provide all the information in the config.

Introduce the `enableBatchedRefs` boolean flag. When the flag is set
to true, invoke the batch-apply-object endpoint, otherwise (default
behaviour for backward compatibility) fall back to the apply-object
endpoint.

Bug: Issue 40015567
Change-Id: Idfaba2ede18adb7e5175990b156f617f83750570
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
index a698ac4..dac27aa 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
@@ -389,105 +389,42 @@
     return batchApplyObjectData.stream().anyMatch(e -> e.revisionData().isEmpty() && !e.isDelete());
   }
 
-  private boolean callSendObject(
-      Source source,
+  private Optional<HttpResult> callSendObject(
+      FetchApiClient fetchClient,
+      String remoteName,
+      URIish uri,
       NameKey project,
       String refName,
       long eventCreatedOn,
       boolean isDelete,
-      List<RevisionData> revision,
-      ReplicationState state)
-      throws MissingParentObjectException {
-    boolean resultIsSuccessful = true;
-    if (source.wouldFetchProject(project) && source.wouldFetchRef(refName)) {
-      for (String apiUrl : source.getApis()) {
-        try {
-          URIish uri = new URIish(apiUrl);
-          FetchApiClient fetchClient = fetchClientFactory.create(source);
-          repLog.info(
-              "Pull replication REST API apply object to {} for {}:{} - {}",
-              apiUrl,
-              project,
-              refName,
-              revision);
-          Context<String> apiTimer = applyObjectMetrics.startEnd2End(source.getRemoteConfigName());
-          HttpResult result =
-              isDelete
-                  ? fetchClient.callSendObject(
-                      project, refName, eventCreatedOn, isDelete, null, uri)
-                  : fetchClient.callSendObjects(project, refName, eventCreatedOn, revision, uri);
-          boolean resultSuccessful = result.isSuccessful();
-          repLog.info(
-              "Pull replication REST API apply object to {} COMPLETED for {}:{} - {}, HTTP Result:"
-                  + " {} - time:{} ms",
-              apiUrl,
-              project,
-              refName,
-              revision,
-              result,
-              apiTimer.stop() / 1000000.0);
+      List<RevisionData> revision)
+      throws MissingParentObjectException, IOException {
+    String revisionDataStr =
+        Optional.ofNullable(revision).orElse(ImmutableList.of()).stream()
+            .map(RevisionData::toString)
+            .collect(Collectors.joining(","));
+    repLog.info(
+        "Pull replication REST API apply object to {} for {}:{} - {}",
+        uri,
+        project,
+        refName,
+        revisionDataStr);
+    Context<String> apiTimer = applyObjectMetrics.startEnd2End(remoteName);
+    HttpResult result =
+        isDelete
+            ? fetchClient.callSendObject(project, refName, eventCreatedOn, isDelete, null, uri)
+            : fetchClient.callSendObjects(project, refName, eventCreatedOn, revision, uri);
+    repLog.info(
+        "Pull replication REST API apply object to {} COMPLETED for {}:{} - {}, HTTP Result:"
+            + " {} - time:{} ms",
+        uri,
+        project,
+        refName,
+        revisionDataStr,
+        result,
+        apiTimer.stop() / 1000000.0);
 
-          if (!resultSuccessful
-              && result.isProjectMissing(project)
-              && source.isCreateMissingRepositories()) {
-            result = initProject(project, uri, fetchClient, result);
-            repLog.info("Missing project {} created, HTTP Result:{}", project, result);
-          }
-
-          if (!resultSuccessful) {
-            if (result.isParentObjectMissing()) {
-
-              if ((RefNames.isNoteDbMetaRef(refName) || applyObjectsRefsFilter.match(refName))
-                  && revision.size() == 1) {
-                List<RevisionData> allRevisions =
-                    fetchWholeMetaHistory(project, refName, revision.get(0));
-                repLog.info(
-                    "Pull replication REST API apply object to {} for {}:{} - {}",
-                    apiUrl,
-                    project,
-                    refName,
-                    allRevisions);
-                return callSendObject(
-                    source, project, refName, eventCreatedOn, isDelete, allRevisions, state);
-              }
-
-              throw new MissingParentObjectException(
-                  project, refName, source.getRemoteConfigName());
-            }
-          }
-
-          resultIsSuccessful &= resultSuccessful;
-        } catch (URISyntaxException e) {
-          repLog.warn(
-              "Pull replication REST API apply object to {} *FAILED* for {}:{} - {}",
-              apiUrl,
-              project,
-              refName,
-              revision,
-              e);
-          stateLog.error(String.format("Cannot parse pull replication api url:%s", apiUrl), state);
-          resultIsSuccessful = false;
-        } catch (IOException e) {
-          repLog.warn(
-              "Pull replication REST API apply object to {} *FAILED* for {}:{} - {}",
-              apiUrl,
-              project,
-              refName,
-              revision,
-              e);
-          stateLog.error(
-              String.format(
-                  "Exception during the pull replication fetch rest api call. Endpoint url:%s,"
-                      + " message:%s",
-                  apiUrl, e.getMessage()),
-              e,
-              state);
-          resultIsSuccessful = false;
-        }
-      }
-    }
-
-    return resultIsSuccessful;
+    return Optional.of(result);
   }
 
   private boolean callBatchSendObject(
@@ -509,36 +446,66 @@
             .map(BatchApplyObjectData::toString)
             .collect(Collectors.joining(","));
     FetchApiClient fetchClient = fetchClientFactory.create(source);
+    String remoteName = source.getRemoteConfigName();
 
     for (String apiUrl : source.getApis()) {
       try {
+        boolean resultSuccessful = true;
+        Optional<HttpResult> result = Optional.empty();
         URIish uri = new URIish(apiUrl);
-        repLog.info(
-            "Pull replication REST API batch apply object to {} for {}:[{}]",
-            apiUrl,
-            project,
-            batchApplyObjectStr);
-        Context<String> apiTimer = applyObjectMetrics.startEnd2End(source.getRemoteConfigName());
-        HttpResult result =
-            fetchClient.callBatchSendObject(project, filteredRefsBatch, eventCreatedOn, uri);
-        boolean resultSuccessful = result.isSuccessful();
-        repLog.info(
-            "Pull replication REST API batch apply object to {} COMPLETED for {}:[{}], HTTP  Result:"
-                + " {} - time:{} ms",
-            apiUrl,
-            project,
-            batchApplyObjectStr,
-            result,
-            apiTimer.stop() / 1000000.0);
+        if (source.enableBatchedRefs()) {
+          repLog.info(
+              "Pull replication REST API batch apply object to {} for {}:[{}]",
+              apiUrl,
+              project,
+              batchApplyObjectStr);
+          Context<String> apiTimer = applyObjectMetrics.startEnd2End(remoteName);
+          result =
+              Optional.of(
+                  fetchClient.callBatchSendObject(project, filteredRefsBatch, eventCreatedOn, uri));
+          resultSuccessful = result.map(HttpResult::isSuccessful).orElse(false);
+          repLog.info(
+              "Pull replication REST API batch apply object to {} COMPLETED for {}:[{}], HTTP  Result:"
+                  + " {} - time:{} ms",
+              apiUrl,
+              project,
+              batchApplyObjectStr,
+              result,
+              apiTimer.stop() / 1000000.0);
+        } else {
+          repLog.info(
+              "REST API batch apply object not enabled for source {}, using REST API apply object to {} for {}:[{}]",
+              remoteName,
+              apiUrl,
+              project,
+              batchApplyObjectStr);
+          for (BatchApplyObjectData batchApplyObject : filteredRefsBatch) {
+            result =
+                callSendObject(
+                    fetchClient,
+                    remoteName,
+                    uri,
+                    project,
+                    batchApplyObject.refName(),
+                    eventCreatedOn,
+                    batchApplyObject.isDelete(),
+                    batchApplyObject.revisionData().map(ImmutableList::of).orElse(null));
+
+            resultSuccessful = result.map(HttpResult::isSuccessful).orElse(false);
+            if (!resultSuccessful) {
+              break;
+            }
+          }
+        }
 
         if (!resultSuccessful
-            && result.isProjectMissing(project)
+            && result.map(r -> r.isProjectMissing(project)).orElse(false)
             && source.isCreateMissingRepositories()) {
-          result = initProject(project, uri, fetchClient, result);
+          result = initProject(project, uri, fetchClient);
           repLog.info("Missing project {} created, HTTP Result:{}", project, result);
         }
 
-        if (!resultSuccessful && result.isParentObjectMissing()) {
+        if (!resultSuccessful && result.map(HttpResult::isParentObjectMissing).orElse(false)) {
           resultSuccessful = true;
           for (BatchApplyObjectData batchApplyObject : filteredRefsBatch) {
             String refName = batchApplyObject.refName();
@@ -549,15 +516,20 @@
               List<RevisionData> allRevisions =
                   fetchWholeMetaHistory(project, refName, maybeRevisionData.get());
 
-              resultSuccessful &=
+              Optional<HttpResult> sendObjectResult =
                   callSendObject(
-                      source,
+                      fetchClient,
+                      remoteName,
+                      uri,
                       project,
                       refName,
                       eventCreatedOn,
                       batchApplyObject.isDelete(),
-                      allRevisions,
-                      state);
+                      allRevisions);
+              resultSuccessful = sendObjectResult.map(HttpResult::isSuccessful).orElse(false);
+              if (!resultSuccessful) {
+                break;
+              }
             } else {
               throw new MissingParentObjectException(
                   project, refName, source.getRemoteConfigName());
@@ -565,21 +537,6 @@
           }
         }
 
-        if (!resultSuccessful && !result.isSendBatchObjectAvailable()) {
-          resultSuccessful = true;
-          for (BatchApplyObjectData batchApplyObjectData : filteredRefsBatch) {
-            resultSuccessful &=
-                callSendObject(
-                    source,
-                    project,
-                    batchApplyObjectData.refName(),
-                    eventCreatedOn,
-                    batchApplyObjectData.isDelete(),
-                    batchApplyObjectData.revisionData().map(ImmutableList::of).orElse(null),
-                    state);
-          }
-        }
-
         batchResultSuccessful &= resultSuccessful;
       } catch (URISyntaxException e) {
         repLog.warn(
@@ -648,9 +605,9 @@
             repLog.info(
                 "Pull replication REST API fetch to {} for {}:{}", apiUrl, project, refName);
             Context<String> timer = fetchMetrics.startEnd2End(source.getRemoteConfigName());
-            HttpResult result = fetchClient.callFetch(project, refName, uri);
+            Optional<HttpResult> result = Optional.of(fetchClient.callFetch(project, refName, uri));
             long elapsedMs = TimeUnit.NANOSECONDS.toMillis(timer.stop());
-            boolean resultSuccessful = result.isSuccessful();
+            boolean resultSuccessful = result.map(HttpResult::isSuccessful).orElse(false);
             repLog.info(
                 "Pull replication REST API fetch to {} COMPLETED for {}:{}, HTTP Result:"
                     + " {} - time:{} ms",
@@ -660,19 +617,19 @@
                 result,
                 elapsedMs);
             if (!resultSuccessful
-                && result.isProjectMissing(project)
+                && result.map(r -> r.isProjectMissing(project)).orElse(false)
                 && source.isCreateMissingRepositories()) {
-              result = initProject(project, uri, fetchClient, result);
+              result = initProject(project, uri, fetchClient);
             }
             if (!resultSuccessful) {
               stateLog.warn(
                   String.format(
                       "Pull replication rest api fetch call failed. Endpoint url: %s, reason:%s",
-                      apiUrl, result.getMessage().orElse("unknown")),
+                      apiUrl, result.flatMap(HttpResult::getMessage).orElse("unknown")),
                   state);
             }
 
-            resultIsSuccessful &= result.isSuccessful();
+            resultIsSuccessful &= result.map(HttpResult::isSuccessful).orElse(false);
           } catch (URISyntaxException e) {
             stateLog.error(
                 String.format("Cannot parse pull replication api url:%s", apiUrl), state);
@@ -698,12 +655,12 @@
     return maxRetries == 0 || attempt < maxRetries;
   }
 
-  private HttpResult initProject(
-      Project.NameKey project, URIish uri, FetchApiClient fetchClient, HttpResult result)
-      throws IOException {
+  private Optional<HttpResult> initProject(
+      Project.NameKey project, URIish uri, FetchApiClient fetchClient) throws IOException {
     HttpResult initProjectResult = fetchClient.initProject(project, uri);
+    Optional<HttpResult> result = Optional.empty();
     if (initProjectResult.isSuccessful()) {
-      result = fetchClient.callFetch(project, FetchOne.ALL_REFS, uri);
+      result = Optional.of(fetchClient.callFetch(project, FetchOne.ALL_REFS, uri));
     } else {
       String errorMessage = initProjectResult.getMessage().map(e -> " - Error: " + e).orElse("");
       repLog.error("Cannot create project " + project + errorMessage);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java
index 5ab3859..19fca4d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java
@@ -875,6 +875,10 @@
     return config.replicateProjectDeletions();
   }
 
+  public boolean enableBatchedRefs() {
+    return config.enableBatchedRefs();
+  }
+
   void scheduleUpdateHead(String apiUrl, Project.NameKey project, String newHead) {
     try {
       URIish apiURI = new URIish(apiUrl);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfiguration.java
index 0a22a5a..d3dad9b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfiguration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/SourceConfiguration.java
@@ -16,6 +16,7 @@
 
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.googlesource.gerrit.plugins.replication.RemoteConfiguration;
 import java.util.concurrent.TimeUnit;
@@ -23,6 +24,7 @@
 import org.eclipse.jgit.transport.RemoteConfig;
 
 public class SourceConfiguration implements RemoteConfiguration {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
   static final int DEFAULT_REPLICATION_DELAY = 4;
   static final int DEFAULT_RESCHEDULE_DELAY = 3;
   static final int DEFAULT_SLOW_LATENCY_THRESHOLD_SECS = 900;
@@ -56,6 +58,7 @@
   private int slowLatencyThreshold;
   private boolean useCGitClient;
   private int refsBatchSize;
+  private boolean enableBatchedRefs;
 
   public SourceConfiguration(RemoteConfig remoteConfig, Config cfg) {
     this.remoteConfig = remoteConfig;
@@ -110,6 +113,15 @@
                 "shutDownDrainTimeout",
                 DEFAULT_DRAIN_SHUTDOWN_TIMEOUT_SECS,
                 TimeUnit.SECONDS);
+
+    enableBatchedRefs = cfg.getBoolean("remote", name, "enableBatchedRefs", false);
+    if (!enableBatchedRefs) {
+      logger.atWarning().log(
+          "You haven't enabled batched refs in the node, as such you are not "
+              + "leveraging the performance improvements introduced by the batch-apply-object API. Consider "
+              + "upgrading the plugin to the latest version and consult the plugin's documentation for more "
+              + "details on the `enableBatchedRefs` configuration.");
+    }
   }
 
   @Override
@@ -237,4 +249,8 @@
   public int getShutDownDrainTimeout() {
     return shutDownDrainTimeout;
   }
+
+  public boolean enableBatchedRefs() {
+    return enableBatchedRefs;
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/HttpResult.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/HttpResult.java
index 6428ece..ec9d65f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/HttpResult.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/HttpResult.java
@@ -15,7 +15,6 @@
 package com.googlesource.gerrit.plugins.replication.pull.client;
 
 import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
 
 import com.google.gerrit.entities.Project;
 import java.util.Optional;
@@ -52,8 +51,4 @@
         ? "OK"
         : "FAILED" + ", status=" + responseCode + message.map(s -> " '" + s + "'").orElse("");
   }
-
-  public boolean isSendBatchObjectAvailable() {
-    return responseCode != SC_NOT_FOUND;
-  }
 }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 6287773..128179c 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -574,6 +574,21 @@
 	By default, replicates without matching, i.e. replicates
 	everything from all remotes.
 
+remote.NAME.enableBatchedRefs
+:	Choose whether the batch-apply-object endpoint is enabled.
+	If you set this to `true`, then there will be a single call
+	to the batch-apply-object endpoint with all the refs from
+	the batch ref update included. The default behaviour means
+	one call to the apply object(s) endpoint per ref.
+
+	*NOTE*: the default value is only needed for backwards
+	compatibility to allow migrating transparently to the
+	latest pull-replication plugin version. Once the migration is
+	over, this value should be set to `true` to leverage the
+	performance improvements introduced by the `batch-apply-object` API.
+
+	By default, false.
+
 Directory `replication`
 --------------------
 The optional directory `$site_path/etc/replication` contains Git-style
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
index 53997b7..a5bffd3 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
@@ -135,6 +135,7 @@
     when(source.wouldFetchRef(anyString())).thenReturn(true);
     ImmutableList<String> apis = ImmutableList.of("http://localhost:18080");
     when(source.getApis()).thenReturn(apis);
+    when(source.enableBatchedRefs()).thenReturn(true);
     when(sourceCollection.getAll()).thenReturn(Lists.newArrayList(source));
     when(rd.get()).thenReturn(sourceCollection);
     when(config.getBoolean("event", "stream-events", "enableBatchRefUpdatedEvents", false))
@@ -397,9 +398,8 @@
 
     when(batchHttpResult.isSuccessful()).thenReturn(false);
     when(batchHttpResult.isParentObjectMissing()).thenReturn(false);
-    when(batchHttpResult.isSendBatchObjectAvailable()).thenReturn(false);
-    when(httpResult.isSuccessful()).thenReturn(false);
-    when(httpResult.isParentObjectMissing()).thenReturn(false);
+    lenient().when(httpResult.isSuccessful()).thenReturn(false);
+    lenient().when(httpResult.isParentObjectMissing()).thenReturn(false);
 
     objectUnderTest.onEvent(event);
 
@@ -451,61 +451,14 @@
   }
 
   @Test
-  public void
-      shouldFallbackToApplyAllParentObjectsWhenSendBatchObjectNotAvailableAndParentObjectIsMissingOnMetaRef()
-          throws IOException {
-    Event event = generateBatchRefUpdateEvent("refs/changes/01/1/meta");
+  public void shouldCallSendObjectsIfBatchedRefsNotEnabledAtSource() throws IOException {
+    Event event = generateBatchRefUpdateEvent("refs/changes/01/1/1");
+    when(source.enableBatchedRefs()).thenReturn(false);
     objectUnderTest.start();
-
-    when(batchHttpResult.isSuccessful()).thenReturn(false);
-    when(batchHttpResult.isParentObjectMissing()).thenReturn(false);
-    when(batchHttpResult.isSendBatchObjectAvailable()).thenReturn(false);
-    when(httpResult.isSuccessful()).thenReturn(false, true);
-    when(httpResult.isParentObjectMissing()).thenReturn(true, false);
-
     objectUnderTest.onEvent(event);
 
-    verify(fetchRestApiClient, times(2))
-        .callSendObjects(any(), anyString(), anyLong(), revisionsDataCaptor.capture(), any());
-    List<List<RevisionData>> revisionsDataValues = revisionsDataCaptor.getAllValues();
-    assertThat(revisionsDataValues).hasSize(2);
-
-    List<RevisionData> firstRevisionsValues = revisionsDataValues.get(0);
-    assertThat(firstRevisionsValues).hasSize(1);
-    assertThat(firstRevisionsValues).contains(revisionData);
-
-    List<RevisionData> secondRevisionsValues = revisionsDataValues.get(1);
-    assertThat(secondRevisionsValues).hasSize(1 + revisionDataParentObjectIds.size());
-  }
-
-  @Test
-  public void
-      shouldFallbackToApplyAllParentObjectsWhenSendBatchObjectNotAvailableAndParentObjectIsMissingOnAllowedRefs()
-          throws IOException {
-    String refName = "refs/tags/test-tag";
-    Event event = generateBatchRefUpdateEvent(refName);
-    objectUnderTest.start();
-
-    when(batchHttpResult.isSuccessful()).thenReturn(false);
-    when(batchHttpResult.isParentObjectMissing()).thenReturn(false);
-    when(batchHttpResult.isSendBatchObjectAvailable()).thenReturn(false);
-    when(httpResult.isSuccessful()).thenReturn(false, true);
-    when(httpResult.isParentObjectMissing()).thenReturn(true, false);
-    when(applyObjectsRefsFilter.match(refName)).thenReturn(true);
-
-    objectUnderTest.onEvent(event);
-
-    verify(fetchRestApiClient, times(2))
-        .callSendObjects(any(), anyString(), anyLong(), revisionsDataCaptor.capture(), any());
-    List<List<RevisionData>> revisionsDataValues = revisionsDataCaptor.getAllValues();
-    assertThat(revisionsDataValues).hasSize(2);
-
-    List<RevisionData> firstRevisionsValues = revisionsDataValues.get(0);
-    assertThat(firstRevisionsValues).hasSize(1);
-    assertThat(firstRevisionsValues).contains(revisionData);
-
-    List<RevisionData> secondRevisionsValues = revisionsDataValues.get(1);
-    assertThat(secondRevisionsValues).hasSize(1 + revisionDataParentObjectIds.size());
+    verify(fetchRestApiClient, never()).callBatchSendObject(any(), any(), anyLong(), any());
+    verify(fetchRestApiClient).callSendObjects(any(), anyString(), anyLong(), any(), any());
   }
 
   @Test
@@ -516,7 +469,6 @@
     when(batchHttpResult.isParentObjectMissing()).thenReturn(true);
     when(applyObjectsRefsFilter.match(any())).thenReturn(true, true);
     when(httpResult.isSuccessful()).thenReturn(true, false);
-    when(httpResult.isParentObjectMissing()).thenReturn(true);
 
     objectUnderTest.start();
     objectUnderTest.onEvent(event);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/HttpResultTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/HttpResultTest.java
index 900723e..3f73729 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/HttpResultTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/HttpResultTest.java
@@ -30,25 +30,22 @@
   public static Iterable<Object[]> data() {
     return Arrays.asList(
         new Object[][] {
-          {HttpServletResponse.SC_OK, true, true},
-          {HttpServletResponse.SC_CREATED, true, true},
-          {HttpServletResponse.SC_ACCEPTED, true, true},
-          {HttpServletResponse.SC_NO_CONTENT, true, true},
-          {HttpServletResponse.SC_BAD_REQUEST, false, true},
-          {HttpServletResponse.SC_NOT_FOUND, false, false},
-          {HttpServletResponse.SC_CONFLICT, false, true}
+          {HttpServletResponse.SC_OK, true},
+          {HttpServletResponse.SC_CREATED, true},
+          {HttpServletResponse.SC_ACCEPTED, true},
+          {HttpServletResponse.SC_NO_CONTENT, true},
+          {HttpServletResponse.SC_BAD_REQUEST, false},
+          {HttpServletResponse.SC_NOT_FOUND, false},
+          {HttpServletResponse.SC_CONFLICT, false}
         });
   }
 
   private Integer httpStatus;
   private boolean isSuccessful;
-  private boolean isSendBatchObjectAvailable;
 
-  public HttpResultTest(
-      Integer httpStatus, Boolean isSuccessful, Boolean isSendBatchObjectAvailable) {
+  public HttpResultTest(Integer httpStatus, Boolean isSuccessful) {
     this.httpStatus = httpStatus;
     this.isSuccessful = isSuccessful;
-    this.isSendBatchObjectAvailable = isSendBatchObjectAvailable;
   }
 
   @Test
@@ -56,10 +53,4 @@
     HttpResult httpResult = new HttpResult(httpStatus, Optional.empty());
     assertThat(httpResult.isSuccessful()).isEqualTo(isSuccessful);
   }
-
-  @Test
-  public void httpResultIsSendBatchObjectAvailable() {
-    HttpResult httpResult = new HttpResult(httpStatus, Optional.empty());
-    assertThat(httpResult.isSendBatchObjectAvailable()).isEqualTo(isSendBatchObjectAvailable);
-  }
 }