Skip to content

Commit bf8a371

Browse files
committed
feat(tests): Refactor test environment to use a single, shared MockServer instance
1 parent 036eb75 commit bf8a371

15 files changed

Lines changed: 83 additions & 170 deletions

oauth2/core/src/test/java/com/dremio/iceberg/authmgr/oauth2/endpoint/EndpointProviderTest.java

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,11 @@
2222

2323
import com.dremio.iceberg.authmgr.oauth2.http.HttpClient;
2424
import com.dremio.iceberg.authmgr.oauth2.test.TestEnvironment;
25-
import com.dremio.iceberg.authmgr.oauth2.test.server.UnitTestHttpServer;
25+
import com.dremio.iceberg.authmgr.oauth2.test.TestServer;
2626
import com.nimbusds.oauth2.sdk.ParseException;
2727
import org.junit.jupiter.api.Test;
28-
import org.junit.jupiter.params.ParameterizedTest;
29-
import org.junit.jupiter.params.provider.CsvSource;
30-
import org.junit.jupiter.params.provider.ValueSource;
28+
import org.junitpioneer.jupiter.cartesian.CartesianTest;
29+
import org.junitpioneer.jupiter.cartesian.CartesianTest.Values;
3130
import org.mockserver.model.HttpRequest;
3231
import org.mockserver.model.HttpResponse;
3332
import org.mockserver.model.JsonBody;
@@ -50,12 +49,19 @@ void withoutDiscovery() {
5049
}
5150
}
5251

