Merge branch 'stable-3.13' into stable-3.14

* stable-3.13:
  Adapt tests to updated Gerrit API

Change-Id: If498339806d346d042e283c14fe587dd4065ba7d
diff --git a/README.md b/README.md
index bd49524..464eb52 100644
--- a/README.md
+++ b/README.md
@@ -131,7 +131,7 @@
 
 ```
 [healtcheck]
-  failFileFlaPath="data/healthcheck/fail"
+  failFileFlagPath="data/healthcheck/fail"
 ```
 
 This will make the healthcheck endpoint return 500 even if the node is otherwise
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckExceptionHook.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckExceptionHook.java
new file mode 100644
index 0000000..b74b591
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckExceptionHook.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2025 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.healthcheck;
+
+import static com.googlesource.gerrit.plugins.healthcheck.filter.HealthCheckStatusFilter.GSON;
+import static com.googlesource.gerrit.plugins.healthcheck.filter.HealthCheckStatusFilter.JSON_MAGIC_STRING;
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.ExceptionHook;
+import com.google.inject.AbstractModule;
+import java.util.Optional;
+
+public class HealthCheckExceptionHook implements ExceptionHook {
+
+  @Override
+  public Optional<Status> getStatus(Throwable throwable) {
+    if (throwable instanceof HealthCheckFailedException exc) {
+      return Optional.of(
+          Status.create(
+              SC_INTERNAL_SERVER_ERROR, JSON_MAGIC_STRING + GSON.toJson(exc.getResult())));
+    }
+    return Optional.empty();
+  }
+
+  static AbstractModule module() {
+    return new AbstractModule() {
+
+      @Override
+      protected void configure() {
+        DynamicSet.bind(binder(), ExceptionHook.class).to(HealthCheckExceptionHook.class);
+      }
+    };
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckFailedException.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckFailedException.java
new file mode 100644
index 0000000..2ea0c14
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckFailedException.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2025 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.healthcheck;
+
+import java.util.Map;
+
+public class HealthCheckFailedException extends Exception {
+  private final Map<String, Object> result;
+
+  public HealthCheckFailedException(Map<String, Object> result) {
+    this.result = result;
+  }
+
+  Map<String, Object> getResult() {
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "Health check failed: " + result;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/Module.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/Module.java
index 54cd5a0..d13108a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/Module.java
@@ -20,6 +20,7 @@
 
   @Override
   protected void configure() {
+    install(HealthCheckExceptionHook.module());
     install(new HealthCheckSubsystemsModule());
     install(new HealthCheckApiModule());
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/api/HealthCheckStatusEndpoint.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/api/HealthCheckStatusEndpoint.java
index 58ab01b..fe73cc5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/api/HealthCheckStatusEndpoint.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/api/HealthCheckStatusEndpoint.java
@@ -19,6 +19,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.healthcheck.HealthCheckConfig;
+import com.googlesource.gerrit.plugins.healthcheck.HealthCheckFailedException;
 import com.googlesource.gerrit.plugins.healthcheck.check.GlobalHealthCheck;
 import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck;
 import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck.Result;
@@ -45,21 +46,23 @@
   public Response<Map<String, Object>> apply(ConfigResource resource)
       throws AuthException, BadRequestException, ResourceConflictException, Exception {
     if (failFlagFileExists()) {
-      return Response.withStatusCode(
-          HttpServletResponse.SC_INTERNAL_SERVER_ERROR, Map.of("reason", "Fail Flag File exists"));
+      throw new HealthCheckFailedException(Map.of("reason", "Fail Flag File exists"));
     }
     HealthCheck.StatusSummary globalHealthCheckStatus = healthChecks.run();
 
     Map<String, Object> result = globalHealthCheckStatus.subChecks();
     result.put("ts", globalHealthCheckStatus.ts());
     result.put("elapsed", globalHealthCheckStatus.elapsed());
-    return Response.withStatusCode(getHTTPResultCode(globalHealthCheckStatus), result);
+    return Response.withStatusCode(getHTTPResultCode(globalHealthCheckStatus, result), result);
   }
 
-  private int getHTTPResultCode(HealthCheck.StatusSummary checkStatus) {
-    return checkStatus.result() == Result.FAILED
-        ? HttpServletResponse.SC_INTERNAL_SERVER_ERROR
-        : HttpServletResponse.SC_OK;
+  private int getHTTPResultCode(HealthCheck.StatusSummary checkStatus, Map<String, Object> result)
+      throws HealthCheckFailedException {
+    if (checkStatus.result() == Result.FAILED) {
+      throw new HealthCheckFailedException(result);
+    }
+
+    return HttpServletResponse.SC_OK;
   }
 
   private boolean failFlagFileExists() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/AuthHealthCheck.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/AuthHealthCheck.java
index ffb7d16..63fce89 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/AuthHealthCheck.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/AuthHealthCheck.java
@@ -58,7 +58,7 @@
   protected Result doCheck() throws Exception {
     AuthRequest authRequest = authRequestFactory.createForUser(username);
     authRequest.setPassword(password);
-    realm.authenticate(authRequest);
+    var unused = realm.authenticate(authRequest);
 
     Optional<AccountState> accountState = byIdCache.getByUsername(username);
     if (!accountState.isPresent()) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/BlockedThreadsConfigurator.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/BlockedThreadsConfigurator.java
index 8bfe53c..e122066 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/BlockedThreadsConfigurator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/BlockedThreadsConfigurator.java
@@ -98,7 +98,7 @@
 
     if (specsClassified.size() == 1) {
       Map.Entry<Boolean, List<Threshold>> entry = specsClassified.entrySet().iterator().next();
-      return Boolean.TRUE == entry.getKey()
+      return Boolean.TRUE.equals(entry.getKey())
           ? deduplicatePrefixes(entry.getValue())
           : deduplicateGlobal(entry.getValue());
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/filter/HealthCheckStatusFilter.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/filter/HealthCheckStatusFilter.java
index 1b52d4c..5415bc2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/filter/HealthCheckStatusFilter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/filter/HealthCheckStatusFilter.java
@@ -20,16 +20,20 @@
 import com.google.gerrit.httpd.AllRequestFilter;
 import com.google.gerrit.httpd.restapi.RestApiServlet;
 import com.google.gerrit.json.OutputFormat;
+import com.google.gerrit.server.ExceptionHook;
 import com.google.gerrit.server.config.ConfigResource;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gson.Gson;
 import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.healthcheck.HealthCheckExceptionHook;
 import com.googlesource.gerrit.plugins.healthcheck.api.HealthCheckStatusEndpoint;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
@@ -39,22 +43,27 @@
 import org.eclipse.jgit.lib.Config;
 
 public class HealthCheckStatusFilter extends AllRequestFilter {
+  public static final Gson GSON = OutputFormat.JSON.newGsonBuilder().create();
+  public static final String JSON_MAGIC_STRING =
+      new String(RestApiServlet.JSON_MAGIC, StandardCharsets.UTF_8);
+
   private final HealthCheckStatusEndpoint statusEndpoint;
-  private final Gson gson;
   private final String pluginName;
   private final Config cfg;
   private final String uriPattern;
+  private final HealthCheckExceptionHook exceptionHook;
 
   @Inject
   public HealthCheckStatusFilter(
       HealthCheckStatusEndpoint statusEndpoint,
       @PluginName String pluginName,
-      @GerritServerConfig Config cfg) {
+      @GerritServerConfig Config cfg,
+      HealthCheckExceptionHook exceptionHook) {
     this.statusEndpoint = statusEndpoint;
-    this.gson = OutputFormat.JSON.newGsonBuilder().create();
     this.pluginName = pluginName;
     this.cfg = cfg;
     this.uriPattern = getUriPattern();
+    this.exceptionHook = exceptionHook;
   }
 
   private static List<String> extractUriPrefixes(String[] listenUrls) {
@@ -120,19 +129,25 @@
     return httpServletRequest.getRequestURI().matches(uriPattern);
   }
 
-  private void doStatusCheck(HttpServletResponse httpResponse) throws ServletException {
+  private void doStatusCheck(HttpServletResponse httpResponse)
+      throws ServletException, IOException {
     try {
       Response<Map<String, Object>> healthStatus = statusEndpoint.apply(new ConfigResource());
-      String healthStatusJson = gson.toJson(healthStatus.value());
+      String healthStatusJson = GSON.toJson(healthStatus.value());
       if (healthStatus.statusCode() == HttpServletResponse.SC_OK) {
         PrintWriter writer = httpResponse.getWriter();
-        writer.print(new String(RestApiServlet.JSON_MAGIC));
+        writer.print(JSON_MAGIC_STRING);
         writer.print(healthStatusJson);
       } else {
         httpResponse.sendError(healthStatus.statusCode(), healthStatusJson);
       }
     } catch (Exception e) {
-      throw new ServletException(e);
+      Optional<ExceptionHook.Status> status = exceptionHook.getStatus(e);
+      if (status.isPresent()) {
+        httpResponse.sendError(status.get().statusCode(), status.get().statusMessage());
+      } else {
+        throw new ServletException(e);
+      }
     }
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/AbstractHealthCheckIntegrationTest.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/AbstractHealthCheckIntegrationTest.java
index 1b1cd70..649e83b 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/AbstractHealthCheckIntegrationTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/AbstractHealthCheckIntegrationTest.java
@@ -86,7 +86,7 @@
     for (int i = 0; i < numChanges; i++) {
       createChange("refs/for/master");
     }
-    accountCreator.create(config.getUsername(HealthCheckNames.AUTH));
+    var unused = accountCreator.create(config.getUsername(HealthCheckNames.AUTH));
 
     healthCheckUriPath =
         String.format(
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckStatusEndpointTest.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckStatusEndpointTest.java
index af09d07..4185a21 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckStatusEndpointTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckStatusEndpointTest.java
@@ -15,6 +15,7 @@
 package com.googlesource.gerrit.plugins.healthcheck;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 import static com.googlesource.gerrit.plugins.healthcheck.HealthCheckConfig.DEFAULT_CONFIG;
 
 import com.google.common.util.concurrent.ListeningExecutorService;
@@ -102,7 +103,7 @@
   }
 
   @Test
-  public void shouldReturnServerErrorWhenOneChecksTimesOut() throws Exception {
+  public void shouldThrowHealthCheckFailedWhenOneChecksTimesOut() throws Exception {
     Injector injector =
         testInjector(
             new AbstractModule() {
@@ -124,13 +125,12 @@
 
     HealthCheckStatusEndpoint healthCheckApi =
         injector.getInstance(HealthCheckStatusEndpoint.class);
-    Response<?> resp = healthCheckApi.apply(null);
 
-    assertThat(resp.statusCode()).isEqualTo(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+    assertThrows(HealthCheckFailedException.class, () -> healthCheckApi.apply(null));
   }
 
   @Test
-  public void shouldReturnServerErrorWhenAtLeastOneCheckIsFailing() throws Exception {
+  public void shouldThrowHealthCheckFailedWhenAtLeastOneCheckIsFailing() throws Exception {
     Injector injector =
         testInjector(
             new AbstractModule() {
@@ -166,9 +166,8 @@
 
     HealthCheckStatusEndpoint healthCheckApi =
         injector.getInstance(HealthCheckStatusEndpoint.class);
-    Response<?> resp = healthCheckApi.apply(null);
 
-    assertThat(resp.statusCode()).isEqualTo(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+    assertThrows(HealthCheckFailedException.class, () -> healthCheckApi.apply(null));
   }
 
   private Injector testInjector(AbstractModule testModule) {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HttpActiveWorkersCheckTest.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HttpActiveWorkersCheckTest.java
index 60c233a..1bfc2eb 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HttpActiveWorkersCheckTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HttpActiveWorkersCheckTest.java
@@ -118,7 +118,7 @@
         healtchCheckConfig,
         injector.getInstance(ThreadSettingsConfig.class),
         injector.getInstance(MetricRegistry.class),
-        new TestMetricMaker());
+        TestMetricMaker.getInstance());
   }
 
   private Injector testInjector(AbstractModule testModule) {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/filter/HealthCheckStatusFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/filter/HealthCheckStatusFilterTest.java
index 9d44f21..6dc606d 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/filter/HealthCheckStatusFilterTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/filter/HealthCheckStatusFilterTest.java
@@ -58,7 +58,7 @@
   private HealthCheckStatusFilter createFilter(List<String> listenUrl) {
     Config cfg = new Config();
     cfg.setStringList("httpd", null, "listenUrl", listenUrl);
-    return new HealthCheckStatusFilter(null, "healthcheck", cfg);
+    return new HealthCheckStatusFilter(null, "healthcheck", cfg, null);
   }
 
   private HttpServletRequest createRequest(String path) {