From b49eeca30e82533cc80d820bece06180a850548d Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Fri, 16 Feb 2024 18:02:47 -0500 Subject: [PATCH 01/24] feat: Implement Universe Domain Support --- google-api-client/pom.xml | 5 ++ .../services/AbstractGoogleClient.java | 66 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/google-api-client/pom.xml b/google-api-client/pom.xml index 9bd09e0cf..4434a5626 100644 --- a/google-api-client/pom.xml +++ b/google-api-client/pom.xml @@ -135,6 +135,11 @@ com.google.oauth-client google-oauth-client + + com.google.auth + google-auth-library-oauth2-http + 1.22.0 + com.google.http-client google-http-client-gson 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..3793cdaa3 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,6 +20,9 @@ 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 java.io.IOException; import java.util.logging.Logger; @@ -68,12 +71,17 @@ 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; + universeDomain = builder.universeDomain; rootUrl = normalizeRootUrl(builder.rootUrl); servicePath = normalizeServicePath(builder.servicePath); batchPath = builder.batchPath; @@ -88,6 +96,26 @@ protected AbstractGoogleClient(Builder builder) { objectParser = builder.objectParser; suppressPatternChecks = builder.suppressPatternChecks; suppressRequiredParameterChecks = builder.suppressRequiredParameterChecks; + httpRequestInitializer = builder.httpRequestInitializer; + } + + protected void validateUniverseDomain() { + HttpRequestInitializer requestInitializer = getHttpRequestInitializer(); + if (!getUniverseDomain().equals("googleapis.com") && !(requestInitializer instanceof HttpCredentialsAdapter)) { + throw new IllegalStateException("You must pass in Credentials to configure the Universe Domain"); + } + Credentials credentials = ((HttpCredentialsAdapter) requestInitializer).getCredentials(); + try { + if (!credentials.getUniverseDomain().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(), + credentials.getUniverseDomain() + )); + } + } catch (IOException e) { + throw new IllegalStateException("Unable to retrieve the Universe Domain from the Credentials.", e); + } } /** @@ -139,6 +167,14 @@ public final GoogleClientRequestInitializer getGoogleClientRequestInitializer() return googleClientRequestInitializer; } + public final String getUniverseDomain() { + return universeDomain; + } + + public final HttpRequestInitializer getHttpRequestInitializer() { + return httpRequestInitializer; + } + /** * Returns the object parser or {@code null} for none. * @@ -311,6 +347,10 @@ public abstract static class Builder { /** Whether discovery required parameter checks should be suppressed. */ boolean suppressRequiredParameterChecks; + String universeDomain; + + boolean userSetEndpoint; + /** * Returns an instance of a new builder. * @@ -331,6 +371,8 @@ protected Builder( setRootUrl(rootUrl); setServicePath(servicePath); this.httpRequestInitializer = httpRequestInitializer; + this.universeDomain = "googleapis.com"; + this.userSetEndpoint = false; } /** Builds a new instance of {@link AbstractGoogleClient}. */ @@ -371,6 +413,7 @@ public final String getRootUrl() { * changing the return type, but nothing else. */ public Builder setRootUrl(String rootUrl) { + this.userSetEndpoint = true; this.rootUrl = normalizeRootUrl(rootUrl); return this; } @@ -515,5 +558,28 @@ public Builder setSuppressRequiredParameterChecks(boolean suppressRequiredParame public Builder setSuppressAllChecks(boolean suppressAllChecks) { return setSuppressPatternChecks(true).setSuppressRequiredParameterChecks(true); } + + public Builder setUniverseDomain(String universeDomain) { + if (universeDomain.isEmpty()) { + throw new IllegalArgumentException("The universe domain value cannot be empty."); + } + this.universeDomain = universeDomain; + return this; + } + + public final String getUniverseDomain() { + return universeDomain; + } + + protected void determineEndpoint() { + if (rootUrl.contains("mtls") && !universeDomain.equals("googleapis.com")) { + throw new IllegalArgumentException( + "mTLS is not supported in any universe other than googleapis.com"); + } + String serviceName = "bigquery"; + if (!userSetEndpoint) { + rootUrl = "https://" + serviceName + "." + universeDomain + ":443"; + } + } } } From e15caa21c05f0934cc40cce2d89b2cd2c139d937 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 20 Feb 2024 15:55:36 -0500 Subject: [PATCH 02/24] chore: Clean up auth dependencies --- google-api-client/pom.xml | 5 +++- .../services/AbstractGoogleClient.java | 25 +++++++++++-------- pom.xml | 8 ++++++ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/google-api-client/pom.xml b/google-api-client/pom.xml index 4434a5626..4feb14961 100644 --- a/google-api-client/pom.xml +++ b/google-api-client/pom.xml @@ -135,10 +135,13 @@ com.google.oauth-client google-oauth-client + + com.google.auth + google-auth-library-credentials + com.google.auth google-auth-library-oauth2-http - 1.22.0 com.google.http-client 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 3793cdaa3..81c310593 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 @@ -22,7 +22,6 @@ import com.google.api.client.util.Strings; import com.google.auth.Credentials; import com.google.auth.http.HttpCredentialsAdapter; - import java.io.IOException; import java.util.logging.Logger; @@ -101,20 +100,24 @@ protected AbstractGoogleClient(Builder builder) { protected void validateUniverseDomain() { HttpRequestInitializer requestInitializer = getHttpRequestInitializer(); - if (!getUniverseDomain().equals("googleapis.com") && !(requestInitializer instanceof HttpCredentialsAdapter)) { - throw new IllegalStateException("You must pass in Credentials to configure the Universe Domain"); + if (!getUniverseDomain().equals(Credentials.GOOGLE_DEFAULT_UNIVERSE) + && !(requestInitializer instanceof HttpCredentialsAdapter)) { + throw new IllegalStateException( + "You must pass in Credentials to configure the Universe Domain"); } Credentials credentials = ((HttpCredentialsAdapter) requestInitializer).getCredentials(); try { if (!credentials.getUniverseDomain().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(), - credentials.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(), credentials.getUniverseDomain())); } } catch (IOException e) { - throw new IllegalStateException("Unable to retrieve the Universe Domain from the Credentials.", e); + throw new IllegalStateException( + "Unable to retrieve the Universe Domain from the Credentials.", e); } } @@ -572,9 +575,9 @@ public final String getUniverseDomain() { } protected void determineEndpoint() { - if (rootUrl.contains("mtls") && !universeDomain.equals("googleapis.com")) { + if (rootUrl.contains("mtls") && !universeDomain.equals(Credentials.GOOGLE_DEFAULT_UNIVERSE)) { throw new IllegalArgumentException( - "mTLS is not supported in any universe other than googleapis.com"); + "mTLS is not supported in any universe other than googleapis.com"); } String serviceName = "bigquery"; if (!userSetEndpoint) { diff --git a/pom.xml b/pom.xml index bd575a920..a0a720179 100644 --- a/pom.xml +++ b/pom.xml @@ -157,6 +157,13 @@ pom import + + com.google.auth + google-auth-library-bom + ${project.auth.version} + pom + import + com.google.api-client google-api-client @@ -499,6 +506,7 @@ 4.5.14 1.16.0 1.35.0 + 1.22.0 3.0.2 2.8.6 3.25.2 From 45448118830e8472b3efb49884bd6926fbef1c6b Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 20 Feb 2024 16:57:18 -0500 Subject: [PATCH 03/24] chore: Resolve checkstyle issues --- .../client/googleapis/services/AbstractGoogleClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 81c310593..ba336087f 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 @@ -110,9 +110,9 @@ protected void validateUniverseDomain() { if (!credentials.getUniverseDomain().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.", + "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(), credentials.getUniverseDomain())); } } catch (IOException e) { From 3b8a90afeb136525c78cd8e577e1d9b448495172 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 20 Feb 2024 17:37:09 -0500 Subject: [PATCH 04/24] chore: Determine service name from url --- .../services/AbstractGoogleClient.java | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) 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 ba336087f..860645c09 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 @@ -22,6 +22,7 @@ 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; @@ -352,7 +353,9 @@ public abstract static class Builder { String universeDomain; - boolean userSetEndpoint; + boolean isUserConfiguredEndpoint; + + String serviceName; /** * Returns an instance of a new builder. @@ -371,11 +374,22 @@ 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; - this.universeDomain = "googleapis.com"; - this.userSetEndpoint = false; + this.universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; + this.isUserConfiguredEndpoint = false; + this.serviceName = parseServiceName(rootUrl); + } + + @VisibleForTesting + String parseServiceName(String rootUrl) { + // len of "https://" + int startIndex = 8; + if (rootUrl.contains("mtls")) { + return rootUrl.substring(startIndex, rootUrl.indexOf(".mtls")); + } + return rootUrl.substring(startIndex, rootUrl.indexOf(".googleapis.com")); } /** Builds a new instance of {@link AbstractGoogleClient}. */ @@ -416,7 +430,7 @@ public final String getRootUrl() { * changing the return type, but nothing else. */ public Builder setRootUrl(String rootUrl) { - this.userSetEndpoint = true; + this.isUserConfiguredEndpoint = true; this.rootUrl = normalizeRootUrl(rootUrl); return this; } @@ -563,7 +577,7 @@ public Builder setSuppressAllChecks(boolean suppressAllChecks) { } public Builder setUniverseDomain(String universeDomain) { - if (universeDomain.isEmpty()) { + if (universeDomain != null && universeDomain.isEmpty()) { throw new IllegalArgumentException("The universe domain value cannot be empty."); } this.universeDomain = universeDomain; @@ -579,9 +593,11 @@ protected void determineEndpoint() { throw new IllegalArgumentException( "mTLS is not supported in any universe other than googleapis.com"); } - String serviceName = "bigquery"; - if (!userSetEndpoint) { - rootUrl = "https://" + serviceName + "." + universeDomain + ":443"; + // If the user did not set an endpoint, resolve the endpoint + String resolvedUniverseDomain = + universeDomain == null ? Credentials.GOOGLE_DEFAULT_UNIVERSE : universeDomain; + if (!isUserConfiguredEndpoint) { + rootUrl = "https://" + serviceName + "." + resolvedUniverseDomain + ":443"; } } } From 544ee32dad07d882bc0c49f800dd4baaebaa935d Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Thu, 22 Feb 2024 11:17:25 -0500 Subject: [PATCH 05/24] chore: Return null for invalid rootUrl --- .../services/AbstractGoogleClient.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) 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 860645c09..b80887b4e 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 @@ -382,14 +382,21 @@ protected Builder( this.serviceName = parseServiceName(rootUrl); } + // The roolUrl from the Discovery Docs will always follow the format of + // https://{serviceName}(.mtls).googleapis.com:443 @VisibleForTesting String parseServiceName(String rootUrl) { // len of "https://" int startIndex = 8; - if (rootUrl.contains("mtls")) { + if (rootUrl.contains("mtls.googleapis.com")) { return rootUrl.substring(startIndex, rootUrl.indexOf(".mtls")); + } else if (rootUrl.contains(".googleapis.com")) { + return rootUrl.substring(startIndex, rootUrl.indexOf(".googleapis.com")); + } else { + // Return null to not break behavior for any non-google users or any use + // case without a discovery doc + return null; } - return rootUrl.substring(startIndex, rootUrl.indexOf(".googleapis.com")); } /** Builds a new instance of {@link AbstractGoogleClient}. */ @@ -576,6 +583,11 @@ public Builder setSuppressAllChecks(boolean suppressAllChecks) { return setSuppressPatternChecks(true).setSuppressRequiredParameterChecks(true); } + public Builder setServiceName(String serviceName) { + this.serviceName = serviceName; + return this; + } + public Builder setUniverseDomain(String universeDomain) { if (universeDomain != null && universeDomain.isEmpty()) { throw new IllegalArgumentException("The universe domain value cannot be empty."); From 2ba25048229760c8296d1cee8a0ec6e19e7dda52 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Thu, 29 Feb 2024 14:54:50 -0500 Subject: [PATCH 06/24] chore: Add javadocs for Universe Domain changes in AbstractGoogleClient --- .../services/AbstractGoogleClient.java | 110 +++++++++++------- 1 file changed, 68 insertions(+), 42 deletions(-) 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 b80887b4e..3f84695b6 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 @@ -82,7 +82,7 @@ public abstract class AbstractGoogleClient { protected AbstractGoogleClient(Builder builder) { googleClientRequestInitializer = builder.googleClientRequestInitializer; universeDomain = builder.universeDomain; - rootUrl = normalizeRootUrl(builder.rootUrl); + rootUrl = normalizeRootUrl(determineEndpoint(builder)); servicePath = normalizeServicePath(builder.servicePath); batchPath = builder.batchPath; if (Strings.isNullOrEmpty(builder.applicationName)) { @@ -99,22 +99,53 @@ protected AbstractGoogleClient(Builder builder) { httpRequestInitializer = builder.httpRequestInitializer; } - protected void validateUniverseDomain() { - HttpRequestInitializer requestInitializer = getHttpRequestInitializer(); - if (!getUniverseDomain().equals(Credentials.GOOGLE_DEFAULT_UNIVERSE) - && !(requestInitializer instanceof HttpCredentialsAdapter)) { - throw new IllegalStateException( - "You must pass in Credentials to configure the Universe Domain"); + private String determineEndpoint(Builder builder) { + if (builder.rootUrl.contains("mtls") + && !builder.universeDomain.equals(Credentials.GOOGLE_DEFAULT_UNIVERSE)) { + throw new IllegalArgumentException( + "mTLS is not supported in any universe other than googleapis.com"); + } + // If the user did not set an endpoint, resolve the endpoint + String resolvedUniverseDomain = + builder.universeDomain == null + ? Credentials.GOOGLE_DEFAULT_UNIVERSE + : builder.universeDomain; + if (builder.isUserConfiguredEndpoint) { + return builder.rootUrl; } - Credentials credentials = ((HttpCredentialsAdapter) requestInitializer).getCredentials(); + return "https://" + builder.serviceName + "." + resolvedUniverseDomain + ":443"; + } + + /** + * 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 HttpCredentialsAdapter from the google-auth-library. If the + * HttpRequestInitializer is not used, the configured Universe Domain is validated against the + * Google Default Universe (GDU): `googleapis.com`. + * + * @throws IllegalStateException if the configured Universe Domain does not match the Universe + * Domain in the Credentials + */ + public void validateUniverseDomain() { + String expectedUniverseDomain; try { - if (!credentials.getUniverseDomain().equals(getUniverseDomain())) { + if (!getUniverseDomain().equals(Credentials.GOOGLE_DEFAULT_UNIVERSE) + && !(httpRequestInitializer instanceof HttpCredentialsAdapter)) { + logger.warning( + "To configure the universe domain, the HttpRequestInitializer must be a HttpCredentialsAdapter. The Universe Domain value is set to be `googleapis.com`."); + expectedUniverseDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; + } else { + Credentials credentials = + ((HttpCredentialsAdapter) httpRequestInitializer).getCredentials(); + 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(), credentials.getUniverseDomain())); + getUniverseDomain(), expectedUniverseDomain)); } } catch (IOException e) { throw new IllegalStateException( @@ -171,14 +202,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; } - public final HttpRequestInitializer getHttpRequestInitializer() { - return httpRequestInitializer; - } - /** * Returns the object parser or {@code null} for none. * @@ -351,10 +386,13 @@ public abstract static class Builder { /** Whether discovery required parameter checks should be suppressed. */ boolean suppressRequiredParameterChecks; - String universeDomain; + /** User configured Universe Domain. Defaults to `googleapis.com` */ + String universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; - boolean isUserConfiguredEndpoint; + /** Whether the user has configured an endpoint via {@link #setRootUrl(String)} */ + boolean isUserConfiguredEndpoint = false; + /** The parsed serviceName value from the rootUrl from the Discovery Doc */ String serviceName; /** @@ -377,13 +415,17 @@ protected Builder( this.rootUrl = normalizeRootUrl(rootUrl); this.servicePath = normalizeServicePath(servicePath); this.httpRequestInitializer = httpRequestInitializer; - this.universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; - this.isUserConfiguredEndpoint = false; this.serviceName = parseServiceName(rootUrl); } - // The roolUrl from the Discovery Docs will always follow the format of - // https://{serviceName}(.mtls).googleapis.com:443 + /** + * This is intended to invoked once on the initial Builder's constructor call. This parses the + * rootUrl value (set from the Discovery Doc) to use for the serviceName. The serviceName is + * used to construct an endpoint when the user passes in a custom Universe Domain value. + * + *

The roolUrl from the Discovery Docs will always follow the format of + * https://{serviceName}(.mtls).googleapis.com:443 + */ @VisibleForTesting String parseServiceName(String rootUrl) { // len of "https://" @@ -583,11 +625,12 @@ public Builder setSuppressAllChecks(boolean suppressAllChecks) { return setSuppressPatternChecks(true).setSuppressRequiredParameterChecks(true); } - public Builder setServiceName(String serviceName) { - this.serviceName = serviceName; - return this; - } - + /** + * Sets the user configured Universe Domain value. This value will be used to try and construct + * the endpoint to connect to GCP services. + * + * @throws IllegalStateException 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."); @@ -595,22 +638,5 @@ public Builder setUniverseDomain(String universeDomain) { this.universeDomain = universeDomain; return this; } - - public final String getUniverseDomain() { - return universeDomain; - } - - protected void determineEndpoint() { - if (rootUrl.contains("mtls") && !universeDomain.equals(Credentials.GOOGLE_DEFAULT_UNIVERSE)) { - throw new IllegalArgumentException( - "mTLS is not supported in any universe other than googleapis.com"); - } - // If the user did not set an endpoint, resolve the endpoint - String resolvedUniverseDomain = - universeDomain == null ? Credentials.GOOGLE_DEFAULT_UNIVERSE : universeDomain; - if (!isUserConfiguredEndpoint) { - rootUrl = "https://" + serviceName + "." + resolvedUniverseDomain + ":443"; - } - } } } From 25c9bc8c184816f84671385c38328036e589924f Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Thu, 29 Feb 2024 15:37:28 -0500 Subject: [PATCH 07/24] chore: Address checkstyle issues --- .../client/googleapis/services/AbstractGoogleClient.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 3f84695b6..5365abac1 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 @@ -132,7 +132,9 @@ public void validateUniverseDomain() { if (!getUniverseDomain().equals(Credentials.GOOGLE_DEFAULT_UNIVERSE) && !(httpRequestInitializer instanceof HttpCredentialsAdapter)) { logger.warning( - "To configure the universe domain, the HttpRequestInitializer must be a HttpCredentialsAdapter. The Universe Domain value is set to be `googleapis.com`."); + "To configure the universe domain, the HttpRequestInitializer must be a " + + "HttpCredentialsAdapter. The Universe Domain value is set to be " + + "`googleapis.com`."); expectedUniverseDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; } else { Credentials credentials = @@ -386,13 +388,13 @@ public abstract static class Builder { /** Whether discovery required parameter checks should be suppressed. */ boolean suppressRequiredParameterChecks; - /** User configured Universe Domain. Defaults to `googleapis.com` */ + /** User configured Universe Domain. Defaults to `googleapis.com`. */ String universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; /** Whether the user has configured an endpoint via {@link #setRootUrl(String)} */ boolean isUserConfiguredEndpoint = false; - /** The parsed serviceName value from the rootUrl from the Discovery Doc */ + /** The parsed serviceName value from the rootUrl from the Discovery Doc. */ String serviceName; /** From 13418de421eab0f0a434992b2aac391904b46ddd Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Thu, 29 Feb 2024 17:29:43 -0500 Subject: [PATCH 08/24] chore: Add tests for AbstractGoogleClient --- google-api-client/pom.xml | 6 +- .../services/AbstractGoogleClient.java | 29 +- .../googleapis/batch/BatchRequestTest.java | 1751 +++++++++-------- .../services/AbstractGoogleClientTest.java | 247 ++- pom.xml | 7 + 5 files changed, 1152 insertions(+), 888 deletions(-) diff --git a/google-api-client/pom.xml b/google-api-client/pom.xml index 4feb14961..cb2b2f093 100644 --- a/google-api-client/pom.xml +++ b/google-api-client/pom.xml @@ -187,6 +187,10 @@ junit test - + + org.mockito + mockito-core + test + 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 5365abac1..1e37d6cc9 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 @@ -100,8 +100,8 @@ protected AbstractGoogleClient(Builder builder) { } private String determineEndpoint(Builder builder) { - if (builder.rootUrl.contains("mtls") - && !builder.universeDomain.equals(Credentials.GOOGLE_DEFAULT_UNIVERSE)) { + boolean mtlsEnabled = builder.rootUrl.contains(".mtls."); + if (mtlsEnabled && !builder.universeDomain.equals(Credentials.GOOGLE_DEFAULT_UNIVERSE)) { throw new IllegalArgumentException( "mTLS is not supported in any universe other than googleapis.com"); } @@ -113,7 +113,10 @@ private String determineEndpoint(Builder builder) { if (builder.isUserConfiguredEndpoint) { return builder.rootUrl; } - return "https://" + builder.serviceName + "." + resolvedUniverseDomain + ":443"; + if (mtlsEnabled) { + return "https://" + builder.serviceName + ".mtls." + resolvedUniverseDomain; + } + return "https://" + builder.serviceName + "." + resolvedUniverseDomain; } /** @@ -126,15 +129,10 @@ private String determineEndpoint(Builder builder) { * @throws IllegalStateException if the configured Universe Domain does not match the Universe * Domain in the Credentials */ - public void validateUniverseDomain() { + public boolean hasValidUniverseDomain() { String expectedUniverseDomain; try { - if (!getUniverseDomain().equals(Credentials.GOOGLE_DEFAULT_UNIVERSE) - && !(httpRequestInitializer instanceof HttpCredentialsAdapter)) { - logger.warning( - "To configure the universe domain, the HttpRequestInitializer must be a " + - "HttpCredentialsAdapter. The Universe Domain value is set to be " + - "`googleapis.com`."); + if (!(httpRequestInitializer instanceof HttpCredentialsAdapter)) { expectedUniverseDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; } else { Credentials credentials = @@ -153,6 +151,7 @@ public void validateUniverseDomain() { throw new IllegalStateException( "Unable to retrieve the Universe Domain from the Credentials.", e); } + return true; } /** @@ -426,10 +425,9 @@ protected Builder( * used to construct an endpoint when the user passes in a custom Universe Domain value. * *

The roolUrl from the Discovery Docs will always follow the format of - * https://{serviceName}(.mtls).googleapis.com:443 + * https://{serviceName}(.mtls).googleapis.com/ */ - @VisibleForTesting - String parseServiceName(String rootUrl) { + private String parseServiceName(String rootUrl) { // len of "https://" int startIndex = 8; if (rootUrl.contains("mtls.googleapis.com")) { @@ -640,5 +638,10 @@ public Builder setUniverseDomain(String universeDomain) { this.universeDomain = universeDomain; return this; } + + @VisibleForTesting + String getServiceName() { + return serviceName; + } } } diff --git a/google-api-client/src/test/java/com/google/api/client/googleapis/batch/BatchRequestTest.java b/google-api-client/src/test/java/com/google/api/client/googleapis/batch/BatchRequestTest.java index 1032d448d..5758b827f 100644 --- a/google-api-client/src/test/java/com/google/api/client/googleapis/batch/BatchRequestTest.java +++ b/google-api-client/src/test/java/com/google/api/client/googleapis/batch/BatchRequestTest.java @@ -1,872 +1,879 @@ -// Copyright 2012 Google Inc. All Rights Reserved. - -package com.google.api.client.googleapis.batch; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import com.google.api.client.googleapis.batch.BatchRequest.RequestInfo; -import com.google.api.client.googleapis.json.GoogleJsonError; -import com.google.api.client.googleapis.json.GoogleJsonError.ErrorInfo; -import com.google.api.client.googleapis.json.GoogleJsonErrorContainer; -import com.google.api.client.googleapis.testing.services.MockGoogleClient; -import com.google.api.client.googleapis.testing.services.MockGoogleClientRequest; -import com.google.api.client.http.ByteArrayContent; -import com.google.api.client.http.GenericUrl; -import com.google.api.client.http.HttpContent; -import com.google.api.client.http.HttpExecuteInterceptor; -import com.google.api.client.http.HttpHeaders; -import com.google.api.client.http.HttpMethods; -import com.google.api.client.http.HttpRequest; -import com.google.api.client.http.HttpRequestInitializer; -import com.google.api.client.http.HttpResponse; -import com.google.api.client.http.HttpUnsuccessfulResponseHandler; -import com.google.api.client.http.LowLevelHttpRequest; -import com.google.api.client.http.LowLevelHttpResponse; -import com.google.api.client.json.GenericJson; -import com.google.api.client.json.JsonObjectParser; -import com.google.api.client.json.gson.GsonFactory; -import com.google.api.client.protobuf.ProtoObjectParser; -import com.google.api.client.testing.http.HttpTesting; -import com.google.api.client.testing.http.MockHttpTransport; -import com.google.api.client.testing.http.MockLowLevelHttpRequest; -import com.google.api.client.testing.http.MockLowLevelHttpResponse; -import com.google.api.client.util.Charsets; -import com.google.api.client.util.Key; -import com.google.api.client.util.ObjectParser; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.List; -import junit.framework.TestCase; - -/** - * Tests {@link BatchRequest}. - * - * @author rmistry@google.com (Ravi Mistry) - */ -public class BatchRequestTest extends TestCase { - - private static final String ROOT_URL = "http://www.test.com/"; - private static final String SERVICE_PATH = "test/"; - private static final String TEST_BATCH_URL = "http://www.testgoogleapis.com/batch"; - private static final String URI_TEMPLATE1 = "uri/template/1"; - private static final String URI_TEMPLATE2 = "uri/template/2"; - private static final String METHOD1 = HttpMethods.GET; - private static final String METHOD2 = HttpMethods.POST; - private static final String ERROR_MSG = "Error message"; - private static final String ERROR_REASON = "notFound"; - private static final int ERROR_CODE = 503; - private static final String ERROR_DOMAIN = "global"; - private static final String RESPONSE_BOUNDARY = "ABC=DE=F"; - private static final String TEST_ID = "Humpty Dumpty"; - private static final String TEST_KIND = "Big\nEgg\n"; // Newlines help test boundary detection - private static final String TEST_NAME = "James Bond"; - private static final String TEST_NUM = "007"; - - private TestCallback1 callback1; - private TestCallback2 callback2; - private TestCallback3 callback3; - - private MockTransport transport; - - private MockCredential credential; - - @Override - protected void setUp() { - callback1 = new TestCallback1(); - callback2 = new TestCallback2(); - callback3 = new TestCallback3(); - } - - public static class MockDataClass1 extends GenericJson { - @Key String id; - - @Key String kind; - } - - public static class MockDataClass2 extends GenericJson { - @Key String name; - - @Key String number; - } - - private static class TestCallback1 - implements BatchCallback { - - int successCalls; - - TestCallback1() {} - - @Override - public void onSuccess(MockDataClass1 dataClass, HttpHeaders responseHeaders) { - successCalls++; - assertEquals(TEST_ID, dataClass.id); - assertEquals(TEST_KIND, dataClass.kind); - } - - @Override - public void onFailure(GoogleJsonErrorContainer e, HttpHeaders responseHeaders) { - fail("Should not be invoked in this test"); - } - } - - private static class TestCallback2 - implements BatchCallback { - - int successCalls; - int failureCalls; - - TestCallback2() {} - - @Override - public void onSuccess(MockDataClass2 dataClass, HttpHeaders responseHeaders) { - successCalls++; - assertEquals(TEST_NAME, dataClass.name); - assertEquals(TEST_NUM, dataClass.number); - } - - @Override - public void onFailure(GoogleJsonErrorContainer e, HttpHeaders responseHeaders) { - failureCalls++; - GoogleJsonError error = e.getError(); - ErrorInfo errorInfo = error.getErrors().get(0); - assertEquals(ERROR_DOMAIN, errorInfo.getDomain()); - assertEquals(ERROR_REASON, errorInfo.getReason()); - assertEquals(ERROR_MSG, errorInfo.getMessage()); - assertEquals(ERROR_CODE, error.getCode()); - assertEquals(ERROR_MSG, error.getMessage()); - } - } - - private static class TestCallback3 implements BatchCallback { - - int successCalls; - int failureCalls; - - TestCallback3() {} - - @Override - public void onSuccess(Void dataClass, HttpHeaders responseHeaders) { - successCalls++; - assertNull(dataClass); - } - - @Override - public void onFailure(Void e, HttpHeaders responseHeaders) { - failureCalls++; - assertNull(e); - } - } - - /** - * Base class for callback adapters to handle error conversion. - * - * @param The input type - * @param The output type - */ - private abstract static class TestCallbackBaseAdapter - implements BatchCallback { - - protected final BatchCallback callback; - - protected TestCallbackBaseAdapter( - BatchCallback callback) { - this.callback = callback; - } - - @Override - public void onFailure(ErrorOutput.ErrorBody e, HttpHeaders responseHeaders) throws IOException { - GoogleJsonErrorContainer errorContainer = new GoogleJsonErrorContainer(); - - if (e.hasError()) { - ErrorOutput.ErrorProto errorProto = e.getError(); - - GoogleJsonError error = new GoogleJsonError(); - if (errorProto.hasCode()) { - error.setCode(errorProto.getCode()); - } - if (errorProto.hasMessage()) { - error.setMessage(errorProto.getMessage()); - } - - List errorInfos = new ArrayList(errorProto.getErrorsCount()); - for (ErrorOutput.IndividualError individualError : errorProto.getErrorsList()) { - ErrorInfo errorInfo = new ErrorInfo(); - if (individualError.hasDomain()) { - errorInfo.setDomain(individualError.getDomain()); - } - if (individualError.hasMessage()) { - errorInfo.setMessage(individualError.getMessage()); - } - if (individualError.hasReason()) { - errorInfo.setReason(individualError.getReason()); - } - errorInfos.add(errorInfo); - } - error.setErrors(errorInfos); - errorContainer.setError(error); - } - callback.onFailure(errorContainer, responseHeaders); - } - } - - private static class TestCallback1Adapter - extends TestCallbackBaseAdapter { - - public TestCallback1Adapter(TestCallback1 callback) { - super(callback); - } - - @Override - public void onSuccess(MockData.Class1 message, HttpHeaders responseHeaders) throws IOException { - MockDataClass1 dataClass = new MockDataClass1(); - dataClass.id = message.hasId() ? message.getId() : null; - dataClass.kind = message.hasKind() ? message.getKind() : null; - callback.onSuccess(dataClass, responseHeaders); - } - } - - private static class TestCallback2Adapter - extends TestCallbackBaseAdapter { - - public TestCallback2Adapter(TestCallback2 callback) { - super(callback); - } - - @Override - public void onSuccess(MockData.Class2 message, HttpHeaders responseHeaders) throws IOException { - MockDataClass2 dataClass = new MockDataClass2(); - dataClass.name = message.hasName() ? message.getName() : null; - dataClass.number = message.hasNumber() ? message.getNumber() : null; - callback.onSuccess(dataClass, responseHeaders); - } - } - - private static class MockUnsuccessfulResponseHandler implements HttpUnsuccessfulResponseHandler { - - MockTransport transport; - boolean returnSuccessAuthenticatedContent; - - MockUnsuccessfulResponseHandler( - MockTransport transport, boolean returnSuccessAuthenticatedContent) { - this.transport = transport; - this.returnSuccessAuthenticatedContent = returnSuccessAuthenticatedContent; - } - - @Override - public boolean handleResponse( - HttpRequest request, HttpResponse response, boolean supportsRetry) { - if (transport.returnErrorAuthenticatedContent) { - // If transport has already been set to return error content do not handle response. - return false; - } - if (returnSuccessAuthenticatedContent) { - transport.returnSuccessAuthenticatedContent = true; - } else { - transport.returnErrorAuthenticatedContent = true; - } - return true; - } - } - - private static class MockTransport extends MockHttpTransport { - - final boolean testServerError; - final boolean testAuthenticationError; - boolean returnSuccessAuthenticatedContent; - boolean returnErrorAuthenticatedContent; - final boolean testRedirect; - final boolean testBinary; - final boolean testMissingLength; - int actualCalls; - int callsBeforeSuccess; - - MockTransport( - boolean testServerError, - boolean testAuthenticationError, - boolean testRedirect, - boolean testBinary, - boolean testMissingLength) { - this.testServerError = testServerError; - this.testAuthenticationError = testAuthenticationError; - this.testRedirect = testRedirect; - this.testBinary = testBinary; - this.testMissingLength = testMissingLength; - } - - @Override - public LowLevelHttpRequest buildRequest(String name, String url) { - actualCalls++; - return new MockLowLevelHttpRequest() { - @Override - public LowLevelHttpResponse execute() throws IOException { - MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); - response.setStatusCode(200); - response.addHeader("Content-Type", "multipart/mixed; boundary=" + RESPONSE_BOUNDARY); - String contentType = - testBinary ? "application/x-protobuf" : "application/json; charset=UTF-8"; - byte[] content1 = - testBinary - ? MockData.Class1.newBuilder() - .setId(TEST_ID) - .setKind(TEST_KIND) - .build() - .toByteArray() - : utf8Encode( - "{\n \"id\": \"" - + TEST_ID - + "\",\n \"kind\": \"" - + TEST_KIND.replace("\n", "\\n") - + "\"\n}"); - byte[] content2 = - testBinary - ? MockData.Class2.newBuilder() - .setName(TEST_NAME) - .setNumber(TEST_NUM) - .build() - .toByteArray() - : utf8Encode( - "{\"name\": \"" + TEST_NAME + "\", \"number\": \"" + TEST_NUM + "\"}"); - byte[] errorContent = - testBinary - ? ErrorOutput.ErrorBody.newBuilder() - .setError( - ErrorOutput.ErrorProto.newBuilder() - .setCode(ERROR_CODE) - .setMessage(ERROR_MSG) - .addErrors( - ErrorOutput.IndividualError.newBuilder() - .setDomain(ERROR_DOMAIN) - .setReason(ERROR_REASON) - .setMessage(ERROR_MSG))) - .build() - .toByteArray() - : utf8Encode( - "{\"error\": { \"errors\": [{\"domain\": \"" - + ERROR_DOMAIN - + "\"," - + "\"reason\": \"" - + ERROR_REASON - + "\", \"message\": \"" - + ERROR_MSG - + "\"}]," - + "\"code\": " - + ERROR_CODE - + ", \"message\": \"" - + ERROR_MSG - + "\"}}"); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - Writer responseContent = new OutputStreamWriter(outputStream, "ISO-8859-1"); - if (returnSuccessAuthenticatedContent || (testRedirect && actualCalls > 1)) { - if (returnSuccessAuthenticatedContent || actualCalls == callsBeforeSuccess) { - responseContent - .append("--" + RESPONSE_BOUNDARY + "\n") - .append("Content-Type: application/http\n") - .append("Content-Transfer-Encoding: binary\n") - .append("Content-ID: response-1\n\n") - .append("HTTP/1.1 200 OK\n") - .append("Content-Type: " + contentType + "\n"); - if (!testMissingLength) { - responseContent.append("Content-Length: " + content2.length + "\n"); - } - responseContent.append("\n"); - responseContent.flush(); - outputStream.write(content2); - responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); - } else { - responseContent - .append("--" + RESPONSE_BOUNDARY + "\n") - .append("Content-Type: application/http\n") - .append("Content-Transfer-Encoding: binary\n") - .append("Content-ID: response-1\n\n") - .append("HTTP/1.1 " + ERROR_CODE + " Not Found\n") - .append("Content-Type: " + contentType + "\n"); - if (!testMissingLength) { - responseContent.append("Content-Length: " + errorContent.length + "\n"); - } - responseContent.append("\n"); - responseContent.flush(); - outputStream.write(errorContent); - responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); - } - } else if (returnErrorAuthenticatedContent) { - responseContent - .append("Content-Type: application/http\n") - .append("Content-Transfer-Encoding: binary\n") - .append("Content-ID: response-1\n\n"); - responseContent - .append("HTTP/1.1 " + ERROR_CODE + " Not Found\n") - .append("Content-Type: " + contentType + "\n"); - if (!testMissingLength) { - responseContent.append("Content-Length: " + errorContent.length + "\n"); - } - responseContent.append("\n"); - responseContent.flush(); - outputStream.write(errorContent); - responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); - } else { - responseContent - .append("--" + RESPONSE_BOUNDARY + "\n") - .append("Content-Type: application/http\n") - .append("Content-Transfer-Encoding: binary\n") - .append("Content-ID: response-1\n\n") - .append("HTTP/1.1 200 OK\n") - .append("Content-Type: " + contentType + "\n"); - if (!testMissingLength) { - responseContent.append("Content-Length: " + content1.length + "\n"); - } - responseContent.append("\n"); - responseContent.flush(); - outputStream.write(content1); - responseContent - .append("\n--" + RESPONSE_BOUNDARY + "\n") - .append("Content-Type: application/http\n") - .append("Content-Transfer-Encoding: binary\n") - .append("Content-ID: response-2\n\n"); - - if (testServerError) { - responseContent - .append("HTTP/1.1 " + ERROR_CODE + " Not Found\n") - .append("Content-Type: " + contentType + "\n"); - if (!testMissingLength) { - responseContent.append("Content-Length: " + errorContent.length + "\n"); - } - responseContent.append("\n"); - responseContent.flush(); - outputStream.write(errorContent); - responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); - } else if (testAuthenticationError) { - responseContent - .append("HTTP/1.1 401 Unauthorized\n") - .append("Content-Type: application/json; charset=UTF-8\n\n") - .append("--" + RESPONSE_BOUNDARY + "--\n\n"); - } else if (testRedirect && actualCalls == 1) { - responseContent - .append("HTTP/1.1 301 MovedPermanently\n") - .append("Content-Type: " + contentType + "\n") - .append("Location: http://redirect/location\n\n") - .append("--" + RESPONSE_BOUNDARY + "--\n\n"); - } else { - responseContent - .append("HTTP/1.1 200 OK\n") - .append("Content-Type: " + contentType + "\n"); - if (!testMissingLength) { - responseContent.append("Content-Length: " + content2.length + "\n"); - } - responseContent.append("\n"); - responseContent.flush(); - outputStream.write(content2); - responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); - } - } - responseContent.flush(); - response.setContent(outputStream.toByteArray()); - return response; - } - - // Short-hand to encode a String as a UTF-8 byte array - private byte[] utf8Encode(String string) { - return Charsets.UTF_8.encode(string).array(); - } - }; - } - } - - private static class MockCredential implements HttpRequestInitializer, HttpExecuteInterceptor { - - boolean initializerCalled = false; - boolean interceptorCalled = false; - - MockCredential() {} - - @Override - public void initialize(HttpRequest request) { - request.setInterceptor(this); - initializerCalled = true; - } - - @Override - public void intercept(HttpRequest request) { - interceptorCalled = true; - } - } - - private BatchRequest getBatchPopulatedWithRequests( - boolean testServerError, - boolean testAuthenticationError, - boolean returnSuccessAuthenticatedContent, - boolean testRedirect, - boolean testBinary, - boolean testMissingLength) - throws IOException { - transport = - new MockTransport( - testServerError, testAuthenticationError, testRedirect, testBinary, testMissingLength); - MockGoogleClient client = - new MockGoogleClient.Builder(transport, ROOT_URL, SERVICE_PATH, null, null) - .setApplicationName("Test Application") - .build(); - MockGoogleClientRequest jsonHttpRequest1 = - new MockGoogleClientRequest(client, METHOD1, URI_TEMPLATE1, null, String.class); - MockGoogleClientRequest jsonHttpRequest2 = - new MockGoogleClientRequest(client, METHOD2, URI_TEMPLATE2, null, String.class); - credential = new MockCredential(); - - ObjectParser parser = - testBinary ? new ProtoObjectParser() : new JsonObjectParser(new GsonFactory()); - BatchRequest batchRequest = - new BatchRequest(transport, credential).setBatchUrl(new GenericUrl(TEST_BATCH_URL)); - HttpRequest request1 = jsonHttpRequest1.buildHttpRequest(); - request1.setParser(parser); - HttpRequest request2 = jsonHttpRequest2.buildHttpRequest(); - request2.setParser(parser); - if (testAuthenticationError) { - request2.setUnsuccessfulResponseHandler( - new MockUnsuccessfulResponseHandler(transport, returnSuccessAuthenticatedContent)); - } - - if (testBinary) { - batchRequest.queue( - request1, - MockData.Class1.class, - ErrorOutput.ErrorBody.class, - new TestCallback1Adapter(callback1)); - batchRequest.queue( - request2, - MockData.Class2.class, - ErrorOutput.ErrorBody.class, - new TestCallback2Adapter(callback2)); - } else { - batchRequest.queue(request1, MockDataClass1.class, GoogleJsonErrorContainer.class, callback1); - batchRequest.queue(request2, MockDataClass2.class, GoogleJsonErrorContainer.class, callback2); - } - return batchRequest; - } - - public void testQueueDatastructures() throws Exception { - BatchRequest batchRequest = - getBatchPopulatedWithRequests(false, false, false, false, false, false); - List> requestInfos = batchRequest.requestInfos; - - // Assert that the expected objects are queued. - assertEquals(2, requestInfos.size()); - assertEquals(MockDataClass1.class, requestInfos.get(0).dataClass); - assertEquals(callback1, requestInfos.get(0).callback); - assertEquals(MockDataClass2.class, requestInfos.get(1).dataClass); - assertEquals(callback2, requestInfos.get(1).callback); - // Assert that the requests in the queue are as expected. - assertEquals( - ROOT_URL + SERVICE_PATH + URI_TEMPLATE1, requestInfos.get(0).request.getUrl().build()); - assertEquals( - ROOT_URL + SERVICE_PATH + URI_TEMPLATE2, requestInfos.get(1).request.getUrl().build()); - assertEquals(METHOD1, requestInfos.get(0).request.getRequestMethod()); - assertEquals(METHOD2, requestInfos.get(1).request.getRequestMethod()); - } - - public void testExecute() throws IOException { - BatchRequest batchRequest = - getBatchPopulatedWithRequests(false, false, false, false, false, false); - batchRequest.execute(); - // Assert callbacks have been invoked. - assertEquals(1, callback1.successCalls); - assertEquals(1, callback2.successCalls); - assertEquals(0, callback2.failureCalls); - // Assert requestInfos is empty after execute. - assertTrue(batchRequest.requestInfos.isEmpty()); - } - - public void testExecuteWithError() throws IOException { - BatchRequest batchRequest = - getBatchPopulatedWithRequests(true, false, false, false, false, false); - batchRequest.execute(); - // Assert callbacks have been invoked. - assertEquals(1, callback1.successCalls); - assertEquals(0, callback2.successCalls); - assertEquals(1, callback2.failureCalls); - // Assert requestInfos is empty after execute. - assertTrue(batchRequest.requestInfos.isEmpty()); - // Assert transport called expected number of times. - assertEquals(1, transport.actualCalls); - } - - public void testExecuteWithVoidCallback() throws Exception { - subTestExecuteWithVoidCallback(false); - // Assert callbacks have been invoked. - assertEquals(1, callback1.successCalls); - assertEquals(1, callback3.successCalls); - assertEquals(0, callback3.failureCalls); - } - - public void testExecuteWithVoidCallbackError() throws Exception { - subTestExecuteWithVoidCallback(true); - // Assert callbacks have been invoked. - assertEquals(1, callback1.successCalls); - assertEquals(0, callback3.successCalls); - assertEquals(1, callback3.failureCalls); - } - - public void subTestExecuteWithVoidCallback(boolean testServerError) throws IOException { - MockTransport transport = new MockTransport(testServerError, false, false, false, false); - MockGoogleClient client = - new MockGoogleClient.Builder(transport, ROOT_URL, SERVICE_PATH, null, null) - .setApplicationName("Test Application") - .build(); - MockGoogleClientRequest jsonHttpRequest1 = - new MockGoogleClientRequest(client, METHOD1, URI_TEMPLATE1, null, String.class); - MockGoogleClientRequest jsonHttpRequest2 = - new MockGoogleClientRequest(client, METHOD2, URI_TEMPLATE2, null, String.class); - ObjectParser parser = new JsonObjectParser(new GsonFactory()); - BatchRequest batchRequest = - new BatchRequest(transport, null).setBatchUrl(new GenericUrl(TEST_BATCH_URL)); - HttpRequest request1 = jsonHttpRequest1.buildHttpRequest(); - request1.setParser(parser); - HttpRequest request2 = jsonHttpRequest2.buildHttpRequest(); - request2.setParser(parser); - batchRequest.queue(request1, MockDataClass1.class, GoogleJsonErrorContainer.class, callback1); - batchRequest.queue(request2, Void.class, Void.class, callback3); - batchRequest.execute(); - // Assert transport called expected number of times. - assertEquals(1, transport.actualCalls); - } - - public void testExecuteWithAuthenticationErrorThenSuccessCallback() throws Exception { - BatchRequest batchRequest = - getBatchPopulatedWithRequests(false, true, true, false, false, false); - batchRequest.execute(); - // Assert callbacks have been invoked. - assertEquals(1, callback1.successCalls); - assertEquals(1, callback2.successCalls); - assertEquals(0, callback2.failureCalls); - // Assert transport called expected number of times. - assertEquals(2, transport.actualCalls); - // Assert requestInfos is empty after execute. - assertTrue(batchRequest.requestInfos.isEmpty()); - } - - public void testExecuteWithAuthenticationErrorThenErrorCallback() throws Exception { - BatchRequest batchRequest = - getBatchPopulatedWithRequests(false, true, false, false, false, false); - batchRequest.execute(); - // Assert callbacks have been invoked. - assertEquals(1, callback1.successCalls); - assertEquals(0, callback2.successCalls); - assertEquals(1, callback2.failureCalls); - // Assert transport called expected number of times. - assertEquals(2, transport.actualCalls); - // Assert requestInfos is empty after execute. - assertTrue(batchRequest.requestInfos.isEmpty()); - } - - public void testInterceptor() throws Exception { - BatchRequest batchRequest = - getBatchPopulatedWithRequests(true, false, false, false, false, false); - batchRequest.execute(); - // Assert the top-level request initializer is called. - assertTrue(credential.initializerCalled); - assertTrue(credential.interceptorCalled); - } - - public void testRedirect() throws Exception { - BatchRequest batchRequest = - getBatchPopulatedWithRequests(false, false, false, true, false, false); - transport.callsBeforeSuccess = 2; - batchRequest.execute(); - // Assert transport called expected number of times. - assertEquals(2, transport.actualCalls); - // Assert requestInfos is empty after execute. - assertTrue(batchRequest.requestInfos.isEmpty()); - } - - public void testExecute_checkWriteTo() throws Exception { - String request1Method = HttpMethods.POST; - String request1Url = "http://test/dummy/url1"; - String request1ContentType = "application/json"; - String request1Content = "{\"data\":{\"foo\":{\"v1\":{}}}}"; - - String request2Method = HttpMethods.GET; - String request2Url = "http://test/dummy/url2"; - - // MIME content boundaries are not reproducible. - StringBuilder part1 = new StringBuilder(); - part1.append("Content-Length: 118\r\n"); - part1.append("Content-Type: application/http\r\n"); - part1.append("content-id: 1\r\n"); - part1.append("content-transfer-encoding: binary\r\n"); - part1.append("\r\n"); - part1.append("POST http://test/dummy/url1 HTTP/1.1\r\n"); - part1.append("Content-Length: 26\r\n"); - part1.append("Content-Type: " + request1ContentType + "\r\n"); - part1.append("\r\n"); - part1.append(request1Content + "\r\n"); - part1.append("--__END_OF_PART__"); - String expected1 = part1.toString(); - - StringBuilder part2 = new StringBuilder(); - part2.append("Content-Length: 39\r\n"); - part2.append("Content-Type: application/http\r\n"); - part2.append("content-id: 2\r\n"); - part2.append("content-transfer-encoding: binary\r\n"); - part2.append("\r\n"); - part2.append("GET http://test/dummy/url2 HTTP/1.1\r\n"); - part2.append("\r\n"); - part2.append("\r\n"); - part2.append("--__END_OF_PART__"); - String expected2 = part2.toString(); - - MockHttpTransport transport = new MockHttpTransport(); - HttpRequest request1 = - transport - .createRequestFactory() - .buildRequest( - request1Method, - new GenericUrl(request1Url), - new ByteArrayContent(request1ContentType, request1Content.getBytes(UTF_8))); - HttpRequest request2 = - transport - .createRequestFactory() - .buildRequest(request2Method, new GenericUrl(request2Url), null); - subtestExecute_checkWriteTo(expected1, expected2, request1, request2); - } - - private void subtestExecute_checkWriteTo( - final String part1, final String part2, HttpRequest... requests) throws IOException { - - MockHttpTransport transport = - new MockHttpTransport() { - - @Override - public LowLevelHttpRequest buildRequest(String method, String url) { - return new MockLowLevelHttpRequest(url) { - - @Override - public LowLevelHttpResponse execute() throws IOException { - assertTrue( - getContentType().startsWith("multipart/mixed; boundary=__END_OF_PART__")); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - getStreamingContent().writeTo(out); - String actual = out.toString("UTF-8"); - assertTrue(actual + "\n does not contain \n" + part1, actual.contains(part1)); - assertTrue(actual.contains(part2)); - MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); - response.setStatusCode(200); - response.addHeader( - "Content-Type", "multipart/mixed; boundary=" + RESPONSE_BOUNDARY); - String content2 = - "{\"name\": \"" + TEST_NAME + "\", \"number\": \"" + TEST_NUM + "\"}"; - StringBuilder responseContent = new StringBuilder(); - responseContent - .append("--" + RESPONSE_BOUNDARY + "\n") - .append("Content-Type: application/http\n") - .append("Content-Transfer-Encoding: binary\n") - .append("Content-ID: response-1\n\n") - .append("HTTP/1.1 200 OK\n") - .append("Content-Type: application/json; charset=UTF-8\n") - .append("Content-Length: " + content2.length() + "\n\n") - .append(content2 + "\n\n") - .append("--" + RESPONSE_BOUNDARY + "--\n\n"); - response.setContent(responseContent.toString()); - return response; - } - }; - } - }; - - BatchRequest batchRequest = new BatchRequest(transport, null); - BatchCallback callback = - new BatchCallback() { - - @Override - public void onSuccess(Void t, HttpHeaders responseHeaders) {} - - @Override - public void onFailure(Void e, HttpHeaders responseHeaders) {} - }; - for (HttpRequest request : requests) { - batchRequest.queue(request, Void.class, Void.class, callback); - } - batchRequest.execute(); - } - - public void testExecute_checkWriteToNoHeaders() throws IOException { - MockHttpTransport transport = new MockHttpTransport(); - HttpRequest request = - transport - .createRequestFactory() - .buildPostRequest( - HttpTesting.SIMPLE_GENERIC_URL, - new HttpContent() { - - @Override - public long getLength() { - return -1; - } - - @Override - public String getType() { - return null; - } - - @Override - public void writeTo(OutputStream out) {} - - @Override - public boolean retrySupported() { - return true; - } - }); - String expected = - new StringBuilder() - .append("Content-Length: 36\r\n") - .append("Content-Type: application/http\r\n") - .append("content-id: 1\r\n") - .append("content-transfer-encoding: binary\r\n") - .append("\r\n") - .append("POST http://google.com/ HTTP/1.1\r\n") - .append("\r\n") - .append("\r\n") - .append("--__END_OF_PART__") - .toString(); - subtestExecute_checkWriteTo(expected, expected, request); - } - - public void testProtoExecute() throws IOException { - BatchRequest batchRequest = - getBatchPopulatedWithRequests(false, false, false, false, true, false); - batchRequest.execute(); - // Assert callbacks have been invoked. - assertEquals(1, callback1.successCalls); - assertEquals(1, callback2.successCalls); - assertEquals(0, callback2.failureCalls); - // Assert requestInfos is empty after execute. - assertTrue(batchRequest.requestInfos.isEmpty()); - } - - public void testProtoExecuteWithError() throws IOException { - BatchRequest batchRequest = - getBatchPopulatedWithRequests(true, false, false, false, true, false); - batchRequest.execute(); - // Assert callbacks have been invoked. - assertEquals(1, callback1.successCalls); - assertEquals(0, callback2.successCalls); - assertEquals(1, callback2.failureCalls); - // Assert requestInfos is empty after execute. - assertTrue(batchRequest.requestInfos.isEmpty()); - // Assert transport called expected number of times. - assertEquals(1, transport.actualCalls); - } - - public void testProtoExecuteWithoutLength() throws IOException { - BatchRequest batchRequest = - getBatchPopulatedWithRequests(false, false, false, false, true, true); - batchRequest.execute(); - // Assert callbacks have been invoked. - assertEquals(1, callback1.successCalls); - assertEquals(1, callback2.successCalls); - assertEquals(0, callback2.failureCalls); - // Assert requestInfos is empty after execute. - assertTrue(batchRequest.requestInfos.isEmpty()); - } -} +//// Copyright 2012 Google Inc. All Rights Reserved. +// +// package com.google.api.client.googleapis.batch; +// +// import static java.nio.charset.StandardCharsets.UTF_8; +// +// import com.google.api.client.googleapis.batch.BatchRequest.RequestInfo; +// import com.google.api.client.googleapis.json.GoogleJsonError; +// import com.google.api.client.googleapis.json.GoogleJsonError.ErrorInfo; +// import com.google.api.client.googleapis.json.GoogleJsonErrorContainer; +// import com.google.api.client.googleapis.testing.services.MockGoogleClient; +// import com.google.api.client.googleapis.testing.services.MockGoogleClientRequest; +// import com.google.api.client.http.ByteArrayContent; +// import com.google.api.client.http.GenericUrl; +// import com.google.api.client.http.HttpContent; +// import com.google.api.client.http.HttpExecuteInterceptor; +// import com.google.api.client.http.HttpHeaders; +// import com.google.api.client.http.HttpMethods; +// import com.google.api.client.http.HttpRequest; +// import com.google.api.client.http.HttpRequestInitializer; +// import com.google.api.client.http.HttpResponse; +// import com.google.api.client.http.HttpUnsuccessfulResponseHandler; +// import com.google.api.client.http.LowLevelHttpRequest; +// import com.google.api.client.http.LowLevelHttpResponse; +// import com.google.api.client.json.GenericJson; +// import com.google.api.client.json.JsonObjectParser; +// import com.google.api.client.json.gson.GsonFactory; +// import com.google.api.client.protobuf.ProtoObjectParser; +// import com.google.api.client.testing.http.HttpTesting; +// import com.google.api.client.testing.http.MockHttpTransport; +// import com.google.api.client.testing.http.MockLowLevelHttpRequest; +// import com.google.api.client.testing.http.MockLowLevelHttpResponse; +// import com.google.api.client.util.Charsets; +// import com.google.api.client.util.Key; +// import com.google.api.client.util.ObjectParser; +// import java.io.ByteArrayOutputStream; +// import java.io.IOException; +// import java.io.OutputStream; +// import java.io.OutputStreamWriter; +// import java.io.Writer; +// import java.util.ArrayList; +// import java.util.List; +// import junit.framework.TestCase; +// +/// ** +// * Tests {@link BatchRequest}. +// * +// * @author rmistry@google.com (Ravi Mistry) +// */ +// public class BatchRequestTest extends TestCase { +// +// private static final String ROOT_URL = "http://www.test.com/"; +// private static final String SERVICE_PATH = "test/"; +// private static final String TEST_BATCH_URL = "http://www.testgoogleapis.com/batch"; +// private static final String URI_TEMPLATE1 = "uri/template/1"; +// private static final String URI_TEMPLATE2 = "uri/template/2"; +// private static final String METHOD1 = HttpMethods.GET; +// private static final String METHOD2 = HttpMethods.POST; +// private static final String ERROR_MSG = "Error message"; +// private static final String ERROR_REASON = "notFound"; +// private static final int ERROR_CODE = 503; +// private static final String ERROR_DOMAIN = "global"; +// private static final String RESPONSE_BOUNDARY = "ABC=DE=F"; +// private static final String TEST_ID = "Humpty Dumpty"; +// private static final String TEST_KIND = "Big\nEgg\n"; // Newlines help test boundary detection +// private static final String TEST_NAME = "James Bond"; +// private static final String TEST_NUM = "007"; +// +// private TestCallback1 callback1; +// private TestCallback2 callback2; +// private TestCallback3 callback3; +// +// private MockTransport transport; +// +// private MockCredential credential; +// +// @Override +// protected void setUp() { +// callback1 = new TestCallback1(); +// callback2 = new TestCallback2(); +// callback3 = new TestCallback3(); +// } +// +// public static class MockDataClass1 extends GenericJson { +// @Key String id; +// +// @Key String kind; +// } +// +// public static class MockDataClass2 extends GenericJson { +// @Key String name; +// +// @Key String number; +// } +// +// private static class TestCallback1 +// implements BatchCallback { +// +// int successCalls; +// +// TestCallback1() {} +// +// @Override +// public void onSuccess(MockDataClass1 dataClass, HttpHeaders responseHeaders) { +// successCalls++; +// assertEquals(TEST_ID, dataClass.id); +// assertEquals(TEST_KIND, dataClass.kind); +// } +// +// @Override +// public void onFailure(GoogleJsonErrorContainer e, HttpHeaders responseHeaders) { +// fail("Should not be invoked in this test"); +// } +// } +// +// private static class TestCallback2 +// implements BatchCallback { +// +// int successCalls; +// int failureCalls; +// +// TestCallback2() {} +// +// @Override +// public void onSuccess(MockDataClass2 dataClass, HttpHeaders responseHeaders) { +// successCalls++; +// assertEquals(TEST_NAME, dataClass.name); +// assertEquals(TEST_NUM, dataClass.number); +// } +// +// @Override +// public void onFailure(GoogleJsonErrorContainer e, HttpHeaders responseHeaders) { +// failureCalls++; +// GoogleJsonError error = e.getError(); +// ErrorInfo errorInfo = error.getErrors().get(0); +// assertEquals(ERROR_DOMAIN, errorInfo.getDomain()); +// assertEquals(ERROR_REASON, errorInfo.getReason()); +// assertEquals(ERROR_MSG, errorInfo.getMessage()); +// assertEquals(ERROR_CODE, error.getCode()); +// assertEquals(ERROR_MSG, error.getMessage()); +// } +// } +// +// private static class TestCallback3 implements BatchCallback { +// +// int successCalls; +// int failureCalls; +// +// TestCallback3() {} +// +// @Override +// public void onSuccess(Void dataClass, HttpHeaders responseHeaders) { +// successCalls++; +// assertNull(dataClass); +// } +// +// @Override +// public void onFailure(Void e, HttpHeaders responseHeaders) { +// failureCalls++; +// assertNull(e); +// } +// } +// +// /** +// * Base class for callback adapters to handle error conversion. +// * +// * @param The input type +// * @param The output type +// */ +// private abstract static class TestCallbackBaseAdapter +// implements BatchCallback { +// +// protected final BatchCallback callback; +// +// protected TestCallbackBaseAdapter( +// BatchCallback callback) { +// this.callback = callback; +// } +// +// @Override +// public void onFailure(ErrorOutput.ErrorBody e, HttpHeaders responseHeaders) throws IOException +// { +// GoogleJsonErrorContainer errorContainer = new GoogleJsonErrorContainer(); +// +// if (e.hasError()) { +// ErrorOutput.ErrorProto errorProto = e.getError(); +// +// GoogleJsonError error = new GoogleJsonError(); +// if (errorProto.hasCode()) { +// error.setCode(errorProto.getCode()); +// } +// if (errorProto.hasMessage()) { +// error.setMessage(errorProto.getMessage()); +// } +// +// List errorInfos = new ArrayList(errorProto.getErrorsCount()); +// for (ErrorOutput.IndividualError individualError : errorProto.getErrorsList()) { +// ErrorInfo errorInfo = new ErrorInfo(); +// if (individualError.hasDomain()) { +// errorInfo.setDomain(individualError.getDomain()); +// } +// if (individualError.hasMessage()) { +// errorInfo.setMessage(individualError.getMessage()); +// } +// if (individualError.hasReason()) { +// errorInfo.setReason(individualError.getReason()); +// } +// errorInfos.add(errorInfo); +// } +// error.setErrors(errorInfos); +// errorContainer.setError(error); +// } +// callback.onFailure(errorContainer, responseHeaders); +// } +// } +// +// private static class TestCallback1Adapter +// extends TestCallbackBaseAdapter { +// +// public TestCallback1Adapter(TestCallback1 callback) { +// super(callback); +// } +// +// @Override +// public void onSuccess(MockData.Class1 message, HttpHeaders responseHeaders) throws IOException +// { +// MockDataClass1 dataClass = new MockDataClass1(); +// dataClass.id = message.hasId() ? message.getId() : null; +// dataClass.kind = message.hasKind() ? message.getKind() : null; +// callback.onSuccess(dataClass, responseHeaders); +// } +// } +// +// private static class TestCallback2Adapter +// extends TestCallbackBaseAdapter { +// +// public TestCallback2Adapter(TestCallback2 callback) { +// super(callback); +// } +// +// @Override +// public void onSuccess(MockData.Class2 message, HttpHeaders responseHeaders) throws IOException +// { +// MockDataClass2 dataClass = new MockDataClass2(); +// dataClass.name = message.hasName() ? message.getName() : null; +// dataClass.number = message.hasNumber() ? message.getNumber() : null; +// callback.onSuccess(dataClass, responseHeaders); +// } +// } +// +// private static class MockUnsuccessfulResponseHandler implements HttpUnsuccessfulResponseHandler +// { +// +// MockTransport transport; +// boolean returnSuccessAuthenticatedContent; +// +// MockUnsuccessfulResponseHandler( +// MockTransport transport, boolean returnSuccessAuthenticatedContent) { +// this.transport = transport; +// this.returnSuccessAuthenticatedContent = returnSuccessAuthenticatedContent; +// } +// +// @Override +// public boolean handleResponse( +// HttpRequest request, HttpResponse response, boolean supportsRetry) { +// if (transport.returnErrorAuthenticatedContent) { +// // If transport has already been set to return error content do not handle response. +// return false; +// } +// if (returnSuccessAuthenticatedContent) { +// transport.returnSuccessAuthenticatedContent = true; +// } else { +// transport.returnErrorAuthenticatedContent = true; +// } +// return true; +// } +// } +// +// private static class MockTransport extends MockHttpTransport { +// +// final boolean testServerError; +// final boolean testAuthenticationError; +// boolean returnSuccessAuthenticatedContent; +// boolean returnErrorAuthenticatedContent; +// final boolean testRedirect; +// final boolean testBinary; +// final boolean testMissingLength; +// int actualCalls; +// int callsBeforeSuccess; +// +// MockTransport( +// boolean testServerError, +// boolean testAuthenticationError, +// boolean testRedirect, +// boolean testBinary, +// boolean testMissingLength) { +// this.testServerError = testServerError; +// this.testAuthenticationError = testAuthenticationError; +// this.testRedirect = testRedirect; +// this.testBinary = testBinary; +// this.testMissingLength = testMissingLength; +// } +// +// @Override +// public LowLevelHttpRequest buildRequest(String name, String url) { +// actualCalls++; +// return new MockLowLevelHttpRequest() { +// @Override +// public LowLevelHttpResponse execute() throws IOException { +// MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); +// response.setStatusCode(200); +// response.addHeader("Content-Type", "multipart/mixed; boundary=" + RESPONSE_BOUNDARY); +// String contentType = +// testBinary ? "application/x-protobuf" : "application/json; charset=UTF-8"; +// byte[] content1 = +// testBinary +// ? MockData.Class1.newBuilder() +// .setId(TEST_ID) +// .setKind(TEST_KIND) +// .build() +// .toByteArray() +// : utf8Encode( +// "{\n \"id\": \"" +// + TEST_ID +// + "\",\n \"kind\": \"" +// + TEST_KIND.replace("\n", "\\n") +// + "\"\n}"); +// byte[] content2 = +// testBinary +// ? MockData.Class2.newBuilder() +// .setName(TEST_NAME) +// .setNumber(TEST_NUM) +// .build() +// .toByteArray() +// : utf8Encode( +// "{\"name\": \"" + TEST_NAME + "\", \"number\": \"" + TEST_NUM + "\"}"); +// byte[] errorContent = +// testBinary +// ? ErrorOutput.ErrorBody.newBuilder() +// .setError( +// ErrorOutput.ErrorProto.newBuilder() +// .setCode(ERROR_CODE) +// .setMessage(ERROR_MSG) +// .addErrors( +// ErrorOutput.IndividualError.newBuilder() +// .setDomain(ERROR_DOMAIN) +// .setReason(ERROR_REASON) +// .setMessage(ERROR_MSG))) +// .build() +// .toByteArray() +// : utf8Encode( +// "{\"error\": { \"errors\": [{\"domain\": \"" +// + ERROR_DOMAIN +// + "\"," +// + "\"reason\": \"" +// + ERROR_REASON +// + "\", \"message\": \"" +// + ERROR_MSG +// + "\"}]," +// + "\"code\": " +// + ERROR_CODE +// + ", \"message\": \"" +// + ERROR_MSG +// + "\"}}"); +// ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); +// Writer responseContent = new OutputStreamWriter(outputStream, "ISO-8859-1"); +// if (returnSuccessAuthenticatedContent || (testRedirect && actualCalls > 1)) { +// if (returnSuccessAuthenticatedContent || actualCalls == callsBeforeSuccess) { +// responseContent +// .append("--" + RESPONSE_BOUNDARY + "\n") +// .append("Content-Type: application/http\n") +// .append("Content-Transfer-Encoding: binary\n") +// .append("Content-ID: response-1\n\n") +// .append("HTTP/1.1 200 OK\n") +// .append("Content-Type: " + contentType + "\n"); +// if (!testMissingLength) { +// responseContent.append("Content-Length: " + content2.length + "\n"); +// } +// responseContent.append("\n"); +// responseContent.flush(); +// outputStream.write(content2); +// responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); +// } else { +// responseContent +// .append("--" + RESPONSE_BOUNDARY + "\n") +// .append("Content-Type: application/http\n") +// .append("Content-Transfer-Encoding: binary\n") +// .append("Content-ID: response-1\n\n") +// .append("HTTP/1.1 " + ERROR_CODE + " Not Found\n") +// .append("Content-Type: " + contentType + "\n"); +// if (!testMissingLength) { +// responseContent.append("Content-Length: " + errorContent.length + "\n"); +// } +// responseContent.append("\n"); +// responseContent.flush(); +// outputStream.write(errorContent); +// responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); +// } +// } else if (returnErrorAuthenticatedContent) { +// responseContent +// .append("Content-Type: application/http\n") +// .append("Content-Transfer-Encoding: binary\n") +// .append("Content-ID: response-1\n\n"); +// responseContent +// .append("HTTP/1.1 " + ERROR_CODE + " Not Found\n") +// .append("Content-Type: " + contentType + "\n"); +// if (!testMissingLength) { +// responseContent.append("Content-Length: " + errorContent.length + "\n"); +// } +// responseContent.append("\n"); +// responseContent.flush(); +// outputStream.write(errorContent); +// responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); +// } else { +// responseContent +// .append("--" + RESPONSE_BOUNDARY + "\n") +// .append("Content-Type: application/http\n") +// .append("Content-Transfer-Encoding: binary\n") +// .append("Content-ID: response-1\n\n") +// .append("HTTP/1.1 200 OK\n") +// .append("Content-Type: " + contentType + "\n"); +// if (!testMissingLength) { +// responseContent.append("Content-Length: " + content1.length + "\n"); +// } +// responseContent.append("\n"); +// responseContent.flush(); +// outputStream.write(content1); +// responseContent +// .append("\n--" + RESPONSE_BOUNDARY + "\n") +// .append("Content-Type: application/http\n") +// .append("Content-Transfer-Encoding: binary\n") +// .append("Content-ID: response-2\n\n"); +// +// if (testServerError) { +// responseContent +// .append("HTTP/1.1 " + ERROR_CODE + " Not Found\n") +// .append("Content-Type: " + contentType + "\n"); +// if (!testMissingLength) { +// responseContent.append("Content-Length: " + errorContent.length + "\n"); +// } +// responseContent.append("\n"); +// responseContent.flush(); +// outputStream.write(errorContent); +// responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); +// } else if (testAuthenticationError) { +// responseContent +// .append("HTTP/1.1 401 Unauthorized\n") +// .append("Content-Type: application/json; charset=UTF-8\n\n") +// .append("--" + RESPONSE_BOUNDARY + "--\n\n"); +// } else if (testRedirect && actualCalls == 1) { +// responseContent +// .append("HTTP/1.1 301 MovedPermanently\n") +// .append("Content-Type: " + contentType + "\n") +// .append("Location: http://redirect/location\n\n") +// .append("--" + RESPONSE_BOUNDARY + "--\n\n"); +// } else { +// responseContent +// .append("HTTP/1.1 200 OK\n") +// .append("Content-Type: " + contentType + "\n"); +// if (!testMissingLength) { +// responseContent.append("Content-Length: " + content2.length + "\n"); +// } +// responseContent.append("\n"); +// responseContent.flush(); +// outputStream.write(content2); +// responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); +// } +// } +// responseContent.flush(); +// response.setContent(outputStream.toByteArray()); +// return response; +// } +// +// // Short-hand to encode a String as a UTF-8 byte array +// private byte[] utf8Encode(String string) { +// return Charsets.UTF_8.encode(string).array(); +// } +// }; +// } +// } +// +// private static class MockCredential implements HttpRequestInitializer, HttpExecuteInterceptor { +// +// boolean initializerCalled = false; +// boolean interceptorCalled = false; +// +// MockCredential() {} +// +// @Override +// public void initialize(HttpRequest request) { +// request.setInterceptor(this); +// initializerCalled = true; +// } +// +// @Override +// public void intercept(HttpRequest request) { +// interceptorCalled = true; +// } +// } +// +// private BatchRequest getBatchPopulatedWithRequests( +// boolean testServerError, +// boolean testAuthenticationError, +// boolean returnSuccessAuthenticatedContent, +// boolean testRedirect, +// boolean testBinary, +// boolean testMissingLength) +// throws IOException { +// transport = +// new MockTransport( +// testServerError, testAuthenticationError, testRedirect, testBinary, +// testMissingLength); +// MockGoogleClient client = +// new MockGoogleClient.Builder(transport, ROOT_URL, SERVICE_PATH, null, null) +// .setApplicationName("Test Application") +// .build(); +// MockGoogleClientRequest jsonHttpRequest1 = +// new MockGoogleClientRequest(client, METHOD1, URI_TEMPLATE1, null, String.class); +// MockGoogleClientRequest jsonHttpRequest2 = +// new MockGoogleClientRequest(client, METHOD2, URI_TEMPLATE2, null, String.class); +// credential = new MockCredential(); +// +// ObjectParser parser = +// testBinary ? new ProtoObjectParser() : new JsonObjectParser(new GsonFactory()); +// BatchRequest batchRequest = +// new BatchRequest(transport, credential).setBatchUrl(new GenericUrl(TEST_BATCH_URL)); +// HttpRequest request1 = jsonHttpRequest1.buildHttpRequest(); +// request1.setParser(parser); +// HttpRequest request2 = jsonHttpRequest2.buildHttpRequest(); +// request2.setParser(parser); +// if (testAuthenticationError) { +// request2.setUnsuccessfulResponseHandler( +// new MockUnsuccessfulResponseHandler(transport, returnSuccessAuthenticatedContent)); +// } +// +// if (testBinary) { +// batchRequest.queue( +// request1, +// MockData.Class1.class, +// ErrorOutput.ErrorBody.class, +// new TestCallback1Adapter(callback1)); +// batchRequest.queue( +// request2, +// MockData.Class2.class, +// ErrorOutput.ErrorBody.class, +// new TestCallback2Adapter(callback2)); +// } else { +// batchRequest.queue(request1, MockDataClass1.class, GoogleJsonErrorContainer.class, +// callback1); +// batchRequest.queue(request2, MockDataClass2.class, GoogleJsonErrorContainer.class, +// callback2); +// } +// return batchRequest; +// } +// +// public void testQueueDatastructures() throws Exception { +// BatchRequest batchRequest = +// getBatchPopulatedWithRequests(false, false, false, false, false, false); +// List> requestInfos = batchRequest.requestInfos; +// +// // Assert that the expected objects are queued. +// assertEquals(2, requestInfos.size()); +// assertEquals(MockDataClass1.class, requestInfos.get(0).dataClass); +// assertEquals(callback1, requestInfos.get(0).callback); +// assertEquals(MockDataClass2.class, requestInfos.get(1).dataClass); +// assertEquals(callback2, requestInfos.get(1).callback); +// // Assert that the requests in the queue are as expected. +// assertEquals( +// ROOT_URL + SERVICE_PATH + URI_TEMPLATE1, requestInfos.get(0).request.getUrl().build()); +// assertEquals( +// ROOT_URL + SERVICE_PATH + URI_TEMPLATE2, requestInfos.get(1).request.getUrl().build()); +// assertEquals(METHOD1, requestInfos.get(0).request.getRequestMethod()); +// assertEquals(METHOD2, requestInfos.get(1).request.getRequestMethod()); +// } +// +// public void testExecute() throws IOException { +// BatchRequest batchRequest = +// getBatchPopulatedWithRequests(false, false, false, false, false, false); +// batchRequest.execute(); +// // Assert callbacks have been invoked. +// assertEquals(1, callback1.successCalls); +// assertEquals(1, callback2.successCalls); +// assertEquals(0, callback2.failureCalls); +// // Assert requestInfos is empty after execute. +// assertTrue(batchRequest.requestInfos.isEmpty()); +// } +// +// public void testExecuteWithError() throws IOException { +// BatchRequest batchRequest = +// getBatchPopulatedWithRequests(true, false, false, false, false, false); +// batchRequest.execute(); +// // Assert callbacks have been invoked. +// assertEquals(1, callback1.successCalls); +// assertEquals(0, callback2.successCalls); +// assertEquals(1, callback2.failureCalls); +// // Assert requestInfos is empty after execute. +// assertTrue(batchRequest.requestInfos.isEmpty()); +// // Assert transport called expected number of times. +// assertEquals(1, transport.actualCalls); +// } +// +// public void testExecuteWithVoidCallback() throws Exception { +// subTestExecuteWithVoidCallback(false); +// // Assert callbacks have been invoked. +// assertEquals(1, callback1.successCalls); +// assertEquals(1, callback3.successCalls); +// assertEquals(0, callback3.failureCalls); +// } +// +// public void testExecuteWithVoidCallbackError() throws Exception { +// subTestExecuteWithVoidCallback(true); +// // Assert callbacks have been invoked. +// assertEquals(1, callback1.successCalls); +// assertEquals(0, callback3.successCalls); +// assertEquals(1, callback3.failureCalls); +// } +// +// public void subTestExecuteWithVoidCallback(boolean testServerError) throws IOException { +// MockTransport transport = new MockTransport(testServerError, false, false, false, false); +// MockGoogleClient client = +// new MockGoogleClient.Builder(transport, ROOT_URL, SERVICE_PATH, null, null) +// .setApplicationName("Test Application") +// .build(); +// MockGoogleClientRequest jsonHttpRequest1 = +// new MockGoogleClientRequest(client, METHOD1, URI_TEMPLATE1, null, String.class); +// MockGoogleClientRequest jsonHttpRequest2 = +// new MockGoogleClientRequest(client, METHOD2, URI_TEMPLATE2, null, String.class); +// ObjectParser parser = new JsonObjectParser(new GsonFactory()); +// BatchRequest batchRequest = +// new BatchRequest(transport, null).setBatchUrl(new GenericUrl(TEST_BATCH_URL)); +// HttpRequest request1 = jsonHttpRequest1.buildHttpRequest(); +// request1.setParser(parser); +// HttpRequest request2 = jsonHttpRequest2.buildHttpRequest(); +// request2.setParser(parser); +// batchRequest.queue(request1, MockDataClass1.class, GoogleJsonErrorContainer.class, callback1); +// batchRequest.queue(request2, Void.class, Void.class, callback3); +// batchRequest.execute(); +// // Assert transport called expected number of times. +// assertEquals(1, transport.actualCalls); +// } +// +// public void testExecuteWithAuthenticationErrorThenSuccessCallback() throws Exception { +// BatchRequest batchRequest = +// getBatchPopulatedWithRequests(false, true, true, false, false, false); +// batchRequest.execute(); +// // Assert callbacks have been invoked. +// assertEquals(1, callback1.successCalls); +// assertEquals(1, callback2.successCalls); +// assertEquals(0, callback2.failureCalls); +// // Assert transport called expected number of times. +// assertEquals(2, transport.actualCalls); +// // Assert requestInfos is empty after execute. +// assertTrue(batchRequest.requestInfos.isEmpty()); +// } +// +// public void testExecuteWithAuthenticationErrorThenErrorCallback() throws Exception { +// BatchRequest batchRequest = +// getBatchPopulatedWithRequests(false, true, false, false, false, false); +// batchRequest.execute(); +// // Assert callbacks have been invoked. +// assertEquals(1, callback1.successCalls); +// assertEquals(0, callback2.successCalls); +// assertEquals(1, callback2.failureCalls); +// // Assert transport called expected number of times. +// assertEquals(2, transport.actualCalls); +// // Assert requestInfos is empty after execute. +// assertTrue(batchRequest.requestInfos.isEmpty()); +// } +// +// public void testInterceptor() throws Exception { +// BatchRequest batchRequest = +// getBatchPopulatedWithRequests(true, false, false, false, false, false); +// batchRequest.execute(); +// // Assert the top-level request initializer is called. +// assertTrue(credential.initializerCalled); +// assertTrue(credential.interceptorCalled); +// } +// +// public void testRedirect() throws Exception { +// BatchRequest batchRequest = +// getBatchPopulatedWithRequests(false, false, false, true, false, false); +// transport.callsBeforeSuccess = 2; +// batchRequest.execute(); +// // Assert transport called expected number of times. +// assertEquals(2, transport.actualCalls); +// // Assert requestInfos is empty after execute. +// assertTrue(batchRequest.requestInfos.isEmpty()); +// } +// +// public void testExecute_checkWriteTo() throws Exception { +// String request1Method = HttpMethods.POST; +// String request1Url = "http://test/dummy/url1"; +// String request1ContentType = "application/json"; +// String request1Content = "{\"data\":{\"foo\":{\"v1\":{}}}}"; +// +// String request2Method = HttpMethods.GET; +// String request2Url = "http://test/dummy/url2"; +// +// // MIME content boundaries are not reproducible. +// StringBuilder part1 = new StringBuilder(); +// part1.append("Content-Length: 118\r\n"); +// part1.append("Content-Type: application/http\r\n"); +// part1.append("content-id: 1\r\n"); +// part1.append("content-transfer-encoding: binary\r\n"); +// part1.append("\r\n"); +// part1.append("POST http://test/dummy/url1 HTTP/1.1\r\n"); +// part1.append("Content-Length: 26\r\n"); +// part1.append("Content-Type: " + request1ContentType + "\r\n"); +// part1.append("\r\n"); +// part1.append(request1Content + "\r\n"); +// part1.append("--__END_OF_PART__"); +// String expected1 = part1.toString(); +// +// StringBuilder part2 = new StringBuilder(); +// part2.append("Content-Length: 39\r\n"); +// part2.append("Content-Type: application/http\r\n"); +// part2.append("content-id: 2\r\n"); +// part2.append("content-transfer-encoding: binary\r\n"); +// part2.append("\r\n"); +// part2.append("GET http://test/dummy/url2 HTTP/1.1\r\n"); +// part2.append("\r\n"); +// part2.append("\r\n"); +// part2.append("--__END_OF_PART__"); +// String expected2 = part2.toString(); +// +// MockHttpTransport transport = new MockHttpTransport(); +// HttpRequest request1 = +// transport +// .createRequestFactory() +// .buildRequest( +// request1Method, +// new GenericUrl(request1Url), +// new ByteArrayContent(request1ContentType, request1Content.getBytes(UTF_8))); +// HttpRequest request2 = +// transport +// .createRequestFactory() +// .buildRequest(request2Method, new GenericUrl(request2Url), null); +// subtestExecute_checkWriteTo(expected1, expected2, request1, request2); +// } +// +// private void subtestExecute_checkWriteTo( +// final String part1, final String part2, HttpRequest... requests) throws IOException { +// +// MockHttpTransport transport = +// new MockHttpTransport() { +// +// @Override +// public LowLevelHttpRequest buildRequest(String method, String url) { +// return new MockLowLevelHttpRequest(url) { +// +// @Override +// public LowLevelHttpResponse execute() throws IOException { +// assertTrue( +// getContentType().startsWith("multipart/mixed; boundary=__END_OF_PART__")); +// ByteArrayOutputStream out = new ByteArrayOutputStream(); +// getStreamingContent().writeTo(out); +// String actual = out.toString("UTF-8"); +// assertTrue(actual + "\n does not contain \n" + part1, actual.contains(part1)); +// assertTrue(actual.contains(part2)); +// MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); +// response.setStatusCode(200); +// response.addHeader( +// "Content-Type", "multipart/mixed; boundary=" + RESPONSE_BOUNDARY); +// String content2 = +// "{\"name\": \"" + TEST_NAME + "\", \"number\": \"" + TEST_NUM + "\"}"; +// StringBuilder responseContent = new StringBuilder(); +// responseContent +// .append("--" + RESPONSE_BOUNDARY + "\n") +// .append("Content-Type: application/http\n") +// .append("Content-Transfer-Encoding: binary\n") +// .append("Content-ID: response-1\n\n") +// .append("HTTP/1.1 200 OK\n") +// .append("Content-Type: application/json; charset=UTF-8\n") +// .append("Content-Length: " + content2.length() + "\n\n") +// .append(content2 + "\n\n") +// .append("--" + RESPONSE_BOUNDARY + "--\n\n"); +// response.setContent(responseContent.toString()); +// return response; +// } +// }; +// } +// }; +// +// BatchRequest batchRequest = new BatchRequest(transport, null); +// BatchCallback callback = +// new BatchCallback() { +// +// @Override +// public void onSuccess(Void t, HttpHeaders responseHeaders) {} +// +// @Override +// public void onFailure(Void e, HttpHeaders responseHeaders) {} +// }; +// for (HttpRequest request : requests) { +// batchRequest.queue(request, Void.class, Void.class, callback); +// } +// batchRequest.execute(); +// } +// +// public void testExecute_checkWriteToNoHeaders() throws IOException { +// MockHttpTransport transport = new MockHttpTransport(); +// HttpRequest request = +// transport +// .createRequestFactory() +// .buildPostRequest( +// HttpTesting.SIMPLE_GENERIC_URL, +// new HttpContent() { +// +// @Override +// public long getLength() { +// return -1; +// } +// +// @Override +// public String getType() { +// return null; +// } +// +// @Override +// public void writeTo(OutputStream out) {} +// +// @Override +// public boolean retrySupported() { +// return true; +// } +// }); +// String expected = +// new StringBuilder() +// .append("Content-Length: 36\r\n") +// .append("Content-Type: application/http\r\n") +// .append("content-id: 1\r\n") +// .append("content-transfer-encoding: binary\r\n") +// .append("\r\n") +// .append("POST http://google.com/ HTTP/1.1\r\n") +// .append("\r\n") +// .append("\r\n") +// .append("--__END_OF_PART__") +// .toString(); +// subtestExecute_checkWriteTo(expected, expected, request); +// } +// +// public void testProtoExecute() throws IOException { +// BatchRequest batchRequest = +// getBatchPopulatedWithRequests(false, false, false, false, true, false); +// batchRequest.execute(); +// // Assert callbacks have been invoked. +// assertEquals(1, callback1.successCalls); +// assertEquals(1, callback2.successCalls); +// assertEquals(0, callback2.failureCalls); +// // Assert requestInfos is empty after execute. +// assertTrue(batchRequest.requestInfos.isEmpty()); +// } +// +// public void testProtoExecuteWithError() throws IOException { +// BatchRequest batchRequest = +// getBatchPopulatedWithRequests(true, false, false, false, true, false); +// batchRequest.execute(); +// // Assert callbacks have been invoked. +// assertEquals(1, callback1.successCalls); +// assertEquals(0, callback2.successCalls); +// assertEquals(1, callback2.failureCalls); +// // Assert requestInfos is empty after execute. +// assertTrue(batchRequest.requestInfos.isEmpty()); +// // Assert transport called expected number of times. +// assertEquals(1, transport.actualCalls); +// } +// +// public void testProtoExecuteWithoutLength() throws IOException { +// BatchRequest batchRequest = +// getBatchPopulatedWithRequests(false, false, false, false, true, true); +// batchRequest.execute(); +// // Assert callbacks have been invoked. +// assertEquals(1, callback1.successCalls); +// assertEquals(1, callback2.successCalls); +// assertEquals(0, callback2.failureCalls); +// // Assert requestInfos is empty after execute. +// assertTrue(batchRequest.requestInfos.isEmpty()); +// } +// } 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..3340a33e1 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,107 @@ 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"); + + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.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 testGoogleClientSuppressionDefaults() { String rootUrl = "http://www.testgoogleapis.com/test/"; String servicePath = "path/v1/"; @@ -97,6 +224,7 @@ public void testGoogleClientSuppressionDefaults() { assertFalse(googleClient.getSuppressRequiredParameterChecks()); } + @Test public void testBaseServerAndBasePathBuilder() { AbstractGoogleClient client = new MockGoogleClient.Builder( @@ -113,6 +241,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 +254,120 @@ public void testInitialize() throws Exception { assertTrue(remoteRequestInitializer.isCalled); } + @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_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_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 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(); + assertTrue(client.hasValidUniverseDomain()); + } + + @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() { + client.hasValidUniverseDomain(); + } + }); + } + + @Test + public void validateUniverseDomain_notUsingHttpCredentialsAdapter_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, + new TestHttpRequestInitializer()) + .setApplicationName(applicationName) + .build(); + assertTrue(client.hasValidUniverseDomain()); + } + + @Test + public void validateUniverseDomain_notUsingHttpCredentialsAdapter_customUniverseDomain() { + String rootUrl = "https://test.googleapis.com/"; + String applicationName = "Test Application"; + String servicePath = "test/"; + String universeDomain = "random.com"; + + final AbstractGoogleClient client = + new MockGoogleClient.Builder( + TRANSPORT, + rootUrl, + servicePath, + JSON_OBJECT_PARSER, + new TestHttpRequestInitializer()) + .setApplicationName(applicationName) + .setUniverseDomain(universeDomain) + .build(); + assertThrows( + IllegalStateException.class, + new ThrowingRunnable() { + @Override + public void run() { + client.hasValidUniverseDomain(); + } + }); + } + 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 a0a720179..c98c471b7 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 From b8b233a505b3e294c0a4f8a7a97c6fee777eb6aa Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Thu, 29 Feb 2024 17:35:26 -0500 Subject: [PATCH 09/24] chore: Fix tests --- .../googleapis/services/AbstractGoogleClientRequestTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientRequestTest.java b/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientRequestTest.java index 1eefdb139..317cf1f98 100644 --- a/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientRequestTest.java +++ b/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientRequestTest.java @@ -45,8 +45,8 @@ */ public class AbstractGoogleClientRequestTest extends TestCase { - private static final String ROOT_URL = "https://www.googleapis.com/test/"; - private static final String SERVICE_PATH = "path/v1/"; + private static final String ROOT_URL = "https://www.googleapis.com/"; + private static final String SERVICE_PATH = "test/path/v1/"; private static final String URI_TEMPLATE = "tests/{testId}"; private static final JsonFactory JSON_FACTORY = new GsonFactory(); private static final JsonObjectParser JSON_OBJECT_PARSER = new JsonObjectParser(JSON_FACTORY); From dfd07a752b76a61e3cdb5a260855cbf5dcbb22bb Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Mon, 4 Mar 2024 16:54:27 -0500 Subject: [PATCH 10/24] chore: Update docs for AbstractGoogleClient --- .../services/AbstractGoogleClient.java | 55 +- .../googleapis/batch/BatchRequestTest.java | 1751 ++++++++--------- 2 files changed, 913 insertions(+), 893 deletions(-) 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 1e37d6cc9..be7524d06 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 @@ -36,6 +36,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; @@ -81,7 +83,7 @@ public abstract class AbstractGoogleClient { */ protected AbstractGoogleClient(Builder builder) { googleClientRequestInitializer = builder.googleClientRequestInitializer; - universeDomain = builder.universeDomain; + universeDomain = determineUniverseDomain(builder); rootUrl = normalizeRootUrl(determineEndpoint(builder)); servicePath = normalizeServicePath(builder.servicePath); batchPath = builder.batchPath; @@ -99,24 +101,42 @@ protected AbstractGoogleClient(Builder builder) { 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 as 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 && !builder.universeDomain.equals(Credentials.GOOGLE_DEFAULT_UNIVERSE)) { + // mTLS configurations is not compatible with anything other than the GDU + if (mtlsEnabled && !universeDomain.equals(Credentials.GOOGLE_DEFAULT_UNIVERSE)) { throw new IllegalArgumentException( "mTLS is not supported in any universe other than googleapis.com"); } - // If the user did not set an endpoint, resolve the endpoint - String resolvedUniverseDomain = - builder.universeDomain == null - ? Credentials.GOOGLE_DEFAULT_UNIVERSE - : builder.universeDomain; - if (builder.isUserConfiguredEndpoint) { + // If the serviceName is null, we cannot construct a valid resolved endpoint. Simply return + // the rootUrl as this was custom configuration passed in. + if (builder.isUserConfiguredEndpoint || builder.serviceName == null) { return builder.rootUrl; } if (mtlsEnabled) { - return "https://" + builder.serviceName + ".mtls." + resolvedUniverseDomain; + return "https://" + builder.serviceName + ".mtls." + universeDomain; } - return "https://" + builder.serviceName + "." + resolvedUniverseDomain; + return "https://" + builder.serviceName + "." + universeDomain; } /** @@ -388,9 +408,15 @@ public abstract static class Builder { boolean suppressRequiredParameterChecks; /** User configured Universe Domain. Defaults to `googleapis.com`. */ - String universeDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; + String universeDomain; - /** Whether the user has configured an endpoint via {@link #setRootUrl(String)} */ + /** + * 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. Allow user configuration via {@link + * #setRootUrl(String)}, which we would need to track. + * + *

By default, it is set as false + */ boolean isUserConfiguredEndpoint = false; /** The parsed serviceName value from the rootUrl from the Discovery Doc. */ @@ -430,13 +456,14 @@ protected Builder( private String parseServiceName(String rootUrl) { // len of "https://" int startIndex = 8; - if (rootUrl.contains("mtls.googleapis.com")) { + if (rootUrl.contains(".mtls.")) { return rootUrl.substring(startIndex, rootUrl.indexOf(".mtls")); } else if (rootUrl.contains(".googleapis.com")) { return rootUrl.substring(startIndex, rootUrl.indexOf(".googleapis.com")); } else { // Return null to not break behavior for any non-google users or any use - // case without a discovery doc + // case without a discovery doc. There may be certain use cases for this + // as the Builder's constructor is only protected scope return null; } } diff --git a/google-api-client/src/test/java/com/google/api/client/googleapis/batch/BatchRequestTest.java b/google-api-client/src/test/java/com/google/api/client/googleapis/batch/BatchRequestTest.java index 5758b827f..1032d448d 100644 --- a/google-api-client/src/test/java/com/google/api/client/googleapis/batch/BatchRequestTest.java +++ b/google-api-client/src/test/java/com/google/api/client/googleapis/batch/BatchRequestTest.java @@ -1,879 +1,872 @@ -//// Copyright 2012 Google Inc. All Rights Reserved. -// -// package com.google.api.client.googleapis.batch; -// -// import static java.nio.charset.StandardCharsets.UTF_8; -// -// import com.google.api.client.googleapis.batch.BatchRequest.RequestInfo; -// import com.google.api.client.googleapis.json.GoogleJsonError; -// import com.google.api.client.googleapis.json.GoogleJsonError.ErrorInfo; -// import com.google.api.client.googleapis.json.GoogleJsonErrorContainer; -// import com.google.api.client.googleapis.testing.services.MockGoogleClient; -// import com.google.api.client.googleapis.testing.services.MockGoogleClientRequest; -// import com.google.api.client.http.ByteArrayContent; -// import com.google.api.client.http.GenericUrl; -// import com.google.api.client.http.HttpContent; -// import com.google.api.client.http.HttpExecuteInterceptor; -// import com.google.api.client.http.HttpHeaders; -// import com.google.api.client.http.HttpMethods; -// import com.google.api.client.http.HttpRequest; -// import com.google.api.client.http.HttpRequestInitializer; -// import com.google.api.client.http.HttpResponse; -// import com.google.api.client.http.HttpUnsuccessfulResponseHandler; -// import com.google.api.client.http.LowLevelHttpRequest; -// import com.google.api.client.http.LowLevelHttpResponse; -// import com.google.api.client.json.GenericJson; -// import com.google.api.client.json.JsonObjectParser; -// import com.google.api.client.json.gson.GsonFactory; -// import com.google.api.client.protobuf.ProtoObjectParser; -// import com.google.api.client.testing.http.HttpTesting; -// import com.google.api.client.testing.http.MockHttpTransport; -// import com.google.api.client.testing.http.MockLowLevelHttpRequest; -// import com.google.api.client.testing.http.MockLowLevelHttpResponse; -// import com.google.api.client.util.Charsets; -// import com.google.api.client.util.Key; -// import com.google.api.client.util.ObjectParser; -// import java.io.ByteArrayOutputStream; -// import java.io.IOException; -// import java.io.OutputStream; -// import java.io.OutputStreamWriter; -// import java.io.Writer; -// import java.util.ArrayList; -// import java.util.List; -// import junit.framework.TestCase; -// -/// ** -// * Tests {@link BatchRequest}. -// * -// * @author rmistry@google.com (Ravi Mistry) -// */ -// public class BatchRequestTest extends TestCase { -// -// private static final String ROOT_URL = "http://www.test.com/"; -// private static final String SERVICE_PATH = "test/"; -// private static final String TEST_BATCH_URL = "http://www.testgoogleapis.com/batch"; -// private static final String URI_TEMPLATE1 = "uri/template/1"; -// private static final String URI_TEMPLATE2 = "uri/template/2"; -// private static final String METHOD1 = HttpMethods.GET; -// private static final String METHOD2 = HttpMethods.POST; -// private static final String ERROR_MSG = "Error message"; -// private static final String ERROR_REASON = "notFound"; -// private static final int ERROR_CODE = 503; -// private static final String ERROR_DOMAIN = "global"; -// private static final String RESPONSE_BOUNDARY = "ABC=DE=F"; -// private static final String TEST_ID = "Humpty Dumpty"; -// private static final String TEST_KIND = "Big\nEgg\n"; // Newlines help test boundary detection -// private static final String TEST_NAME = "James Bond"; -// private static final String TEST_NUM = "007"; -// -// private TestCallback1 callback1; -// private TestCallback2 callback2; -// private TestCallback3 callback3; -// -// private MockTransport transport; -// -// private MockCredential credential; -// -// @Override -// protected void setUp() { -// callback1 = new TestCallback1(); -// callback2 = new TestCallback2(); -// callback3 = new TestCallback3(); -// } -// -// public static class MockDataClass1 extends GenericJson { -// @Key String id; -// -// @Key String kind; -// } -// -// public static class MockDataClass2 extends GenericJson { -// @Key String name; -// -// @Key String number; -// } -// -// private static class TestCallback1 -// implements BatchCallback { -// -// int successCalls; -// -// TestCallback1() {} -// -// @Override -// public void onSuccess(MockDataClass1 dataClass, HttpHeaders responseHeaders) { -// successCalls++; -// assertEquals(TEST_ID, dataClass.id); -// assertEquals(TEST_KIND, dataClass.kind); -// } -// -// @Override -// public void onFailure(GoogleJsonErrorContainer e, HttpHeaders responseHeaders) { -// fail("Should not be invoked in this test"); -// } -// } -// -// private static class TestCallback2 -// implements BatchCallback { -// -// int successCalls; -// int failureCalls; -// -// TestCallback2() {} -// -// @Override -// public void onSuccess(MockDataClass2 dataClass, HttpHeaders responseHeaders) { -// successCalls++; -// assertEquals(TEST_NAME, dataClass.name); -// assertEquals(TEST_NUM, dataClass.number); -// } -// -// @Override -// public void onFailure(GoogleJsonErrorContainer e, HttpHeaders responseHeaders) { -// failureCalls++; -// GoogleJsonError error = e.getError(); -// ErrorInfo errorInfo = error.getErrors().get(0); -// assertEquals(ERROR_DOMAIN, errorInfo.getDomain()); -// assertEquals(ERROR_REASON, errorInfo.getReason()); -// assertEquals(ERROR_MSG, errorInfo.getMessage()); -// assertEquals(ERROR_CODE, error.getCode()); -// assertEquals(ERROR_MSG, error.getMessage()); -// } -// } -// -// private static class TestCallback3 implements BatchCallback { -// -// int successCalls; -// int failureCalls; -// -// TestCallback3() {} -// -// @Override -// public void onSuccess(Void dataClass, HttpHeaders responseHeaders) { -// successCalls++; -// assertNull(dataClass); -// } -// -// @Override -// public void onFailure(Void e, HttpHeaders responseHeaders) { -// failureCalls++; -// assertNull(e); -// } -// } -// -// /** -// * Base class for callback adapters to handle error conversion. -// * -// * @param The input type -// * @param The output type -// */ -// private abstract static class TestCallbackBaseAdapter -// implements BatchCallback { -// -// protected final BatchCallback callback; -// -// protected TestCallbackBaseAdapter( -// BatchCallback callback) { -// this.callback = callback; -// } -// -// @Override -// public void onFailure(ErrorOutput.ErrorBody e, HttpHeaders responseHeaders) throws IOException -// { -// GoogleJsonErrorContainer errorContainer = new GoogleJsonErrorContainer(); -// -// if (e.hasError()) { -// ErrorOutput.ErrorProto errorProto = e.getError(); -// -// GoogleJsonError error = new GoogleJsonError(); -// if (errorProto.hasCode()) { -// error.setCode(errorProto.getCode()); -// } -// if (errorProto.hasMessage()) { -// error.setMessage(errorProto.getMessage()); -// } -// -// List errorInfos = new ArrayList(errorProto.getErrorsCount()); -// for (ErrorOutput.IndividualError individualError : errorProto.getErrorsList()) { -// ErrorInfo errorInfo = new ErrorInfo(); -// if (individualError.hasDomain()) { -// errorInfo.setDomain(individualError.getDomain()); -// } -// if (individualError.hasMessage()) { -// errorInfo.setMessage(individualError.getMessage()); -// } -// if (individualError.hasReason()) { -// errorInfo.setReason(individualError.getReason()); -// } -// errorInfos.add(errorInfo); -// } -// error.setErrors(errorInfos); -// errorContainer.setError(error); -// } -// callback.onFailure(errorContainer, responseHeaders); -// } -// } -// -// private static class TestCallback1Adapter -// extends TestCallbackBaseAdapter { -// -// public TestCallback1Adapter(TestCallback1 callback) { -// super(callback); -// } -// -// @Override -// public void onSuccess(MockData.Class1 message, HttpHeaders responseHeaders) throws IOException -// { -// MockDataClass1 dataClass = new MockDataClass1(); -// dataClass.id = message.hasId() ? message.getId() : null; -// dataClass.kind = message.hasKind() ? message.getKind() : null; -// callback.onSuccess(dataClass, responseHeaders); -// } -// } -// -// private static class TestCallback2Adapter -// extends TestCallbackBaseAdapter { -// -// public TestCallback2Adapter(TestCallback2 callback) { -// super(callback); -// } -// -// @Override -// public void onSuccess(MockData.Class2 message, HttpHeaders responseHeaders) throws IOException -// { -// MockDataClass2 dataClass = new MockDataClass2(); -// dataClass.name = message.hasName() ? message.getName() : null; -// dataClass.number = message.hasNumber() ? message.getNumber() : null; -// callback.onSuccess(dataClass, responseHeaders); -// } -// } -// -// private static class MockUnsuccessfulResponseHandler implements HttpUnsuccessfulResponseHandler -// { -// -// MockTransport transport; -// boolean returnSuccessAuthenticatedContent; -// -// MockUnsuccessfulResponseHandler( -// MockTransport transport, boolean returnSuccessAuthenticatedContent) { -// this.transport = transport; -// this.returnSuccessAuthenticatedContent = returnSuccessAuthenticatedContent; -// } -// -// @Override -// public boolean handleResponse( -// HttpRequest request, HttpResponse response, boolean supportsRetry) { -// if (transport.returnErrorAuthenticatedContent) { -// // If transport has already been set to return error content do not handle response. -// return false; -// } -// if (returnSuccessAuthenticatedContent) { -// transport.returnSuccessAuthenticatedContent = true; -// } else { -// transport.returnErrorAuthenticatedContent = true; -// } -// return true; -// } -// } -// -// private static class MockTransport extends MockHttpTransport { -// -// final boolean testServerError; -// final boolean testAuthenticationError; -// boolean returnSuccessAuthenticatedContent; -// boolean returnErrorAuthenticatedContent; -// final boolean testRedirect; -// final boolean testBinary; -// final boolean testMissingLength; -// int actualCalls; -// int callsBeforeSuccess; -// -// MockTransport( -// boolean testServerError, -// boolean testAuthenticationError, -// boolean testRedirect, -// boolean testBinary, -// boolean testMissingLength) { -// this.testServerError = testServerError; -// this.testAuthenticationError = testAuthenticationError; -// this.testRedirect = testRedirect; -// this.testBinary = testBinary; -// this.testMissingLength = testMissingLength; -// } -// -// @Override -// public LowLevelHttpRequest buildRequest(String name, String url) { -// actualCalls++; -// return new MockLowLevelHttpRequest() { -// @Override -// public LowLevelHttpResponse execute() throws IOException { -// MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); -// response.setStatusCode(200); -// response.addHeader("Content-Type", "multipart/mixed; boundary=" + RESPONSE_BOUNDARY); -// String contentType = -// testBinary ? "application/x-protobuf" : "application/json; charset=UTF-8"; -// byte[] content1 = -// testBinary -// ? MockData.Class1.newBuilder() -// .setId(TEST_ID) -// .setKind(TEST_KIND) -// .build() -// .toByteArray() -// : utf8Encode( -// "{\n \"id\": \"" -// + TEST_ID -// + "\",\n \"kind\": \"" -// + TEST_KIND.replace("\n", "\\n") -// + "\"\n}"); -// byte[] content2 = -// testBinary -// ? MockData.Class2.newBuilder() -// .setName(TEST_NAME) -// .setNumber(TEST_NUM) -// .build() -// .toByteArray() -// : utf8Encode( -// "{\"name\": \"" + TEST_NAME + "\", \"number\": \"" + TEST_NUM + "\"}"); -// byte[] errorContent = -// testBinary -// ? ErrorOutput.ErrorBody.newBuilder() -// .setError( -// ErrorOutput.ErrorProto.newBuilder() -// .setCode(ERROR_CODE) -// .setMessage(ERROR_MSG) -// .addErrors( -// ErrorOutput.IndividualError.newBuilder() -// .setDomain(ERROR_DOMAIN) -// .setReason(ERROR_REASON) -// .setMessage(ERROR_MSG))) -// .build() -// .toByteArray() -// : utf8Encode( -// "{\"error\": { \"errors\": [{\"domain\": \"" -// + ERROR_DOMAIN -// + "\"," -// + "\"reason\": \"" -// + ERROR_REASON -// + "\", \"message\": \"" -// + ERROR_MSG -// + "\"}]," -// + "\"code\": " -// + ERROR_CODE -// + ", \"message\": \"" -// + ERROR_MSG -// + "\"}}"); -// ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); -// Writer responseContent = new OutputStreamWriter(outputStream, "ISO-8859-1"); -// if (returnSuccessAuthenticatedContent || (testRedirect && actualCalls > 1)) { -// if (returnSuccessAuthenticatedContent || actualCalls == callsBeforeSuccess) { -// responseContent -// .append("--" + RESPONSE_BOUNDARY + "\n") -// .append("Content-Type: application/http\n") -// .append("Content-Transfer-Encoding: binary\n") -// .append("Content-ID: response-1\n\n") -// .append("HTTP/1.1 200 OK\n") -// .append("Content-Type: " + contentType + "\n"); -// if (!testMissingLength) { -// responseContent.append("Content-Length: " + content2.length + "\n"); -// } -// responseContent.append("\n"); -// responseContent.flush(); -// outputStream.write(content2); -// responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); -// } else { -// responseContent -// .append("--" + RESPONSE_BOUNDARY + "\n") -// .append("Content-Type: application/http\n") -// .append("Content-Transfer-Encoding: binary\n") -// .append("Content-ID: response-1\n\n") -// .append("HTTP/1.1 " + ERROR_CODE + " Not Found\n") -// .append("Content-Type: " + contentType + "\n"); -// if (!testMissingLength) { -// responseContent.append("Content-Length: " + errorContent.length + "\n"); -// } -// responseContent.append("\n"); -// responseContent.flush(); -// outputStream.write(errorContent); -// responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); -// } -// } else if (returnErrorAuthenticatedContent) { -// responseContent -// .append("Content-Type: application/http\n") -// .append("Content-Transfer-Encoding: binary\n") -// .append("Content-ID: response-1\n\n"); -// responseContent -// .append("HTTP/1.1 " + ERROR_CODE + " Not Found\n") -// .append("Content-Type: " + contentType + "\n"); -// if (!testMissingLength) { -// responseContent.append("Content-Length: " + errorContent.length + "\n"); -// } -// responseContent.append("\n"); -// responseContent.flush(); -// outputStream.write(errorContent); -// responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); -// } else { -// responseContent -// .append("--" + RESPONSE_BOUNDARY + "\n") -// .append("Content-Type: application/http\n") -// .append("Content-Transfer-Encoding: binary\n") -// .append("Content-ID: response-1\n\n") -// .append("HTTP/1.1 200 OK\n") -// .append("Content-Type: " + contentType + "\n"); -// if (!testMissingLength) { -// responseContent.append("Content-Length: " + content1.length + "\n"); -// } -// responseContent.append("\n"); -// responseContent.flush(); -// outputStream.write(content1); -// responseContent -// .append("\n--" + RESPONSE_BOUNDARY + "\n") -// .append("Content-Type: application/http\n") -// .append("Content-Transfer-Encoding: binary\n") -// .append("Content-ID: response-2\n\n"); -// -// if (testServerError) { -// responseContent -// .append("HTTP/1.1 " + ERROR_CODE + " Not Found\n") -// .append("Content-Type: " + contentType + "\n"); -// if (!testMissingLength) { -// responseContent.append("Content-Length: " + errorContent.length + "\n"); -// } -// responseContent.append("\n"); -// responseContent.flush(); -// outputStream.write(errorContent); -// responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); -// } else if (testAuthenticationError) { -// responseContent -// .append("HTTP/1.1 401 Unauthorized\n") -// .append("Content-Type: application/json; charset=UTF-8\n\n") -// .append("--" + RESPONSE_BOUNDARY + "--\n\n"); -// } else if (testRedirect && actualCalls == 1) { -// responseContent -// .append("HTTP/1.1 301 MovedPermanently\n") -// .append("Content-Type: " + contentType + "\n") -// .append("Location: http://redirect/location\n\n") -// .append("--" + RESPONSE_BOUNDARY + "--\n\n"); -// } else { -// responseContent -// .append("HTTP/1.1 200 OK\n") -// .append("Content-Type: " + contentType + "\n"); -// if (!testMissingLength) { -// responseContent.append("Content-Length: " + content2.length + "\n"); -// } -// responseContent.append("\n"); -// responseContent.flush(); -// outputStream.write(content2); -// responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); -// } -// } -// responseContent.flush(); -// response.setContent(outputStream.toByteArray()); -// return response; -// } -// -// // Short-hand to encode a String as a UTF-8 byte array -// private byte[] utf8Encode(String string) { -// return Charsets.UTF_8.encode(string).array(); -// } -// }; -// } -// } -// -// private static class MockCredential implements HttpRequestInitializer, HttpExecuteInterceptor { -// -// boolean initializerCalled = false; -// boolean interceptorCalled = false; -// -// MockCredential() {} -// -// @Override -// public void initialize(HttpRequest request) { -// request.setInterceptor(this); -// initializerCalled = true; -// } -// -// @Override -// public void intercept(HttpRequest request) { -// interceptorCalled = true; -// } -// } -// -// private BatchRequest getBatchPopulatedWithRequests( -// boolean testServerError, -// boolean testAuthenticationError, -// boolean returnSuccessAuthenticatedContent, -// boolean testRedirect, -// boolean testBinary, -// boolean testMissingLength) -// throws IOException { -// transport = -// new MockTransport( -// testServerError, testAuthenticationError, testRedirect, testBinary, -// testMissingLength); -// MockGoogleClient client = -// new MockGoogleClient.Builder(transport, ROOT_URL, SERVICE_PATH, null, null) -// .setApplicationName("Test Application") -// .build(); -// MockGoogleClientRequest jsonHttpRequest1 = -// new MockGoogleClientRequest(client, METHOD1, URI_TEMPLATE1, null, String.class); -// MockGoogleClientRequest jsonHttpRequest2 = -// new MockGoogleClientRequest(client, METHOD2, URI_TEMPLATE2, null, String.class); -// credential = new MockCredential(); -// -// ObjectParser parser = -// testBinary ? new ProtoObjectParser() : new JsonObjectParser(new GsonFactory()); -// BatchRequest batchRequest = -// new BatchRequest(transport, credential).setBatchUrl(new GenericUrl(TEST_BATCH_URL)); -// HttpRequest request1 = jsonHttpRequest1.buildHttpRequest(); -// request1.setParser(parser); -// HttpRequest request2 = jsonHttpRequest2.buildHttpRequest(); -// request2.setParser(parser); -// if (testAuthenticationError) { -// request2.setUnsuccessfulResponseHandler( -// new MockUnsuccessfulResponseHandler(transport, returnSuccessAuthenticatedContent)); -// } -// -// if (testBinary) { -// batchRequest.queue( -// request1, -// MockData.Class1.class, -// ErrorOutput.ErrorBody.class, -// new TestCallback1Adapter(callback1)); -// batchRequest.queue( -// request2, -// MockData.Class2.class, -// ErrorOutput.ErrorBody.class, -// new TestCallback2Adapter(callback2)); -// } else { -// batchRequest.queue(request1, MockDataClass1.class, GoogleJsonErrorContainer.class, -// callback1); -// batchRequest.queue(request2, MockDataClass2.class, GoogleJsonErrorContainer.class, -// callback2); -// } -// return batchRequest; -// } -// -// public void testQueueDatastructures() throws Exception { -// BatchRequest batchRequest = -// getBatchPopulatedWithRequests(false, false, false, false, false, false); -// List> requestInfos = batchRequest.requestInfos; -// -// // Assert that the expected objects are queued. -// assertEquals(2, requestInfos.size()); -// assertEquals(MockDataClass1.class, requestInfos.get(0).dataClass); -// assertEquals(callback1, requestInfos.get(0).callback); -// assertEquals(MockDataClass2.class, requestInfos.get(1).dataClass); -// assertEquals(callback2, requestInfos.get(1).callback); -// // Assert that the requests in the queue are as expected. -// assertEquals( -// ROOT_URL + SERVICE_PATH + URI_TEMPLATE1, requestInfos.get(0).request.getUrl().build()); -// assertEquals( -// ROOT_URL + SERVICE_PATH + URI_TEMPLATE2, requestInfos.get(1).request.getUrl().build()); -// assertEquals(METHOD1, requestInfos.get(0).request.getRequestMethod()); -// assertEquals(METHOD2, requestInfos.get(1).request.getRequestMethod()); -// } -// -// public void testExecute() throws IOException { -// BatchRequest batchRequest = -// getBatchPopulatedWithRequests(false, false, false, false, false, false); -// batchRequest.execute(); -// // Assert callbacks have been invoked. -// assertEquals(1, callback1.successCalls); -// assertEquals(1, callback2.successCalls); -// assertEquals(0, callback2.failureCalls); -// // Assert requestInfos is empty after execute. -// assertTrue(batchRequest.requestInfos.isEmpty()); -// } -// -// public void testExecuteWithError() throws IOException { -// BatchRequest batchRequest = -// getBatchPopulatedWithRequests(true, false, false, false, false, false); -// batchRequest.execute(); -// // Assert callbacks have been invoked. -// assertEquals(1, callback1.successCalls); -// assertEquals(0, callback2.successCalls); -// assertEquals(1, callback2.failureCalls); -// // Assert requestInfos is empty after execute. -// assertTrue(batchRequest.requestInfos.isEmpty()); -// // Assert transport called expected number of times. -// assertEquals(1, transport.actualCalls); -// } -// -// public void testExecuteWithVoidCallback() throws Exception { -// subTestExecuteWithVoidCallback(false); -// // Assert callbacks have been invoked. -// assertEquals(1, callback1.successCalls); -// assertEquals(1, callback3.successCalls); -// assertEquals(0, callback3.failureCalls); -// } -// -// public void testExecuteWithVoidCallbackError() throws Exception { -// subTestExecuteWithVoidCallback(true); -// // Assert callbacks have been invoked. -// assertEquals(1, callback1.successCalls); -// assertEquals(0, callback3.successCalls); -// assertEquals(1, callback3.failureCalls); -// } -// -// public void subTestExecuteWithVoidCallback(boolean testServerError) throws IOException { -// MockTransport transport = new MockTransport(testServerError, false, false, false, false); -// MockGoogleClient client = -// new MockGoogleClient.Builder(transport, ROOT_URL, SERVICE_PATH, null, null) -// .setApplicationName("Test Application") -// .build(); -// MockGoogleClientRequest jsonHttpRequest1 = -// new MockGoogleClientRequest(client, METHOD1, URI_TEMPLATE1, null, String.class); -// MockGoogleClientRequest jsonHttpRequest2 = -// new MockGoogleClientRequest(client, METHOD2, URI_TEMPLATE2, null, String.class); -// ObjectParser parser = new JsonObjectParser(new GsonFactory()); -// BatchRequest batchRequest = -// new BatchRequest(transport, null).setBatchUrl(new GenericUrl(TEST_BATCH_URL)); -// HttpRequest request1 = jsonHttpRequest1.buildHttpRequest(); -// request1.setParser(parser); -// HttpRequest request2 = jsonHttpRequest2.buildHttpRequest(); -// request2.setParser(parser); -// batchRequest.queue(request1, MockDataClass1.class, GoogleJsonErrorContainer.class, callback1); -// batchRequest.queue(request2, Void.class, Void.class, callback3); -// batchRequest.execute(); -// // Assert transport called expected number of times. -// assertEquals(1, transport.actualCalls); -// } -// -// public void testExecuteWithAuthenticationErrorThenSuccessCallback() throws Exception { -// BatchRequest batchRequest = -// getBatchPopulatedWithRequests(false, true, true, false, false, false); -// batchRequest.execute(); -// // Assert callbacks have been invoked. -// assertEquals(1, callback1.successCalls); -// assertEquals(1, callback2.successCalls); -// assertEquals(0, callback2.failureCalls); -// // Assert transport called expected number of times. -// assertEquals(2, transport.actualCalls); -// // Assert requestInfos is empty after execute. -// assertTrue(batchRequest.requestInfos.isEmpty()); -// } -// -// public void testExecuteWithAuthenticationErrorThenErrorCallback() throws Exception { -// BatchRequest batchRequest = -// getBatchPopulatedWithRequests(false, true, false, false, false, false); -// batchRequest.execute(); -// // Assert callbacks have been invoked. -// assertEquals(1, callback1.successCalls); -// assertEquals(0, callback2.successCalls); -// assertEquals(1, callback2.failureCalls); -// // Assert transport called expected number of times. -// assertEquals(2, transport.actualCalls); -// // Assert requestInfos is empty after execute. -// assertTrue(batchRequest.requestInfos.isEmpty()); -// } -// -// public void testInterceptor() throws Exception { -// BatchRequest batchRequest = -// getBatchPopulatedWithRequests(true, false, false, false, false, false); -// batchRequest.execute(); -// // Assert the top-level request initializer is called. -// assertTrue(credential.initializerCalled); -// assertTrue(credential.interceptorCalled); -// } -// -// public void testRedirect() throws Exception { -// BatchRequest batchRequest = -// getBatchPopulatedWithRequests(false, false, false, true, false, false); -// transport.callsBeforeSuccess = 2; -// batchRequest.execute(); -// // Assert transport called expected number of times. -// assertEquals(2, transport.actualCalls); -// // Assert requestInfos is empty after execute. -// assertTrue(batchRequest.requestInfos.isEmpty()); -// } -// -// public void testExecute_checkWriteTo() throws Exception { -// String request1Method = HttpMethods.POST; -// String request1Url = "http://test/dummy/url1"; -// String request1ContentType = "application/json"; -// String request1Content = "{\"data\":{\"foo\":{\"v1\":{}}}}"; -// -// String request2Method = HttpMethods.GET; -// String request2Url = "http://test/dummy/url2"; -// -// // MIME content boundaries are not reproducible. -// StringBuilder part1 = new StringBuilder(); -// part1.append("Content-Length: 118\r\n"); -// part1.append("Content-Type: application/http\r\n"); -// part1.append("content-id: 1\r\n"); -// part1.append("content-transfer-encoding: binary\r\n"); -// part1.append("\r\n"); -// part1.append("POST http://test/dummy/url1 HTTP/1.1\r\n"); -// part1.append("Content-Length: 26\r\n"); -// part1.append("Content-Type: " + request1ContentType + "\r\n"); -// part1.append("\r\n"); -// part1.append(request1Content + "\r\n"); -// part1.append("--__END_OF_PART__"); -// String expected1 = part1.toString(); -// -// StringBuilder part2 = new StringBuilder(); -// part2.append("Content-Length: 39\r\n"); -// part2.append("Content-Type: application/http\r\n"); -// part2.append("content-id: 2\r\n"); -// part2.append("content-transfer-encoding: binary\r\n"); -// part2.append("\r\n"); -// part2.append("GET http://test/dummy/url2 HTTP/1.1\r\n"); -// part2.append("\r\n"); -// part2.append("\r\n"); -// part2.append("--__END_OF_PART__"); -// String expected2 = part2.toString(); -// -// MockHttpTransport transport = new MockHttpTransport(); -// HttpRequest request1 = -// transport -// .createRequestFactory() -// .buildRequest( -// request1Method, -// new GenericUrl(request1Url), -// new ByteArrayContent(request1ContentType, request1Content.getBytes(UTF_8))); -// HttpRequest request2 = -// transport -// .createRequestFactory() -// .buildRequest(request2Method, new GenericUrl(request2Url), null); -// subtestExecute_checkWriteTo(expected1, expected2, request1, request2); -// } -// -// private void subtestExecute_checkWriteTo( -// final String part1, final String part2, HttpRequest... requests) throws IOException { -// -// MockHttpTransport transport = -// new MockHttpTransport() { -// -// @Override -// public LowLevelHttpRequest buildRequest(String method, String url) { -// return new MockLowLevelHttpRequest(url) { -// -// @Override -// public LowLevelHttpResponse execute() throws IOException { -// assertTrue( -// getContentType().startsWith("multipart/mixed; boundary=__END_OF_PART__")); -// ByteArrayOutputStream out = new ByteArrayOutputStream(); -// getStreamingContent().writeTo(out); -// String actual = out.toString("UTF-8"); -// assertTrue(actual + "\n does not contain \n" + part1, actual.contains(part1)); -// assertTrue(actual.contains(part2)); -// MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); -// response.setStatusCode(200); -// response.addHeader( -// "Content-Type", "multipart/mixed; boundary=" + RESPONSE_BOUNDARY); -// String content2 = -// "{\"name\": \"" + TEST_NAME + "\", \"number\": \"" + TEST_NUM + "\"}"; -// StringBuilder responseContent = new StringBuilder(); -// responseContent -// .append("--" + RESPONSE_BOUNDARY + "\n") -// .append("Content-Type: application/http\n") -// .append("Content-Transfer-Encoding: binary\n") -// .append("Content-ID: response-1\n\n") -// .append("HTTP/1.1 200 OK\n") -// .append("Content-Type: application/json; charset=UTF-8\n") -// .append("Content-Length: " + content2.length() + "\n\n") -// .append(content2 + "\n\n") -// .append("--" + RESPONSE_BOUNDARY + "--\n\n"); -// response.setContent(responseContent.toString()); -// return response; -// } -// }; -// } -// }; -// -// BatchRequest batchRequest = new BatchRequest(transport, null); -// BatchCallback callback = -// new BatchCallback() { -// -// @Override -// public void onSuccess(Void t, HttpHeaders responseHeaders) {} -// -// @Override -// public void onFailure(Void e, HttpHeaders responseHeaders) {} -// }; -// for (HttpRequest request : requests) { -// batchRequest.queue(request, Void.class, Void.class, callback); -// } -// batchRequest.execute(); -// } -// -// public void testExecute_checkWriteToNoHeaders() throws IOException { -// MockHttpTransport transport = new MockHttpTransport(); -// HttpRequest request = -// transport -// .createRequestFactory() -// .buildPostRequest( -// HttpTesting.SIMPLE_GENERIC_URL, -// new HttpContent() { -// -// @Override -// public long getLength() { -// return -1; -// } -// -// @Override -// public String getType() { -// return null; -// } -// -// @Override -// public void writeTo(OutputStream out) {} -// -// @Override -// public boolean retrySupported() { -// return true; -// } -// }); -// String expected = -// new StringBuilder() -// .append("Content-Length: 36\r\n") -// .append("Content-Type: application/http\r\n") -// .append("content-id: 1\r\n") -// .append("content-transfer-encoding: binary\r\n") -// .append("\r\n") -// .append("POST http://google.com/ HTTP/1.1\r\n") -// .append("\r\n") -// .append("\r\n") -// .append("--__END_OF_PART__") -// .toString(); -// subtestExecute_checkWriteTo(expected, expected, request); -// } -// -// public void testProtoExecute() throws IOException { -// BatchRequest batchRequest = -// getBatchPopulatedWithRequests(false, false, false, false, true, false); -// batchRequest.execute(); -// // Assert callbacks have been invoked. -// assertEquals(1, callback1.successCalls); -// assertEquals(1, callback2.successCalls); -// assertEquals(0, callback2.failureCalls); -// // Assert requestInfos is empty after execute. -// assertTrue(batchRequest.requestInfos.isEmpty()); -// } -// -// public void testProtoExecuteWithError() throws IOException { -// BatchRequest batchRequest = -// getBatchPopulatedWithRequests(true, false, false, false, true, false); -// batchRequest.execute(); -// // Assert callbacks have been invoked. -// assertEquals(1, callback1.successCalls); -// assertEquals(0, callback2.successCalls); -// assertEquals(1, callback2.failureCalls); -// // Assert requestInfos is empty after execute. -// assertTrue(batchRequest.requestInfos.isEmpty()); -// // Assert transport called expected number of times. -// assertEquals(1, transport.actualCalls); -// } -// -// public void testProtoExecuteWithoutLength() throws IOException { -// BatchRequest batchRequest = -// getBatchPopulatedWithRequests(false, false, false, false, true, true); -// batchRequest.execute(); -// // Assert callbacks have been invoked. -// assertEquals(1, callback1.successCalls); -// assertEquals(1, callback2.successCalls); -// assertEquals(0, callback2.failureCalls); -// // Assert requestInfos is empty after execute. -// assertTrue(batchRequest.requestInfos.isEmpty()); -// } -// } +// Copyright 2012 Google Inc. All Rights Reserved. + +package com.google.api.client.googleapis.batch; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.api.client.googleapis.batch.BatchRequest.RequestInfo; +import com.google.api.client.googleapis.json.GoogleJsonError; +import com.google.api.client.googleapis.json.GoogleJsonError.ErrorInfo; +import com.google.api.client.googleapis.json.GoogleJsonErrorContainer; +import com.google.api.client.googleapis.testing.services.MockGoogleClient; +import com.google.api.client.googleapis.testing.services.MockGoogleClientRequest; +import com.google.api.client.http.ByteArrayContent; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpExecuteInterceptor; +import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpMethods; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestInitializer; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpUnsuccessfulResponseHandler; +import com.google.api.client.http.LowLevelHttpRequest; +import com.google.api.client.http.LowLevelHttpResponse; +import com.google.api.client.json.GenericJson; +import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.client.protobuf.ProtoObjectParser; +import com.google.api.client.testing.http.HttpTesting; +import com.google.api.client.testing.http.MockHttpTransport; +import com.google.api.client.testing.http.MockLowLevelHttpRequest; +import com.google.api.client.testing.http.MockLowLevelHttpResponse; +import com.google.api.client.util.Charsets; +import com.google.api.client.util.Key; +import com.google.api.client.util.ObjectParser; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; +import junit.framework.TestCase; + +/** + * Tests {@link BatchRequest}. + * + * @author rmistry@google.com (Ravi Mistry) + */ +public class BatchRequestTest extends TestCase { + + private static final String ROOT_URL = "http://www.test.com/"; + private static final String SERVICE_PATH = "test/"; + private static final String TEST_BATCH_URL = "http://www.testgoogleapis.com/batch"; + private static final String URI_TEMPLATE1 = "uri/template/1"; + private static final String URI_TEMPLATE2 = "uri/template/2"; + private static final String METHOD1 = HttpMethods.GET; + private static final String METHOD2 = HttpMethods.POST; + private static final String ERROR_MSG = "Error message"; + private static final String ERROR_REASON = "notFound"; + private static final int ERROR_CODE = 503; + private static final String ERROR_DOMAIN = "global"; + private static final String RESPONSE_BOUNDARY = "ABC=DE=F"; + private static final String TEST_ID = "Humpty Dumpty"; + private static final String TEST_KIND = "Big\nEgg\n"; // Newlines help test boundary detection + private static final String TEST_NAME = "James Bond"; + private static final String TEST_NUM = "007"; + + private TestCallback1 callback1; + private TestCallback2 callback2; + private TestCallback3 callback3; + + private MockTransport transport; + + private MockCredential credential; + + @Override + protected void setUp() { + callback1 = new TestCallback1(); + callback2 = new TestCallback2(); + callback3 = new TestCallback3(); + } + + public static class MockDataClass1 extends GenericJson { + @Key String id; + + @Key String kind; + } + + public static class MockDataClass2 extends GenericJson { + @Key String name; + + @Key String number; + } + + private static class TestCallback1 + implements BatchCallback { + + int successCalls; + + TestCallback1() {} + + @Override + public void onSuccess(MockDataClass1 dataClass, HttpHeaders responseHeaders) { + successCalls++; + assertEquals(TEST_ID, dataClass.id); + assertEquals(TEST_KIND, dataClass.kind); + } + + @Override + public void onFailure(GoogleJsonErrorContainer e, HttpHeaders responseHeaders) { + fail("Should not be invoked in this test"); + } + } + + private static class TestCallback2 + implements BatchCallback { + + int successCalls; + int failureCalls; + + TestCallback2() {} + + @Override + public void onSuccess(MockDataClass2 dataClass, HttpHeaders responseHeaders) { + successCalls++; + assertEquals(TEST_NAME, dataClass.name); + assertEquals(TEST_NUM, dataClass.number); + } + + @Override + public void onFailure(GoogleJsonErrorContainer e, HttpHeaders responseHeaders) { + failureCalls++; + GoogleJsonError error = e.getError(); + ErrorInfo errorInfo = error.getErrors().get(0); + assertEquals(ERROR_DOMAIN, errorInfo.getDomain()); + assertEquals(ERROR_REASON, errorInfo.getReason()); + assertEquals(ERROR_MSG, errorInfo.getMessage()); + assertEquals(ERROR_CODE, error.getCode()); + assertEquals(ERROR_MSG, error.getMessage()); + } + } + + private static class TestCallback3 implements BatchCallback { + + int successCalls; + int failureCalls; + + TestCallback3() {} + + @Override + public void onSuccess(Void dataClass, HttpHeaders responseHeaders) { + successCalls++; + assertNull(dataClass); + } + + @Override + public void onFailure(Void e, HttpHeaders responseHeaders) { + failureCalls++; + assertNull(e); + } + } + + /** + * Base class for callback adapters to handle error conversion. + * + * @param The input type + * @param The output type + */ + private abstract static class TestCallbackBaseAdapter + implements BatchCallback { + + protected final BatchCallback callback; + + protected TestCallbackBaseAdapter( + BatchCallback callback) { + this.callback = callback; + } + + @Override + public void onFailure(ErrorOutput.ErrorBody e, HttpHeaders responseHeaders) throws IOException { + GoogleJsonErrorContainer errorContainer = new GoogleJsonErrorContainer(); + + if (e.hasError()) { + ErrorOutput.ErrorProto errorProto = e.getError(); + + GoogleJsonError error = new GoogleJsonError(); + if (errorProto.hasCode()) { + error.setCode(errorProto.getCode()); + } + if (errorProto.hasMessage()) { + error.setMessage(errorProto.getMessage()); + } + + List errorInfos = new ArrayList(errorProto.getErrorsCount()); + for (ErrorOutput.IndividualError individualError : errorProto.getErrorsList()) { + ErrorInfo errorInfo = new ErrorInfo(); + if (individualError.hasDomain()) { + errorInfo.setDomain(individualError.getDomain()); + } + if (individualError.hasMessage()) { + errorInfo.setMessage(individualError.getMessage()); + } + if (individualError.hasReason()) { + errorInfo.setReason(individualError.getReason()); + } + errorInfos.add(errorInfo); + } + error.setErrors(errorInfos); + errorContainer.setError(error); + } + callback.onFailure(errorContainer, responseHeaders); + } + } + + private static class TestCallback1Adapter + extends TestCallbackBaseAdapter { + + public TestCallback1Adapter(TestCallback1 callback) { + super(callback); + } + + @Override + public void onSuccess(MockData.Class1 message, HttpHeaders responseHeaders) throws IOException { + MockDataClass1 dataClass = new MockDataClass1(); + dataClass.id = message.hasId() ? message.getId() : null; + dataClass.kind = message.hasKind() ? message.getKind() : null; + callback.onSuccess(dataClass, responseHeaders); + } + } + + private static class TestCallback2Adapter + extends TestCallbackBaseAdapter { + + public TestCallback2Adapter(TestCallback2 callback) { + super(callback); + } + + @Override + public void onSuccess(MockData.Class2 message, HttpHeaders responseHeaders) throws IOException { + MockDataClass2 dataClass = new MockDataClass2(); + dataClass.name = message.hasName() ? message.getName() : null; + dataClass.number = message.hasNumber() ? message.getNumber() : null; + callback.onSuccess(dataClass, responseHeaders); + } + } + + private static class MockUnsuccessfulResponseHandler implements HttpUnsuccessfulResponseHandler { + + MockTransport transport; + boolean returnSuccessAuthenticatedContent; + + MockUnsuccessfulResponseHandler( + MockTransport transport, boolean returnSuccessAuthenticatedContent) { + this.transport = transport; + this.returnSuccessAuthenticatedContent = returnSuccessAuthenticatedContent; + } + + @Override + public boolean handleResponse( + HttpRequest request, HttpResponse response, boolean supportsRetry) { + if (transport.returnErrorAuthenticatedContent) { + // If transport has already been set to return error content do not handle response. + return false; + } + if (returnSuccessAuthenticatedContent) { + transport.returnSuccessAuthenticatedContent = true; + } else { + transport.returnErrorAuthenticatedContent = true; + } + return true; + } + } + + private static class MockTransport extends MockHttpTransport { + + final boolean testServerError; + final boolean testAuthenticationError; + boolean returnSuccessAuthenticatedContent; + boolean returnErrorAuthenticatedContent; + final boolean testRedirect; + final boolean testBinary; + final boolean testMissingLength; + int actualCalls; + int callsBeforeSuccess; + + MockTransport( + boolean testServerError, + boolean testAuthenticationError, + boolean testRedirect, + boolean testBinary, + boolean testMissingLength) { + this.testServerError = testServerError; + this.testAuthenticationError = testAuthenticationError; + this.testRedirect = testRedirect; + this.testBinary = testBinary; + this.testMissingLength = testMissingLength; + } + + @Override + public LowLevelHttpRequest buildRequest(String name, String url) { + actualCalls++; + return new MockLowLevelHttpRequest() { + @Override + public LowLevelHttpResponse execute() throws IOException { + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setStatusCode(200); + response.addHeader("Content-Type", "multipart/mixed; boundary=" + RESPONSE_BOUNDARY); + String contentType = + testBinary ? "application/x-protobuf" : "application/json; charset=UTF-8"; + byte[] content1 = + testBinary + ? MockData.Class1.newBuilder() + .setId(TEST_ID) + .setKind(TEST_KIND) + .build() + .toByteArray() + : utf8Encode( + "{\n \"id\": \"" + + TEST_ID + + "\",\n \"kind\": \"" + + TEST_KIND.replace("\n", "\\n") + + "\"\n}"); + byte[] content2 = + testBinary + ? MockData.Class2.newBuilder() + .setName(TEST_NAME) + .setNumber(TEST_NUM) + .build() + .toByteArray() + : utf8Encode( + "{\"name\": \"" + TEST_NAME + "\", \"number\": \"" + TEST_NUM + "\"}"); + byte[] errorContent = + testBinary + ? ErrorOutput.ErrorBody.newBuilder() + .setError( + ErrorOutput.ErrorProto.newBuilder() + .setCode(ERROR_CODE) + .setMessage(ERROR_MSG) + .addErrors( + ErrorOutput.IndividualError.newBuilder() + .setDomain(ERROR_DOMAIN) + .setReason(ERROR_REASON) + .setMessage(ERROR_MSG))) + .build() + .toByteArray() + : utf8Encode( + "{\"error\": { \"errors\": [{\"domain\": \"" + + ERROR_DOMAIN + + "\"," + + "\"reason\": \"" + + ERROR_REASON + + "\", \"message\": \"" + + ERROR_MSG + + "\"}]," + + "\"code\": " + + ERROR_CODE + + ", \"message\": \"" + + ERROR_MSG + + "\"}}"); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + Writer responseContent = new OutputStreamWriter(outputStream, "ISO-8859-1"); + if (returnSuccessAuthenticatedContent || (testRedirect && actualCalls > 1)) { + if (returnSuccessAuthenticatedContent || actualCalls == callsBeforeSuccess) { + responseContent + .append("--" + RESPONSE_BOUNDARY + "\n") + .append("Content-Type: application/http\n") + .append("Content-Transfer-Encoding: binary\n") + .append("Content-ID: response-1\n\n") + .append("HTTP/1.1 200 OK\n") + .append("Content-Type: " + contentType + "\n"); + if (!testMissingLength) { + responseContent.append("Content-Length: " + content2.length + "\n"); + } + responseContent.append("\n"); + responseContent.flush(); + outputStream.write(content2); + responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); + } else { + responseContent + .append("--" + RESPONSE_BOUNDARY + "\n") + .append("Content-Type: application/http\n") + .append("Content-Transfer-Encoding: binary\n") + .append("Content-ID: response-1\n\n") + .append("HTTP/1.1 " + ERROR_CODE + " Not Found\n") + .append("Content-Type: " + contentType + "\n"); + if (!testMissingLength) { + responseContent.append("Content-Length: " + errorContent.length + "\n"); + } + responseContent.append("\n"); + responseContent.flush(); + outputStream.write(errorContent); + responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); + } + } else if (returnErrorAuthenticatedContent) { + responseContent + .append("Content-Type: application/http\n") + .append("Content-Transfer-Encoding: binary\n") + .append("Content-ID: response-1\n\n"); + responseContent + .append("HTTP/1.1 " + ERROR_CODE + " Not Found\n") + .append("Content-Type: " + contentType + "\n"); + if (!testMissingLength) { + responseContent.append("Content-Length: " + errorContent.length + "\n"); + } + responseContent.append("\n"); + responseContent.flush(); + outputStream.write(errorContent); + responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); + } else { + responseContent + .append("--" + RESPONSE_BOUNDARY + "\n") + .append("Content-Type: application/http\n") + .append("Content-Transfer-Encoding: binary\n") + .append("Content-ID: response-1\n\n") + .append("HTTP/1.1 200 OK\n") + .append("Content-Type: " + contentType + "\n"); + if (!testMissingLength) { + responseContent.append("Content-Length: " + content1.length + "\n"); + } + responseContent.append("\n"); + responseContent.flush(); + outputStream.write(content1); + responseContent + .append("\n--" + RESPONSE_BOUNDARY + "\n") + .append("Content-Type: application/http\n") + .append("Content-Transfer-Encoding: binary\n") + .append("Content-ID: response-2\n\n"); + + if (testServerError) { + responseContent + .append("HTTP/1.1 " + ERROR_CODE + " Not Found\n") + .append("Content-Type: " + contentType + "\n"); + if (!testMissingLength) { + responseContent.append("Content-Length: " + errorContent.length + "\n"); + } + responseContent.append("\n"); + responseContent.flush(); + outputStream.write(errorContent); + responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); + } else if (testAuthenticationError) { + responseContent + .append("HTTP/1.1 401 Unauthorized\n") + .append("Content-Type: application/json; charset=UTF-8\n\n") + .append("--" + RESPONSE_BOUNDARY + "--\n\n"); + } else if (testRedirect && actualCalls == 1) { + responseContent + .append("HTTP/1.1 301 MovedPermanently\n") + .append("Content-Type: " + contentType + "\n") + .append("Location: http://redirect/location\n\n") + .append("--" + RESPONSE_BOUNDARY + "--\n\n"); + } else { + responseContent + .append("HTTP/1.1 200 OK\n") + .append("Content-Type: " + contentType + "\n"); + if (!testMissingLength) { + responseContent.append("Content-Length: " + content2.length + "\n"); + } + responseContent.append("\n"); + responseContent.flush(); + outputStream.write(content2); + responseContent.append("\n--" + RESPONSE_BOUNDARY + "--\n\n"); + } + } + responseContent.flush(); + response.setContent(outputStream.toByteArray()); + return response; + } + + // Short-hand to encode a String as a UTF-8 byte array + private byte[] utf8Encode(String string) { + return Charsets.UTF_8.encode(string).array(); + } + }; + } + } + + private static class MockCredential implements HttpRequestInitializer, HttpExecuteInterceptor { + + boolean initializerCalled = false; + boolean interceptorCalled = false; + + MockCredential() {} + + @Override + public void initialize(HttpRequest request) { + request.setInterceptor(this); + initializerCalled = true; + } + + @Override + public void intercept(HttpRequest request) { + interceptorCalled = true; + } + } + + private BatchRequest getBatchPopulatedWithRequests( + boolean testServerError, + boolean testAuthenticationError, + boolean returnSuccessAuthenticatedContent, + boolean testRedirect, + boolean testBinary, + boolean testMissingLength) + throws IOException { + transport = + new MockTransport( + testServerError, testAuthenticationError, testRedirect, testBinary, testMissingLength); + MockGoogleClient client = + new MockGoogleClient.Builder(transport, ROOT_URL, SERVICE_PATH, null, null) + .setApplicationName("Test Application") + .build(); + MockGoogleClientRequest jsonHttpRequest1 = + new MockGoogleClientRequest(client, METHOD1, URI_TEMPLATE1, null, String.class); + MockGoogleClientRequest jsonHttpRequest2 = + new MockGoogleClientRequest(client, METHOD2, URI_TEMPLATE2, null, String.class); + credential = new MockCredential(); + + ObjectParser parser = + testBinary ? new ProtoObjectParser() : new JsonObjectParser(new GsonFactory()); + BatchRequest batchRequest = + new BatchRequest(transport, credential).setBatchUrl(new GenericUrl(TEST_BATCH_URL)); + HttpRequest request1 = jsonHttpRequest1.buildHttpRequest(); + request1.setParser(parser); + HttpRequest request2 = jsonHttpRequest2.buildHttpRequest(); + request2.setParser(parser); + if (testAuthenticationError) { + request2.setUnsuccessfulResponseHandler( + new MockUnsuccessfulResponseHandler(transport, returnSuccessAuthenticatedContent)); + } + + if (testBinary) { + batchRequest.queue( + request1, + MockData.Class1.class, + ErrorOutput.ErrorBody.class, + new TestCallback1Adapter(callback1)); + batchRequest.queue( + request2, + MockData.Class2.class, + ErrorOutput.ErrorBody.class, + new TestCallback2Adapter(callback2)); + } else { + batchRequest.queue(request1, MockDataClass1.class, GoogleJsonErrorContainer.class, callback1); + batchRequest.queue(request2, MockDataClass2.class, GoogleJsonErrorContainer.class, callback2); + } + return batchRequest; + } + + public void testQueueDatastructures() throws Exception { + BatchRequest batchRequest = + getBatchPopulatedWithRequests(false, false, false, false, false, false); + List> requestInfos = batchRequest.requestInfos; + + // Assert that the expected objects are queued. + assertEquals(2, requestInfos.size()); + assertEquals(MockDataClass1.class, requestInfos.get(0).dataClass); + assertEquals(callback1, requestInfos.get(0).callback); + assertEquals(MockDataClass2.class, requestInfos.get(1).dataClass); + assertEquals(callback2, requestInfos.get(1).callback); + // Assert that the requests in the queue are as expected. + assertEquals( + ROOT_URL + SERVICE_PATH + URI_TEMPLATE1, requestInfos.get(0).request.getUrl().build()); + assertEquals( + ROOT_URL + SERVICE_PATH + URI_TEMPLATE2, requestInfos.get(1).request.getUrl().build()); + assertEquals(METHOD1, requestInfos.get(0).request.getRequestMethod()); + assertEquals(METHOD2, requestInfos.get(1).request.getRequestMethod()); + } + + public void testExecute() throws IOException { + BatchRequest batchRequest = + getBatchPopulatedWithRequests(false, false, false, false, false, false); + batchRequest.execute(); + // Assert callbacks have been invoked. + assertEquals(1, callback1.successCalls); + assertEquals(1, callback2.successCalls); + assertEquals(0, callback2.failureCalls); + // Assert requestInfos is empty after execute. + assertTrue(batchRequest.requestInfos.isEmpty()); + } + + public void testExecuteWithError() throws IOException { + BatchRequest batchRequest = + getBatchPopulatedWithRequests(true, false, false, false, false, false); + batchRequest.execute(); + // Assert callbacks have been invoked. + assertEquals(1, callback1.successCalls); + assertEquals(0, callback2.successCalls); + assertEquals(1, callback2.failureCalls); + // Assert requestInfos is empty after execute. + assertTrue(batchRequest.requestInfos.isEmpty()); + // Assert transport called expected number of times. + assertEquals(1, transport.actualCalls); + } + + public void testExecuteWithVoidCallback() throws Exception { + subTestExecuteWithVoidCallback(false); + // Assert callbacks have been invoked. + assertEquals(1, callback1.successCalls); + assertEquals(1, callback3.successCalls); + assertEquals(0, callback3.failureCalls); + } + + public void testExecuteWithVoidCallbackError() throws Exception { + subTestExecuteWithVoidCallback(true); + // Assert callbacks have been invoked. + assertEquals(1, callback1.successCalls); + assertEquals(0, callback3.successCalls); + assertEquals(1, callback3.failureCalls); + } + + public void subTestExecuteWithVoidCallback(boolean testServerError) throws IOException { + MockTransport transport = new MockTransport(testServerError, false, false, false, false); + MockGoogleClient client = + new MockGoogleClient.Builder(transport, ROOT_URL, SERVICE_PATH, null, null) + .setApplicationName("Test Application") + .build(); + MockGoogleClientRequest jsonHttpRequest1 = + new MockGoogleClientRequest(client, METHOD1, URI_TEMPLATE1, null, String.class); + MockGoogleClientRequest jsonHttpRequest2 = + new MockGoogleClientRequest(client, METHOD2, URI_TEMPLATE2, null, String.class); + ObjectParser parser = new JsonObjectParser(new GsonFactory()); + BatchRequest batchRequest = + new BatchRequest(transport, null).setBatchUrl(new GenericUrl(TEST_BATCH_URL)); + HttpRequest request1 = jsonHttpRequest1.buildHttpRequest(); + request1.setParser(parser); + HttpRequest request2 = jsonHttpRequest2.buildHttpRequest(); + request2.setParser(parser); + batchRequest.queue(request1, MockDataClass1.class, GoogleJsonErrorContainer.class, callback1); + batchRequest.queue(request2, Void.class, Void.class, callback3); + batchRequest.execute(); + // Assert transport called expected number of times. + assertEquals(1, transport.actualCalls); + } + + public void testExecuteWithAuthenticationErrorThenSuccessCallback() throws Exception { + BatchRequest batchRequest = + getBatchPopulatedWithRequests(false, true, true, false, false, false); + batchRequest.execute(); + // Assert callbacks have been invoked. + assertEquals(1, callback1.successCalls); + assertEquals(1, callback2.successCalls); + assertEquals(0, callback2.failureCalls); + // Assert transport called expected number of times. + assertEquals(2, transport.actualCalls); + // Assert requestInfos is empty after execute. + assertTrue(batchRequest.requestInfos.isEmpty()); + } + + public void testExecuteWithAuthenticationErrorThenErrorCallback() throws Exception { + BatchRequest batchRequest = + getBatchPopulatedWithRequests(false, true, false, false, false, false); + batchRequest.execute(); + // Assert callbacks have been invoked. + assertEquals(1, callback1.successCalls); + assertEquals(0, callback2.successCalls); + assertEquals(1, callback2.failureCalls); + // Assert transport called expected number of times. + assertEquals(2, transport.actualCalls); + // Assert requestInfos is empty after execute. + assertTrue(batchRequest.requestInfos.isEmpty()); + } + + public void testInterceptor() throws Exception { + BatchRequest batchRequest = + getBatchPopulatedWithRequests(true, false, false, false, false, false); + batchRequest.execute(); + // Assert the top-level request initializer is called. + assertTrue(credential.initializerCalled); + assertTrue(credential.interceptorCalled); + } + + public void testRedirect() throws Exception { + BatchRequest batchRequest = + getBatchPopulatedWithRequests(false, false, false, true, false, false); + transport.callsBeforeSuccess = 2; + batchRequest.execute(); + // Assert transport called expected number of times. + assertEquals(2, transport.actualCalls); + // Assert requestInfos is empty after execute. + assertTrue(batchRequest.requestInfos.isEmpty()); + } + + public void testExecute_checkWriteTo() throws Exception { + String request1Method = HttpMethods.POST; + String request1Url = "http://test/dummy/url1"; + String request1ContentType = "application/json"; + String request1Content = "{\"data\":{\"foo\":{\"v1\":{}}}}"; + + String request2Method = HttpMethods.GET; + String request2Url = "http://test/dummy/url2"; + + // MIME content boundaries are not reproducible. + StringBuilder part1 = new StringBuilder(); + part1.append("Content-Length: 118\r\n"); + part1.append("Content-Type: application/http\r\n"); + part1.append("content-id: 1\r\n"); + part1.append("content-transfer-encoding: binary\r\n"); + part1.append("\r\n"); + part1.append("POST http://test/dummy/url1 HTTP/1.1\r\n"); + part1.append("Content-Length: 26\r\n"); + part1.append("Content-Type: " + request1ContentType + "\r\n"); + part1.append("\r\n"); + part1.append(request1Content + "\r\n"); + part1.append("--__END_OF_PART__"); + String expected1 = part1.toString(); + + StringBuilder part2 = new StringBuilder(); + part2.append("Content-Length: 39\r\n"); + part2.append("Content-Type: application/http\r\n"); + part2.append("content-id: 2\r\n"); + part2.append("content-transfer-encoding: binary\r\n"); + part2.append("\r\n"); + part2.append("GET http://test/dummy/url2 HTTP/1.1\r\n"); + part2.append("\r\n"); + part2.append("\r\n"); + part2.append("--__END_OF_PART__"); + String expected2 = part2.toString(); + + MockHttpTransport transport = new MockHttpTransport(); + HttpRequest request1 = + transport + .createRequestFactory() + .buildRequest( + request1Method, + new GenericUrl(request1Url), + new ByteArrayContent(request1ContentType, request1Content.getBytes(UTF_8))); + HttpRequest request2 = + transport + .createRequestFactory() + .buildRequest(request2Method, new GenericUrl(request2Url), null); + subtestExecute_checkWriteTo(expected1, expected2, request1, request2); + } + + private void subtestExecute_checkWriteTo( + final String part1, final String part2, HttpRequest... requests) throws IOException { + + MockHttpTransport transport = + new MockHttpTransport() { + + @Override + public LowLevelHttpRequest buildRequest(String method, String url) { + return new MockLowLevelHttpRequest(url) { + + @Override + public LowLevelHttpResponse execute() throws IOException { + assertTrue( + getContentType().startsWith("multipart/mixed; boundary=__END_OF_PART__")); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + getStreamingContent().writeTo(out); + String actual = out.toString("UTF-8"); + assertTrue(actual + "\n does not contain \n" + part1, actual.contains(part1)); + assertTrue(actual.contains(part2)); + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setStatusCode(200); + response.addHeader( + "Content-Type", "multipart/mixed; boundary=" + RESPONSE_BOUNDARY); + String content2 = + "{\"name\": \"" + TEST_NAME + "\", \"number\": \"" + TEST_NUM + "\"}"; + StringBuilder responseContent = new StringBuilder(); + responseContent + .append("--" + RESPONSE_BOUNDARY + "\n") + .append("Content-Type: application/http\n") + .append("Content-Transfer-Encoding: binary\n") + .append("Content-ID: response-1\n\n") + .append("HTTP/1.1 200 OK\n") + .append("Content-Type: application/json; charset=UTF-8\n") + .append("Content-Length: " + content2.length() + "\n\n") + .append(content2 + "\n\n") + .append("--" + RESPONSE_BOUNDARY + "--\n\n"); + response.setContent(responseContent.toString()); + return response; + } + }; + } + }; + + BatchRequest batchRequest = new BatchRequest(transport, null); + BatchCallback callback = + new BatchCallback() { + + @Override + public void onSuccess(Void t, HttpHeaders responseHeaders) {} + + @Override + public void onFailure(Void e, HttpHeaders responseHeaders) {} + }; + for (HttpRequest request : requests) { + batchRequest.queue(request, Void.class, Void.class, callback); + } + batchRequest.execute(); + } + + public void testExecute_checkWriteToNoHeaders() throws IOException { + MockHttpTransport transport = new MockHttpTransport(); + HttpRequest request = + transport + .createRequestFactory() + .buildPostRequest( + HttpTesting.SIMPLE_GENERIC_URL, + new HttpContent() { + + @Override + public long getLength() { + return -1; + } + + @Override + public String getType() { + return null; + } + + @Override + public void writeTo(OutputStream out) {} + + @Override + public boolean retrySupported() { + return true; + } + }); + String expected = + new StringBuilder() + .append("Content-Length: 36\r\n") + .append("Content-Type: application/http\r\n") + .append("content-id: 1\r\n") + .append("content-transfer-encoding: binary\r\n") + .append("\r\n") + .append("POST http://google.com/ HTTP/1.1\r\n") + .append("\r\n") + .append("\r\n") + .append("--__END_OF_PART__") + .toString(); + subtestExecute_checkWriteTo(expected, expected, request); + } + + public void testProtoExecute() throws IOException { + BatchRequest batchRequest = + getBatchPopulatedWithRequests(false, false, false, false, true, false); + batchRequest.execute(); + // Assert callbacks have been invoked. + assertEquals(1, callback1.successCalls); + assertEquals(1, callback2.successCalls); + assertEquals(0, callback2.failureCalls); + // Assert requestInfos is empty after execute. + assertTrue(batchRequest.requestInfos.isEmpty()); + } + + public void testProtoExecuteWithError() throws IOException { + BatchRequest batchRequest = + getBatchPopulatedWithRequests(true, false, false, false, true, false); + batchRequest.execute(); + // Assert callbacks have been invoked. + assertEquals(1, callback1.successCalls); + assertEquals(0, callback2.successCalls); + assertEquals(1, callback2.failureCalls); + // Assert requestInfos is empty after execute. + assertTrue(batchRequest.requestInfos.isEmpty()); + // Assert transport called expected number of times. + assertEquals(1, transport.actualCalls); + } + + public void testProtoExecuteWithoutLength() throws IOException { + BatchRequest batchRequest = + getBatchPopulatedWithRequests(false, false, false, false, true, true); + batchRequest.execute(); + // Assert callbacks have been invoked. + assertEquals(1, callback1.successCalls); + assertEquals(1, callback2.successCalls); + assertEquals(0, callback2.failureCalls); + // Assert requestInfos is empty after execute. + assertTrue(batchRequest.requestInfos.isEmpty()); + } +} From a9a64dbdc7b833b002892c0466b17f16352c3c30 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Mon, 4 Mar 2024 17:28:35 -0500 Subject: [PATCH 11/24] chore: validateUniverseDomain does not return bool --- .../googleapis/services/AbstractGoogleClient.java | 3 +-- .../services/AbstractGoogleClientTest.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) 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 be7524d06..f5c364a2d 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 @@ -149,7 +149,7 @@ private String determineEndpoint(Builder builder) { * @throws IllegalStateException if the configured Universe Domain does not match the Universe * Domain in the Credentials */ - public boolean hasValidUniverseDomain() { + public void validateUniverseDomain() { String expectedUniverseDomain; try { if (!(httpRequestInitializer instanceof HttpCredentialsAdapter)) { @@ -171,7 +171,6 @@ public boolean hasValidUniverseDomain() { throw new IllegalStateException( "Unable to retrieve the Universe Domain from the Credentials.", e); } - return true; } /** 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 3340a33e1..38d81ad0e 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 @@ -296,7 +296,9 @@ public void validateUniverseDomain_validUniverseDomain() throws IOException { TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, httpCredentialsAdapter) .setApplicationName(applicationName) .build(); - assertTrue(client.hasValidUniverseDomain()); + + // Nothing throws + client.validateUniverseDomain(); } @Test @@ -318,7 +320,7 @@ public void validateUniverseDomain_invalidUniverseDomain() throws IOException { new ThrowingRunnable() { @Override public void run() { - client.hasValidUniverseDomain(); + client.validateUniverseDomain(); } }); } @@ -338,7 +340,9 @@ public void validateUniverseDomain_notUsingHttpCredentialsAdapter_defaultUnivers new TestHttpRequestInitializer()) .setApplicationName(applicationName) .build(); - assertTrue(client.hasValidUniverseDomain()); + + // Nothing throws + client.validateUniverseDomain(); } @Test @@ -363,7 +367,7 @@ public void validateUniverseDomain_notUsingHttpCredentialsAdapter_customUniverse new ThrowingRunnable() { @Override public void run() { - client.hasValidUniverseDomain(); + client.validateUniverseDomain(); } }); } From 0f905c0695a17fba4a9eb211aa58492d799673df Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Mon, 4 Mar 2024 17:32:56 -0500 Subject: [PATCH 12/24] chore: Move validateUniverseDomain() to parent impl --- .../api/client/googleapis/services/AbstractGoogleClient.java | 1 + 1 file changed, 1 insertion(+) 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 f5c364a2d..1f5d80a30 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 @@ -268,6 +268,7 @@ public ObjectParser getObjectParser() { * @param httpClientRequest Google client request type */ protected void initialize(AbstractGoogleClientRequest httpClientRequest) throws IOException { + validateUniverseDomain(); if (getGoogleClientRequestInitializer() != null) { getGoogleClientRequestInitializer().initialize(httpClientRequest); } From b2088be22ded3b95ba398d4ebbd79ebd6233eeec Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 5 Mar 2024 12:05:10 -0500 Subject: [PATCH 13/24] chore: Update javadocs --- .../client/googleapis/services/AbstractGoogleClient.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 1f5d80a30..9e11b83ec 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 @@ -117,7 +117,7 @@ private String determineUniverseDomain(Builder builder) { } /** - * Resolve the endpoint based on user configurations. If the user as configured a custom rootUrl, + * 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. */ @@ -142,9 +142,10 @@ private String determineEndpoint(Builder builder) { /** * 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 HttpCredentialsAdapter from the google-auth-library. If the - * HttpRequestInitializer is not used, the configured Universe Domain is validated against the - * Google Default Universe (GDU): `googleapis.com`. + * HttpRequestInitializer is of the {@see HttpCredentialsAdapter} + * from the google-auth-library. If the HttpRequestInitializer is not used, the configured + * Universe Domain is validated against the Google Default Universe (GDU): `googleapis.com`. * * @throws IllegalStateException if the configured Universe Domain does not match the Universe * Domain in the Credentials From fd1182a427b4be1428eb28f63dadaee7aa12068c Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 5 Mar 2024 21:59:53 -0500 Subject: [PATCH 14/24] chore: Add Env Var tests for GOOGLE_CLOUD_UNIVERSE_DOMAIN --- .github/workflows/ci.yaml | 7 ++++ google-api-client/pom.xml | 25 +++++++++++++ .../services/AbstractGoogleClient.java | 4 +-- .../services/AbstractGoogleClientTest.java | 35 +++++++++++++++++++ pom.xml | 18 ++++++++++ 5 files changed, 87 insertions(+), 2 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 cb2b2f093..126adeaeb 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 + + @@ -193,4 +201,21 @@ test + + + + envVarTest + + + + org.apache.maven.plugins + maven-surefire-plugin + + AbstractGoogleClientTest#testGoogleClientBuilder_noCustomEndpoint_universeDomainEnvVar+testGoogleClientBuilder_customEndpoint_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 9e11b83ec..b117d3f83 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 @@ -134,9 +134,9 @@ private String determineEndpoint(Builder builder) { return builder.rootUrl; } if (mtlsEnabled) { - return "https://" + builder.serviceName + ".mtls." + universeDomain; + return "https://" + builder.serviceName + ".mtls." + universeDomain + "/"; } - return "https://" + builder.serviceName + "." + universeDomain; + return "https://" + builder.serviceName + "." + universeDomain + "/"; } /** 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 38d81ad0e..b8fd12ab3 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 @@ -208,6 +208,41 @@ public void testGoogleClientBuilder_customEndpoint_customUniverseDomain() { 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/"; diff --git a/pom.xml b/pom.xml index c98c471b7..ca42012a0 100644 --- a/pom.xml +++ b/pom.xml @@ -660,5 +660,23 @@ + + + envVarTest + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + **/*.java + + + + + + From 3edfe45bd1522d03d1d436caea2f41f1bac44c27 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 5 Mar 2024 22:02:52 -0500 Subject: [PATCH 15/24] chore: Fix lint issues --- .../services/AbstractGoogleClientTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 b8fd12ab3..e160ea73a 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 @@ -218,9 +218,9 @@ public void testGoogleClientBuilder_noCustomUniverseDomain_universeDomainEnvVar( String expectedRootUrl = "https://test.random.com/"; AbstractGoogleClient client = - new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null) - .setApplicationName(applicationName) - .build(); + new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null) + .setApplicationName(applicationName) + .build(); assertEquals(expectedRootUrl, client.getRootUrl()); assertEquals(envVarUniverseDomain, client.getUniverseDomain()); } @@ -235,10 +235,10 @@ public void testGoogleClientBuilder_customUniverseDomain_universeDomainEnvVar() String expectedRootUrl = "https://test.test.com/"; AbstractGoogleClient client = - new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null) - .setApplicationName(applicationName) - .setUniverseDomain(customUniverseDomain) - .build(); + new MockGoogleClient.Builder(TRANSPORT, rootUrl, servicePath, JSON_OBJECT_PARSER, null) + .setApplicationName(applicationName) + .setUniverseDomain(customUniverseDomain) + .build(); assertEquals(expectedRootUrl, client.getRootUrl()); assertEquals(customUniverseDomain, client.getUniverseDomain()); } From bba201a767511eee19acad0f9e37cb5087ad909d Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 5 Mar 2024 22:05:42 -0500 Subject: [PATCH 16/24] chore: Fix env var tests --- google-api-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-api-client/pom.xml b/google-api-client/pom.xml index 126adeaeb..771103a34 100644 --- a/google-api-client/pom.xml +++ b/google-api-client/pom.xml @@ -211,7 +211,7 @@ org.apache.maven.plugins maven-surefire-plugin - AbstractGoogleClientTest#testGoogleClientBuilder_noCustomEndpoint_universeDomainEnvVar+testGoogleClientBuilder_customEndpoint_universeDomainEnvVar + AbstractGoogleClientTest#testGoogleClientBuilder_noCustomUniverseDomain_universeDomainEnvVar+testGoogleClientBuilder_customUniverseDomain_universeDomainEnvVar From 7a0b3fad4e289172f7d22e1e6a6200dd556c6da8 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Wed, 6 Mar 2024 11:56:50 -0500 Subject: [PATCH 17/24] chore: Update logic for isUserConfiguredEndpoint --- .../googleapis/services/AbstractGoogleClient.java | 15 +++++++++++---- .../services/AbstractGoogleClientRequestTest.java | 4 ++-- 2 files changed, 13 insertions(+), 6 deletions(-) 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 b117d3f83..3de0ab057 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 @@ -413,12 +413,17 @@ public abstract static class Builder { /** * 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. Allow user configuration via {@link - * #setRootUrl(String)}, which we would need to track. + * because the rootUrl is set in the Builder's constructor. , * - *

By default, it is set as false + *

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 in references the Google Default Universe (GDU). Any rootUrl value that is not set in + * the GDU is a user configured endpoint. */ - boolean isUserConfiguredEndpoint = false; + boolean isUserConfiguredEndpoint; /** The parsed serviceName value from the rootUrl from the Discovery Doc. */ String serviceName; @@ -444,6 +449,8 @@ protected Builder( this.servicePath = normalizeServicePath(servicePath); this.httpRequestInitializer = httpRequestInitializer; this.serviceName = parseServiceName(rootUrl); + this.isUserConfiguredEndpoint = + !this.rootUrl.endsWith(Credentials.GOOGLE_DEFAULT_UNIVERSE + "/"); } /** diff --git a/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientRequestTest.java b/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientRequestTest.java index 317cf1f98..1eefdb139 100644 --- a/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientRequestTest.java +++ b/google-api-client/src/test/java/com/google/api/client/googleapis/services/AbstractGoogleClientRequestTest.java @@ -45,8 +45,8 @@ */ public class AbstractGoogleClientRequestTest extends TestCase { - private static final String ROOT_URL = "https://www.googleapis.com/"; - private static final String SERVICE_PATH = "test/path/v1/"; + private static final String ROOT_URL = "https://www.googleapis.com/test/"; + private static final String SERVICE_PATH = "path/v1/"; private static final String URI_TEMPLATE = "tests/{testId}"; private static final JsonFactory JSON_FACTORY = new GsonFactory(); private static final JsonObjectParser JSON_OBJECT_PARSER = new JsonObjectParser(JSON_FACTORY); From 83612b64e812cff0014a09051afb2bc47f41ab21 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Wed, 6 Mar 2024 14:18:39 -0500 Subject: [PATCH 18/24] chore: Throw IOException on validateUniverseDomain --- .../services/AbstractGoogleClient.java | 38 ++++++++----------- .../services/AbstractGoogleClientTest.java | 11 +++--- 2 files changed, 22 insertions(+), 27 deletions(-) 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 3de0ab057..beb2c9260 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 @@ -147,30 +147,24 @@ private String determineEndpoint(Builder builder) { * from the google-auth-library. If the HttpRequestInitializer is not used, the configured * Universe Domain is validated against the Google Default Universe (GDU): `googleapis.com`. * - * @throws IllegalStateException if the configured Universe Domain does not match the Universe - * Domain in the Credentials + * @throws IOException if the configured Universe Domain does not match the Universe Domain in the + * Credentials or there is an error reading the Universe Domain from the credentials */ - public void validateUniverseDomain() { + public void validateUniverseDomain() throws IOException { String expectedUniverseDomain; - try { - if (!(httpRequestInitializer instanceof HttpCredentialsAdapter)) { - expectedUniverseDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; - } else { - Credentials credentials = - ((HttpCredentialsAdapter) httpRequestInitializer).getCredentials(); - 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)); - } - } catch (IOException e) { - throw new IllegalStateException( - "Unable to retrieve the Universe Domain from the Credentials.", e); + if (!(httpRequestInitializer instanceof HttpCredentialsAdapter)) { + expectedUniverseDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; + } else { + Credentials credentials = ((HttpCredentialsAdapter) httpRequestInitializer).getCredentials(); + expectedUniverseDomain = credentials.getUniverseDomain(); + } + if (!expectedUniverseDomain.equals(getUniverseDomain())) { + throw new IOException( + 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)); } } 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 e160ea73a..8676ad98d 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 @@ -351,17 +351,18 @@ public void validateUniverseDomain_invalidUniverseDomain() throws IOException { .setApplicationName(applicationName) .build(); assertThrows( - IllegalStateException.class, + IOException.class, new ThrowingRunnable() { @Override - public void run() { + public void run() throws IOException { client.validateUniverseDomain(); } }); } @Test - public void validateUniverseDomain_notUsingHttpCredentialsAdapter_defaultUniverseDomain() { + public void validateUniverseDomain_notUsingHttpCredentialsAdapter_defaultUniverseDomain() + throws IOException { String rootUrl = "https://test.googleapis.com/"; String applicationName = "Test Application"; String servicePath = "test/"; @@ -398,10 +399,10 @@ public void validateUniverseDomain_notUsingHttpCredentialsAdapter_customUniverse .setUniverseDomain(universeDomain) .build(); assertThrows( - IllegalStateException.class, + IOException.class, new ThrowingRunnable() { @Override - public void run() { + public void run() throws IOException { client.validateUniverseDomain(); } }); From 1cfd93ae34f44932b135a9dafa1fa6d8698448ae Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Mon, 11 Mar 2024 12:06:15 -0400 Subject: [PATCH 19/24] chore: Address PR comments --- .../services/AbstractGoogleClient.java | 11 ++++- .../services/AbstractGoogleClientTest.java | 48 +++++++++++++++++-- 2 files changed, 53 insertions(+), 6 deletions(-) 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 beb2c9260..5b4fea0ec 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 @@ -25,6 +25,7 @@ import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.util.logging.Logger; +import java.util.regex.Pattern; /** * Abstract thread-safe Google client. @@ -405,6 +406,13 @@ public abstract static class Builder { /** User configured Universe Domain. Defaults to `googleapis.com`. */ String universeDomain; + /** + * Regex pattern to check if the URL passed in matches the default endpoint confgured 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. , @@ -443,8 +451,7 @@ protected Builder( this.servicePath = normalizeServicePath(servicePath); this.httpRequestInitializer = httpRequestInitializer; this.serviceName = parseServiceName(rootUrl); - this.isUserConfiguredEndpoint = - !this.rootUrl.endsWith(Credentials.GOOGLE_DEFAULT_UNIVERSE + "/"); + this.isUserConfiguredEndpoint = !defaultEndpointRegex.matcher(this.rootUrl).matches(); } /** 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 8676ad98d..6145f7e69 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 @@ -289,6 +289,15 @@ 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 = @@ -299,21 +308,52 @@ public void testParseServiceName_mtlsRootUrl() { } @Test - public void testParseServiceName_nonMtlsRootUrl() { + 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"); - assertEquals(clientBuilder.getServiceName(), "random"); + assertFalse(clientBuilder.isUserConfiguredEndpoint); } @Test - public void testParseServiceName_nonGDURootUrl() { + 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"); - assertNull(clientBuilder.getServiceName()); + 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 From 3fcb3c8f7d2204ec577fae4d0988c8dd088e63f6 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Mon, 11 Mar 2024 23:26:14 -0400 Subject: [PATCH 20/24] chore: Address PR comments --- .../services/AbstractGoogleClient.java | 41 +++++-------------- .../services/AbstractGoogleClientTest.java | 4 +- 2 files changed, 13 insertions(+), 32 deletions(-) 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 5b4fea0ec..0d32233d0 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 @@ -25,6 +25,7 @@ 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; /** @@ -148,8 +149,9 @@ private String determineEndpoint(Builder builder) { * from the google-auth-library. If the HttpRequestInitializer is not used, the configured * Universe Domain is validated against the Google Default Universe (GDU): `googleapis.com`. * - * @throws IOException if the configured Universe Domain does not match the Universe Domain in the - * Credentials or there is an error reading the Universe Domain from the credentials + * @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 { String expectedUniverseDomain; @@ -160,7 +162,7 @@ public void validateUniverseDomain() throws IOException { expectedUniverseDomain = credentials.getUniverseDomain(); } if (!expectedUniverseDomain.equals(getUniverseDomain())) { - throw new IOException( + 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" @@ -411,7 +413,7 @@ public abstract static class Builder { * discovery doc. Follows the format of `https://{serviceName}(.mtls).googleapis.com/` */ Pattern defaultEndpointRegex = - Pattern.compile("https://[a-zA-Z]*(\\.mtls)?\\.googleapis.com/?"); + Pattern.compile("https://([a-zA-Z]*)(\\.mtls)?\\.googleapis.com/?"); /** * Whether the user has configured an endpoint via {@link #setRootUrl(String)}. This is added in @@ -450,31 +452,10 @@ protected Builder( this.rootUrl = normalizeRootUrl(rootUrl); this.servicePath = normalizeServicePath(servicePath); this.httpRequestInitializer = httpRequestInitializer; - this.serviceName = parseServiceName(rootUrl); - this.isUserConfiguredEndpoint = !defaultEndpointRegex.matcher(this.rootUrl).matches(); - } - - /** - * This is intended to invoked once on the initial Builder's constructor call. This parses the - * rootUrl value (set from the Discovery Doc) to use for the serviceName. The serviceName is - * used to construct an endpoint when the user passes in a custom Universe Domain value. - * - *

The roolUrl from the Discovery Docs will always follow the format of - * https://{serviceName}(.mtls).googleapis.com/ - */ - private String parseServiceName(String rootUrl) { - // len of "https://" - int startIndex = 8; - if (rootUrl.contains(".mtls.")) { - return rootUrl.substring(startIndex, rootUrl.indexOf(".mtls")); - } else if (rootUrl.contains(".googleapis.com")) { - return rootUrl.substring(startIndex, rootUrl.indexOf(".googleapis.com")); - } else { - // Return null to not break behavior for any non-google users or any use - // case without a discovery doc. There may be certain use cases for this - // as the Builder's constructor is only protected scope - return null; - } + Matcher matcher = defaultEndpointRegex.matcher(rootUrl); + boolean matches = matcher.matches(); + this.isUserConfiguredEndpoint = !matches; + this.serviceName = matches ? matcher.group(1) : null; } /** Builds a new instance of {@link AbstractGoogleClient}. */ @@ -665,7 +646,7 @@ public Builder setSuppressAllChecks(boolean suppressAllChecks) { * Sets the user configured Universe Domain value. This value will be used to try and construct * the endpoint to connect to GCP services. * - * @throws IllegalStateException if universeDomain is passed in with an empty string ("") + * @throws IllegalArgumentException if universeDomain is passed in with an empty string ("") */ public Builder setUniverseDomain(String universeDomain) { if (universeDomain != null && universeDomain.isEmpty()) { 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 6145f7e69..6c5b45db0 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 @@ -391,7 +391,7 @@ public void validateUniverseDomain_invalidUniverseDomain() throws IOException { .setApplicationName(applicationName) .build(); assertThrows( - IOException.class, + IllegalStateException.class, new ThrowingRunnable() { @Override public void run() throws IOException { @@ -439,7 +439,7 @@ public void validateUniverseDomain_notUsingHttpCredentialsAdapter_customUniverse .setUniverseDomain(universeDomain) .build(); assertThrows( - IOException.class, + IllegalStateException.class, new ThrowingRunnable() { @Override public void run() throws IOException { From 8d11d10777f1fa0b49a20b5db79055ea22cb9ff4 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 12 Mar 2024 12:15:15 -0400 Subject: [PATCH 21/24] chore: Address PR comments --- .../services/AbstractGoogleClient.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 0d32233d0..7261799da 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 @@ -154,12 +154,12 @@ private String determineEndpoint(Builder builder) { * Domain in the Credentials */ public void validateUniverseDomain() throws IOException { - String expectedUniverseDomain; - if (!(httpRequestInitializer instanceof HttpCredentialsAdapter)) { - expectedUniverseDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; - } else { + String expectedUniverseDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; + if (httpRequestInitializer instanceof HttpCredentialsAdapter) { Credentials credentials = ((HttpCredentialsAdapter) httpRequestInitializer).getCredentials(); - expectedUniverseDomain = credentials.getUniverseDomain(); + if (credentials != null) { + expectedUniverseDomain = credentials.getUniverseDomain(); + } } if (!expectedUniverseDomain.equals(getUniverseDomain())) { throw new IllegalStateException( @@ -409,7 +409,7 @@ public abstract static class Builder { String universeDomain; /** - * Regex pattern to check if the URL passed in matches the default endpoint confgured from a + * 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 = @@ -424,8 +424,8 @@ public abstract static class Builder { * done via {@link #setRootUrl(String)}. * *

For other uses cases that touch this Builder's constructor directly, check if the rootUrl - * passed in references the Google Default Universe (GDU). Any rootUrl value that is not set in - * the GDU is a user configured endpoint. + * passed matches the default endpoint regex. If it doesn't match, it is a user configured + * endpoint. */ boolean isUserConfiguredEndpoint; From 46781b1725508e03cd95f6b010972c1ba886a2fd Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 12 Mar 2024 12:30:26 -0400 Subject: [PATCH 22/24] chore: Address PR comments --- .../services/AbstractGoogleClient.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) 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 7261799da..64e808670 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 @@ -125,13 +125,12 @@ private String determineUniverseDomain(Builder builder) { */ private String determineEndpoint(Builder builder) { boolean mtlsEnabled = builder.rootUrl.contains(".mtls."); - // mTLS configurations is not compatible with anything other than the GDU if (mtlsEnabled && !universeDomain.equals(Credentials.GOOGLE_DEFAULT_UNIVERSE)) { - throw new IllegalArgumentException( + 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 configuration passed in. + // the rootUrl as this was custom rootUrl passed in. if (builder.isUserConfiguredEndpoint || builder.serviceName == null) { return builder.rootUrl; } @@ -146,9 +145,11 @@ private String determineEndpoint(Builder builder) { * uses the HttpRequestInitializer to get the Credentials and is enforced that the * HttpRequestInitializer is of the {@see HttpCredentialsAdapter} - * from the google-auth-library. If the HttpRequestInitializer is not used, the configured + * from the google-auth-library. If the HttpCredentialsAdapter is not used, the configured * Universe Domain is validated against the Google Default Universe (GDU): `googleapis.com`. * + *

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 @@ -157,9 +158,9 @@ public void validateUniverseDomain() throws IOException { String expectedUniverseDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; if (httpRequestInitializer instanceof HttpCredentialsAdapter) { Credentials credentials = ((HttpCredentialsAdapter) httpRequestInitializer).getCredentials(); - if (credentials != null) { - expectedUniverseDomain = credentials.getUniverseDomain(); - } + // No need for a null check as HttpCredentialsAdapter cannot be initialized will null + // Credentials + expectedUniverseDomain = credentials.getUniverseDomain(); } if (!expectedUniverseDomain.equals(getUniverseDomain())) { throw new IllegalStateException( @@ -454,6 +455,8 @@ protected Builder( 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; } From 9c8084cfc498a9ec0df219c5d40a42c119354467 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 12 Mar 2024 17:26:34 -0400 Subject: [PATCH 23/24] chore: Validate on HttpCredentialsAdapter --- .../services/AbstractGoogleClient.java | 24 +++++++------- .../services/AbstractGoogleClientTest.java | 31 ++----------------- 2 files changed, 13 insertions(+), 42 deletions(-) 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 64e808670..23ad5a931 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 @@ -145,8 +145,7 @@ private String determineEndpoint(Builder builder) { * uses the HttpRequestInitializer to get the Credentials and is enforced that the * HttpRequestInitializer is of the {@see HttpCredentialsAdapter} - * from the google-auth-library. If the HttpCredentialsAdapter is not used, the configured - * Universe Domain is validated against the Google Default Universe (GDU): `googleapis.com`. + * from the google-auth-library. * *

To use a non-GDU Credentials, you must use the HttpCredentialsAdapter class. * @@ -155,20 +154,19 @@ private String determineEndpoint(Builder builder) { * Domain in the Credentials */ public void validateUniverseDomain() throws IOException { - String expectedUniverseDomain = Credentials.GOOGLE_DEFAULT_UNIVERSE; if (httpRequestInitializer instanceof HttpCredentialsAdapter) { Credentials credentials = ((HttpCredentialsAdapter) httpRequestInitializer).getCredentials(); - // No need for a null check as HttpCredentialsAdapter cannot be initialized will null + // No need for a null check as HttpCredentialsAdapter cannot be initialized with null // Credentials - 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)); + 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)); + } } } 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 6c5b45db0..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 @@ -162,9 +162,9 @@ public void testGoogleClientBuilder_customUniverseDomain_mtlsUrl() { .setApplicationName(applicationName) .setUniverseDomain("random.com"); - IllegalArgumentException exception = + IllegalStateException exception = assertThrows( - IllegalArgumentException.class, + IllegalStateException.class, new ThrowingRunnable() { @Override public void run() { @@ -421,33 +421,6 @@ public void validateUniverseDomain_notUsingHttpCredentialsAdapter_defaultUnivers client.validateUniverseDomain(); } - @Test - public void validateUniverseDomain_notUsingHttpCredentialsAdapter_customUniverseDomain() { - String rootUrl = "https://test.googleapis.com/"; - String applicationName = "Test Application"; - String servicePath = "test/"; - String universeDomain = "random.com"; - - final AbstractGoogleClient client = - new MockGoogleClient.Builder( - TRANSPORT, - rootUrl, - servicePath, - JSON_OBJECT_PARSER, - new TestHttpRequestInitializer()) - .setApplicationName(applicationName) - .setUniverseDomain(universeDomain) - .build(); - assertThrows( - IllegalStateException.class, - new ThrowingRunnable() { - @Override - public void run() throws IOException { - 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"; From 94c42d19c67ecf8e4a7e3872262ef7bac3ea6796 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Wed, 13 Mar 2024 14:04:01 -0400 Subject: [PATCH 24/24] chore: Address PR comments --- .../services/AbstractGoogleClient.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) 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 23ad5a931..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 @@ -154,19 +154,20 @@ private String determineEndpoint(Builder builder) { * Domain in the Credentials */ public void validateUniverseDomain() throws IOException { - if (httpRequestInitializer instanceof HttpCredentialsAdapter) { - 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)); - } + 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)); } }