From e260b9fd5d87024e09f3a376f183ae1fbdc741fb Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Tue, 28 Jan 2020 11:56:41 -0800 Subject: [PATCH 1/6] Enabled automatic HTTP retries for FirebaseProjectManagement --- .../firebase/internal/ApiClientUtils.java | 16 ++++++++++++-- .../FirebaseProjectManagementServiceImpl.java | 17 +++++++++------ ...ebaseProjectManagementServiceImplTest.java | 21 +++++++++++-------- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/google/firebase/internal/ApiClientUtils.java b/src/main/java/com/google/firebase/internal/ApiClientUtils.java index 7506ff8ee..4620cd68a 100644 --- a/src/main/java/com/google/firebase/internal/ApiClientUtils.java +++ b/src/main/java/com/google/firebase/internal/ApiClientUtils.java @@ -43,9 +43,21 @@ public class ApiClientUtils { * @return A new {@code HttpRequestFactory} instance. */ public static HttpRequestFactory newAuthorizedRequestFactory(FirebaseApp app) { + return newAuthorizedRequestFactory(app, DEFAULT_RETRY_CONFIG); + } + + /** + * Creates a new {@code HttpRequestFactory} which provides authorization (OAuth2), timeouts and + * automatic retries. + * + * @param app {@link FirebaseApp} from which to obtain authorization credentials. + * @param retryConfig {@link RetryConfig} instance or null to disable retries. + * @return A new {@code HttpRequestFactory} instance. + */ + public static HttpRequestFactory newAuthorizedRequestFactory( + FirebaseApp app, @Nullable RetryConfig retryConfig) { HttpTransport transport = app.getOptions().getHttpTransport(); - return transport.createRequestFactory( - new FirebaseRequestInitializer(app, DEFAULT_RETRY_CONFIG)); + return transport.createRequestFactory(new FirebaseRequestInitializer(app, retryConfig)); } public static HttpRequestFactory newUnauthorizedRequestFactory(FirebaseApp app) { diff --git a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java index 72c570c6d..5d645cec1 100644 --- a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java +++ b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponseInterceptor; import com.google.api.client.util.Base64; import com.google.api.client.util.Key; @@ -32,8 +33,8 @@ import com.google.common.collect.ImmutableMap; import com.google.firebase.FirebaseApp; import com.google.firebase.ImplFirebaseTrampolines; +import com.google.firebase.internal.ApiClientUtils; import com.google.firebase.internal.CallableOperation; -import com.google.firebase.internal.FirebaseRequestInitializer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -63,17 +64,21 @@ class FirebaseProjectManagementServiceImpl implements AndroidAppService, IosAppS new CreateIosAppFromAppIdFunction(); FirebaseProjectManagementServiceImpl(FirebaseApp app) { - this(app, Sleeper.DEFAULT, new FirebaseAppScheduler(app)); + this( + app, + Sleeper.DEFAULT, + new FirebaseAppScheduler(app), + ApiClientUtils.newAuthorizedRequestFactory(app)); } - FirebaseProjectManagementServiceImpl(FirebaseApp app, Sleeper sleeper, Scheduler scheduler) { + @VisibleForTesting + FirebaseProjectManagementServiceImpl( + FirebaseApp app, Sleeper sleeper, Scheduler scheduler, HttpRequestFactory requestFactory) { this.app = checkNotNull(app); this.sleeper = checkNotNull(sleeper); this.scheduler = checkNotNull(scheduler); this.httpHelper = new HttpHelper( - app.getOptions().getJsonFactory(), - app.getOptions().getHttpTransport().createRequestFactory( - new FirebaseRequestInitializer(app))); + app.getOptions().getJsonFactory(), checkNotNull(requestFactory)); } @VisibleForTesting diff --git a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java index a2fe8728d..619b14c60 100644 --- a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java +++ b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java @@ -29,6 +29,7 @@ import com.google.api.client.googleapis.util.Utils; import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpResponseInterceptor; import com.google.api.client.json.JsonParser; @@ -43,6 +44,7 @@ import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; +import com.google.firebase.internal.ApiClientUtils; import com.google.firebase.testing.MultiRequestMockHttpTransport; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -370,7 +372,7 @@ public void listIosAppsAsyncMultiplePages() throws Exception { MockLowLevelHttpResponse secondRpcResponse = new MockLowLevelHttpResponse(); secondRpcResponse.setContent(LIST_IOS_APPS_PAGE_2_RESPONSE); serviceImpl = initServiceImpl( - ImmutableList.of(firstRpcResponse, secondRpcResponse), + ImmutableList.of(firstRpcResponse, secondRpcResponse), interceptor); List iosAppList = serviceImpl.listIosAppsAsync(PROJECT_ID).get(); @@ -400,7 +402,7 @@ public void createIosApp() throws Exception { MockLowLevelHttpResponse thirdRpcResponse = new MockLowLevelHttpResponse(); thirdRpcResponse.setContent(CREATE_IOS_GET_OPERATION_ATTEMPT_2_RESPONSE); serviceImpl = initServiceImpl( - ImmutableList.of( + ImmutableList.of( firstRpcResponse, secondRpcResponse, thirdRpcResponse), interceptor); @@ -624,7 +626,7 @@ public void listAndroidAppsMultiplePages() throws Exception { MockLowLevelHttpResponse secondRpcResponse = new MockLowLevelHttpResponse(); secondRpcResponse.setContent(LIST_ANDROID_APPS_PAGE_2_RESPONSE); serviceImpl = initServiceImpl( - ImmutableList.of(firstRpcResponse, secondRpcResponse), + ImmutableList.of(firstRpcResponse, secondRpcResponse), interceptor); List androidAppList = serviceImpl.listAndroidApps(PROJECT_ID); @@ -652,7 +654,7 @@ public void listAndroidAppsAsyncMultiplePages() throws Exception { MockLowLevelHttpResponse secondRpcResponse = new MockLowLevelHttpResponse(); secondRpcResponse.setContent(LIST_ANDROID_APPS_PAGE_2_RESPONSE); serviceImpl = initServiceImpl( - ImmutableList.of(firstRpcResponse, secondRpcResponse), + ImmutableList.of(firstRpcResponse, secondRpcResponse), interceptor); List androidAppList = serviceImpl.listAndroidAppsAsync(PROJECT_ID).get(); @@ -682,7 +684,7 @@ public void createAndroidApp() throws Exception { MockLowLevelHttpResponse thirdRpcResponse = new MockLowLevelHttpResponse(); thirdRpcResponse.setContent(CREATE_ANDROID_GET_OPERATION_ATTEMPT_2_RESPONSE); serviceImpl = initServiceImpl( - ImmutableList.of( + ImmutableList.of( firstRpcResponse, secondRpcResponse, thirdRpcResponse), interceptor); @@ -714,7 +716,7 @@ public void createAndroidAppAsync() throws Exception { MockLowLevelHttpResponse thirdRpcResponse = new MockLowLevelHttpResponse(); thirdRpcResponse.setContent(CREATE_ANDROID_GET_OPERATION_ATTEMPT_2_RESPONSE); serviceImpl = initServiceImpl( - ImmutableList.of( + ImmutableList.of( firstRpcResponse, secondRpcResponse, thirdRpcResponse), interceptor); @@ -918,7 +920,7 @@ public void deleteShaCertificateAsync() throws Exception { private static FirebaseProjectManagementServiceImpl initServiceImpl( MockLowLevelHttpResponse mockResponse, MultiRequestTestResponseInterceptor interceptor) { - return initServiceImpl(ImmutableList.of(mockResponse), interceptor); + return initServiceImpl(ImmutableList.of(mockResponse), interceptor); } private static FirebaseProjectManagementServiceImpl initServiceImpl( @@ -931,8 +933,9 @@ private static FirebaseProjectManagementServiceImpl initServiceImpl( .setHttpTransport(transport) .build(); FirebaseApp app = FirebaseApp.initializeApp(options); - FirebaseProjectManagementServiceImpl serviceImpl = - new FirebaseProjectManagementServiceImpl(app, new MockSleeper(), new MockScheduler()); + HttpRequestFactory requestFactory = ApiClientUtils.newAuthorizedRequestFactory(app, null); + FirebaseProjectManagementServiceImpl serviceImpl = new FirebaseProjectManagementServiceImpl( + app, new MockSleeper(), new MockScheduler(), requestFactory); serviceImpl.setInterceptor(interceptor); return serviceImpl; } From fffdae80be973ce6e1243df9b8f5d29155d51802 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Tue, 28 Jan 2020 13:50:19 -0800 Subject: [PATCH 2/6] Added some test cases --- .../firebase/internal/ApiClientUtilsTest.java | 13 ++++++++++ ...ebaseProjectManagementServiceImplTest.java | 25 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java b/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java index 78eabaf32..c19f5f567 100644 --- a/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java +++ b/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java @@ -69,6 +69,19 @@ public void testAuthorizedHttpClient() throws IOException { assertEquals(retryConfig.getRetryStatusCodes(), ImmutableList.of(500, 503)); } + @Test + public void testAuthorizedHttpClientWithoutRetry() throws IOException { + FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS); + + HttpRequestFactory requestFactory = ApiClientUtils.newAuthorizedRequestFactory(app, null); + + assertTrue(requestFactory.getInitializer() instanceof FirebaseRequestInitializer); + HttpRequest request = requestFactory.buildGetRequest(TEST_URL); + assertEquals("Bearer test-token", request.getHeaders().getAuthorization()); + HttpUnsuccessfulResponseHandler retryHandler = request.getUnsuccessfulResponseHandler(); + assertFalse(retryHandler instanceof RetryHandlerDecorator); + } + @Test public void testUnauthorizedHttpClient() throws IOException { FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS); diff --git a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java index 619b14c60..1bf408375 100644 --- a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java +++ b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java @@ -917,6 +917,30 @@ public void deleteShaCertificateAsync() throws Exception { checkRequestHeader(expectedUrl, HttpMethod.DELETE); } + @Test + public void testHttpRetries() throws Exception { + List mockResponses = ImmutableList.of( + firstRpcResponse.setStatusCode(503).setContent("{}"), + new MockLowLevelHttpResponse().setContent("{}")); + MockHttpTransport transport = new MultiRequestMockHttpTransport(mockResponses); + FirebaseOptions options = new FirebaseOptions.Builder() + .setCredentials(new MockGoogleCredentials("test-token")) + .setProjectId(PROJECT_ID) + .setHttpTransport(transport) + .build(); + FirebaseApp app = FirebaseApp.initializeApp(options); + // Initialize client with the default HTTP configuration, which enables retries. + FirebaseProjectManagementServiceImpl serviceImpl = + new FirebaseProjectManagementServiceImpl(app); + serviceImpl.setInterceptor(interceptor); + + serviceImpl.deleteShaCertificate(SHA1_RESOURCE_NAME); + + String expectedUrl = String.format( + "%s/v1beta1/%s", FIREBASE_PROJECT_MANAGEMENT_URL, SHA1_RESOURCE_NAME); + checkRequestHeader(expectedUrl, HttpMethod.DELETE); + } + private static FirebaseProjectManagementServiceImpl initServiceImpl( MockLowLevelHttpResponse mockResponse, MultiRequestTestResponseInterceptor interceptor) { @@ -933,6 +957,7 @@ private static FirebaseProjectManagementServiceImpl initServiceImpl( .setHttpTransport(transport) .build(); FirebaseApp app = FirebaseApp.initializeApp(options); + // Explicitly disable retries for unit tests. HttpRequestFactory requestFactory = ApiClientUtils.newAuthorizedRequestFactory(app, null); FirebaseProjectManagementServiceImpl serviceImpl = new FirebaseProjectManagementServiceImpl( app, new MockSleeper(), new MockScheduler(), requestFactory); From 78ff994ea3fbae34729a0c8d75f4d88fe9717dfe Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Tue, 28 Jan 2020 15:42:05 -0800 Subject: [PATCH 3/6] Added helper function for disabling exponential backoff during tests --- .../firebase/internal/ApiClientUtils.java | 20 +++++++++++ ...ebaseProjectManagementServiceImplTest.java | 34 ++++++++----------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/google/firebase/internal/ApiClientUtils.java b/src/main/java/com/google/firebase/internal/ApiClientUtils.java index 4620cd68a..570971927 100644 --- a/src/main/java/com/google/firebase/internal/ApiClientUtils.java +++ b/src/main/java/com/google/firebase/internal/ApiClientUtils.java @@ -19,6 +19,7 @@ import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpTransport; +import com.google.api.client.testing.util.MockSleeper; import com.google.common.collect.ImmutableList; import com.google.firebase.FirebaseApp; @@ -35,6 +36,13 @@ public class ApiClientUtils { .setMaxIntervalMillis(60 * 1000) .build(); + private static final RetryConfig TEST_RETRY_CONFIG = RetryConfig.builder() + .setMaxRetries(4) + .setRetryStatusCodes(ImmutableList.of(500, 503)) + .setMaxIntervalMillis(60 * 1000) + .setSleeper(new MockSleeper()) + .build(); + /** * Creates a new {@code HttpRequestFactory} which provides authorization (OAuth2), timeouts and * automatic retries. @@ -65,6 +73,18 @@ public static HttpRequestFactory newUnauthorizedRequestFactory(FirebaseApp app) return transport.createRequestFactory(); } + /** + * Creates a new {@code HttpRequestFactory} which provides authorization (OAuth2), timeouts and + * automatic retries. Bypasses exponential backoff between consecutive retries for faster + * execution during tests. + * + * @param app {@link FirebaseApp} from which to obtain authorization credentials. + * @return A new {@code HttpRequestFactory} instance. + */ + public static HttpRequestFactory newAuthorizedRequestFactoryForTests(FirebaseApp app) { + return newAuthorizedRequestFactory(app, TEST_RETRY_CONFIG); + } + public static void disconnectQuietly(HttpResponse response) { if (response != null) { try { diff --git a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java index 1bf408375..a09374dac 100644 --- a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java +++ b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java @@ -264,9 +264,8 @@ public void getIosAppAsync() throws Exception { @Test public void getIosAppHttpError() { - firstRpcResponse.setStatusCode(500); - firstRpcResponse.setContent("{}"); - serviceImpl = initServiceImpl(firstRpcResponse, interceptor); + List mockResponses = errorResponseSequence(500, "{}"); + serviceImpl = initServiceImpl(mockResponses, interceptor); try { serviceImpl.getIosApp(IOS_APP_ID); @@ -546,9 +545,8 @@ public void getAndroidAppAsync() throws Exception { @Test public void getAndroidAppHttpError() { - firstRpcResponse.setStatusCode(500); - firstRpcResponse.setContent("{}"); - serviceImpl = initServiceImpl(firstRpcResponse, interceptor); + List mockResponses = errorResponseSequence(500, "{}"); + serviceImpl = initServiceImpl(mockResponses, interceptor); try { serviceImpl.getAndroidApp(ANDROID_APP_ID); @@ -922,17 +920,7 @@ public void testHttpRetries() throws Exception { List mockResponses = ImmutableList.of( firstRpcResponse.setStatusCode(503).setContent("{}"), new MockLowLevelHttpResponse().setContent("{}")); - MockHttpTransport transport = new MultiRequestMockHttpTransport(mockResponses); - FirebaseOptions options = new FirebaseOptions.Builder() - .setCredentials(new MockGoogleCredentials("test-token")) - .setProjectId(PROJECT_ID) - .setHttpTransport(transport) - .build(); - FirebaseApp app = FirebaseApp.initializeApp(options); - // Initialize client with the default HTTP configuration, which enables retries. - FirebaseProjectManagementServiceImpl serviceImpl = - new FirebaseProjectManagementServiceImpl(app); - serviceImpl.setInterceptor(interceptor); + FirebaseProjectManagementServiceImpl serviceImpl = initServiceImpl(mockResponses, interceptor); serviceImpl.deleteShaCertificate(SHA1_RESOURCE_NAME); @@ -957,14 +945,22 @@ private static FirebaseProjectManagementServiceImpl initServiceImpl( .setHttpTransport(transport) .build(); FirebaseApp app = FirebaseApp.initializeApp(options); - // Explicitly disable retries for unit tests. - HttpRequestFactory requestFactory = ApiClientUtils.newAuthorizedRequestFactory(app, null); + HttpRequestFactory requestFactory = ApiClientUtils.newAuthorizedRequestFactoryForTests(app); FirebaseProjectManagementServiceImpl serviceImpl = new FirebaseProjectManagementServiceImpl( app, new MockSleeper(), new MockScheduler(), requestFactory); serviceImpl.setInterceptor(interceptor); return serviceImpl; } + private static List errorResponseSequence(int status, String content) { + ImmutableList.Builder responses = ImmutableList.builder(); + for (int i = 0; i < 5; i++) { + responses.add(new MockLowLevelHttpResponse().setStatusCode(status).setContent(content)); + } + + return responses.build(); + } + private void checkRequestHeader(String expectedUrl, HttpMethod httpMethod) { assertEquals( "The number of HttpResponses is not equal to 1.", 1, interceptor.getNumberOfResponses()); From 8be0e06112a46920e0e8310b150f3397f6b0b24d Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Tue, 28 Jan 2020 16:31:35 -0800 Subject: [PATCH 4/6] Added util class for simplifying retry tests --- .../firebase/internal/ApiClientUtils.java | 22 +---- .../FirebaseProjectManagementServiceImpl.java | 10 +- .../firebase/internal/TestApiClientUtils.java | 95 +++++++++++++++++++ ...ebaseProjectManagementServiceImplTest.java | 18 +++- 4 files changed, 120 insertions(+), 25 deletions(-) create mode 100644 src/test/java/com/google/firebase/internal/TestApiClientUtils.java diff --git a/src/main/java/com/google/firebase/internal/ApiClientUtils.java b/src/main/java/com/google/firebase/internal/ApiClientUtils.java index 570971927..36ccf5cc8 100644 --- a/src/main/java/com/google/firebase/internal/ApiClientUtils.java +++ b/src/main/java/com/google/firebase/internal/ApiClientUtils.java @@ -19,7 +19,6 @@ import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpTransport; -import com.google.api.client.testing.util.MockSleeper; import com.google.common.collect.ImmutableList; import com.google.firebase.FirebaseApp; @@ -30,19 +29,12 @@ */ public class ApiClientUtils { - private static final RetryConfig DEFAULT_RETRY_CONFIG = RetryConfig.builder() + static final RetryConfig DEFAULT_RETRY_CONFIG = RetryConfig.builder() .setMaxRetries(4) .setRetryStatusCodes(ImmutableList.of(500, 503)) .setMaxIntervalMillis(60 * 1000) .build(); - private static final RetryConfig TEST_RETRY_CONFIG = RetryConfig.builder() - .setMaxRetries(4) - .setRetryStatusCodes(ImmutableList.of(500, 503)) - .setMaxIntervalMillis(60 * 1000) - .setSleeper(new MockSleeper()) - .build(); - /** * Creates a new {@code HttpRequestFactory} which provides authorization (OAuth2), timeouts and * automatic retries. @@ -73,18 +65,6 @@ public static HttpRequestFactory newUnauthorizedRequestFactory(FirebaseApp app) return transport.createRequestFactory(); } - /** - * Creates a new {@code HttpRequestFactory} which provides authorization (OAuth2), timeouts and - * automatic retries. Bypasses exponential backoff between consecutive retries for faster - * execution during tests. - * - * @param app {@link FirebaseApp} from which to obtain authorization credentials. - * @return A new {@code HttpRequestFactory} instance. - */ - public static HttpRequestFactory newAuthorizedRequestFactoryForTests(FirebaseApp app) { - return newAuthorizedRequestFactory(app, TEST_RETRY_CONFIG); - } - public static void disconnectQuietly(HttpResponse response) { if (response != null) { try { diff --git a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java index 5d645cec1..8abced696 100644 --- a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java +++ b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java @@ -56,6 +56,7 @@ class FirebaseProjectManagementServiceImpl implements AndroidAppService, IosAppS private final FirebaseApp app; private final Sleeper sleeper; private final Scheduler scheduler; + private final HttpRequestFactory requestFactory; private final HttpHelper httpHelper; private final CreateAndroidAppFromAppIdFunction createAndroidAppFromAppIdFunction = @@ -77,8 +78,13 @@ class FirebaseProjectManagementServiceImpl implements AndroidAppService, IosAppS this.app = checkNotNull(app); this.sleeper = checkNotNull(sleeper); this.scheduler = checkNotNull(scheduler); - this.httpHelper = new HttpHelper( - app.getOptions().getJsonFactory(), checkNotNull(requestFactory)); + this.requestFactory = checkNotNull(requestFactory); + this.httpHelper = new HttpHelper(app.getOptions().getJsonFactory(), requestFactory); + } + + @VisibleForTesting + HttpRequestFactory getRequestFactory() { + return requestFactory; } @VisibleForTesting diff --git a/src/test/java/com/google/firebase/internal/TestApiClientUtils.java b/src/test/java/com/google/firebase/internal/TestApiClientUtils.java new file mode 100644 index 000000000..9f4bc2d09 --- /dev/null +++ b/src/test/java/com/google/firebase/internal/TestApiClientUtils.java @@ -0,0 +1,95 @@ +/* + * Copyright 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.internal; + +import static com.google.firebase.internal.ApiClientUtils.DEFAULT_RETRY_CONFIG; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpUnsuccessfulResponseHandler; +import com.google.api.client.testing.util.MockSleeper; +import com.google.firebase.FirebaseApp; +import com.google.firebase.internal.RetryInitializer.RetryHandlerDecorator; +import java.io.IOException; + +public class TestApiClientUtils { + + private static final RetryConfig TEST_RETRY_CONFIG = RetryConfig.builder() + .setMaxRetries(DEFAULT_RETRY_CONFIG.getMaxRetries()) + .setRetryStatusCodes(DEFAULT_RETRY_CONFIG.getRetryStatusCodes()) + .setMaxIntervalMillis(DEFAULT_RETRY_CONFIG.getMaxIntervalMillis()) + .setSleeper(new MockSleeper()) + .build(); + + private static final GenericUrl TEST_URL = new GenericUrl("https://firebase.google.com"); + + /** + * Creates a new {@code HttpRequestFactory} which provides authorization (OAuth2), timeouts and + * automatic retries. Bypasses exponential backoff between consecutive retries for faster + * execution during tests. + * + * @param app {@link FirebaseApp} from which to obtain authorization credentials. + * @return A new {@code HttpRequestFactory} instance. + */ + public static HttpRequestFactory delayBypassedRequestFactory(FirebaseApp app) { + return ApiClientUtils.newAuthorizedRequestFactory(app, TEST_RETRY_CONFIG); + } + + /** + * Creates a new {@code HttpRequestFactory} which provides authorization (OAuth2), timeouts but + * no retries. + * + * @param app {@link FirebaseApp} from which to obtain authorization credentials. + * @return A new {@code HttpRequestFactory} instance. + */ + public static HttpRequestFactory retryDisabledRequestFactory(FirebaseApp app) { + return ApiClientUtils.newAuthorizedRequestFactory(app, null); + } + + /** + * Checks whther the given HttpRequestFactory has been configured for authorization and + * automatic retries. + * + * @param requestFactory The HttpRequestFactory to check. + */ + public static void assertAuthAndRetrySupport(HttpRequestFactory requestFactory) { + assertTrue(requestFactory.getInitializer() instanceof FirebaseRequestInitializer); + HttpRequest request; + try { + request = requestFactory.buildGetRequest(TEST_URL); + } catch (IOException e) { + throw new RuntimeException("Failed to initialize request", e); + } + + // Verify authorization + assertTrue(request.getHeaders().getAuthorization().startsWith("Bearer ")); + + // Verify retry support + HttpUnsuccessfulResponseHandler retryHandler = request.getUnsuccessfulResponseHandler(); + assertTrue(retryHandler instanceof RetryHandlerDecorator); + RetryConfig retryConfig = ((RetryHandlerDecorator) retryHandler).getRetryHandler() + .getRetryConfig(); + assertEquals(DEFAULT_RETRY_CONFIG.getMaxRetries(), retryConfig.getMaxRetries()); + assertEquals(DEFAULT_RETRY_CONFIG.getMaxIntervalMillis(), retryConfig.getMaxIntervalMillis()); + assertFalse(retryConfig.isRetryOnIOExceptions()); + assertEquals(DEFAULT_RETRY_CONFIG.getRetryStatusCodes(), retryConfig.getRetryStatusCodes()); + } +} diff --git a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java index a09374dac..7d009a5a7 100644 --- a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java +++ b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java @@ -44,7 +44,7 @@ import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; -import com.google.firebase.internal.ApiClientUtils; +import com.google.firebase.internal.TestApiClientUtils; import com.google.firebase.testing.MultiRequestMockHttpTransport; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -915,6 +915,20 @@ public void deleteShaCertificateAsync() throws Exception { checkRequestHeader(expectedUrl, HttpMethod.DELETE); } + @Test + public void testAuthAndRetriesSupport() { + FirebaseOptions options = new FirebaseOptions.Builder() + .setCredentials(new MockGoogleCredentials("test-token")) + .setProjectId(PROJECT_ID) + .build(); + FirebaseApp app = FirebaseApp.initializeApp(options); + + FirebaseProjectManagementServiceImpl serviceImpl = + new FirebaseProjectManagementServiceImpl(app); + + TestApiClientUtils.assertAuthAndRetrySupport(serviceImpl.getRequestFactory()); + } + @Test public void testHttpRetries() throws Exception { List mockResponses = ImmutableList.of( @@ -945,7 +959,7 @@ private static FirebaseProjectManagementServiceImpl initServiceImpl( .setHttpTransport(transport) .build(); FirebaseApp app = FirebaseApp.initializeApp(options); - HttpRequestFactory requestFactory = ApiClientUtils.newAuthorizedRequestFactoryForTests(app); + HttpRequestFactory requestFactory = TestApiClientUtils.delayBypassedRequestFactory(app); FirebaseProjectManagementServiceImpl serviceImpl = new FirebaseProjectManagementServiceImpl( app, new MockSleeper(), new MockScheduler(), requestFactory); serviceImpl.setInterceptor(interceptor); From e7742c35b4e71cae1ba3072ee0fbfbabddacf092 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Tue, 28 Jan 2020 16:49:31 -0800 Subject: [PATCH 5/6] Simplified test cases --- ...ebaseProjectManagementServiceImplTest.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java index 7d009a5a7..a21f139c0 100644 --- a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java +++ b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java @@ -264,8 +264,9 @@ public void getIosAppAsync() throws Exception { @Test public void getIosAppHttpError() { - List mockResponses = errorResponseSequence(500, "{}"); - serviceImpl = initServiceImpl(mockResponses, interceptor); + firstRpcResponse.setStatusCode(500); + firstRpcResponse.setContent("{}"); + serviceImpl = initServiceImpl(firstRpcResponse, interceptor); try { serviceImpl.getIosApp(IOS_APP_ID); @@ -545,8 +546,9 @@ public void getAndroidAppAsync() throws Exception { @Test public void getAndroidAppHttpError() { - List mockResponses = errorResponseSequence(500, "{}"); - serviceImpl = initServiceImpl(mockResponses, interceptor); + firstRpcResponse.setStatusCode(500); + firstRpcResponse.setContent("{}"); + serviceImpl = initServiceImpl(firstRpcResponse, interceptor); try { serviceImpl.getAndroidApp(ANDROID_APP_ID); @@ -934,7 +936,17 @@ public void testHttpRetries() throws Exception { List mockResponses = ImmutableList.of( firstRpcResponse.setStatusCode(503).setContent("{}"), new MockLowLevelHttpResponse().setContent("{}")); - FirebaseProjectManagementServiceImpl serviceImpl = initServiceImpl(mockResponses, interceptor); + MockHttpTransport transport = new MultiRequestMockHttpTransport(mockResponses); + FirebaseOptions options = new FirebaseOptions.Builder() + .setCredentials(new MockGoogleCredentials("test-token")) + .setProjectId(PROJECT_ID) + .setHttpTransport(transport) + .build(); + FirebaseApp app = FirebaseApp.initializeApp(options); + HttpRequestFactory requestFactory = TestApiClientUtils.delayBypassedRequestFactory(app); + FirebaseProjectManagementServiceImpl serviceImpl = new FirebaseProjectManagementServiceImpl( + app, new MockSleeper(), new MockScheduler(), requestFactory); + serviceImpl.setInterceptor(interceptor); serviceImpl.deleteShaCertificate(SHA1_RESOURCE_NAME); @@ -959,7 +971,7 @@ private static FirebaseProjectManagementServiceImpl initServiceImpl( .setHttpTransport(transport) .build(); FirebaseApp app = FirebaseApp.initializeApp(options); - HttpRequestFactory requestFactory = TestApiClientUtils.delayBypassedRequestFactory(app); + HttpRequestFactory requestFactory = TestApiClientUtils.retryDisabledRequestFactory(app); FirebaseProjectManagementServiceImpl serviceImpl = new FirebaseProjectManagementServiceImpl( app, new MockSleeper(), new MockScheduler(), requestFactory); serviceImpl.setInterceptor(interceptor); From 666fa3cbd1412bfd0b4e3395433d72cbd49b2841 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Tue, 28 Jan 2020 16:52:04 -0800 Subject: [PATCH 6/6] Removed unused method --- .../FirebaseProjectManagementServiceImplTest.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java index a21f139c0..87afe609c 100644 --- a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java +++ b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java @@ -978,15 +978,6 @@ private static FirebaseProjectManagementServiceImpl initServiceImpl( return serviceImpl; } - private static List errorResponseSequence(int status, String content) { - ImmutableList.Builder responses = ImmutableList.builder(); - for (int i = 0; i < 5; i++) { - responses.add(new MockLowLevelHttpResponse().setStatusCode(status).setContent(content)); - } - - return responses.build(); - } - private void checkRequestHeader(String expectedUrl, HttpMethod httpMethod) { assertEquals( "The number of HttpResponses is not equal to 1.", 1, interceptor.getNumberOfResponses());