You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by ad...@apache.org on 2026/06/23 11:58:27 UTC

(polaris) branch main updated: Remove TestEnvironmentExtension (#4841)

This is an automated email from the ASF dual-hosted git repository.

adutra pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git


The following commit(s) were added to refs/heads/main by this push:
     new e6edacff11 Remove TestEnvironmentExtension (#4841)
e6edacff11 is described below

commit e6edacff111af6ec022020aebe5d8f5e24361f1a
Author: Alexandre Dutra <ad...@apache.org>
AuthorDate: Tue Jun 23 13:58:22 2026 +0200

    Remove TestEnvironmentExtension (#4841)
    
    This extension was introduced in the early stages of Polaris. Its main purpose was to expose the management port to some tests.
    
    We now have a better option for achieving this with the `ServerManager` SPI and various `RestApi` clients.
    
    This PR removes that extension and replaces it with a new `MetricsApi extends RestApi` class. The `Server` interface now exposes a new `managementUri()` method.
---
 integration-tests/build.gradle.kts                 |  1 +
 .../env/{Server.java => PlatformApiEndpoints.java} | 40 +++++-----
 .../polaris/service/it/env/PlatformMetricsApi.java | 18 +++--
 .../service/it/env/PolarisApiEndpoints.java        | 22 ++++--
 .../polaris/service/it/env/PolarisClient.java      | 39 +++++++++-
 .../org/apache/polaris/service/it/env/RestApi.java | 12 ++-
 .../org/apache/polaris/service/it/env/Server.java  | 13 ++++
 .../it/ext/PolarisIntegrationTestExtension.java    | 13 ++++
 .../service/it/env/PlatformApiEndpointsTest.java   | 52 +++++++++++++
 .../service/it/env/PolarisApiEndpointsTest.java    | 64 +++++++++++++--
 .../src/main/resources/application-it.properties   |  4 +-
 .../src/main/resources/application-test.properties |  5 ++
 .../src/main/resources/application.properties      |  2 -
 .../polaris/service/metrics/MetricsTestBase.java   | 32 ++++----
 .../service/ratelimiter/RateLimiterFilterTest.java | 38 ++++-----
 .../test/DefaultTestEnvironmentResolver.java       | 39 ----------
 .../polaris/service/test/TestEnvironment.java      | 42 ----------
 .../service/test/TestEnvironmentExtension.java     | 91 ----------------------
 .../service/test/TestEnvironmentResolver.java      | 26 -------
 .../apache/polaris/service/it/ServerManager.java   | 29 +++++--
 20 files changed, 290 insertions(+), 292 deletions(-)

diff --git a/integration-tests/build.gradle.kts b/integration-tests/build.gradle.kts
index 518000c8a8..edb4f7185c 100644
--- a/integration-tests/build.gradle.kts
+++ b/integration-tests/build.gradle.kts
@@ -78,6 +78,7 @@ dependencies {
   implementation(libs.assertj.core)
   implementation(libs.mockito.core)
   implementation(libs.awaitility)
+  implementation(libs.hawkular.agent.prometheus.scraper)
   implementation(libs.s3mock.testcontainers)
   implementation(project(":polaris-runtime-test-common"))
   implementation(project(":polaris-minio-testcontainer"))
diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/env/Server.java b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PlatformApiEndpoints.java
similarity index 51%
copy from integration-tests/src/main/java/org/apache/polaris/service/it/env/Server.java
copy to integration-tests/src/main/java/org/apache/polaris/service/it/env/PlatformApiEndpoints.java
index fea53d8917..8f6cd35eb2 100644
--- a/integration-tests/src/main/java/org/apache/polaris/service/it/env/Server.java
+++ b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PlatformApiEndpoints.java
@@ -18,37 +18,33 @@
  */
 package org.apache.polaris.service.it.env;
 
+import static org.apache.polaris.service.it.env.PolarisApiEndpoints.appendPath;
+
+import java.io.Serializable;
 import java.net.URI;
-import java.util.Map;
 
 /**
- * This is a holder for access information to a particular Polaris Server. Test cases may use only
- * the provided admin credentials or create new principals.
+ * Endpoints exposed by the server's management interface.
+ *
+ * <p>These endpoints are platform-dependent and may not be always available. This class assumes
+ * that Polaris is running on a Quarkus platform, and adopts its naming conventions. It may not work
+ * well on other platforms.
  */
-public interface Server extends AutoCloseable {
+public final class PlatformApiEndpoints implements Serializable {
 
-  String DEFAULT_REALM_HEADER = "Polaris-Realm";
-  String DEFAULT_REALM_ID = "POLARIS";
+  private final URI metricsApiEndpoint;
+  private final URI healthApiEndpoint;
 
-  default String realmId() {
-    return DEFAULT_REALM_ID;
+  public PlatformApiEndpoints(URI managementUri) {
+    this.metricsApiEndpoint = appendPath(managementUri, "metrics");
+    this.healthApiEndpoint = appendPath(managementUri, "health");
   }
 
-  default String realmHeaderName() {
-    return DEFAULT_REALM_HEADER;
+  public URI metricsApiEndpoint() {
+    return metricsApiEndpoint;
   }
 
-  default Map<String, String> headers() {
-    return Map.of(realmHeaderName(), realmId());
+  public URI healthApiEndpoint() {
+    return healthApiEndpoint;
   }
-
-  /**
-   * The base URI to all Polaris APIs (e.g. the common base of the Iceberg REST API endpoints and
-   * Polaris Management API endpoints).
-   *
-   * @see PolarisApiEndpoints
-   */
-  URI baseUri();
-
-  ClientPrincipal adminCredentials();
 }
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/test/TestMetricsUtil.java b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PlatformMetricsApi.java
similarity index 81%
rename from runtime/service/src/test/java/org/apache/polaris/service/test/TestMetricsUtil.java
rename to integration-tests/src/main/java/org/apache/polaris/service/it/env/PlatformMetricsApi.java
index 088cdd4f7a..9e64ed18de 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/test/TestMetricsUtil.java
+++ b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PlatformMetricsApi.java
@@ -16,12 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.polaris.service.test;
+package org.apache.polaris.service.it.env;
 
-import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.assertj.core.api.Assertions.assertThat;
 
 import jakarta.ws.rs.client.Client;
-import jakarta.ws.rs.client.ClientBuilder;
 import jakarta.ws.rs.core.Response;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
@@ -33,12 +32,15 @@ import org.hawkular.agent.prometheus.text.TextPrometheusMetricsProcessor;
 import org.hawkular.agent.prometheus.types.MetricFamily;
 import org.hawkular.agent.prometheus.walkers.CollectorPrometheusMetricsWalker;
 
-/** Utils for working with metrics in tests */
-public class TestMetricsUtil {
+public class PlatformMetricsApi extends RestApi {
 
-  public static Map<String, MetricFamily> fetchMetrics(URI baseManagementUri) {
-    try (Client client = ClientBuilder.newBuilder().build();
-        Response response = client.target(baseManagementUri.resolve("q/metrics")).request().get()) {
+  PlatformMetricsApi(Client client, URI metricsApiEndpoint) {
+    super(client, metricsApiEndpoint, "text/plain");
+  }
+
+  /** Fetches all Prometheus metric families from {@code q/metrics}. */
+  public Map<String, MetricFamily> fetchMetrics() {
+    try (Response response = request().get()) {
       assertThat(response).returns(Response.Status.OK.getStatusCode(), Response::getStatus);
       String body = response.readEntity(String.class);
       InputStream inputStream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisApiEndpoints.java b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisApiEndpoints.java
index 78e62bcd29..109f4089d9 100644
--- a/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisApiEndpoints.java
+++ b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisApiEndpoints.java
@@ -30,26 +30,24 @@ import java.util.stream.Collectors;
  */
 public final class PolarisApiEndpoints implements Serializable {
 
-  private final URI baseUri;
+  private final URI catalogApiEndpoint;
+  private final URI managementApiEndpoint;
   private final String realmId;
   private final Map<String, String> headers;
 
-  public PolarisApiEndpoints(URI baseUri, String realmId, String realmHeaderName) {
-    this(baseUri, realmId, Map.of(realmHeaderName, realmId));
-  }
-
   public PolarisApiEndpoints(URI baseUri, String realmId, Map<String, String> headers) {
-    this.baseUri = baseUri;
+    this.catalogApiEndpoint = appendPath(baseUri, "api/catalog");
+    this.managementApiEndpoint = appendPath(baseUri, "api/management");
     this.realmId = realmId;
     this.headers = headers;
   }
 
   public URI catalogApiEndpoint() {
-    return baseUri.resolve(baseUri.getRawPath() + "/api/catalog").normalize();
+    return catalogApiEndpoint;
   }
 
   public URI managementApiEndpoint() {
-    return baseUri.resolve(baseUri.getRawPath() + "/api/management").normalize();
+    return managementApiEndpoint;
   }
 
   public String realmId() {
@@ -65,4 +63,12 @@ public final class PolarisApiEndpoints implements Serializable {
         .map(e -> Map.entry(keyPrefix + e.getKey(), e.getValue()))
         .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
   }
+
+  static URI appendPath(URI base, String path) {
+    String baseStr = base.toString();
+    if (baseStr.endsWith("/")) {
+      baseStr = baseStr.substring(0, baseStr.length() - 1);
+    }
+    return URI.create(baseStr + "/" + path).normalize();
+  }
 }
diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisClient.java b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisClient.java
index 2a5c31eeab..ac0ec018aa 100644
--- a/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisClient.java
+++ b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisClient.java
@@ -33,14 +33,18 @@ import java.util.Random;
 import org.apache.iceberg.rest.RESTSerializers;
 import org.apache.polaris.core.admin.model.PrincipalWithCredentials;
 import org.apache.polaris.service.it.ext.PolarisServerManager;
+import org.jspecify.annotations.Nullable;
 
 /**
- * This is a holder for the heavy-weight HTTP client for accessing Polaris APIs. This class provides
- * method for constructing light-weight API wrappers for Iceberg REST and Polaris Management API
- * that reuse the same shared HTTP client.
+ * This is a holder for the heavy-weight HTTP client for accessing Polaris APIs, and optionally,
+ * platform APIs (such as metrics or health endpoints).
+ *
+ * <p>This class provides methods for constructing light-weight API wrappers for Iceberg REST APis,
+ * Polaris Management APIs, and platform APIs that reuse the same shared HTTP client.
  */
 public final class PolarisClient implements AutoCloseable {
   private final PolarisApiEndpoints endpoints;
+  private final @Nullable PlatformApiEndpoints platformEndpoints;
   private final Client client;
   // Use an alphanumeric ID for widest compatibility in HTTP and SQL.
   // Use MAX_RADIX for shorter output.
@@ -48,15 +52,35 @@ public final class PolarisClient implements AutoCloseable {
       Long.toString(Math.abs(new Random().nextLong()), Character.MAX_RADIX);
 
   private PolarisClient(PolarisApiEndpoints endpoints) {
-    this.endpoints = endpoints;
+    this(endpoints, null);
+  }
 
+  private PolarisClient(
+      PolarisApiEndpoints endpoints, @Nullable PlatformApiEndpoints platformEndpoints) {
+    this.endpoints = endpoints;
+    this.platformEndpoints = platformEndpoints;
     this.client = polarisServerManager().createClient();
   }
 
+  /**
+   * Builds a {@link PolarisClient} for the given {@link PolarisApiEndpoints}. The returned client
+   * cannot access platform endpoints; use {@link #polarisClient(PolarisApiEndpoints,
+   * PlatformApiEndpoints)} if platform endpoints are required by the test.
+   */
   public static PolarisClient polarisClient(PolarisApiEndpoints endpoints) {
     return new PolarisClient(endpoints);
   }
 
+  /**
+   * Builds a {@link PolarisClient} for the given {@link PolarisApiEndpoints} and {@link
+   * PlatformApiEndpoints}. The returned client can access both Polaris APIs and platform endpoints
+   * (e.g. metrics or health endpoints).
+   */
+  public static PolarisClient polarisClient(
+      PolarisApiEndpoints endpoints, PlatformApiEndpoints platformEndpoints) {
+    return new PolarisClient(endpoints, platformEndpoints);
+  }
+
   /**
    * Utility method that creates an {@link ObjectMapper} sufficient for (de-)serializing client-side
    * payloads for Iceberg REST and Polaris Management APIs.
@@ -103,6 +127,13 @@ public final class PolarisClient implements AutoCloseable {
     return new PolicyApi(client, endpoints, authToken, endpoints.catalogApiEndpoint());
   }
 
+  public PlatformMetricsApi platformMetricsApi() {
+    if (platformEndpoints == null) {
+      throw new IllegalStateException("Platform endpoints are not available");
+    }
+    return new PlatformMetricsApi(client, platformEndpoints.metricsApiEndpoint());
+  }
+
   /** Requests an access token from the Polaris server for the given principal. */
   public String obtainToken(PrincipalWithCredentials credentials) {
     return obtainToken(new ClientPrincipal(credentials));
diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/env/RestApi.java b/integration-tests/src/main/java/org/apache/polaris/service/it/env/RestApi.java
index 13964af0ac..c295586d1d 100644
--- a/integration-tests/src/main/java/org/apache/polaris/service/it/env/RestApi.java
+++ b/integration-tests/src/main/java/org/apache/polaris/service/it/env/RestApi.java
@@ -28,10 +28,20 @@ import java.util.Map;
 public class RestApi {
   private final Client client;
   private final URI uri;
+  private final String contentType;
 
   RestApi(Client client, URI uri) {
+    this(client, uri, "application/json");
+  }
+
+  RestApi(Client client, URI uri, String contentType) {
     this.client = client;
     this.uri = uri;
+    this.contentType = contentType;
+  }
+
+  public Invocation.Builder request() {
+    return client.target(uri).request(contentType);
   }
 
   public Invocation.Builder request(String path) {
@@ -59,7 +69,7 @@ public class RestApi {
     for (Map.Entry<String, String> entry : queryParams.entrySet()) {
       target = target.queryParam(entry.getKey(), entry.getValue());
     }
-    Invocation.Builder request = target.request("application/json");
+    Invocation.Builder request = target.request(contentType);
     headers.forEach(request::header);
     return request;
   }
diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/env/Server.java b/integration-tests/src/main/java/org/apache/polaris/service/it/env/Server.java
index fea53d8917..911b3fec4a 100644
--- a/integration-tests/src/main/java/org/apache/polaris/service/it/env/Server.java
+++ b/integration-tests/src/main/java/org/apache/polaris/service/it/env/Server.java
@@ -20,6 +20,7 @@ package org.apache.polaris.service.it.env;
 
 import java.net.URI;
 import java.util.Map;
+import java.util.Optional;
 
 /**
  * This is a holder for access information to a particular Polaris Server. Test cases may use only
@@ -50,5 +51,17 @@ public interface Server extends AutoCloseable {
    */
   URI baseUri();
 
+  /**
+   * The base URI for the platform management interface (exposing metrics and health checks). Not to
+   * be confused with the Polaris Management API. This URI includes the management interface root
+   * path, usually {@code /q} on Quarkus platforms.
+   *
+   * <p>The management URI is sometimes unavailable, e.g. in the case of a @QuarkusIntegrationTest;
+   * in such cases this method returns empty.
+   */
+  default Optional<URI> managementUri() {
+    return Optional.empty();
+  }
+
   ClientPrincipal adminCredentials();
 }
diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/ext/PolarisIntegrationTestExtension.java b/integration-tests/src/main/java/org/apache/polaris/service/it/ext/PolarisIntegrationTestExtension.java
index f3abce39f9..ad377f409c 100644
--- a/integration-tests/src/main/java/org/apache/polaris/service/it/ext/PolarisIntegrationTestExtension.java
+++ b/integration-tests/src/main/java/org/apache/polaris/service/it/ext/PolarisIntegrationTestExtension.java
@@ -20,8 +20,10 @@ package org.apache.polaris.service.it.ext;
 
 import static org.apache.polaris.service.it.ext.PolarisServerManagerLoader.polarisServerManager;
 
+import java.util.Optional;
 import org.apache.polaris.service.it.env.ClientCredentials;
 import org.apache.polaris.service.it.env.ClientPrincipal;
+import org.apache.polaris.service.it.env.PlatformApiEndpoints;
 import org.apache.polaris.service.it.env.PolarisApiEndpoints;
 import org.apache.polaris.service.it.env.Server;
 import org.junit.jupiter.api.extension.ExtensionContext;
@@ -45,6 +47,7 @@ public class PolarisIntegrationTestExtension implements ParameterResolver {
       throws ParameterResolutionException {
     Class<?> type = parameterContext.getParameter().getType();
     return type.isAssignableFrom(PolarisApiEndpoints.class)
+        || type.isAssignableFrom(PlatformApiEndpoints.class)
         || type.isAssignableFrom(ClientPrincipal.class)
         || type.isAssignableFrom(ClientCredentials.class);
   }
@@ -57,6 +60,10 @@ public class PolarisIntegrationTestExtension implements ParameterResolver {
     Class<?> type = parameterContext.getParameter().getType();
     if (type.isAssignableFrom(PolarisApiEndpoints.class)) {
       return env.endpoints();
+    } else if (type.isAssignableFrom(PlatformApiEndpoints.class)) {
+      return env.quarkusEndpoints()
+          .orElseThrow(
+              () -> new IllegalStateException("Quarkus management endpoint is not available"));
     } else if (type.isAssignableFrom(ClientPrincipal.class)) {
       return env.server.adminCredentials();
     } else if (type.isAssignableFrom(ClientCredentials.class)) {
@@ -88,17 +95,23 @@ public class PolarisIntegrationTestExtension implements ParameterResolver {
   private static class Env implements AutoCloseable {
     private final Server server;
     private final PolarisApiEndpoints endpoints;
+    private final Optional<PlatformApiEndpoints> platformEndpoints;
 
     private Env(Server server) {
       this.server = server;
       this.endpoints =
           new PolarisApiEndpoints(server.baseUri(), server.realmId(), server.headers());
+      this.platformEndpoints = server.managementUri().map(PlatformApiEndpoints::new);
     }
 
     PolarisApiEndpoints endpoints() {
       return endpoints;
     }
 
+    Optional<PlatformApiEndpoints> quarkusEndpoints() {
+      return platformEndpoints;
+    }
+
     @Override
     public void close() throws Exception {
       server.close();
diff --git a/integration-tests/src/test/java/org/apache/polaris/service/it/env/PlatformApiEndpointsTest.java b/integration-tests/src/test/java/org/apache/polaris/service/it/env/PlatformApiEndpointsTest.java
new file mode 100644
index 0000000000..407424a778
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/polaris/service/it/env/PlatformApiEndpointsTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   https://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 org.apache.polaris.service.it.env;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.net.URI;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+public class PlatformApiEndpointsTest {
+
+  @ParameterizedTest
+  @CsvSource({
+    "https://example.com          , https://example.com/metrics",
+    "https://example.com/         , https://example.com/metrics",
+    "https://example.com/polaris  , https://example.com/polaris/metrics",
+    "https://example.com/polaris/ , https://example.com/polaris/metrics"
+  })
+  void testQuarkusMetricsApiEndpoint(URI baseUri, URI expectedUri) {
+    PlatformApiEndpoints endpoints = new PlatformApiEndpoints(baseUri);
+    assertThat(endpoints.metricsApiEndpoint()).isEqualTo(expectedUri);
+  }
+
+  @ParameterizedTest
+  @CsvSource({
+    "https://example.com          , https://example.com/health",
+    "https://example.com/         , https://example.com/health",
+    "https://example.com/polaris  , https://example.com/polaris/health",
+    "https://example.com/polaris/ , https://example.com/polaris/health"
+  })
+  void testQuarkusHealthApiEndpoint(URI baseUri, URI expectedUri) {
+    PlatformApiEndpoints endpoints = new PlatformApiEndpoints(baseUri);
+    assertThat(endpoints.healthApiEndpoint()).isEqualTo(expectedUri);
+  }
+}
diff --git a/integration-tests/src/test/java/org/apache/polaris/service/it/env/PolarisApiEndpointsTest.java b/integration-tests/src/test/java/org/apache/polaris/service/it/env/PolarisApiEndpointsTest.java
index 1ba9d9db5d..3b1b3d0285 100644
--- a/integration-tests/src/test/java/org/apache/polaris/service/it/env/PolarisApiEndpointsTest.java
+++ b/integration-tests/src/test/java/org/apache/polaris/service/it/env/PolarisApiEndpointsTest.java
@@ -7,7 +7,7 @@
  * "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
+ *   https://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
@@ -18,19 +18,67 @@
  */
 package org.apache.polaris.service.it.env;
 
+import static org.assertj.core.api.Assertions.assertThat;
+
 import java.net.URI;
-import org.junit.jupiter.api.Assertions;
+import java.util.Map;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
 
 /** Unit tests for PolarisApiEndpoints */
 public class PolarisApiEndpointsTest {
+
+  @ParameterizedTest
+  @CsvSource({
+    "https://example.com          , https://example.com/api/catalog",
+    "https://example.com/         , https://example.com/api/catalog",
+    "https://example.com/polaris  , https://example.com/polaris/api/catalog",
+    "https://example.com/polaris/ , https://example.com/polaris/api/catalog"
+  })
+  void testCatalogApiEndpoint(URI baseUri, URI expectedUri) {
+    PolarisApiEndpoints endpoints = new PolarisApiEndpoints(baseUri, "realm1", Map.of());
+    assertThat(endpoints.catalogApiEndpoint()).isEqualTo(expectedUri);
+  }
+
+  @ParameterizedTest
+  @CsvSource({
+    "https://example.com          , https://example.com/api/management",
+    "https://example.com/         , https://example.com/api/management",
+    "https://example.com/polaris  , https://example.com/polaris/api/management",
+    "https://example.com/polaris/ , https://example.com/polaris/api/management"
+  })
+  void testManagementApiEndpoint(URI baseUri, URI expectedUri) {
+    PolarisApiEndpoints endpoints = new PolarisApiEndpoints(baseUri, "realm1", Map.of());
+    assertThat(endpoints.managementApiEndpoint()).isEqualTo(expectedUri);
+  }
+
+  @Test
+  void testRealmId() {
+    PolarisApiEndpoints endpoints =
+        new PolarisApiEndpoints(URI.create("https://example.com/irrelevant"), "realm1", Map.of());
+    assertThat(endpoints.realmId()).isEqualTo("realm1");
+  }
+
+  @Test
+  void testExtraHeaders() {
+    PolarisApiEndpoints endpoints =
+        new PolarisApiEndpoints(
+            URI.create("https://example.com/irrelevant"),
+            "realm1",
+            Map.of("key1", "value1", "key2", "value2"));
+    assertThat(endpoints.extraHeaders())
+        .containsOnly(Map.entry("key1", "value1"), Map.entry("key2", "value2"));
+  }
+
   @Test
-  void testEndpointRespectsPathPrefix() {
+  void testExtraHeadersPrefix() {
     PolarisApiEndpoints endpoints =
-        new PolarisApiEndpoints(URI.create("http://myserver.com/polaris"), "", "Polaris-Realm");
-    Assertions.assertEquals(
-        "http://myserver.com/polaris/api/catalog", endpoints.catalogApiEndpoint().toString());
-    Assertions.assertEquals(
-        "http://myserver.com/polaris/api/management", endpoints.managementApiEndpoint().toString());
+        new PolarisApiEndpoints(
+            URI.create("https://example.com/irrelevant"),
+            "realm1",
+            Map.of("key1", "value1", "key2", "value2"));
+    assertThat(endpoints.extraHeaders("prefix."))
+        .containsOnly(Map.entry("prefix.key1", "value1"), Map.entry("prefix.key2", "value2"));
   }
 }
diff --git a/runtime/defaults/src/main/resources/application-it.properties b/runtime/defaults/src/main/resources/application-it.properties
index 108a9585b9..df2d901463 100644
--- a/runtime/defaults/src/main/resources/application-it.properties
+++ b/runtime/defaults/src/main/resources/application-it.properties
@@ -21,8 +21,10 @@
 # Note: Quarkus integration tests cannot use QuarkusTestProfile.
 
 quarkus.http.limits.max-body-size=1000000
-quarkus.http.port=0
 
+# For integration tests, we must *override* the HTTP and management ports themselves to zero;
+# modifying the test-ports has no effect in these tests.
+quarkus.http.port=0
 quarkus.management.port=0
 
 # polaris.persistence.type=jdbc
diff --git a/runtime/defaults/src/main/resources/application-test.properties b/runtime/defaults/src/main/resources/application-test.properties
index 4f31702e27..c8d2d1672d 100644
--- a/runtime/defaults/src/main/resources/application-test.properties
+++ b/runtime/defaults/src/main/resources/application-test.properties
@@ -20,6 +20,11 @@
 # Configuration common to ALL unit tests (executed with "test" profile – Gradle "test" tasks).
 # Per-test specific configuration should use QuarkusTestProfile
 
+# For unit tests, the default HTTP and management *test-ports* (8081 and 9001 respectively) must be
+# overridden to zero, so that Quarkus will automatically select an available port.
+quarkus.http.test-port=0
+quarkus.management.test-port=0
+
 quarkus.datasource.devservices.enabled=false
 quarkus.keycloak.devservices.enabled=false
 quarkus.mongodb.devservices.enabled=false
diff --git a/runtime/defaults/src/main/resources/application.properties b/runtime/defaults/src/main/resources/application.properties
index 6fe3b9ad63..626c6beb40 100644
--- a/runtime/defaults/src/main/resources/application.properties
+++ b/runtime/defaults/src/main/resources/application.properties
@@ -66,7 +66,6 @@ quarkus.http.cors.enabled=false
 #quarkus.http.cors.access-control-allow-credentials=true
 
 quarkus.http.port=8181
-quarkus.http.test-port=0
 
 quarkus.log.level=INFO
 quarkus.log.console.enabled=true
@@ -88,7 +87,6 @@ quarkus.log.file.rotation.max-backup-index=14
 # quarkus.log.category."org.apache.iceberg.rest".level=INFO
 
 quarkus.management.port=8182
-quarkus.management.test-port=0
 
 # OIDC settings. These settings are required only when using external authentication providers.
 # See https://quarkus.io/guides/security-oidc-configuration-properties-reference
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/metrics/MetricsTestBase.java b/runtime/service/src/test/java/org/apache/polaris/service/metrics/MetricsTestBase.java
index a04fc7d04c..28b2e4782d 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/metrics/MetricsTestBase.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/metrics/MetricsTestBase.java
@@ -28,13 +28,12 @@ import java.time.Duration;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicReference;
 import org.apache.polaris.service.it.env.ClientPrincipal;
+import org.apache.polaris.service.it.env.PlatformApiEndpoints;
+import org.apache.polaris.service.it.env.PlatformMetricsApi;
 import org.apache.polaris.service.it.env.PolarisApiEndpoints;
 import org.apache.polaris.service.it.env.PolarisClient;
 import org.apache.polaris.service.it.ext.PolarisIntegrationTestExtension;
 import org.apache.polaris.service.ratelimiter.MockRateLimiter;
-import org.apache.polaris.service.test.TestEnvironment;
-import org.apache.polaris.service.test.TestEnvironmentExtension;
-import org.apache.polaris.service.test.TestMetricsUtil;
 import org.awaitility.Awaitility;
 import org.hawkular.agent.prometheus.types.MetricFamily;
 import org.hawkular.agent.prometheus.types.Summary;
@@ -47,7 +46,6 @@ import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
-@ExtendWith(TestEnvironmentExtension.class)
 @ExtendWith(PolarisIntegrationTestExtension.class)
 public abstract class MetricsTestBase {
 
@@ -58,20 +56,22 @@ public abstract class MetricsTestBase {
   @Inject MeterRegistry registry;
   @Inject MetricsConfiguration metricsConfiguration;
 
-  private TestEnvironment testEnv;
-  private PolarisApiEndpoints endpoints;
+  private PlatformMetricsApi platformMetricsApi;
+  private PolarisApiEndpoints polarisEndpoints;
   private PolarisClient client;
   private ClientPrincipal principal;
   private String adminToken;
 
   @BeforeAll
   public void setUp(
-      TestEnvironment testEnv, PolarisApiEndpoints endpoints, ClientPrincipal principal) {
+      PolarisApiEndpoints polarisEndpoints,
+      PlatformApiEndpoints quarkusEndpoints,
+      ClientPrincipal principal) {
     MockRateLimiter.allowProceed = true;
-    this.testEnv = testEnv;
-    this.endpoints = endpoints;
+    this.polarisEndpoints = polarisEndpoints;
     this.principal = principal;
-    client = PolarisClient.polarisClient(this.endpoints);
+    client = PolarisClient.polarisClient(polarisEndpoints, quarkusEndpoints);
+    platformMetricsApi = client.platformMetricsApi();
     adminToken = client.obtainToken(principal.credentials());
   }
 
@@ -93,7 +93,7 @@ public abstract class MetricsTestBase {
         .atMost(Duration.ofMinutes(2))
         .untilAsserted(
             () -> {
-              value.set(TestMetricsUtil.fetchMetrics(testEnv.baseManagementUri()));
+              value.set(platformMetricsApi.fetchMetrics());
               assertThat(value.get()).containsKey(API_METRIC_NAME);
               assertThat(value.get()).containsKey(HTTP_METRIC_NAME);
             });
@@ -115,7 +115,7 @@ public abstract class MetricsTestBase {
                       Map.entry(
                           "realm_id",
                           metricsConfiguration.realmIdTag().enableInApiMetrics()
-                              ? endpoints.realmId()
+                              ? polarisEndpoints.realmId()
                               : ""),
                       Map.entry(
                           "principal",
@@ -144,7 +144,8 @@ public abstract class MetricsTestBase {
                       Map.entry("status", "200"),
                       Map.entry("uri", "/api/management/v1/principals/{principalName}"));
               if (metricsConfiguration.realmIdTag().enableInHttpMetrics()) {
-                assertThat(metric.getLabels()).containsEntry("realm_id", endpoints.realmId());
+                assertThat(metric.getLabels())
+                    .containsEntry("realm_id", polarisEndpoints.realmId());
               } else {
                 assertThat(metric.getLabels()).doesNotContainKey("realm_id");
               }
@@ -170,7 +171,7 @@ public abstract class MetricsTestBase {
                       Map.entry(
                           "realm_id",
                           metricsConfiguration.realmIdTag().enableInApiMetrics()
-                              ? endpoints.realmId()
+                              ? polarisEndpoints.realmId()
                               : ""),
                       Map.entry(
                           "principal",
@@ -198,7 +199,8 @@ public abstract class MetricsTestBase {
                       Map.entry("status", "404"),
                       Map.entry("uri", "/api/management/v1/principals/{principalName}"));
               if (metricsConfiguration.realmIdTag().enableInHttpMetrics()) {
-                assertThat(metric.getLabels()).containsEntry("realm_id", endpoints.realmId());
+                assertThat(metric.getLabels())
+                    .containsEntry("realm_id", polarisEndpoints.realmId());
               } else {
                 assertThat(metric.getLabels()).doesNotContainKey("realm_id");
               }
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/ratelimiter/RateLimiterFilterTest.java b/runtime/service/src/test/java/org/apache/polaris/service/ratelimiter/RateLimiterFilterTest.java
index 9322a9159b..a7e7d766ec 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/ratelimiter/RateLimiterFilterTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/ratelimiter/RateLimiterFilterTest.java
@@ -43,13 +43,12 @@ import org.apache.polaris.service.events.PolarisEvent;
 import org.apache.polaris.service.events.PolarisEventType;
 import org.apache.polaris.service.events.listeners.TestPolarisEventListener;
 import org.apache.polaris.service.it.env.ClientCredentials;
+import org.apache.polaris.service.it.env.PlatformApiEndpoints;
+import org.apache.polaris.service.it.env.PlatformMetricsApi;
 import org.apache.polaris.service.it.env.PolarisApiEndpoints;
 import org.apache.polaris.service.it.env.PolarisClient;
 import org.apache.polaris.service.it.ext.PolarisIntegrationTestExtension;
 import org.apache.polaris.service.ratelimiter.RateLimiterFilterTest.Profile;
-import org.apache.polaris.service.test.TestEnvironment;
-import org.apache.polaris.service.test.TestEnvironmentExtension;
-import org.apache.polaris.service.test.TestMetricsUtil;
 import org.hawkular.agent.prometheus.types.MetricFamily;
 import org.hawkular.agent.prometheus.types.Summary;
 import org.junit.jupiter.api.AfterAll;
@@ -64,7 +63,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
 @QuarkusTest
 @TestInstance(Lifecycle.PER_CLASS)
 @TestProfile(Profile.class)
-@ExtendWith(TestEnvironmentExtension.class)
 @ExtendWith(PolarisIntegrationTestExtension.class)
 public class RateLimiterFilterTest {
 
@@ -97,18 +95,20 @@ public class RateLimiterFilterTest {
   @Identifier("test")
   TestPolarisEventListener polarisEventListener;
 
-  private TestEnvironment testEnv;
-  private PolarisApiEndpoints endpoints;
+  private PlatformMetricsApi platformMetricsApi;
+  private PolarisApiEndpoints polarisEndpoints;
   private PolarisClient client;
   private String adminToken;
 
   @BeforeAll
   public void setUp(
-      TestEnvironment testEnv, PolarisApiEndpoints endpoints, ClientCredentials credentials) {
+      PolarisApiEndpoints polarisEndpoints,
+      PlatformApiEndpoints quarkusEndpoints,
+      ClientCredentials credentials) {
     MockRateLimiter.allowProceed = true;
-    this.testEnv = testEnv;
-    this.endpoints = endpoints;
-    client = PolarisClient.polarisClient(endpoints);
+    this.polarisEndpoints = polarisEndpoints;
+    client = PolarisClient.polarisClient(polarisEndpoints, quarkusEndpoints);
+    platformMetricsApi = client.platformMetricsApi();
     adminToken = client.obtainToken(credentials);
   }
 
@@ -128,7 +128,8 @@ public class RateLimiterFilterTest {
 
   @Test
   public void testRateLimiter() {
-    Consumer<Status> requestAsserter = constructRequestAsserter(testEnv, adminToken, "POLARIS");
+    Consumer<Status> requestAsserter =
+        constructRequestAsserter(polarisEndpoints, adminToken, "POLARIS");
 
     for (int i = 0; i < 3; i++) {
       MockRateLimiter.allowProceed = true;
@@ -139,7 +140,8 @@ public class RateLimiterFilterTest {
 
     // Ensure that a different realm identifier gets a separate limit
     MockRateLimiter.allowProceed = true;
-    Consumer<Status> requestAsserter2 = constructRequestAsserter(testEnv, adminToken, "POLARIS2");
+    Consumer<Status> requestAsserter2 =
+        constructRequestAsserter(polarisEndpoints, adminToken, "POLARIS2");
     requestAsserter2.accept(Status.OK);
   }
 
@@ -161,7 +163,7 @@ public class RateLimiterFilterTest {
   @Test
   public void testMetricsAreEmittedWhenRateLimiting() {
     Consumer<Status> requestAsserter =
-        constructRequestAsserter(testEnv, adminToken, endpoints.realmId());
+        constructRequestAsserter(polarisEndpoints, adminToken, polarisEndpoints.realmId());
 
     for (int i = 0; i < 3; i++) {
       MockRateLimiter.allowProceed = true;
@@ -177,7 +179,7 @@ public class RateLimiterFilterTest {
     // http_server_requests_seconds_count{application="Polaris",environment="prod",method="GET",outcome="CLIENT_ERROR",realm_id="org_apache_polaris_service_ratelimiter_RateLimiterFilterTest",status="429",uri="/api/management/v1/principal-roles"} 1.0
     // polaris_principal_roles_listPrincipalRoles_seconds_count{application="Polaris",class="org.apache.polaris.service.admin.api.PolarisPrincipalRolesApi",environment="prod",exception="none",method="listPrincipalRoles"} 50.0
 
-    Map<String, MetricFamily> metrics = TestMetricsUtil.fetchMetrics(testEnv.baseManagementUri());
+    Map<String, MetricFamily> metrics = platformMetricsApi.fetchMetrics();
 
     assertThat(metrics)
         .isNotEmpty()
@@ -191,7 +193,7 @@ public class RateLimiterFilterTest {
                   .contains(
                       Map.entry("application", "Polaris"),
                       Map.entry("environment", "prod"),
-                      Map.entry("realm_id", endpoints.realmId()),
+                      Map.entry("realm_id", polarisEndpoints.realmId()),
                       Map.entry("method", "GET"),
                       Map.entry("outcome", "CLIENT_ERROR"),
                       Map.entry("status", String.valueOf(Status.TOO_MANY_REQUESTS.getStatusCode())),
@@ -209,7 +211,7 @@ public class RateLimiterFilterTest {
                   .contains(
                       Map.entry("application", "Polaris"),
                       Map.entry("environment", "prod"),
-                      Map.entry("realm_id", endpoints.realmId()),
+                      Map.entry("realm_id", polarisEndpoints.realmId()),
                       Map.entry(
                           "class", "org.apache.polaris.service.admin.api.PolarisPrincipalRolesApi"),
                       Map.entry("exception", "none"),
@@ -227,12 +229,12 @@ public class RateLimiterFilterTest {
    * whether the rate limiter intervenes.
    */
   private static Consumer<Status> constructRequestAsserter(
-      TestEnvironment testEnv, String adminToken, String realm) {
+      PolarisApiEndpoints endpoints, String adminToken, String realm) {
     return (Status status) -> {
       try (Client client = ClientBuilder.newBuilder().build();
           Response response =
               client
-                  .target(String.format("%s/api/management/v1/principal-roles", testEnv.baseUri()))
+                  .target(String.format("%s/v1/principal-roles", endpoints.managementApiEndpoint()))
                   .request("application/json")
                   .header("Authorization", "Bearer " + adminToken)
                   .header("Polaris-Realm", realm)
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/test/DefaultTestEnvironmentResolver.java b/runtime/service/src/test/java/org/apache/polaris/service/test/DefaultTestEnvironmentResolver.java
deleted file mode 100644
index fc9686a9d1..0000000000
--- a/runtime/service/src/test/java/org/apache/polaris/service/test/DefaultTestEnvironmentResolver.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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 org.apache.polaris.service.test;
-
-import org.eclipse.microprofile.config.ConfigProvider;
-import org.junit.jupiter.api.extension.ExtensionContext;
-
-public class DefaultTestEnvironmentResolver implements TestEnvironmentResolver {
-
-  /** Resolves the TestEnvironment to point to the local Quarkus Application instance. */
-  @Override
-  public TestEnvironment resolveTestEnvironment(ExtensionContext extensionContext) {
-    var quarkusManagementPort =
-        ConfigProvider.getConfig().getConfigValue("quarkus.management.port");
-    var localManagementPort = Integer.parseInt(quarkusManagementPort.getValue());
-    var quarkusHttpPort = ConfigProvider.getConfig().getConfigValue("quarkus.http.port");
-    var localPort = Integer.parseInt(quarkusHttpPort.getValue());
-
-    return new TestEnvironment(
-        String.format("http://localhost:%d/", localPort),
-        String.format("http://localhost:%d/", localManagementPort));
-  }
-}
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/test/TestEnvironment.java b/runtime/service/src/test/java/org/apache/polaris/service/test/TestEnvironment.java
deleted file mode 100644
index 705c40605a..0000000000
--- a/runtime/service/src/test/java/org/apache/polaris/service/test/TestEnvironment.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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 org.apache.polaris.service.test;
-
-import java.net.URI;
-import java.util.UUID;
-
-/**
- * Defines the test environment that a test should run in.
- *
- * @param baseUri The base URL that requests should target, for example {@code
- *     http://localhost:8181}
- * @param baseManagementUri The base URL that requests should target for the management interface,
- *     for example {@code http://localhost:8182}
- * @param testId An ID unique to this test. This can be used to prefix resource names, such as
- *     catalog names, to prevent collision.
- */
-public record TestEnvironment(URI baseUri, URI baseManagementUri, String testId) {
-
-  public TestEnvironment(String baseUri, String baseManagementUri) {
-    this(
-        URI.create(baseUri),
-        URI.create(baseManagementUri),
-        UUID.randomUUID().toString().replace("-", ""));
-  }
-}
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/test/TestEnvironmentExtension.java b/runtime/service/src/test/java/org/apache/polaris/service/test/TestEnvironmentExtension.java
deleted file mode 100644
index 011d85282c..0000000000
--- a/runtime/service/src/test/java/org/apache/polaris/service/test/TestEnvironmentExtension.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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 org.apache.polaris.service.test;
-
-import java.lang.reflect.InvocationTargetException;
-import java.util.Optional;
-import org.junit.jupiter.api.extension.ExtensionContext;
-import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
-import org.junit.jupiter.api.extension.ParameterContext;
-import org.junit.jupiter.api.extension.ParameterResolutionException;
-import org.junit.jupiter.api.extension.ParameterResolver;
-
-/**
- * JUnit test extension that determines the TestEnvironment. Falls back to targeting the local
- * Dropwizard instance.
- */
-public class TestEnvironmentExtension implements ParameterResolver {
-  /**
-   * Environment variable that specifies the test environment resolver. This should be a class name.
-   * If this is not set, falls back to DropwizardTestEnvironmentResolver.
-   */
-  public static final String ENV_TEST_ENVIRONMENT_RESOLVER_IMPL =
-      "INTEGRATION_TEST_ENVIRONMENT_RESOLVER_IMPL";
-
-  private static final String ENV_PROPERTY_KEY = "testenvironment";
-
-  public static TestEnvironment getEnv(ExtensionContext extensionContext)
-      throws IllegalAccessException {
-    // This must be cached because the TestEnvironment has a randomly generated ID
-    return extensionContext
-        .getStore(Namespace.create(extensionContext.getRequiredTestClass()))
-        .computeIfAbsent(
-            ENV_PROPERTY_KEY,
-            (k) -> getTestEnvironmentResolver().resolveTestEnvironment(extensionContext),
-            TestEnvironment.class);
-  }
-
-  @Override
-  public boolean supportsParameter(
-      ParameterContext parameterContext, ExtensionContext extensionContext)
-      throws ParameterResolutionException {
-    return parameterContext.getParameter().getType().equals(TestEnvironment.class);
-  }
-
-  @Override
-  public Object resolveParameter(
-      ParameterContext parameterContext, ExtensionContext extensionContext)
-      throws ParameterResolutionException {
-    try {
-      return getEnv(extensionContext);
-    } catch (IllegalAccessException e) {
-      throw new ParameterResolutionException(e.getMessage());
-    }
-  }
-
-  private static TestEnvironmentResolver getTestEnvironmentResolver() {
-    String impl =
-        Optional.ofNullable(System.getenv(ENV_TEST_ENVIRONMENT_RESOLVER_IMPL))
-            .orElse(DefaultTestEnvironmentResolver.class.getName());
-
-    try {
-      return (TestEnvironmentResolver) (Class.forName(impl).getDeclaredConstructor().newInstance());
-    } catch (InstantiationException
-        | IllegalAccessException
-        | IllegalArgumentException
-        | InvocationTargetException
-        | ClassNotFoundException
-        | NoSuchMethodException e) {
-      throw new IllegalArgumentException(
-          String.format(
-              "Failed to initialize TestEnvironmentResolver using implementation %s.", impl),
-          e);
-    }
-  }
-}
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/test/TestEnvironmentResolver.java b/runtime/service/src/test/java/org/apache/polaris/service/test/TestEnvironmentResolver.java
deleted file mode 100644
index 7064fbabde..0000000000
--- a/runtime/service/src/test/java/org/apache/polaris/service/test/TestEnvironmentResolver.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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 org.apache.polaris.service.test;
-
-import org.junit.jupiter.api.extension.ExtensionContext;
-
-/** Interface for determining the test environment that tests should run in */
-public interface TestEnvironmentResolver {
-  TestEnvironment resolveTestEnvironment(ExtensionContext extensionContext);
-}
diff --git a/runtime/service/src/testFixtures/java/org/apache/polaris/service/it/ServerManager.java b/runtime/service/src/testFixtures/java/org/apache/polaris/service/it/ServerManager.java
index 4f1849c098..dbe8d8cb9d 100644
--- a/runtime/service/src/testFixtures/java/org/apache/polaris/service/it/ServerManager.java
+++ b/runtime/service/src/testFixtures/java/org/apache/polaris/service/it/ServerManager.java
@@ -18,9 +18,12 @@
  */
 package org.apache.polaris.service.it;
 
-import io.quarkus.test.common.ListeningAddress;
+import io.quarkus.test.common.http.TestHTTPResourceManager;
+import io.quarkus.test.config.ConfigInjector;
 import io.quarkus.test.config.ValueRegistryInjector;
+import io.quarkus.value.registry.ValueRegistry;
 import java.net.URI;
+import java.util.Optional;
 import org.apache.polaris.service.it.env.ClientCredentials;
 import org.apache.polaris.service.it.env.ClientPrincipal;
 import org.apache.polaris.service.it.env.Server;
@@ -29,13 +32,30 @@ import org.junit.jupiter.api.extension.ExtensionContext;
 
 public class ServerManager implements PolarisServerManager {
 
+  private static final ValueRegistry.RuntimeKey<Integer> MANAGEMENT_PORT =
+      ValueRegistry.RuntimeKey.intKey("quarkus.management.port");
+
   @Override
   public Server serverForContext(ExtensionContext context) {
     return new Server() {
 
       @Override
       public URI baseUri() {
-        return URI.create(String.format("http://localhost:%d", getQuarkusTestPort(context)));
+        var registry = ValueRegistryInjector.get(context);
+        var config = ConfigInjector.get(context);
+        return URI.create(TestHTTPResourceManager.testUrl(registry, config));
+      }
+
+      @Override
+      public Optional<URI> managementUri() {
+        var registry = ValueRegistryInjector.get(context);
+        var config = ConfigInjector.get(context);
+        // Probe whether the actual port to use has been registered. If not, the management
+        // interface is not available, and we are likely in a @QuarkusIntegrationTest.
+        int dynamicPort = registry.getOrDefault(MANAGEMENT_PORT, -1);
+        return dynamicPort == -1
+            ? Optional.empty()
+            : Optional.of(URI.create(TestHTTPResourceManager.testManagementUrl(registry, config)));
       }
 
       @Override
@@ -51,9 +71,4 @@ public class ServerManager implements PolarisServerManager {
       }
     };
   }
-
-  private static int getQuarkusTestPort(ExtensionContext context) {
-    var registry = ValueRegistryInjector.get(context);
-    return registry.get(ListeningAddress.HTTP_PORT);
-  }
 }