53-
@ParameterizedTest
54-
@ValueSource(booleans = {true, false})
55-
void withDiscovery(boolean includeDeviceAuthEndpoint) {
52+
@CartesianTest
53+
void withDiscovery(
54+
@Values(booleans = {true, false}) boolean includeDeviceAuthEndpoint,
55+
@Values(
56+
strings = {
57+
".well-known/openid-configuration",
58+
".well-known/oauth-authorization-server"
59+
})
60+
String wellKnownPath) {
5661
try (TestEnvironment env =
5762
TestEnvironment.builder()
5863
.includeDeviceAuthEndpointInDiscoveryMetadata(includeDeviceAuthEndpoint)
64+
.wellKnownPath(wellKnownPath)
5965
.build()) {
6066
EndpointProvider endpointProvider =
6167
EndpointProvider.create(env.getOAuth2Config(), HttpClient.DEFAULT);
@@ -74,33 +80,6 @@ void withDiscovery(boolean includeDeviceAuthEndpoint) {
7480
}
7581
}
7682

77-
@ParameterizedTest
78-
@CsvSource({
79-
"'' , /.well-known/openid-configuration",
80-
"/ , /.well-known/openid-configuration",
81-
"'' , /.well-known/oauth-authorization-server",
82-
"/ , /.well-known/oauth-authorization-server",
83-
"/realms/master , /realms/master/.well-known/openid-configuration",
84-
"/realms/master/ , /realms/master/.well-known/openid-configuration",
85-
"/realms/master , /realms/master/.well-known/oauth-authorization-server",
86-
"/realms/master/ , /realms/master/.well-known/oauth-authorization-server"
87-
})
88-
void fetchOpenIdProviderMetadataSuccess(String contextPath, String wellKnownPath) {
89-
try (TestEnvironment env =
90-
TestEnvironment.builder()
91-
.authorizationServerContextPath(contextPath)
92-
.wellKnownPath(wellKnownPath)
93-
.build()) {
94-
EndpointProvider endpointProvider =
95-
EndpointProvider.create(env.getOAuth2Config(), HttpClient.DEFAULT);
96-
var actual = endpointProvider.getOpenIdProviderMetadata();
97-
assertThat(actual.getTokenEndpointURI()).isEqualTo(env.getTokenEndpoint());
98-
assertThat(actual.getAuthorizationEndpointURI()).isEqualTo(env.getAuthorizationEndpoint());
99-
assertThat(actual.getDeviceAuthorizationEndpointURI())
100-
.isEqualTo(env.getDeviceAuthorizationEndpoint());
101-
}
102-
}
103-
10483
@Test
10584
void fetchOpenIdProviderMetadataWrongEndpoint() {
10685
try (TestEnvironment env = TestEnvironment.builder().createDefaultExpectations(false).build()) {
@@ -128,9 +107,8 @@ void fetchOpenIdProviderMetadataWrongEndpoint() {
128107
@Test
129108
void fetchOpenIdProviderMetadataWrongData() {
130109
try (TestEnvironment env = TestEnvironment.builder().createDefaultExpectations(false).build()) {
131-
((UnitTestHttpServer) env.getServer())
132-
.getClientAndServer()
133-
.when(HttpRequest.request())
110+
TestServer.getInstance()
111+
.when(HttpRequest.request().withPath(env.getAuthorizationServerUrl().getPath() + ".*"))
134112
.respond(
135113
HttpResponse.response()
136114
.withStatusCode(200)

oauth2/core/src/testFixtures/java/com/dremio/iceberg/authmgr/oauth2/test/TestEnvironment.java

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,6 @@
4646
import com.dremio.iceberg.authmgr.oauth2.test.expectation.ImmutablePasswordExpectation;
4747
import com.dremio.iceberg.authmgr.oauth2.test.expectation.ImmutableRefreshTokenExpectation;
4848
import com.dremio.iceberg.authmgr.oauth2.test.expectation.ImmutableTokenExchangeExpectation;
49-
import com.dremio.iceberg.authmgr.oauth2.test.server.HttpServer;
50-
import com.dremio.iceberg.authmgr.oauth2.test.server.IntegrationTestHttpServer;
51-
import com.dremio.iceberg.authmgr.oauth2.test.server.UnitTestHttpServer;
5249
import com.dremio.iceberg.authmgr.oauth2.test.user.InteractiveUserEmulator;
5350
import com.dremio.iceberg.authmgr.oauth2.test.user.UserBehavior;
5451
import com.dremio.iceberg.authmgr.oauth2.test.user.UserEmulator;
@@ -79,6 +76,7 @@
7976
import java.util.OptionalInt;
8077
import java.util.concurrent.ScheduledExecutorService;
8178
import java.util.concurrent.TimeUnit;
79+
import java.util.concurrent.atomic.AtomicInteger;
8280
import javax.net.ssl.SSLContext;
8381
import org.apache.iceberg.CatalogProperties;
8482
import org.apache.iceberg.catalog.SessionCatalog.SessionContext;
@@ -94,6 +92,8 @@
9492
@Value.Immutable(copy = false)
9593
public abstract class TestEnvironment implements AutoCloseable {
9694

95+
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
96+
9797
public static Builder builder() {
9898
return ImmutableTestEnvironment.builder();
9999
}
@@ -105,6 +105,11 @@ public void validate() {
105105
}
106106
}
107107

108+
@Value.Default
109+
public String getId() {
110+
return "env" + ID_COUNTER.incrementAndGet();
111+
}
112+
108113
@Value.Default
109114
public GrantType getGrantType() {
110115
return GrantType.CLIENT_CREDENTIALS;
@@ -140,11 +145,6 @@ public boolean isCreateDefaultExpectations() {
140145
return isUnitTest();
141146
}
142147

143-
@Value.Lazy
144-
public HttpServer getServer() {
145-
return isUnitTest() ? new UnitTestHttpServer(isSsl()) : new IntegrationTestHttpServer();
146-
}
147-
148148
@Value.Default
149149
public boolean isSsl() {
150150
return false;
@@ -161,7 +161,9 @@ public int getExecutorPoolSize() {
161161
}
162162

163163
public void reset() {
164-
getServer().reset();
164+
if (isUnitTest()) {
165+
TestServer.clear(getId());
166+
}
165167
}
166168

167169
@Override
@@ -175,24 +177,31 @@ public void close() {
175177
} catch (InterruptedException e) {
176178
Thread.currentThread().interrupt();
177179
}
178-
getServer().close();
180+
reset();
179181
}
180182

181183
@Value.Default
182184
public URI getServerRootUrl() {
183-
// Note: the default value is for unit tests; integration tests must provide the server root URL
184-
// to avoid circular dependencies when creating the TestEnvironment instance
185-
return getServer().getRootUrl();
185+
if (!isUnitTest()) {
186+
throw new IllegalStateException("Server root URL must be provided for integration tests");
187+
}
188+
return URI.create(
189+
(isSsl() ? "https" : "http")
190+
+ "://localhost:"
191+
+ TestServer.getInstance().getLocalPort()
192+
+ "/"
193+
+ getId()
194+
+ "/");
186195
}
187196

188197
@Value.Default
189198
public String getAuthorizationServerContextPath() {
190-
return "/realms/master/";
199+
return "realms/master/";
191200
}
192201

193202
@Value.Default
194203
public String getCatalogServerContextPath() {
195-
return "/api/catalog/";
204+
return "api/catalog/";
196205
}
197206

198207
@Value.Default

oauth2/core/src/testFixtures/java/com/dremio/iceberg/authmgr/oauth2/test/server/UnitTestHttpServer.java renamed to oauth2/core/src/testFixtures/java/com/dremio/iceberg/authmgr/oauth2/test/TestServer.java

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,25 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package com.dremio.iceberg.authmgr.oauth2.test.server;
16+
package com.dremio.iceberg.authmgr.oauth2.test;
1717

1818
import java.io.IOException;
19-
import java.net.URI;
2019
import java.nio.file.Files;
2120
import java.nio.file.Path;
2221
import java.nio.file.Paths;
23-
import java.util.concurrent.atomic.AtomicInteger;
2422
import org.mockserver.configuration.Configuration;
2523
import org.mockserver.integration.ClientAndServer;
24+
import org.mockserver.model.HttpRequest;
2625

27-
public class UnitTestHttpServer implements HttpServer {
26+
public final class TestServer {
2827

29-
private static final AtomicInteger COUNTER = new AtomicInteger(1);
28+
private static final ClientAndServer INSTANCE;
3029

31-
private final boolean ssl;
32-
private final ClientAndServer clientAndServer;
33-
34-
public UnitTestHttpServer(boolean ssl) {
35-
this.ssl = ssl;
30+
static {
3631
Configuration configuration = Configuration.configuration();
3732
String outputDir = System.getProperty("authmgr.test.mockserver.memoryUsageCsvDirectory");
3833
if (outputDir != null) {
39-
Path outputPath = Paths.get(outputDir).resolve("server-" + COUNTER.getAndIncrement());
34+
Path outputPath = Paths.get(outputDir);
4035
try {
4136
Files.createDirectories(outputPath);
4237
} catch (IOException e) {
@@ -45,25 +40,18 @@ public UnitTestHttpServer(boolean ssl) {
4540
configuration.outputMemoryUsageCsv(true);
4641
configuration.memoryUsageCsvDirectory(outputPath.toString());
4742
}
48-
clientAndServer = ClientAndServer.startClientAndServer(configuration);
49-
}
50-
51-
public ClientAndServer getClientAndServer() {
52-
return clientAndServer;
43+
INSTANCE = ClientAndServer.startClientAndServer(configuration);
44+
Runtime.getRuntime().addShutdownHook(new Thread(INSTANCE::close));
5345
}
5446

55-
@Override
56-
public URI getRootUrl() {
57-
return URI.create((ssl ? "https" : "http") + "://localhost:" + clientAndServer.getLocalPort());
58-
}
47+
private TestServer() {}
5948

60-
@Override
61-
public void reset() {
62-
clientAndServer.reset();
49+
public static ClientAndServer getInstance() {
50+
return INSTANCE;
6351
}
6452

65-
@Override
66-
public void close() {
67-
clientAndServer.close();
53+
/** Clears all expectations and responses for the given test environment id. */
54+
public static void clear(String testEnvironmentId) {
55+
INSTANCE.clear(HttpRequest.request().withPath("/" + testEnvironmentId + "/.*"));
6856
}
6957
}

oauth2/core/src/testFixtures/java/com/dremio/iceberg/authmgr/oauth2/test/expectation/AbstractExpectation.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,12 @@
1616
package com.dremio.iceberg.authmgr.oauth2.test.expectation;
1717

1818
import com.dremio.iceberg.authmgr.oauth2.test.TestEnvironment;
19-
import com.dremio.iceberg.authmgr.oauth2.test.server.UnitTestHttpServer;
2019
import org.immutables.value.Value;
21-
import org.mockserver.integration.ClientAndServer;
2220

2321
public abstract class AbstractExpectation {
2422

2523
@Value.Parameter(order = 1)
2624
protected abstract TestEnvironment getTestEnvironment();
2725

2826
public abstract void create();
29-
30-
protected ClientAndServer getClientAndServer() {
31-
return ((UnitTestHttpServer) getTestEnvironment().getServer()).getClientAndServer();
32-
}
3327
}

oauth2/core/src/testFixtures/java/com/dremio/iceberg/authmgr/oauth2/test/expectation/AuthorizationCodeExpectation.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static com.dremio.iceberg.authmgr.oauth2.test.TestConstants.SCOPE2;
2222
import static com.dremio.iceberg.authmgr.oauth2.test.expectation.ErrorExpectation.AUTHORIZATION_SERVER_ERROR_RESPONSE;
2323

24+
import com.dremio.iceberg.authmgr.oauth2.test.TestServer;
2425
import com.dremio.iceberg.authmgr.tools.immutables.AuthManagerImmutable;
2526
import com.google.common.collect.ImmutableMap;
2627
import com.nimbusds.oauth2.sdk.AuthorizationCode;
@@ -120,7 +121,7 @@ private void createAuthEndpointExpectation() {
120121
request.withQueryStringParameter(
121122
"code_challenge_method", getTestEnvironment().getCodeChallengeMethod().getValue());
122123
}
123-
getClientAndServer()
124+
TestServer.getInstance()
124125
.when(request)
125126
.respond(
126127
httpRequest -> {

oauth2/core/src/testFixtures/java/com/dremio/iceberg/authmgr/oauth2/test/expectation/ConfigEndpointExpectation.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static com.dremio.iceberg.authmgr.oauth2.test.expectation.ExpectationUtils.getJsonBody;
1919

20+
import com.dremio.iceberg.authmgr.oauth2.test.TestServer;
2021
import com.dremio.iceberg.authmgr.tools.immutables.AuthManagerImmutable;
2122
import com.google.common.collect.ImmutableList;
2223
import java.util.HashMap;
@@ -61,7 +62,7 @@ public void create() {
6162
.withOverrides(new HashMap<>())
6263
.withEndpoints(endpoints)
6364
.build();
64-
getClientAndServer()
65+
TestServer.getInstance()
6566
.when(
6667
HttpRequest.request()
6768
.withMethod("GET")

oauth2/core/src/testFixtures/java/com/dremio/iceberg/authmgr/oauth2/test/expectation/DeviceCodeExpectation.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static com.dremio.iceberg.authmgr.oauth2.test.expectation.ErrorExpectation.AUTHORIZATION_SERVER_ERROR_RESPONSE;
2121
import static org.mockserver.model.Parameter.param;
2222

23+
import com.dremio.iceberg.authmgr.oauth2.test.TestServer;
2324
import com.dremio.iceberg.authmgr.tools.immutables.AuthManagerImmutable;
2425
import com.google.common.collect.ImmutableMap;
2526
import com.nimbusds.oauth2.sdk.GrantType;
@@ -82,7 +83,7 @@ protected HttpResponse response(
8283
}
8384

8485
private void createDeviceAuthEndpointExpectation() {
85-
getClientAndServer()
86+
TestServer.getInstance()
8687
.when(
8788
HttpRequest.request()
8889
.withMethod("POST")
@@ -119,7 +120,7 @@ private void createDeviceAuthEndpointExpectation() {
119120
private void createDeviceVerificationEndpointExpectation() {
120121
String path = getTestEnvironment().getDeviceVerificationEndpoint().getPath();
121122
// Expect the device verification page to be opened in a browser
122-
getClientAndServer()
123+
TestServer.getInstance()
123124
.when(HttpRequest.request().withMethod("GET").withPath(path))
124125
.respond(
125126
HttpResponse.response()
@@ -137,7 +138,7 @@ private void createDeviceVerificationEndpointExpectation() {
137138
+ "</body></html>",
138139
MediaType.TEXT_HTML)));
139140
// Expect the device verification code to be sent by the user after opening the page
140-
getClientAndServer()
141+
TestServer.getInstance()
141142
.when(
142143
HttpRequest.request()
143144
.withMethod("POST")

oauth2/core/src/testFixtures/java/com/dremio/iceberg/authmgr/oauth2/test/expectation/ErrorExpectation.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.dremio.iceberg.authmgr.oauth2.test.expectation;
1717

18+
import com.dremio.iceberg.authmgr.oauth2.test.TestServer;
1819
import com.dremio.iceberg.authmgr.tools.immutables.AuthManagerImmutable;
1920
import org.mockserver.model.HttpRequest;
2021
import org.mockserver.model.HttpResponse;
@@ -42,15 +43,15 @@ public abstract class ErrorExpectation extends AbstractExpectation {
4243

4344
@Override
4445
public void create() {
45-
getClientAndServer()
46+
TestServer.getInstance()
4647
.when(
4748
HttpRequest.request()
48-
.withPath(getTestEnvironment().getAuthorizationServerContextPath() + ".*"))
49+
.withPath(getTestEnvironment().getAuthorizationServerUrl().getPath() + ".*"))
4950
.respond(AUTHORIZATION_SERVER_ERROR_RESPONSE);
50-
getClientAndServer()
51+
TestServer.getInstance()
5152
.when(
5253
HttpRequest.request()
53-
.withPath(getTestEnvironment().getCatalogServerContextPath() + ".*"))
54+
.withPath(getTestEnvironment().getCatalogServerUrl().getPath() + ".*"))
5455
.respond(CATALOG_SERVER_ERROR_RESPONSE);
5556
}
5657
}

oauth2/core/src/testFixtures/java/com/dremio/iceberg/authmgr/oauth2/test/expectation/InitialTokenFetchExpectation.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
*/
1616
package com.dremio.iceberg.authmgr.oauth2.test.expectation;
1717

18+
import com.dremio.iceberg.authmgr.oauth2.test.TestServer;
19+
1820
public abstract class InitialTokenFetchExpectation extends AbstractTokenEndpointExpectation {
1921

2022
@Override
2123
public void create() {
22-
getClientAndServer()
24+
TestServer.getInstance()
2325
.when(request())
2426
.respond(httpRequest -> response(httpRequest, "access_initial", "refresh_initial"));
2527
}

0 commit comments

Comments
 (0)