From 4adfed9e0ce65a82d0d7229611f43a34c5cb3a3d Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Wed, 13 Mar 2024 19:23:02 +0000 Subject: [PATCH] feat: Add Universe Domain Support (#2435) * feat: Implement Universe Domain Support * chore: Clean up auth dependencies * chore: Resolve checkstyle issues * chore: Determine service name from url * chore: Return null for invalid rootUrl * chore: Add javadocs for Universe Domain changes in AbstractGoogleClient * chore: Address checkstyle issues * chore: Add tests for AbstractGoogleClient * chore: Fix tests * chore: Update docs for AbstractGoogleClient * chore: validateUniverseDomain does not return bool * chore: Move validateUniverseDomain() to parent impl * chore: Update javadocs * chore: Add Env Var tests for GOOGLE_CLOUD_UNIVERSE_DOMAIN * chore: Fix lint issues * chore: Fix env var tests * chore: Update logic for isUserConfiguredEndpoint * chore: Throw IOException on validateUniverseDomain * chore: Address PR comments * chore: Address PR comments * chore: Address PR comments * chore: Address PR comments * chore: Validate on HttpCredentialsAdapter * chore: Address PR comments --- .github/workflows/ci.yaml | 7 + google-api-client/pom.xml | 39 ++- .../services/AbstractGoogleClient.java | 153 ++++++++- .../services/AbstractGoogleClientTest.java | 300 +++++++++++++++++- pom.xml | 33 ++ 5 files changed, 526 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4b1d0bbb0..37fc04345 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,6 +20,13 @@ jobs: - run: .kokoro/build.sh env: JOB_TYPE: test + # The `envVarTest` profile runs tests that require an environment variable + - name: Env Var Tests + run: | + mvn test -B -ntp -Dclirr.skip=true -Denforcer.skip=true -PenvVarTest + # Set the Env Var for this step only + env: + GOOGLE_CLOUD_UNIVERSE_DOMAIN: random.com windows: runs-on: windows-latest steps: diff --git a/google-api-client/pom.xml b/google-api-client/pom.xml index 9bd09e0cf..771103a34 100644 --- a/google-api-client/pom.xml +++ b/google-api-client/pom.xml @@ -111,6 +111,14 @@ commons-codec:commons-codec + + org.apache.maven.plugins + maven-surefire-plugin + + + !AbstractGoogleClientTest#testGoogleClientBuilder_noCustomUniverseDomain_universeDomainEnvVar+testGoogleClientBuilder_customUniverseDomain_universeDomainEnvVar + + @@ -135,6 +143,14 @@ com.google.oauth-client google-oauth-client + + com.google.auth + google-auth-library-credentials + + + com.google.auth + google-auth-library-oauth2-http + com.google.http-client google-http-client-gson @@ -179,6 +195,27 @@ junit test - + + org.mockito + mockito-core + test + + + + + envVarTest + + + + org.apache.maven.plugins + maven-surefire-plugin + + AbstractGoogleClientTest#testGoogleClientBuilder_noCustomUniverseDomain_universeDomainEnvVar+testGoogleClientBuilder_customUniverseDomain_universeDomainEnvVar + + + + + + diff --git a/google-api-client/src/main/java/com/google/api/client/googleapis/services/AbstractGoogleClient.java b/google-api-client/src/main/java/com/google/api/client/googleapis/services/AbstractGoogleClient.java index 421dc0a46..53cc57fa7 100644 --- a/google-api-client/src/main/java/com/google/api/client/googleapis/services/AbstractGoogleClient.java +++ b/google-api-client/src/main/java/com/google/api/client/googleapis/services/AbstractGoogleClient.java @@ -20,8 +20,13 @@ import com.google.api.client.util.ObjectParser; import com.google.api.client.util.Preconditions; import com.google.api.client.util.Strings; +import com.google.auth.Credentials; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Abstract thread-safe Google client. @@ -33,6 +38,8 @@ public abstract class AbstractGoogleClient { private static final Logger logger = Logger.getLogger(AbstractGoogleClient.class.getName()); + private static final String GOOGLE_CLOUD_UNIVERSE_DOMAIN = "GOOGLE_CLOUD_UNIVERSE_DOMAIN"; + /** The request factory for connections to the server. */ private final HttpRequestFactory requestFactory; @@ -68,13 +75,18 @@ public abstract class AbstractGoogleClient { /** Whether discovery required parameter checks should be suppressed. */ private final boolean suppressRequiredParameterChecks; + private final String universeDomain; + + private final HttpRequestInitializer httpRequestInitializer; + /** * @param builder builder * @since 1.14 */ protected AbstractGoogleClient(Builder builder) { googleClientRequestInitializer = builder.googleClientRequestInitializer; - rootUrl = normalizeRootUrl(builder.rootUrl); + universeDomain = determineUniverseDomain(builder); + rootUrl = normalizeRootUrl(determineEndpoint(builder)); servicePath = normalizeServicePath(builder.servicePath); batchPath = builder.batchPath; if (Strings.isNullOrEmpty(builder.applicationName)) { @@ -88,6 +100,75 @@ protected AbstractGoogleClient(Builder builder) { objectParser = builder.objectParser; suppressPatternChecks = builder.suppressPatternChecks; suppressRequiredParameterChecks = builder.suppressRequiredParameterChecks; + httpRequestInitializer = builder.httpRequestInitializer; + } + + /** + * Resolve the Universe Domain to be used when resolving the endpoint. The logic for resolving the + * universe domain is the following order: 1. Use the user configured value is set, 2. Use the + * Universe Domain Env Var if set, 3. Default to the Google Default Universe + */ + private String determineUniverseDomain(Builder builder) { + String resolvedUniverseDomain = builder.universeDomain; + if (resolvedUniverseDomain == null) { + resolvedUniverseDomain = System.getenv(GOOGLE_CLOUD_UNIVERSE_DOMAIN); + } + return resolvedUniverseDomain == null + ? Credentials.GOOGLE_DEFAULT_UNIVERSE + : resolvedUniverseDomain; + } + + /** + * Resolve the endpoint based on user configurations. If the user has configured a custom rootUrl, + * use that value. Otherwise, construct the endpoint based on the serviceName and the + * universeDomain. + */ + private String determineEndpoint(Builder builder) { + boolean mtlsEnabled = builder.rootUrl.contains(".mtls."); + if (mtlsEnabled && !universeDomain.equals(Credentials.GOOGLE_DEFAULT_UNIVERSE)) { + throw new IllegalStateException( + "mTLS is not supported in any universe other than googleapis.com"); + } + // If the serviceName is null, we cannot construct a valid resolved endpoint. Simply return + // the rootUrl as this was custom rootUrl passed in. + if (builder.isUserConfiguredEndpoint || builder.serviceName == null) { + return builder.rootUrl; + } + if (mtlsEnabled) { + return "https://" + builder.serviceName + ".mtls." + universeDomain + "/"; + } + return "https://" + builder.serviceName + "." + universeDomain + "/"; + } + + /** + * Check that the User configured universe domain matches the Credentials' universe domain. This + * uses the HttpRequestInitializer to get the Credentials and is enforced that the + * HttpRequestInitializer is of the {@see HttpCredentialsAdapter} + * from the google-auth-library. + * + *

To use a non-GDU Credentials, you must use the HttpCredentialsAdapter class. + * + * @throws IOException if there is an error reading the Universe Domain from the credentials + * @throws IllegalStateException if the configured Universe Domain does not match the Universe + * Domain in the Credentials + */ + public void validateUniverseDomain() throws IOException { + if (!(httpRequestInitializer instanceof HttpCredentialsAdapter)) { + return; + } + Credentials credentials = ((HttpCredentialsAdapter) httpRequestInitializer).getCredentials(); + // No need for a null check as HttpCredentialsAdapter cannot be initialized with null + // Credentials + String expectedUniverseDomain = credentials.getUniverseDomain(); + if (!expectedUniverseDomain.equals(getUniverseDomain())) { + throw new IllegalStateException( + String.format( + "The configured universe domain (%s) does not match the universe domain found" + + " in the credentials (%s). If you haven't configured the universe domain" + + " explicitly, `googleapis.com` is the default.", + getUniverseDomain(), expectedUniverseDomain)); + } } /** @@ -139,6 +220,18 @@ public final GoogleClientRequestInitializer getGoogleClientRequestInitializer() return googleClientRequestInitializer; } + /** + * Universe Domain is the domain for Google Cloud Services. It follows the format of + * `{ServiceName}.{UniverseDomain}`. For example, speech.googleapis.com would have a Universe + * Domain value of `googleapis.com` and cloudasset.test.com would have a Universe Domain of + * `test.com`. If this value is not set, this will default to `googleapis.com`. + * + * @return The configured Universe Domain or the Google Default Universe (googleapis.com) + */ + public final String getUniverseDomain() { + return universeDomain; + } + /** * Returns the object parser or {@code null} for none. * @@ -173,6 +266,7 @@ public ObjectParser getObjectParser() { * @param httpClientRequest Google client request type */ protected void initialize(AbstractGoogleClientRequest httpClientRequest) throws IOException { + validateUniverseDomain(); if (getGoogleClientRequestInitializer() != null) { getGoogleClientRequestInitializer().initialize(httpClientRequest); } @@ -311,6 +405,33 @@ public abstract static class Builder { /** Whether discovery required parameter checks should be suppressed. */ boolean suppressRequiredParameterChecks; + /** User configured Universe Domain. Defaults to `googleapis.com`. */ + String universeDomain; + + /** + * Regex pattern to check if the URL passed in matches the default endpoint configured from a + * discovery doc. Follows the format of `https://{serviceName}(.mtls).googleapis.com/` + */ + Pattern defaultEndpointRegex = + Pattern.compile("https://([a-zA-Z]*)(\\.mtls)?\\.googleapis.com/?"); + + /** + * Whether the user has configured an endpoint via {@link #setRootUrl(String)}. This is added in + * because the rootUrl is set in the Builder's constructor. , + * + *

Apiary clients don't allow user configurations to this Builder's constructor, so this + * would be set to false by default for Apiary libraries. User configuration to the rootUrl is + * done via {@link #setRootUrl(String)}. + * + *

For other uses cases that touch this Builder's constructor directly, check if the rootUrl + * passed matches the default endpoint regex. If it doesn't match, it is a user configured + * endpoint. + */ + boolean isUserConfiguredEndpoint; + + /** The parsed serviceName value from the rootUrl from the Discovery Doc. */ + String serviceName; + /** * Returns an instance of a new builder. * @@ -328,9 +449,15 @@ protected Builder( HttpRequestInitializer httpRequestInitializer) { this.transport = Preconditions.checkNotNull(transport); this.objectParser = objectParser; - setRootUrl(rootUrl); - setServicePath(servicePath); + this.rootUrl = normalizeRootUrl(rootUrl); + this.servicePath = normalizeServicePath(servicePath); this.httpRequestInitializer = httpRequestInitializer; + Matcher matcher = defaultEndpointRegex.matcher(rootUrl); + boolean matches = matcher.matches(); + // Checked here for the use case where users extend this class and may pass in + // a custom endpoint + this.isUserConfiguredEndpoint = !matches; + this.serviceName = matches ? matcher.group(1) : null; } /** Builds a new instance of {@link AbstractGoogleClient}. */ @@ -371,6 +498,7 @@ public final String getRootUrl() { * changing the return type, but nothing else. */ public Builder setRootUrl(String rootUrl) { + this.isUserConfiguredEndpoint = true; this.rootUrl = normalizeRootUrl(rootUrl); return this; } @@ -515,5 +643,24 @@ public Builder setSuppressRequiredParameterChecks(boolean suppressRequiredParame public Builder setSuppressAllChecks(boolean suppressAllChecks) { return setSuppressPatternChecks(true).setSuppressRequiredParameterChecks(true); } + + /** + * Sets the user configured Universe Domain value. This value will be used to try and construct + * the endpoint to connect to GCP services. + * + * @throws IllegalArgumentException if universeDomain is passed in with an empty string ("") + */ + public Builder setUniverseDomain(String universeDomain) { + if (universeDomain != null && universeDomain.isEmpty()) { + throw new IllegalArgumentException("The universe domain value cannot be empty."); + } + this.universeDomain = universeDomain; + return this; + } + + @VisibleForTesting + String getServiceName() { + return serviceName; + } } } diff --git a/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientTest.java b/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientTest.java index 7d2ff6383..bcb6a3775 100644 --- a/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientTest.java +++ b/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientTest.java @@ -12,6 +12,8 @@ package com.google.api.client.googleapis.services; +import static org.junit.Assert.assertThrows; + import com.google.api.client.googleapis.media.MediaHttpUploader; import com.google.api.client.googleapis.testing.services.MockGoogleClient; import com.google.api.client.googleapis.testing.services.MockGoogleClientRequest; @@ -32,23 +34,46 @@ import com.google.api.client.testing.http.MockLowLevelHttpRequest; import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.api.client.util.Key; +import com.google.auth.Credentials; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.auth.oauth2.GoogleCredentials; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import junit.framework.TestCase; +import org.junit.Test; +import org.junit.function.ThrowingRunnable; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; /** * Tests {@link AbstractGoogleClient}. * * @author Yaniv Inbar */ +@RunWith(MockitoJUnitRunner.class) public class AbstractGoogleClientTest extends TestCase { + @Mock private GoogleCredentials googleCredentials; + + @Mock private HttpCredentialsAdapter httpCredentialsAdapter; + private static final JsonFactory JSON_FACTORY = new GsonFactory(); private static final JsonObjectParser JSON_OBJECT_PARSER = new JsonObjectParser(JSON_FACTORY); private static final HttpTransport TRANSPORT = new MockHttpTransport(); + private static class TestHttpRequestInitializer implements HttpRequestInitializer { + + @Override + public void initialize(HttpRequest httpRequest) { + // no-op + } + } + private static class TestRemoteRequestInitializer implements GoogleClientRequestInitializer { boolean isCalled; @@ -60,9 +85,10 @@ public void initialize(AbstractGoogleClientRequest request) { } } + @Test public void testGoogleClientBuilder() { - String rootUrl = "http://www.testgoogleapis.com/test/"; - String servicePath = "path/v1/"; + String rootUrl = "https://test.googleapis.com/"; + String servicePath = "test/path/v1/"; GoogleClientRequestInitializer jsonHttpRequestInitializer = new TestRemoteRequestInitializer(); String applicationName = "Test Application"; @@ -82,6 +108,142 @@ public void testGoogleClientBuilder() { assertTrue(client.getSuppressRequiredParameterChecks()); } + @Test + public void testGoogleClientBuilder_setsCorrectRootUrl_nonMtlsUrl() { + String rootUrl = "https://test.googleapis.com/"; + String applicationName = "Test Application"; + String servicePath = "test/"; + + AbstractGoogleClient client = + new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null) + .setApplicationName(applicationName) + .build(); + assertEquals(rootUrl, client.getRootUrl()); + assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, client.getUniverseDomain()); + } + + @Test + public void testGoogleClientBuilder_setsCorrectRootUrl_mtlsUrl() { + String rootUrl = "https://test.mtls.googleapis.com/"; + String applicationName = "Test Application"; + String servicePath = "test/"; + + AbstractGoogleClient client = + new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null) + .setApplicationName(applicationName) + .build(); + assertEquals(rootUrl, client.getRootUrl()); + assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, client.getUniverseDomain()); + } + + @Test + public void testGoogleClientBuilder_customUniverseDomain_nonMtlsUrl() { + String rootUrl = "https://test.googleapis.com/"; + String applicationName = "Test Application"; + String servicePath = "test/"; + String universeDomain = "random.com"; + + AbstractGoogleClient client = + new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null) + .setApplicationName(applicationName) + .setUniverseDomain(universeDomain) + .build(); + assertEquals("https://test.random.com/", client.getRootUrl()); + assertEquals(universeDomain, client.getUniverseDomain()); + } + + @Test + public void testGoogleClientBuilder_customUniverseDomain_mtlsUrl() { + String rootUrl = "https://test.mtls.googleapis.com/"; + String applicationName = "Test Application"; + String servicePath = "test/"; + final AbstractGoogleClient.Builder builder = + new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null) + .setApplicationName(applicationName) + .setUniverseDomain("random.com"); + + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + new ThrowingRunnable() { + @Override + public void run() { + builder.build(); + } + }); + assertEquals( + "mTLS is not supported in any universe other than googleapis.com", exception.getMessage()); + } + + @Test + public void testGoogleClientBuilder_customEndpoint_defaultUniverseDomain() { + String rootUrl = "https://test.googleapis.com/"; + String applicationName = "Test Application"; + String servicePath = "test/"; + + AbstractGoogleClient client = + new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null) + .setApplicationName(applicationName) + .setRootUrl("https://randomendpoint.com/") + .build(); + assertEquals("https://randomendpoint.com/", client.getRootUrl()); + assertEquals(Credentials.GOOGLE_DEFAULT_UNIVERSE, client.getUniverseDomain()); + } + + @Test + public void testGoogleClientBuilder_customEndpoint_customUniverseDomain() { + String rootUrl = "https://test.googleapis.com/"; + String applicationName = "Test Application"; + String servicePath = "test/"; + String universeDomain = "random.com"; + String customRootUrl = "https://randomendpoint.com/"; + + AbstractGoogleClient client = + new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null) + .setApplicationName(applicationName) + .setRootUrl(customRootUrl) + .setUniverseDomain(universeDomain) + .build(); + assertEquals(customRootUrl, client.getRootUrl()); + assertEquals(universeDomain, client.getUniverseDomain()); + } + + @Test + public void testGoogleClientBuilder_noCustomUniverseDomain_universeDomainEnvVar() { + String rootUrl = "https://test.googleapis.com/"; + String applicationName = "Test Application"; + String servicePath = "test/"; + // Env Var Universe Domain is `random.com` + String envVarUniverseDomain = "random.com"; + String expectedRootUrl = "https://test.random.com/"; + + AbstractGoogleClient client = + new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null) + .setApplicationName(applicationName) + .build(); + assertEquals(expectedRootUrl, client.getRootUrl()); + assertEquals(envVarUniverseDomain, client.getUniverseDomain()); + } + + @Test + public void testGoogleClientBuilder_customUniverseDomain_universeDomainEnvVar() { + String rootUrl = "https://test.googleapis.com/"; + String applicationName = "Test Application"; + String servicePath = "test/"; + // Env Var Universe Domain is `random.com` + String customUniverseDomain = "test.com"; + String expectedRootUrl = "https://test.test.com/"; + + AbstractGoogleClient client = + new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null) + .setApplicationName(applicationName) + .setUniverseDomain(customUniverseDomain) + .build(); + assertEquals(expectedRootUrl, client.getRootUrl()); + assertEquals(customUniverseDomain, client.getUniverseDomain()); + } + + @Test public void testGoogleClientSuppressionDefaults() { String rootUrl = "http://www.testgoogleapis.com/test/"; String servicePath = "path/v1/"; @@ -97,6 +259,7 @@ public void testGoogleClientSuppressionDefaults() { assertFalse(googleClient.getSuppressRequiredParameterChecks()); } + @Test public void testBaseServerAndBasePathBuilder() { AbstractGoogleClient client = new MockGoogleClient.Builder( @@ -113,6 +276,7 @@ public void testBaseServerAndBasePathBuilder() { assertEquals("http://www.googleapis.com/test/path/v2/", client.getBaseUrl()); } + @Test public void testInitialize() throws Exception { TestRemoteRequestInitializer remoteRequestInitializer = new TestRemoteRequestInitializer(); AbstractGoogleClient client = @@ -125,6 +289,138 @@ public void testInitialize() throws Exception { assertTrue(remoteRequestInitializer.isCalled); } + @Test + public void testParseServiceName_nonMtlsRootUrl() { + AbstractGoogleClient.Builder clientBuilder = + new MockGoogleClient.Builder( + TRANSPORT, "https://random.googleapis.com/", "", JSON_OBJECT_PARSER, null) + .setApplicationName("Test Application"); + assertEquals(clientBuilder.getServiceName(), "random"); + } + + @Test + public void testParseServiceName_mtlsRootUrl() { + AbstractGoogleClient.Builder clientBuilder = + new MockGoogleClient.Builder( + TRANSPORT, "https://test.mtls.googleapis.com/", "", JSON_OBJECT_PARSER, null) + .setApplicationName("Test Application"); + assertEquals(clientBuilder.getServiceName(), "test"); + } + + @Test + public void testParseServiceName_nonGDURootUrl() { + AbstractGoogleClient.Builder clientBuilder = + new MockGoogleClient.Builder( + TRANSPORT, "https://test.random.com/", "", JSON_OBJECT_PARSER, null) + .setApplicationName("Test Application"); + assertNull(clientBuilder.getServiceName()); + } + + @Test + public void testIsUserSetEndpoint_nonMtlsRootUrl() { + AbstractGoogleClient.Builder clientBuilder = + new MockGoogleClient.Builder( + TRANSPORT, "https://random.googleapis.com/", "", JSON_OBJECT_PARSER, null) + .setApplicationName("Test Application"); + assertFalse(clientBuilder.isUserConfiguredEndpoint); + } + + @Test + public void testIsUserSetEndpoint_mtlsRootUrl() { + AbstractGoogleClient.Builder clientBuilder = + new MockGoogleClient.Builder( + TRANSPORT, "https://test.mtls.googleapis.com/", "", JSON_OBJECT_PARSER, null) + .setApplicationName("Test Application"); + assertFalse(clientBuilder.isUserConfiguredEndpoint); + } + + @Test + public void testIsUserSetEndpoint_nonGDURootUrl() { + AbstractGoogleClient.Builder clientBuilder = + new MockGoogleClient.Builder( + TRANSPORT, "https://test.random.com/", "", JSON_OBJECT_PARSER, null) + .setApplicationName("Test Application"); + assertTrue(clientBuilder.isUserConfiguredEndpoint); + } + + @Test + public void testIsUserSetEndpoint_regionalEndpoint() { + AbstractGoogleClient.Builder clientBuilder = + new MockGoogleClient.Builder( + TRANSPORT, + "https://us-east-4.coolservice.googleapis.com/", + "", + JSON_OBJECT_PARSER, + null) + .setApplicationName("Test Application"); + assertTrue(clientBuilder.isUserConfiguredEndpoint); + } + + @Test + public void validateUniverseDomain_validUniverseDomain() throws IOException { + String rootUrl = "https://test.googleapis.com/"; + String applicationName = "Test Application"; + String servicePath = "test/"; + + Mockito.when(httpCredentialsAdapter.getCredentials()).thenReturn(googleCredentials); + Mockito.when(googleCredentials.getUniverseDomain()) + .thenReturn(Credentials.GOOGLE_DEFAULT_UNIVERSE); + + AbstractGoogleClient client = + new MockGoogleClient.Builder( + TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, httpCredentialsAdapter) + .setApplicationName(applicationName) + .build(); + + // Nothing throws + client.validateUniverseDomain(); + } + + @Test + public void validateUniverseDomain_invalidUniverseDomain() throws IOException { + String rootUrl = "https://test.googleapis.com/"; + String applicationName = "Test Application"; + String servicePath = "test/"; + + Mockito.when(httpCredentialsAdapter.getCredentials()).thenReturn(googleCredentials); + Mockito.when(googleCredentials.getUniverseDomain()).thenReturn("invalid.universe.domain"); + + final AbstractGoogleClient client = + new MockGoogleClient.Builder( + TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, httpCredentialsAdapter) + .setApplicationName(applicationName) + .build(); + assertThrows( + IllegalStateException.class, + new ThrowingRunnable() { + @Override + public void run() throws IOException { + client.validateUniverseDomain(); + } + }); + } + + @Test + public void validateUniverseDomain_notUsingHttpCredentialsAdapter_defaultUniverseDomain() + throws IOException { + String rootUrl = "https://test.googleapis.com/"; + String applicationName = "Test Application"; + String servicePath = "test/"; + + AbstractGoogleClient client = + new MockGoogleClient.Builder( + TRANSPORT, + rootUrl, + servicePath, + JSON_OBJECT_PARSER, + new TestHttpRequestInitializer()) + .setApplicationName(applicationName) + .build(); + + // Nothing throws + client.validateUniverseDomain(); + } + private static final String TEST_RESUMABLE_REQUEST_URL = "http://www.test.com/request/url?uploadType=resumable"; private static final String TEST_UPLOAD_URL = "http://www.test.com/media/upload/location"; diff --git a/pom.xml b/pom.xml index 62c35db41..58803a85e 100644 --- a/pom.xml +++ b/pom.xml @@ -102,6 +102,13 @@ junit junit 4.13.2 + test + + + org.mockito + mockito-core + 4.11.0 + test com.google.appengine @@ -157,6 +164,13 @@ pom import + + com.google.auth + google-auth-library-bom + ${project.auth.version} + pom + import + com.google.api-client google-api-client @@ -499,6 +513,7 @@ 4.5.14 1.16.1 1.35.0 + 1.22.0 3.0.2 2.8.6 3.25.3 @@ -645,5 +660,23 @@ + + + envVarTest + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + **/*.java + + + + + +