diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c346c5b9d..62a194733 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -141,6 +141,10 @@ Authentication Admin` role at [Google Cloud Platform Console / IAM & admin](https://console.cloud.google.com/iam-admin). This is required to ensure that exported user records contain the password hashes of the user accounts. Also obtain the web API key of the project from the "Settings > General" page, and save it as + <<<<<<< redacted-passwords +`integration_apikey.txt` at the root of the codebase. Now run the following command to invoke the +integration test suite: + ======= `integration_apikey.txt` at the root of the codebase. Some of the integration tests require an @@ -152,6 +156,7 @@ functionality via the charges may be incurred for active users beyond the Identity Platform free tier. Now run the following command to invoke the integration test suite: + >>>>>>> master ``` mvn verify diff --git a/README.md b/README.md index 406223d06..95f2d8d04 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,20 @@ + <<<<<<< revert-555-hkj-release-730 + <<<<<<< revert-555-hkj-release-730 [![Build Status](https://github.com/firebase/firebase-admin-java/workflows/Continuous%20Integration/badge.svg)](https://github.com/firebase/firebase-admin-java/actions) + ======= +test move pull request + +[![Build Status](https://travis-ci.org/firebase/firebase-admin-java.svg?branch=master)](https://travis-ci.org/firebase/firebase-admin-java) + >>>>>>> test-qiao-public + ======= + <<<<<<< test-yue-public +test new moving process + +[![Build Status](https://travis-ci.org/firebase/firebase-admin-java.svg?branch=master)](https://travis-ci.org/firebase/firebase-admin-java) + ======= +[![Build Status](https://github.com/firebase/firebase-admin-java/workflows/Continuous%20Integration/badge.svg)](https://github.com/firebase/firebase-admin-java/actions) + >>>>>>> master + >>>>>>> master [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.google.firebase/firebase-admin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.google.firebase/firebase-admin) [![Javadoc](https://javadoc-badge.appspot.com/com.google.firebase/firebase-admin.svg)](https://firebase.google.com/docs/reference/admin/java/reference/packages) diff --git a/pom.xml b/pom.xml index 5e206c322..1e20edbd4 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,15 @@ com.google.firebase firebase-admin + <<<<<<< revert-555-hkj-release-730 + 7.2.0 + ======= + <<<<<<< redacted-passwords + 6.14.0 + ======= 7.3.0 + >>>>>>> master + >>>>>>> master jar firebase-admin @@ -59,7 +67,11 @@ UTF-8 UTF-8 ${skipTests} + <<<<<<< redacted-passwords + 4.1.50.Final + ======= 4.1.65.Final + >>>>>>> master @@ -99,7 +111,11 @@ maven-javadoc-plugin - 3.2.0 + <<<<<<< redacted-passwords + 2.10.4 + ======= + 3.3.0 + >>>>>>> master site @@ -257,7 +273,7 @@ maven-checkstyle-plugin - 2.17 + 3.1.2 validate @@ -298,7 +314,11 @@ maven-source-plugin + <<<<<<< redacted-passwords 2.2.1 + ======= + 3.2.1 + >>>>>>> master attach-sources @@ -310,7 +330,11 @@ maven-javadoc-plugin - 3.2.0 + <<<<<<< redacted-passwords + 2.10.4 + ======= + 3.3.0 + >>>>>>> master attach-javadocs @@ -345,14 +369,22 @@ maven-failsafe-plugin + <<<<<<< redacted-passwords + 2.19.1 + ======= 2.22.2 + >>>>>>> master integration-test verify + <<<<<<< redacted-passwords + + ======= + >>>>>>> master @@ -393,7 +425,7 @@ com.google.cloud libraries-bom - 20.4.0 + 20.5.0 pom import @@ -412,30 +444,58 @@ com.google.api-client google-api-client + <<<<<<< redacted-passwords + 1.30.9 + ======= + >>>>>>> master com.google.api-client google-api-client-gson + <<<<<<< redacted-passwords + 1.30.9 + ======= + >>>>>>> master com.google.http-client google-http-client + <<<<<<< redacted-passwords + 1.35.0 + ======= + >>>>>>> master com.google.api api-common + <<<<<<< redacted-passwords + 1.9.2 + ======= + >>>>>>> master com.google.auth google-auth-library-oauth2-http + <<<<<<< redacted-passwords + 0.20.0 + ======= + >>>>>>> master com.google.cloud google-cloud-storage + <<<<<<< redacted-passwords + 1.108.0 + ======= + >>>>>>> master com.google.cloud google-cloud-firestore + <<<<<<< redacted-passwords + 1.34.0 + ======= + >>>>>>> master @@ -468,7 +528,7 @@ org.mockito mockito-core - 2.28.2 + 3.10.0 test diff --git a/src/main/java/com/google/firebase/ErrorCode.java b/src/main/java/com/google/firebase/ErrorCode.java index 0eaa9cec2..c24745c3a 100644 --- a/src/main/java/com/google/firebase/ErrorCode.java +++ b/src/main/java/com/google/firebase/ErrorCode.java @@ -1,5 +1,9 @@ /* - * Copyright 2020 Google Inc. + <<<<<<< hkj-error-handling + * Copyright 2019 Google Inc. + ======= + * Copyright 2021 Google Inc. + >>>>>>> master * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +20,40 @@ package com.google.firebase; + <<<<<<< hkj-error-handling +public enum ErrorCode { + + INVALID_ARGUMENT, + + FAILED_PRECONDITION, + + OUT_OF_RANGE, + + UNAUTHENTICATED, + + PERMISSION_DENIED, + + NOT_FOUND, + + CONFLICT, + + ABORTED, + + ALREADY_EXISTS, + + RESOURCE_EXHAUSTED, + + CANCELLED, + + DATA_LOSS, + + UNKNOWN, + + INTERNAL, + + UNAVAILABLE, + + ======= /** * Platform-wide error codes that can be raised by Admin SDK APIs. */ @@ -105,5 +143,6 @@ public enum ErrorCode { * shorter than the method's default deadline (i.e. requested deadline is not enough for the * server to process the request) and the request did not finish within the deadline. */ + >>>>>>> master DEADLINE_EXCEEDED, } diff --git a/src/main/java/com/google/firebase/FirebaseApp.java b/src/main/java/com/google/firebase/FirebaseApp.java index d525d0673..832af7665 100644 --- a/src/main/java/com/google/firebase/FirebaseApp.java +++ b/src/main/java/com/google/firebase/FirebaseApp.java @@ -34,7 +34,10 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; + <<<<<<< v7 + ======= import com.google.firebase.internal.ApiClientUtils; + >>>>>>> master import com.google.firebase.internal.FirebaseScheduledExecutor; import com.google.firebase.internal.FirebaseService; import com.google.firebase.internal.ListenableFuture2ApiFuture; @@ -316,10 +319,17 @@ public String toString() { } /** + <<<<<<< redacted-passwords + * Deletes this {@link FirebaseApp} object and releases any local state and managed resources + * associated with it. All calls to this {@link FirebaseApp} instance will throw once this method + * has been called. This also releases any managed resources allocated by other services + * (e.g. {@code FirebaseAuth}, {@code FirebaseDatabase}) attached to this object instance. + ======= * Deletes this {@link FirebaseApp} object, and releases any local state and managed resources * associated with it. All calls to this {@link FirebaseApp} instance will throw once this method * has been called. This also releases any managed resources allocated by other services * attached to this object instance (e.g. {@code FirebaseAuth}). + >>>>>>> master * *

A no-op if delete was called before. */ @@ -569,7 +579,11 @@ private static FirebaseOptions getOptionsFromEnvironment() throws IOException { .setCredentials(APPLICATION_DEFAULT_CREDENTIALS) .build(); } + <<<<<<< v7 + JsonFactory jsonFactory = Utils.getDefaultJsonFactory(); + ======= JsonFactory jsonFactory = ApiClientUtils.getDefaultJsonFactory(); + >>>>>>> master FirebaseOptions.Builder builder = FirebaseOptions.builder(); JsonParser parser; if (defaultConfig.startsWith("{")) { diff --git a/src/main/java/com/google/firebase/FirebaseException.java b/src/main/java/com/google/firebase/FirebaseException.java index a5bb80424..e5c82b4ff 100644 --- a/src/main/java/com/google/firebase/FirebaseException.java +++ b/src/main/java/com/google/firebase/FirebaseException.java @@ -29,6 +29,35 @@ public class FirebaseException extends Exception { private final ErrorCode errorCode; + <<<<<<< hkj-error-handling + private final FirebaseHttpResponse response; + + public FirebaseException(@NonNull String detailMessage) { + this(detailMessage, null); + } + + public FirebaseException(@NonNull String detailMessage, Throwable cause) { + this(ErrorCode.UNKNOWN, detailMessage, null, cause); + } + + public FirebaseException( + @NonNull ErrorCode errorCode, + @NonNull String message, + @Nullable FirebaseHttpResponse response, + @Nullable Throwable cause) { + super(message, cause); + checkArgument(!Strings.isNullOrEmpty(message), "Message must not be null or empty"); + this.errorCode = checkNotNull(errorCode); + this.response = response; + } + + public ErrorCode getCode() { + return errorCode; + } + + @Nullable public FirebaseHttpResponse getHttpResponse() { + return response; + ======= private final IncomingHttpResponse httpResponse; public FirebaseException( @@ -67,5 +96,6 @@ public final ErrorCode getErrorCode() { @Nullable public final IncomingHttpResponse getHttpResponse() { return httpResponse; + >>>>>>> master } } diff --git a/src/main/java/com/google/firebase/FirebaseHttpRequest.java b/src/main/java/com/google/firebase/FirebaseHttpRequest.java new file mode 100644 index 000000000..217c8ca9e --- /dev/null +++ b/src/main/java/com/google/firebase/FirebaseHttpRequest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 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; + +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpRequest; +import com.google.common.collect.ImmutableMap; +import java.util.Map; + +public final class FirebaseHttpRequest { + + private final String method; + private final String url; + private final HttpContent content; + private final Map headers; + + public FirebaseHttpRequest(String method, String url) { + this.method = method; + this.url = url; + this.content = null; + this.headers = ImmutableMap.of(); + } + + FirebaseHttpRequest(HttpRequest request) { + this.method = request.getRequestMethod(); + this.url = request.getUrl().toString(); + this.content = request.getContent(); + this.headers = ImmutableMap.copyOf(request.getHeaders()); + } + + public String getMethod() { + return method; + } + + public String getUrl() { + return url; + } + + public HttpContent getContent() { + return content; + } + + public Map getHeaders() { + return headers; + } +} diff --git a/src/main/java/com/google/firebase/FirebaseHttpResponse.java b/src/main/java/com/google/firebase/FirebaseHttpResponse.java new file mode 100644 index 000000000..d2961ee0b --- /dev/null +++ b/src/main/java/com/google/firebase/FirebaseHttpResponse.java @@ -0,0 +1,65 @@ +/* + * Copyright 2019 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; + +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import com.google.common.collect.ImmutableMap; +import java.util.Map; + +public final class FirebaseHttpResponse { + + private final int statusCode; + private final String content; + private final Map headers; + private final FirebaseHttpRequest request; + + public FirebaseHttpResponse(HttpResponse response, String content) { + this.statusCode = response.getStatusCode(); + this.content = content; + this.headers = ImmutableMap.copyOf(response.getHeaders()); + this.request = new FirebaseHttpRequest(response.getRequest()); + } + + public FirebaseHttpResponse(HttpResponseException e, HttpRequest request) { + this(e, new FirebaseHttpRequest(request)); + } + + public FirebaseHttpResponse(HttpResponseException e, FirebaseHttpRequest request) { + this.statusCode = e.getStatusCode(); + this.content = e.getContent(); + this.headers = ImmutableMap.copyOf(e.getHeaders()); + this.request = request; + } + + public int getStatusCode() { + return this.statusCode; + } + + public String getContent() { + return this.content; + } + + public Map getHeaders() { + return this.headers; + } + + public FirebaseHttpRequest getRequest() { + return request; + } +} diff --git a/src/main/java/com/google/firebase/FirebaseOptions.java b/src/main/java/com/google/firebase/FirebaseOptions.java index f13f022db..de6a3fba4 100644 --- a/src/main/java/com/google/firebase/FirebaseOptions.java +++ b/src/main/java/com/google/firebase/FirebaseOptions.java @@ -234,7 +234,11 @@ public Builder toBuilder() { } /** + <<<<<<< v7 + * Builder for constructing {@link FirebaseOptions}. + ======= * Builder for constructing {@link FirebaseOptions}. + >>>>>>> master */ public static final class Builder { @Key("databaseAuthVariableOverride") diff --git a/src/main/java/com/google/firebase/auth/AuthErrorCode.java b/src/main/java/com/google/firebase/auth/AuthErrorCode.java index aa2821475..f558c4943 100644 --- a/src/main/java/com/google/firebase/auth/AuthErrorCode.java +++ b/src/main/java/com/google/firebase/auth/AuthErrorCode.java @@ -37,6 +37,8 @@ public enum AuthErrorCode { EMAIL_ALREADY_EXISTS, /** + <<<<<<< v7 + ======= * No user record found for the given email, typically raised when * generating a password reset link using an email for a user that * is not already registered. @@ -44,6 +46,7 @@ public enum AuthErrorCode { EMAIL_NOT_FOUND, /** + >>>>>>> master * The specified ID token is expired. */ EXPIRED_ID_TOKEN, diff --git a/src/main/java/com/google/firebase/auth/FirebaseAuth.java b/src/main/java/com/google/firebase/auth/FirebaseAuth.java index 27e79960d..5246ae782 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseAuth.java +++ b/src/main/java/com/google/firebase/auth/FirebaseAuth.java @@ -16,11 +16,34 @@ package com.google.firebase.auth; + <<<<<<< redacted-passwords +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.client.json.JsonFactory; +import com.google.api.client.util.Clock; +import com.google.api.core.ApiFuture; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; + ======= + >>>>>>> master import com.google.common.base.Supplier; import com.google.firebase.FirebaseApp; import com.google.firebase.ImplFirebaseTrampolines; import com.google.firebase.auth.multitenancy.TenantManager; import com.google.firebase.internal.FirebaseService; + <<<<<<< redacted-passwords +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + ======= + >>>>>>> master /** * This class is the entry point for all server-side Firebase Authentication actions. @@ -34,15 +57,31 @@ public final class FirebaseAuth extends AbstractFirebaseAuth { private static final String SERVICE_ID = FirebaseAuth.class.getName(); + <<<<<<< redacted-passwords + private static final String ERROR_CUSTOM_TOKEN = "ERROR_CUSTOM_TOKEN"; + + private final Object lock = new Object(); + ======= private final Supplier tenantManager; + >>>>>>> master private FirebaseAuth(final Builder builder) { super(builder); tenantManager = threadSafeMemoize(builder.tenantManager); } + <<<<<<< redacted-passwords + private FirebaseAuth(Builder builder) { + this.firebaseApp = checkNotNull(builder.firebaseApp); + this.tokenFactory = threadSafeMemoize(builder.tokenFactory); + this.idTokenVerifier = threadSafeMemoize(builder.idTokenVerifier); + this.cookieVerifier = threadSafeMemoize(builder.cookieVerifier); + this.userManager = threadSafeMemoize(builder.userManager); + this.jsonFactory = firebaseApp.getOptions().getJsonFactory(); + ======= public TenantManager getTenantManager() { return tenantManager.get(); + >>>>>>> master } /** @@ -69,6 +108,1138 @@ public static synchronized FirebaseAuth getInstance(FirebaseApp app) { return service.getInstance(); } + <<<<<<< redacted-passwords + /** + * Creates a new Firebase session cookie from the given ID token and options. The returned JWT + * can be set as a server-side session cookie with a custom cookie policy. + * + * @param idToken The Firebase ID token to exchange for a session cookie. + * @param options Additional options required to create the cookie. + * @return A Firebase session cookie string. + * @throws IllegalArgumentException If the ID token is null or empty, or if options is null. + * @throws FirebaseAuthException If an error occurs while generating the session cookie. + */ + public String createSessionCookie( + @NonNull String idToken, @NonNull SessionCookieOptions options) throws FirebaseAuthException { + return createSessionCookieOp(idToken, options).call(); + } + + /** + * Similar to {@link #createSessionCookie(String, SessionCookieOptions)} but performs the + * operation asynchronously. + * + * @param idToken The Firebase ID token to exchange for a session cookie. + * @param options Additional options required to create the cookie. + * @return An {@code ApiFuture} which will complete successfully with a session cookie string. + * If an error occurs while generating the cookie or if the specified ID token is invalid, + * the future throws a {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the ID token is null or empty, or if options is null. + */ + public ApiFuture createSessionCookieAsync( + @NonNull String idToken, @NonNull SessionCookieOptions options) { + return createSessionCookieOp(idToken, options).callAsync(firebaseApp); + } + + private CallableOperation createSessionCookieOp( + final String idToken, final SessionCookieOptions options) { + checkArgument(!Strings.isNullOrEmpty(idToken), "idToken must not be null or empty"); + checkNotNull(options, "options must not be null"); + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected String execute() throws FirebaseAuthException { + return userManager.createSessionCookie(idToken, options); + } + }; + } + + /** + * Parses and verifies a Firebase session cookie. + * + *

If verified successfully, returns a parsed version of the cookie from which the UID and the + * other claims can be read. If the cookie is invalid, throws a {@link FirebaseAuthException}. + * + *

This method does not check whether the cookie has been revoked. See + * {@link #verifySessionCookie(String, boolean)}. + * + * @param cookie A Firebase session cookie string to verify and parse. + * @return A {@link FirebaseToken} representing the verified and decoded cookie. + */ + public FirebaseToken verifySessionCookie(String cookie) throws FirebaseAuthException { + return verifySessionCookie(cookie, false); + } + + /** + * Parses and verifies a Firebase session cookie. + * + *

If {@code checkRevoked} is true, additionally verifies that the cookie has not been + * revoked. + * + *

If verified successfully, returns a parsed version of the cookie from which the UID and the + * other claims can be read. If the cookie is invalid or has been revoked while + * {@code checkRevoked} is true, throws a {@link FirebaseAuthException}. + * + * @param cookie A Firebase session cookie string to verify and parse. + * @param checkRevoked A boolean indicating whether to check if the cookie was explicitly + * revoked. + * @return A {@link FirebaseToken} representing the verified and decoded cookie. + */ + public FirebaseToken verifySessionCookie( + String cookie, boolean checkRevoked) throws FirebaseAuthException { + return verifySessionCookieOp(cookie, checkRevoked).call(); + } + + /** + * Similar to {@link #verifySessionCookie(String)} but performs the operation asynchronously. + * + * @param cookie A Firebase session cookie string to verify and parse. + * @return An {@code ApiFuture} which will complete successfully with the parsed cookie, or + * unsuccessfully with the failure Exception. + */ + public ApiFuture verifySessionCookieAsync(String cookie) { + return verifySessionCookieAsync(cookie, false); + } + + /** + * Similar to {@link #verifySessionCookie(String, boolean)} but performs the operation + * asynchronously. + * + * @param cookie A Firebase session cookie string to verify and parse. + * @param checkRevoked A boolean indicating whether to check if the cookie was explicitly + * revoked. + * @return An {@code ApiFuture} which will complete successfully with the parsed cookie, or + * unsuccessfully with the failure Exception. + */ + public ApiFuture verifySessionCookieAsync(String cookie, boolean checkRevoked) { + return verifySessionCookieOp(cookie, checkRevoked).callAsync(firebaseApp); + } + + private CallableOperation verifySessionCookieOp( + final String cookie, final boolean checkRevoked) { + checkArgument(!Strings.isNullOrEmpty(cookie), "Session cookie must not be null or empty"); + final FirebaseTokenVerifier sessionCookieVerifier = getSessionCookieVerifier(checkRevoked); + return new CallableOperation() { + @Override + public FirebaseToken execute() throws FirebaseAuthException { + return sessionCookieVerifier.verifyToken(cookie); + } + }; + } + + @VisibleForTesting + FirebaseTokenVerifier getSessionCookieVerifier(boolean checkRevoked) { + FirebaseTokenVerifier verifier = cookieVerifier.get(); + if (checkRevoked) { + FirebaseUserManager userManager = getUserManager(); + verifier = RevocationCheckDecorator.decorateSessionCookieVerifier(verifier, userManager); + } + return verifier; + } + + /** + * Creates a Firebase custom token for the given UID. This token can then be sent back to a client + * application to be used with the + * signInWithCustomToken + * authentication API. + * + *

{@link FirebaseApp} must have been initialized with service account credentials to use + * call this method. + * + * @param uid The UID to store in the token. This identifies the user to other Firebase services + * (Realtime Database, Firebase Auth, etc.). Should be less than 128 characters. + * @return A Firebase custom token string. + * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not + * been initialized with service account credentials. + * @throws FirebaseAuthException If an error occurs while generating the custom token. + */ + public String createCustomToken(@NonNull String uid) throws FirebaseAuthException { + return createCustomToken(uid, null); + } + + /** + * Creates a Firebase custom token for the given UID, containing the specified additional + * claims. This token can then be sent back to a client application to be used with the + * signInWithCustomToken + * authentication API. + * + *

This method attempts to generate a token using: + *

    + *
  1. the private key of {@link FirebaseApp}'s service account credentials, if provided at + * initialization. + *
  2. the IAM service + * if a service account email was specified via + * {@link com.google.firebase.FirebaseOptions.Builder#setServiceAccountId(String)}. + *
  3. the App Identity + * service if the code is deployed in the Google App Engine standard environment. + *
  4. the + * local Metadata server if the code is deployed in a different GCP-managed environment + * like Google Compute Engine. + *
+ * + *

This method throws an exception when all the above fail. + * + * @param uid The UID to store in the token. This identifies the user to other Firebase services + * (Realtime Database, Firebase Auth, etc.). Should be less than 128 characters. + * @param developerClaims Additional claims to be stored in the token (and made available to + * security rules in Database, Storage, etc.). These must be able to be serialized to JSON + * (e.g. contain only Maps, Arrays, Strings, Booleans, Numbers, etc.) + * @return A Firebase custom token string. + * @throws IllegalArgumentException If the specified uid is null or empty. + * @throws IllegalStateException If the SDK fails to discover a viable approach for signing + * tokens. + * @throws FirebaseAuthException If an error occurs while generating the custom token. + */ + public String createCustomToken(@NonNull String uid, + @Nullable Map developerClaims) throws FirebaseAuthException { + return createCustomTokenOp(uid, developerClaims).call(); + } + + /** + * Similar to {@link #createCustomToken(String)} but performs the operation asynchronously. + * + * @param uid The UID to store in the token. This identifies the user to other Firebase services + * (Realtime Database, Firebase Auth, etc.). Should be less than 128 characters. + * @return An {@code ApiFuture} which will complete successfully with the created Firebase custom + * token, or unsuccessfully with the failure Exception. + * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not + * been initialized with service account credentials. + */ + public ApiFuture createCustomTokenAsync(@NonNull String uid) { + return createCustomTokenAsync(uid, null); + } + + /** + * Similar to {@link #createCustomToken(String, Map)} but performs the operation + * asynchronously. + * + * @param uid The UID to store in the token. This identifies the user to other Firebase services + * (Realtime Database, Storage, etc.). Should be less than 128 characters. + * @param developerClaims Additional claims to be stored in the token (and made available to + * security rules in Database, Storage, etc.). These must be able to be serialized to JSON + * (e.g. contain only Maps, Arrays, Strings, Booleans, Numbers, etc.) + * @return An {@code ApiFuture} which will complete successfully with the created Firebase custom + * token, or unsuccessfully with the failure Exception. + * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not + * been initialized with service account credentials. + */ + public ApiFuture createCustomTokenAsync( + @NonNull String uid, @Nullable Map developerClaims) { + return createCustomTokenOp(uid, developerClaims).callAsync(firebaseApp); + } + + private CallableOperation createCustomTokenOp( + final String uid, final Map developerClaims) { + checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); + final FirebaseTokenFactory tokenFactory = this.tokenFactory.get(); + return new CallableOperation() { + @Override + public String execute() throws FirebaseAuthException { + try { + return tokenFactory.createSignedCustomAuthTokenForUser(uid, developerClaims); + } catch (IOException e) { + throw new FirebaseAuthException(ERROR_CUSTOM_TOKEN, + "Failed to generate a custom token", e); + } + } + }; + } + + /** + * Parses and verifies a Firebase ID Token. + * + *

A Firebase application can identify itself to a trusted backend server by sending its + * Firebase ID Token (accessible via the {@code getToken} API in the Firebase Authentication + * client) with its requests. The backend server can then use the {@code verifyIdToken()} method + * to verify that the token is valid. This method ensures that the token is correctly signed, + * has not expired, and it was issued to the Firebase project associated with this + * {@link FirebaseAuth} instance. + * + *

This method does not check whether a token has been revoked. Use + * {@link #verifyIdToken(String, boolean)} to perform an additional revocation check. + * + * @param token A Firebase ID token string to parse and verify. + * @return A {@link FirebaseToken} representing the verified and decoded token. + * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp} + * instance does not have a project ID associated with it. + * @throws FirebaseAuthException If an error occurs while parsing or validating the token. + */ + public FirebaseToken verifyIdToken(@NonNull String token) throws FirebaseAuthException { + return verifyIdToken(token, false); + } + + /** + * Parses and verifies a Firebase ID Token. + * + *

A Firebase application can identify itself to a trusted backend server by sending its + * Firebase ID Token (accessible via the {@code getToken} API in the Firebase Authentication + * client) with its requests. The backend server can then use the {@code verifyIdToken()} method + * to verify that the token is valid. This method ensures that the token is correctly signed, + * has not expired, and it was issued to the Firebase project associated with this + * {@link FirebaseAuth} instance. + * + *

If {@code checkRevoked} is set to true, this method performs an additional check to see + * if the ID token has been revoked since it was issues. This requires making an additional + * remote API call. + * + * @param token A Firebase ID token string to parse and verify. + * @param checkRevoked A boolean denoting whether to check if the tokens were revoked. + * @return A {@link FirebaseToken} representing the verified and decoded token. + * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp} + * instance does not have a project ID associated with it. + * @throws FirebaseAuthException If an error occurs while parsing or validating the token. + */ + public FirebaseToken verifyIdToken( + @NonNull String token, boolean checkRevoked) throws FirebaseAuthException { + return verifyIdTokenOp(token, checkRevoked).call(); + } + + /** + * Similar to {@link #verifyIdToken(String)} but performs the operation asynchronously. + * + * @param token A Firebase ID Token to verify and parse. + * @return An {@code ApiFuture} which will complete successfully with the parsed token, or + * unsuccessfully with a {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp} + * instance does not have a project ID associated with it. + */ + public ApiFuture verifyIdTokenAsync(@NonNull String token) { + return verifyIdTokenAsync(token, false); + } + + /** + * Similar to {@link #verifyIdToken(String, boolean)} but performs the operation asynchronously. + * + * @param token A Firebase ID Token to verify and parse. + * @param checkRevoked A boolean denoting whether to check if the tokens were revoked. + * @return An {@code ApiFuture} which will complete successfully with the parsed token, or + * unsuccessfully with a {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp} + * instance does not have a project ID associated with it. + */ + public ApiFuture verifyIdTokenAsync(@NonNull String token, boolean checkRevoked) { + return verifyIdTokenOp(token, checkRevoked).callAsync(firebaseApp); + } + + private CallableOperation verifyIdTokenOp( + final String token, final boolean checkRevoked) { + checkArgument(!Strings.isNullOrEmpty(token), "ID token must not be null or empty"); + final FirebaseTokenVerifier verifier = getIdTokenVerifier(checkRevoked); + return new CallableOperation() { + @Override + protected FirebaseToken execute() throws FirebaseAuthException { + return verifier.verifyToken(token); + } + }; + } + + @VisibleForTesting + FirebaseTokenVerifier getIdTokenVerifier(boolean checkRevoked) { + FirebaseTokenVerifier verifier = idTokenVerifier.get(); + if (checkRevoked) { + FirebaseUserManager userManager = getUserManager(); + verifier = RevocationCheckDecorator.decorateIdTokenVerifier(verifier, userManager); + } + return verifier; + } + + /** + * Revokes all refresh tokens for the specified user. + * + *

Updates the user's tokensValidAfterTimestamp to the current UTC time expressed in + * milliseconds since the epoch and truncated to 1 second accuracy. It is important that the + * server on which this is called has its clock set correctly and synchronized. + * + *

While this will revoke all sessions for a specified user and disable any new ID tokens for + * existing sessions from getting minted, existing ID tokens may remain active until their + * natural expiration (one hour). + * To verify that ID tokens are revoked, use {@link #verifyIdTokenAsync(String, boolean)}. + * + * @param uid The user id for which tokens are revoked. + * @throws IllegalArgumentException If the user ID is null or empty. + * @throws FirebaseAuthException If an error occurs while revoking tokens. + */ + public void revokeRefreshTokens(@NonNull String uid) throws FirebaseAuthException { + revokeRefreshTokensOp(uid).call(); + } + + /** + * Similar to {@link #revokeRefreshTokens(String)} but performs the operation asynchronously. + * + * @param uid The user id for which tokens are revoked. + * @return An {@code ApiFuture} which will complete successfully or fail with a + * {@link FirebaseAuthException} in the event of an error. + * @throws IllegalArgumentException If the user ID is null or empty. + */ + public ApiFuture revokeRefreshTokensAsync(@NonNull String uid) { + return revokeRefreshTokensOp(uid).callAsync(firebaseApp); + } + + private CallableOperation revokeRefreshTokensOp(final String uid) { + checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected Void execute() throws FirebaseAuthException { + int currentTimeSeconds = (int) (System.currentTimeMillis() / 1000); + UpdateRequest request = new UpdateRequest(uid).setValidSince(currentTimeSeconds); + userManager.updateUser(request, jsonFactory); + return null; + } + }; + } + + /** + * Gets the user data corresponding to the specified user ID. + * + * @param uid A user ID string. + * @return A {@link UserRecord} instance. + * @throws IllegalArgumentException If the user ID string is null or empty. + * @throws FirebaseAuthException If an error occurs while retrieving user data. + */ + public UserRecord getUser(@NonNull String uid) throws FirebaseAuthException { + return getUserOp(uid).call(); + } + + /** + * Similar to {@link #getUser(String)} but performs the operation asynchronously. + * + * @param uid A user ID string. + * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord} + * instance. If an error occurs while retrieving user data or if the specified user ID does + * not exist, the future throws a {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the user ID string is null or empty. + */ + public ApiFuture getUserAsync(@NonNull String uid) { + return getUserOp(uid).callAsync(firebaseApp); + } + + private CallableOperation getUserOp(final String uid) { + checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected UserRecord execute() throws FirebaseAuthException { + return userManager.getUserById(uid); + } + }; + } + + /** + * Gets the user data corresponding to the specified user email. + * + * @param email A user email address string. + * @return A {@link UserRecord} instance. + * @throws IllegalArgumentException If the email is null or empty. + * @throws FirebaseAuthException If an error occurs while retrieving user data. + */ + public UserRecord getUserByEmail(@NonNull String email) throws FirebaseAuthException { + return getUserByEmailOp(email).call(); + } + + /** + * Similar to {@link #getUserByEmail(String)} but performs the operation asynchronously. + * + * @param email A user email address string. + * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord} + * instance. If an error occurs while retrieving user data or if the email address does not + * correspond to a user, the future throws a {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the email is null or empty. + */ + public ApiFuture getUserByEmailAsync(@NonNull String email) { + return getUserByEmailOp(email).callAsync(firebaseApp); + } + + private CallableOperation getUserByEmailOp( + final String email) { + checkArgument(!Strings.isNullOrEmpty(email), "email must not be null or empty"); + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected UserRecord execute() throws FirebaseAuthException { + return userManager.getUserByEmail(email); + } + }; + } + + /** + * Gets the user data corresponding to the specified user phone number. + * + * @param phoneNumber A user phone number string. + * @return A a {@link UserRecord} instance. + * @throws IllegalArgumentException If the phone number is null or empty. + * @throws FirebaseAuthException If an error occurs while retrieving user data. + */ + public UserRecord getUserByPhoneNumber(@NonNull String phoneNumber) throws FirebaseAuthException { + return getUserByPhoneNumberOp(phoneNumber).call(); + } + + /** + * Gets the user data corresponding to the specified user phone number. + * + * @param phoneNumber A user phone number string. + * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord} + * instance. If an error occurs while retrieving user data or if the phone number does not + * correspond to a user, the future throws a {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the phone number is null or empty. + */ + public ApiFuture getUserByPhoneNumberAsync(@NonNull String phoneNumber) { + return getUserByPhoneNumberOp(phoneNumber).callAsync(firebaseApp); + } + + private CallableOperation getUserByPhoneNumberOp( + final String phoneNumber) { + checkArgument(!Strings.isNullOrEmpty(phoneNumber), "phone number must not be null or empty"); + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected UserRecord execute() throws FirebaseAuthException { + return userManager.getUserByPhoneNumber(phoneNumber); + } + }; + } + + /** + * Gets the user data corresponding to the specified identifiers. + * + *

There are no ordering guarantees; in particular, the nth entry in the users result list is + * not guaranteed to correspond to the nth entry in the input parameters list. + * + *

A maximum of 100 identifiers may be specified. If more than 100 identifiers are + * supplied, this method throws an {@link IllegalArgumentException}. + * + * @param identifiers The identifiers used to indicate which user records should be returned. Must + * have 100 or fewer entries. + * @return The corresponding user records. + * @throws IllegalArgumentException If any of the identifiers are invalid or if more than 100 + * identifiers are specified. + * @throws NullPointerException If the identifiers parameter is null. + * @throws FirebaseAuthException If an error occurs while retrieving user data. + */ + public GetUsersResult getUsers(@NonNull Collection identifiers) + throws FirebaseAuthException { + return getUsersOp(identifiers).call(); + } + + /** + * Gets the user data corresponding to the specified identifiers. + * + *

There are no ordering guarantees; in particular, the nth entry in the users result list is + * not guaranteed to correspond to the nth entry in the input parameters list. + * + *

A maximum of 100 identifiers may be specified. If more than 100 identifiers are + * supplied, this method throws an {@link IllegalArgumentException}. + * + * @param identifiers The identifiers used to indicate which user records should be returned. + * Must have 100 or fewer entries. + * @return An {@code ApiFuture} that resolves to the corresponding user records. + * @throws IllegalArgumentException If any of the identifiers are invalid or if more than 100 + * identifiers are specified. + * @throws NullPointerException If the identifiers parameter is null. + */ + public ApiFuture getUsersAsync(@NonNull Collection identifiers) { + return getUsersOp(identifiers).callAsync(firebaseApp); + } + + private CallableOperation getUsersOp( + @NonNull final Collection identifiers) { + checkNotNull(identifiers, "identifiers must not be null"); + checkArgument(identifiers.size() <= FirebaseUserManager.MAX_GET_ACCOUNTS_BATCH_SIZE, + "identifiers parameter must have <= " + FirebaseUserManager.MAX_GET_ACCOUNTS_BATCH_SIZE + + " entries."); + + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected GetUsersResult execute() throws FirebaseAuthException { + Set users = userManager.getAccountInfo(identifiers); + Set notFound = new HashSet<>(); + for (UserIdentifier id : identifiers) { + if (!isUserFound(id, users)) { + notFound.add(id); + } + } + return new GetUsersResult(users, notFound); + } + }; + } + + private boolean isUserFound(UserIdentifier id, Collection userRecords) { + for (UserRecord userRecord : userRecords) { + if (id.matches(userRecord)) { + return true; + } + } + return false; + } + + /** + * Gets a page of users starting from the specified {@code pageToken}. Page size is + * limited to 1000 users. + * + * @param pageToken A non-empty page token string, or null to retrieve the first page of users. + * @return A {@link ListUsersPage} instance. + * @throws IllegalArgumentException If the specified page token is empty. + * @throws FirebaseAuthException If an error occurs while retrieving user data. + */ + public ListUsersPage listUsers(@Nullable String pageToken) throws FirebaseAuthException { + return listUsers(pageToken, FirebaseUserManager.MAX_LIST_USERS_RESULTS); + } + + /** + * Gets a page of users starting from the specified {@code pageToken}. + * + * @param pageToken A non-empty page token string, or null to retrieve the first page of users. + * @param maxResults Maximum number of users to include in the returned page. This may not + * exceed 1000. + * @return A {@link ListUsersPage} instance. + * @throws IllegalArgumentException If the specified page token is empty, or max results value + * is invalid. + * @throws FirebaseAuthException If an error occurs while retrieving user data. + */ + public ListUsersPage listUsers( + @Nullable String pageToken, int maxResults) throws FirebaseAuthException { + return listUsersOp(pageToken, maxResults).call(); + } + + /** + * Similar to {@link #listUsers(String)} but performs the operation asynchronously. + * + * @param pageToken A non-empty page token string, or null to retrieve the first page of users. + * @return An {@code ApiFuture} which will complete successfully with a {@link ListUsersPage} + * instance. If an error occurs while retrieving user data, the future throws an exception. + * @throws IllegalArgumentException If the specified page token is empty. + */ + public ApiFuture listUsersAsync(@Nullable String pageToken) { + return listUsersAsync(pageToken, FirebaseUserManager.MAX_LIST_USERS_RESULTS); + } + + /** + * Similar to {@link #listUsers(String, int)} but performs the operation asynchronously. + * + * @param pageToken A non-empty page token string, or null to retrieve the first page of users. + * @param maxResults Maximum number of users to include in the returned page. This may not + * exceed 1000. + * @return An {@code ApiFuture} which will complete successfully with a {@link ListUsersPage} + * instance. If an error occurs while retrieving user data, the future throws an exception. + * @throws IllegalArgumentException If the specified page token is empty, or max results value + * is invalid. + */ + public ApiFuture listUsersAsync(@Nullable String pageToken, int maxResults) { + return listUsersOp(pageToken, maxResults).callAsync(firebaseApp); + } + + private CallableOperation listUsersOp( + @Nullable final String pageToken, final int maxResults) { + final FirebaseUserManager userManager = getUserManager(); + final PageFactory factory = new PageFactory( + new DefaultUserSource(userManager, jsonFactory), maxResults, pageToken); + return new CallableOperation() { + @Override + protected ListUsersPage execute() throws FirebaseAuthException { + return factory.create(); + } + }; + } + + /** + * Creates a new user account with the attributes contained in the specified + * {@link CreateRequest}. + * + * @param request A non-null {@link CreateRequest} instance. + * @return A {@link UserRecord} instance corresponding to the newly created account. + * @throws NullPointerException if the provided request is null. + * @throws FirebaseAuthException if an error occurs while creating the user account. + */ + public UserRecord createUser(@NonNull CreateRequest request) throws FirebaseAuthException { + return createUserOp(request).call(); + } + + /** + * Similar to {@link #createUser(CreateRequest)} but performs the operation asynchronously. + * + * @param request A non-null {@link CreateRequest} instance. + * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord} + * instance corresponding to the newly created account. If an error occurs while creating the + * user account, the future throws a {@link FirebaseAuthException}. + * @throws NullPointerException if the provided request is null. + */ + public ApiFuture createUserAsync(@NonNull CreateRequest request) { + return createUserOp(request).callAsync(firebaseApp); + } + + private CallableOperation createUserOp( + final CreateRequest request) { + checkNotNull(request, "create request must not be null"); + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected UserRecord execute() throws FirebaseAuthException { + String uid = userManager.createUser(request); + return userManager.getUserById(uid); + } + }; + } + + /** + * Updates an existing user account with the attributes contained in the specified + * {@link UpdateRequest}. + * + * @param request A non-null {@link UpdateRequest} instance. + * @return A {@link UserRecord} instance corresponding to the updated user account. + * account, the task fails with a {@link FirebaseAuthException}. + * @throws NullPointerException if the provided update request is null. + * @throws FirebaseAuthException if an error occurs while updating the user account. + */ + public UserRecord updateUser(@NonNull UpdateRequest request) throws FirebaseAuthException { + return updateUserOp(request).call(); + } + + /** + * Similar to {@link #updateUser(UpdateRequest)} but performs the operation asynchronously. + * + * @param request A non-null {@link UpdateRequest} instance. + * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord} + * instance corresponding to the updated user account. If an error occurs while updating the + * user account, the future throws a {@link FirebaseAuthException}. + */ + public ApiFuture updateUserAsync(@NonNull UpdateRequest request) { + return updateUserOp(request).callAsync(firebaseApp); + } + + private CallableOperation updateUserOp( + final UpdateRequest request) { + checkNotNull(request, "update request must not be null"); + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected UserRecord execute() throws FirebaseAuthException { + userManager.updateUser(request, jsonFactory); + return userManager.getUserById(request.getUid()); + } + }; + } + + /** + * Sets the specified custom claims on an existing user account. A null claims value removes + * any claims currently set on the user account. The claims should serialize into a valid JSON + * string. The serialized claims must not be larger than 1000 characters. + * + * @param uid A user ID string. + * @param claims A map of custom claims or null. + * @throws FirebaseAuthException If an error occurs while updating custom claims. + * @throws IllegalArgumentException If the user ID string is null or empty, or the claims + * payload is invalid or too large. + */ + public void setCustomUserClaims(@NonNull String uid, + @Nullable Map claims) throws FirebaseAuthException { + setCustomUserClaimsOp(uid, claims).call(); + } + + /** + * @deprecated Use {@link #setCustomUserClaims(String, Map)} instead. + */ + public void setCustomClaims(@NonNull String uid, + @Nullable Map claims) throws FirebaseAuthException { + setCustomUserClaims(uid, claims); + } + + /** + * Similar to {@link #setCustomUserClaims(String, Map)} but performs the operation asynchronously. + * + * @param uid A user ID string. + * @param claims A map of custom claims or null. + * @return An {@code ApiFuture} which will complete successfully when the user account has been + * updated. If an error occurs while deleting the user account, the future throws a + * {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the user ID string is null or empty. + */ + public ApiFuture setCustomUserClaimsAsync( + @NonNull String uid, @Nullable Map claims) { + return setCustomUserClaimsOp(uid, claims).callAsync(firebaseApp); + } + + private CallableOperation setCustomUserClaimsOp( + final String uid, final Map claims) { + checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected Void execute() throws FirebaseAuthException { + final UpdateRequest request = new UpdateRequest(uid).setCustomClaims(claims); + userManager.updateUser(request, jsonFactory); + return null; + } + }; + } + + /** + * Deletes the user identified by the specified user ID. + * + * @param uid A user ID string. + * @throws IllegalArgumentException If the user ID string is null or empty. + * @throws FirebaseAuthException If an error occurs while deleting the user. + */ + public void deleteUser(@NonNull String uid) throws FirebaseAuthException { + deleteUserOp(uid).call(); + } + + /** + * Similar to {@link #deleteUser(String)} but performs the operation asynchronously. + * + * @param uid A user ID string. + * @return An {@code ApiFuture} which will complete successfully when the specified user account + * has been deleted. If an error occurs while deleting the user account, the future throws a + * {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the user ID string is null or empty. + */ + public ApiFuture deleteUserAsync(String uid) { + return deleteUserOp(uid).callAsync(firebaseApp); + } + + private CallableOperation deleteUserOp(final String uid) { + checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected Void execute() throws FirebaseAuthException { + userManager.deleteUser(uid); + return null; + } + }; + } + + /** + * Deletes the users specified by the given identifiers. + * + *

Deleting a non-existing user does not generate an error (the method is idempotent). + * Non-existing users are considered to be successfully deleted and are therefore included in the + * DeleteUsersResult.getSuccessCount() value. + * + *

A maximum of 1000 identifiers may be supplied. If more than 1000 identifiers are + * supplied, this method throws an {@link IllegalArgumentException}. + * + *

This API has a rate limit of 1 QPS. Exceeding the limit may result in a quota exceeded + * error. If you want to delete more than 1000 users, we suggest adding a delay to ensure you + * don't exceed this limit. + * + * @param uids The uids of the users to be deleted. Must have <= 1000 entries. + * @return The total number of successful/failed deletions, as well as the array of errors that + * correspond to the failed deletions. + * @throw IllegalArgumentException If any of the identifiers are invalid or if more than 1000 + * identifiers are specified. + * @throws FirebaseAuthException If an error occurs while deleting users. + */ + public DeleteUsersResult deleteUsers(List uids) throws FirebaseAuthException { + return deleteUsersOp(uids).call(); + } + + /** + * Similar to {@link #deleteUsers(List)} but performs the operation asynchronously. + * + * @param uids The uids of the users to be deleted. Must have <= 1000 entries. + * @return An {@code ApiFuture} that resolves to the total number of successful/failed + * deletions, as well as the array of errors that correspond to the failed deletions. If an + * error occurs while deleting the user account, the future throws a + * {@link FirebaseAuthException}. + * @throw IllegalArgumentException If any of the identifiers are invalid or if more than 1000 + * identifiers are specified. + */ + public ApiFuture deleteUsersAsync(List uids) { + return deleteUsersOp(uids).callAsync(firebaseApp); + } + + private CallableOperation deleteUsersOp( + final List uids) { + checkNotNull(uids, "uids must not be null"); + for (String uid : uids) { + UserRecord.checkUid(uid); + } + checkArgument(uids.size() <= FirebaseUserManager.MAX_DELETE_ACCOUNTS_BATCH_SIZE, + "uids parameter must have <= " + FirebaseUserManager.MAX_DELETE_ACCOUNTS_BATCH_SIZE + + " entries."); + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected DeleteUsersResult execute() throws FirebaseAuthException { + return userManager.deleteUsers(uids); + } + }; + } + + /** + * Imports the provided list of users into Firebase Auth. You can import a maximum of 1000 users + * at a time. This operation is optimized for bulk imports and does not check identifier + * uniqueness which could result in duplications. + * + *

{@link UserImportOptions} is required to import users with passwords. See + * {@link #importUsers(List, UserImportOptions)}. + * + * @param users A non-empty list of users to be imported. Length must not exceed 1000. + * @return A {@link UserImportResult} instance. + * @throws IllegalArgumentException If the users list is null, empty or has more than 1000 + * elements. Or if at least one user specifies a password. + * @throws FirebaseAuthException If an error occurs while importing users. + */ + public UserImportResult importUsers(List users) throws FirebaseAuthException { + return importUsers(users, null); + } + + /** + * Imports the provided list of users into Firebase Auth. At most 1000 users can be imported at a + * time. This operation is optimized for bulk imports and will ignore checks on identifier + * uniqueness which could result in duplications. + * + * @param users A non-empty list of users to be imported. Length must not exceed 1000. + * @param options a {@link UserImportOptions} instance or null. Required when importing users + * with passwords. + * @return A {@link UserImportResult} instance. + * @throws IllegalArgumentException If the users list is null, empty or has more than 1000 + * elements. Or if at least one user specifies a password, and options is null. + * @throws FirebaseAuthException If an error occurs while importing users. + */ + public UserImportResult importUsers(List users, + @Nullable UserImportOptions options) throws FirebaseAuthException { + return importUsersOp(users, options).call(); + } + + /** + * Similar to {@link #importUsers(List)} but performs the operation asynchronously. + * + * @param users A non-empty list of users to be imported. Length must not exceed 1000. + * @return An {@code ApiFuture} which will complete successfully when the user accounts are + * imported. If an error occurs while importing the users, the future throws a + * {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the users list is null, empty or has more than 1000 + * elements. Or if at least one user specifies a password. + */ + public ApiFuture importUsersAsync(List users) { + return importUsersAsync(users, null); + } + + /** + * Similar to {@link #importUsers(List, UserImportOptions)} but performs the operation + * asynchronously. + * + * @param users A non-empty list of users to be imported. Length must not exceed 1000. + * @param options a {@link UserImportOptions} instance or null. Required when importing users + * with passwords. + * @return An {@code ApiFuture} which will complete successfully when the user accounts are + * imported. If an error occurs while importing the users, the future throws a + * {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the users list is null, empty or has more than 1000 + * elements. Or if at least one user specifies a password, and options is null. + */ + public ApiFuture importUsersAsync(List users, + @Nullable UserImportOptions options) { + return importUsersOp(users, options).callAsync(firebaseApp); + } + + private CallableOperation importUsersOp( + final List users, final UserImportOptions options) { + final UserImportRequest request = new UserImportRequest(users, options, jsonFactory); + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected UserImportResult execute() throws FirebaseAuthException { + return userManager.importUsers(request); + } + }; + } + + /** + * Generates the out-of-band email action link for password reset flows for the specified email + * address. + * + * @param email The email of the user whose password is to be reset. + * @return A password reset link. + * @throws IllegalArgumentException If the email address is null or empty. + * @throws FirebaseAuthException If an error occurs while generating the link. + */ + public String generatePasswordResetLink(@NonNull String email) throws FirebaseAuthException { + return generatePasswordResetLink(email, null); + } + + /** + * Generates the out-of-band email action link for password reset flows for the specified email + * address. + * + * @param email The email of the user whose password is to be reset. + * @param settings The action code settings object which defines whether + * the link is to be handled by a mobile app and the additional state information to be + * passed in the deep link. + * @return A password reset link. + * @throws IllegalArgumentException If the email address is null or empty. + * @throws FirebaseAuthException If an error occurs while generating the link. + */ + public String generatePasswordResetLink( + @NonNull String email, @Nullable ActionCodeSettings settings) throws FirebaseAuthException { + return generateEmailActionLinkOp(EmailLinkType.PASSWORD_RESET, email, settings).call(); + } + + /** + * Similar to {@link #generatePasswordResetLink(String)} but performs the operation + * asynchronously. + * + * @param email The email of the user whose password is to be reset. + * @return An {@code ApiFuture} which will complete successfully with the generated email action + * link. If an error occurs while generating the link, the future throws a + * {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the email address is null or empty. + */ + public ApiFuture generatePasswordResetLinkAsync(@NonNull String email) { + return generatePasswordResetLinkAsync(email, null); + } + + /** + * Similar to {@link #generatePasswordResetLink(String, ActionCodeSettings)} but performs the + * operation asynchronously. + * + * @param email The email of the user whose password is to be reset. + * @param settings The action code settings object which defines whether + * the link is to be handled by a mobile app and the additional state information to be + * passed in the deep link. + * @return An {@code ApiFuture} which will complete successfully with the generated email action + * link. If an error occurs while generating the link, the future throws a + * {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the email address is null or empty. + */ + public ApiFuture generatePasswordResetLinkAsync( + @NonNull String email, @Nullable ActionCodeSettings settings) { + return generateEmailActionLinkOp(EmailLinkType.PASSWORD_RESET, email, settings) + .callAsync(firebaseApp); + } + + /** + * Generates the out-of-band email action link for email verification flows for the specified + * email address. + * + * @param email The email of the user to be verified. + * @return An email verification link. + * @throws IllegalArgumentException If the email address is null or empty. + * @throws FirebaseAuthException If an error occurs while generating the link. + */ + public String generateEmailVerificationLink(@NonNull String email) throws FirebaseAuthException { + return generateEmailVerificationLink(email, null); + } + + /** + * Generates the out-of-band email action link for email verification flows for the specified + * email address, using the action code settings provided. + * + * @param email The email of the user to be verified. + * @return An email verification link. + * @throws IllegalArgumentException If the email address is null or empty. + * @throws FirebaseAuthException If an error occurs while generating the link. + */ + public String generateEmailVerificationLink( + @NonNull String email, @Nullable ActionCodeSettings settings) throws FirebaseAuthException { + return generateEmailActionLinkOp(EmailLinkType.VERIFY_EMAIL, email, settings).call(); + } + + /** + * Similar to {@link #generateEmailVerificationLink(String)} but performs the + * operation asynchronously. + * + * @param email The email of the user to be verified. + * @return An {@code ApiFuture} which will complete successfully with the generated email action + * link. If an error occurs while generating the link, the future throws a + * {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the email address is null or empty. + */ + public ApiFuture generateEmailVerificationLinkAsync(@NonNull String email) { + return generateEmailVerificationLinkAsync(email, null); + } + + /** + * Similar to {@link #generateEmailVerificationLink(String, ActionCodeSettings)} but performs the + * operation asynchronously. + * + * @param email The email of the user to be verified. + * @param settings The action code settings object which defines whether + * the link is to be handled by a mobile app and the additional state information to be + * passed in the deep link. + * @return An {@code ApiFuture} which will complete successfully with the generated email action + * link. If an error occurs while generating the link, the future throws a + * {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the email address is null or empty. + */ + public ApiFuture generateEmailVerificationLinkAsync( + @NonNull String email, @Nullable ActionCodeSettings settings) { + return generateEmailActionLinkOp(EmailLinkType.VERIFY_EMAIL, email, settings) + .callAsync(firebaseApp); + } + + /** + * Generates the out-of-band email action link for email link sign-in flows, using the action + * code settings provided. + * + * @param email The email of the user signing in. + * @param settings The action code settings object which defines whether + * the link is to be handled by a mobile app and the additional state information to be + * passed in the deep link. + * @return An email verification link. + * @throws IllegalArgumentException If the email address is null or empty. + * @throws FirebaseAuthException If an error occurs while generating the link. + */ + public String generateSignInWithEmailLink( + @NonNull String email, @NonNull ActionCodeSettings settings) throws FirebaseAuthException { + return generateEmailActionLinkOp(EmailLinkType.EMAIL_SIGNIN, email, settings).call(); + } + + /** + * Similar to {@link #generateSignInWithEmailLink(String, ActionCodeSettings)} but performs the + * operation asynchronously. + * + * @param email The email of the user signing in. + * @param settings The action code settings object which defines whether + * the link is to be handled by a mobile app and the additional state information to be + * passed in the deep link. + * @return An {@code ApiFuture} which will complete successfully with the generated email action + * link. If an error occurs while generating the link, the future throws a + * {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the email address is null or empty. + * @throws NullPointerException If the settings is null. + */ + public ApiFuture generateSignInWithEmailLinkAsync( + String email, @NonNull ActionCodeSettings settings) { + return generateEmailActionLinkOp(EmailLinkType.EMAIL_SIGNIN, email, settings) + .callAsync(firebaseApp); + } + + @VisibleForTesting + FirebaseUserManager getUserManager() { + return this.userManager.get(); + } + + private CallableOperation generateEmailActionLinkOp( + final EmailLinkType type, final String email, final ActionCodeSettings settings) { + checkArgument(!Strings.isNullOrEmpty(email), "email must not be null or empty"); + if (type == EmailLinkType.EMAIL_SIGNIN) { + checkNotNull(settings, "ActionCodeSettings must not be null when generating sign-in links"); + } + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected String execute() throws FirebaseAuthException { + return userManager.getEmailActionLink(type, email, settings); + } + }; + } + + private Supplier threadSafeMemoize(final Supplier supplier) { + return Suppliers.memoize(new Supplier() { + @Override + public T get() { + checkNotNull(supplier); + synchronized (lock) { + return supplier.get(); + } + } + }); + } + + ======= + >>>>>>> master private static FirebaseAuth fromApp(final FirebaseApp app) { return populateBuilderFromApp(builder(), app, null) .setTenantManager(new Supplier() { @@ -77,6 +1248,12 @@ public TenantManager get() { return new TenantManager(app); } }) + .setUserManager(new Supplier() { + @Override + public FirebaseUserManager get() { + return new FirebaseUserManager(app); + } + }) .build(); } @@ -91,7 +1268,16 @@ static Builder builder() { return new Builder(); } + <<<<<<< redacted-passwords + static class Builder { + private FirebaseApp firebaseApp; + private Supplier tokenFactory; + private Supplier idTokenVerifier; + private Supplier cookieVerifier; + private Supplier userManager; + ======= static class Builder extends AbstractFirebaseAuth.Builder { + >>>>>>> master private Supplier tenantManager; @@ -107,8 +1293,27 @@ public Builder setTenantManager(Supplier tenantManager) { return this; } + <<<<<<< redacted-passwords + Builder setUserManager(Supplier userManager) { + this.userManager = userManager; + return this; + } + + FirebaseAuth build() { + return new FirebaseAuth(this); + } + } + + private static class FirebaseAuthService extends FirebaseService { + + FirebaseAuthService(FirebaseApp app) { + super(SERVICE_ID, FirebaseAuth.fromApp(app)); + } + } + ======= public FirebaseAuth build() { return new FirebaseAuth(this); } } + >>>>>>> master } diff --git a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java index 9a3e1ae15..6a82be250 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java +++ b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java @@ -29,7 +29,10 @@ import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.firebase.ErrorCode; + <<<<<<< v7 + ======= import com.google.firebase.auth.internal.Utils; + >>>>>>> master import com.google.firebase.internal.Nullable; import java.io.IOException; import java.math.BigDecimal; @@ -72,7 +75,11 @@ private FirebaseTokenVerifierImpl(Builder builder) { this.docUrl = builder.docUrl; this.invalidTokenErrorCode = checkNotNull(builder.invalidTokenErrorCode); this.expiredTokenErrorCode = checkNotNull(builder.expiredTokenErrorCode); + <<<<<<< v7 + this.tenantId = Strings.nullToEmpty(builder.tenantId); + ======= this.tenantId = builder.tenantId; + >>>>>>> master } /** @@ -164,8 +171,12 @@ private void checkSignature(IdToken token) throws FirebaseAuthException { } } + <<<<<<< v7 + private void checkContents(final IdToken idToken) throws FirebaseAuthException { + ======= private void checkContents(final IdToken idToken, boolean isEmulatorMode) throws FirebaseAuthException { + >>>>>>> master final Header header = idToken.getHeader(); final Payload payload = idToken.getPayload(); @@ -173,7 +184,11 @@ private void checkContents(final IdToken idToken, boolean isEmulatorMode) String errorMessage = null; AuthErrorCode errorCode = invalidTokenErrorCode; + <<<<<<< v7 + if (header.getKeyId() == null) { + ======= if (!isEmulatorMode && header.getKeyId() == null) { + >>>>>>> master errorMessage = getErrorForTokenWithoutKid(header, payload); } else if (!isEmulatorMode && !RS256.equals(header.getAlgorithm())) { errorMessage = String.format( @@ -227,11 +242,19 @@ private void checkContents(final IdToken idToken, boolean isEmulatorMode) throw newException(detailedError, errorCode); } } + <<<<<<< v7 private FirebaseAuthException newException(String message, AuthErrorCode errorCode) { return newException(message, errorCode, null); } + ======= + + private FirebaseAuthException newException(String message, AuthErrorCode errorCode) { + return newException(message, errorCode, null); + } + + >>>>>>> master private FirebaseAuthException newException( String message, AuthErrorCode errorCode, Throwable cause) { return new FirebaseAuthException( @@ -328,11 +351,19 @@ private boolean containsLegacyUidField(IdToken.Payload payload) { } private void checkTenantId(final FirebaseToken firebaseToken) throws FirebaseAuthException { + <<<<<<< v7 + String tokenTenantId = Strings.nullToEmpty(firebaseToken.getTenantId()); + if (!this.tenantId.equals(tokenTenantId)) { + String message = String.format( + "The tenant ID ('%s') of the token did not match the expected value ('%s')", + tokenTenantId, + ======= String tokenTenantId = firebaseToken.getTenantId(); if (this.tenantId != null && !this.tenantId.equals(tokenTenantId)) { String message = String.format( "The tenant ID ('%s') of the token did not match the expected value ('%s')", Strings.nullToEmpty(tokenTenantId), + >>>>>>> master tenantId); throw newException(message, AuthErrorCode.TENANT_ID_MISMATCH); } diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java index 195527841..c81c7ce8d 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java +++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java @@ -21,6 +21,10 @@ import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponseInterceptor; + <<<<<<< redacted-passwords +import com.google.api.client.http.json.JsonHttpContent; + ======= + >>>>>>> master import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonFactory; import com.google.api.client.util.Key; @@ -32,12 +36,27 @@ import com.google.firebase.ErrorCode; import com.google.firebase.FirebaseApp; import com.google.firebase.ImplFirebaseTrampolines; + <<<<<<< redacted-passwords +import com.google.firebase.auth.UserRecord.CreateRequest; +import com.google.firebase.auth.UserRecord.UpdateRequest; + ======= import com.google.firebase.IncomingHttpResponse; import com.google.firebase.auth.internal.AuthHttpClient; + >>>>>>> master import com.google.firebase.auth.internal.BatchDeleteResponse; import com.google.firebase.auth.internal.DownloadAccountResponse; import com.google.firebase.auth.internal.GetAccountInfoRequest; import com.google.firebase.auth.internal.GetAccountInfoResponse; + <<<<<<< redacted-passwords +import com.google.firebase.auth.internal.HttpErrorResponse; +import com.google.firebase.auth.internal.UploadAccountResponse; +import com.google.firebase.internal.ApiClientUtils; +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; +import com.google.firebase.internal.SdkUtils; + +import java.io.IOException; + ======= import com.google.firebase.auth.internal.ListOidcProviderConfigsResponse; import com.google.firebase.auth.internal.ListSamlProviderConfigsResponse; import com.google.firebase.auth.internal.UploadAccountResponse; @@ -46,6 +65,7 @@ import com.google.firebase.internal.HttpRequestInfo; import com.google.firebase.internal.NonNull; import com.google.firebase.internal.Nullable; + >>>>>>> master import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -61,7 +81,10 @@ */ final class FirebaseUserManager { + <<<<<<< redacted-passwords + ======= static final int MAX_LIST_PROVIDER_CONFIGS_RESULTS = 100; + >>>>>>> master static final int MAX_GET_ACCOUNTS_BATCH_SIZE = 100; static final int MAX_DELETE_ACCOUNTS_BATCH_SIZE = 1000; static final int MAX_LIST_USERS_RESULTS = 1000; @@ -79,17 +102,52 @@ final class FirebaseUserManager { private final String userMgtBaseUrl; private final String idpConfigMgtBaseUrl; private final JsonFactory jsonFactory; + <<<<<<< redacted-passwords + private final HttpRequestFactory requestFactory; + private final String clientVersion = "Java/Admin/" + SdkUtils.getVersion(); + + private HttpResponseInterceptor interceptor; + + /** + * Creates a new FirebaseUserManager instance. + * + * @param app A non-null {@link FirebaseApp}. + */ + FirebaseUserManager(@NonNull FirebaseApp app) { + this(app, null); + } + + FirebaseUserManager(@NonNull FirebaseApp app, @Nullable HttpRequestFactory requestFactory) { + checkNotNull(app, "FirebaseApp must not be null"); + String projectId = ImplFirebaseTrampolines.getProjectId(app); + ======= private final AuthHttpClient httpClient; private FirebaseUserManager(Builder builder) { String projectId = builder.projectId; + >>>>>>> master checkArgument(!Strings.isNullOrEmpty(projectId), "Project ID is required to access the auth service. Use a service account credential or " + "set the project ID explicitly via FirebaseOptions. Alternatively you can also " + "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable."); + <<<<<<< redacted-passwords + this.baseUrl = String.format(ID_TOOLKIT_URL, projectId); + this.jsonFactory = app.getOptions().getJsonFactory(); + + if (requestFactory == null) { + requestFactory = ApiClientUtils.newAuthorizedRequestFactory(app); + } + + this.requestFactory = requestFactory; + ======= this.jsonFactory = checkNotNull(builder.jsonFactory, "JsonFactory must not be null"); + <<<<<<< v7 + final String idToolkitUrlV1 = String.format(ID_TOOLKIT_URL, "v1", projectId); + final String idToolkitUrlV2 = String.format(ID_TOOLKIT_URL, "v2", projectId); + ======= final String idToolkitUrlV1 = getIdToolkitUrl(projectId, "v1"); final String idToolkitUrlV2 = getIdToolkitUrl(projectId, "v2"); + >>>>>>> master final String tenantId = builder.tenantId; if (tenantId == null) { this.userMgtBaseUrl = idToolkitUrlV1; @@ -101,6 +159,8 @@ private FirebaseUserManager(Builder builder) { } this.httpClient = new AuthHttpClient(jsonFactory, builder.requestFactory); + <<<<<<< v7 + ======= } private String getIdToolkitUrl(String projectId, String version) { @@ -108,6 +168,8 @@ private String getIdToolkitUrl(String projectId, String version) { return String.format(ID_TOOLKIT_URL_EMULATOR, Utils.getEmulatorHost(), version, projectId); } return String.format(ID_TOOLKIT_URL, version, projectId); + >>>>>>> master + >>>>>>> master } @VisibleForTesting @@ -155,6 +217,45 @@ Set getAccountInfo(@NonNull Collection identifiers) return results; } + <<<<<<< redacted-passwords + Set getAccountInfo(@NonNull Collection identifiers) + throws FirebaseAuthException { + if (identifiers.isEmpty()) { + return new HashSet(); + } + + GetAccountInfoRequest payload = new GetAccountInfoRequest(); + for (UserIdentifier id : identifiers) { + id.populate(payload); + } + + GetAccountInfoResponse response = post( + "/accounts:lookup", payload, GetAccountInfoResponse.class); + + if (response == null) { + throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to parse server response"); + } + + Set results = new HashSet<>(); + if (response.getUsers() != null) { + for (GetAccountInfoResponse.User user : response.getUsers()) { + results.add(new UserRecord(user, jsonFactory)); + } + } + return results; + } + + String createUser(CreateRequest request) throws FirebaseAuthException { + GenericJson response = post( + "/accounts", request.getProperties(), GenericJson.class); + if (response != null) { + String uid = (String) response.get("localId"); + if (!Strings.isNullOrEmpty(uid)) { + return uid; + } + } + throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to create new user"); + ======= UserRecord getUserByProviderUid( String providerId, String uid) throws FirebaseAuthException { final Map payload = ImmutableMap.of( @@ -162,6 +263,7 @@ UserRecord getUserByProviderUid( ImmutableMap.builder() .put("rawId", uid).put("providerId", providerId).build())); return lookupUserAccount(payload, uid); + >>>>>>> master } String createUser(UserRecord.CreateRequest request) throws FirebaseAuthException { @@ -188,6 +290,23 @@ DeleteUsersResult deleteUsers(@NonNull List uids) throws FirebaseAuthExc return new DeleteUsersResult(uids.size(), response); } + /** + * @pre uids != null + * @pre uids.size() <= MAX_DELETE_ACCOUNTS_BATCH_SIZE + */ + DeleteUsersResult deleteUsers(@NonNull List uids) throws FirebaseAuthException { + final Map payload = ImmutableMap.of( + "localIds", uids, + "force", true); + BatchDeleteResponse response = post( + "/accounts:batchDelete", payload, BatchDeleteResponse.class); + if (response == null) { + throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to delete users"); + } + + return new DeleteUsersResult(uids.size(), response); + } + DownloadAccountResponse listUsers(int maxResults, String pageToken) throws FirebaseAuthException { ImmutableMap.Builder builder = ImmutableMap.builder() .put("maxResults", maxResults); @@ -410,9 +529,15 @@ static class Builder { private String tenantId; private HttpRequestFactory requestFactory; private JsonFactory jsonFactory; + <<<<<<< v7 + + private Builder() { } + + ======= private Builder() { } + >>>>>>> master public Builder setProjectId(String projectId) { this.projectId = projectId; return this; diff --git a/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java b/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java index 4b9523c83..43d46a07e 100644 --- a/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java +++ b/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java @@ -61,12 +61,15 @@ final class AuthErrorHandler extends AbstractHttpErrorHandler>>>>>> master "INVALID_DYNAMIC_LINK_DOMAIN", new AuthError( ErrorCode.INVALID_ARGUMENT, diff --git a/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java b/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java index 543f955c3..756b12c0a 100644 --- a/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java +++ b/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java @@ -22,7 +22,10 @@ import com.google.firebase.FirebaseException; import com.google.firebase.ImplFirebaseTrampolines; import com.google.firebase.auth.FirebaseAuthException; + <<<<<<< v7 + ======= import com.google.firebase.auth.internal.Utils; + >>>>>>> master import com.google.firebase.internal.AbstractPlatformErrorHandler; import com.google.firebase.internal.ApiClientUtils; import com.google.firebase.internal.ErrorHandlingHttpClient; @@ -96,11 +99,19 @@ void setInterceptor(HttpResponseInterceptor interceptor) { @Override public byte[] sign(byte[] payload) throws FirebaseAuthException { String encodedPayload = BaseEncoding.base64().encode(payload); + <<<<<<< v7 + Map content = ImmutableMap.of("bytesToSign", encodedPayload); + String encodedUrl = String.format(IAM_SIGN_BLOB_URL, serviceAccount); + HttpRequestInfo requestInfo = HttpRequestInfo.buildJsonPostRequest(encodedUrl, content); + GenericJson parsed = httpClient.sendAndParse(requestInfo, GenericJson.class); + return BaseEncoding.base64().decode((String) parsed.get("signature")); + ======= Map content = ImmutableMap.of("payload", encodedPayload); String encodedUrl = String.format(IAM_SIGN_BLOB_URL, serviceAccount); HttpRequestInfo requestInfo = HttpRequestInfo.buildJsonPostRequest(encodedUrl, content); GenericJson parsed = httpClient.sendAndParse(requestInfo, GenericJson.class); return BaseEncoding.base64().decode((String) parsed.get("signedBlob")); + >>>>>>> master } @Override @@ -109,6 +120,8 @@ public String getAccount() { } } + <<<<<<< v7 + ======= /** * A {@link CryptoSigner} implementation that doesn't sign data. For use with the Auth Emulator * only @@ -128,6 +141,7 @@ public String getAccount() { } } + >>>>>>> master private static class IAMErrorHandler extends AbstractPlatformErrorHandler { diff --git a/src/main/java/com/google/firebase/auth/multitenancy/FirebaseTenantClient.java b/src/main/java/com/google/firebase/auth/multitenancy/FirebaseTenantClient.java index 56ef04eb7..c4f7108b7 100644 --- a/src/main/java/com/google/firebase/auth/multitenancy/FirebaseTenantClient.java +++ b/src/main/java/com/google/firebase/auth/multitenancy/FirebaseTenantClient.java @@ -62,7 +62,11 @@ final class FirebaseTenantClient { "Project ID is required to access the auth service. Use a service account credential or " + "set the project ID explicitly via FirebaseOptions. Alternatively you can also " + "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable."); + <<<<<<< v7 + this.tenantMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v2", projectId); + ======= this.tenantMgtBaseUrl = getTenantMgtBaseUrl(projectId); + >>>>>>> master this.httpClient = new AuthHttpClient(jsonFactory, requestFactory); } diff --git a/src/main/java/com/google/firebase/database/DatabaseError.java b/src/main/java/com/google/firebase/database/DatabaseError.java index 456eb99e6..9aa5ae3c2 100644 --- a/src/main/java/com/google/firebase/database/DatabaseError.java +++ b/src/main/java/com/google/firebase/database/DatabaseError.java @@ -16,10 +16,14 @@ package com.google.firebase.database; +import com.google.common.collect.ImmutableMap; +import com.google.firebase.ErrorCode; +import com.google.firebase.database.DatabaseReference.CompletionListener; import java.io.PrintWriter; import java.io.StringWriter; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ExecutionException; /** * Instances of DatabaseError are passed to callbacks when an operation failed. They contain a @@ -27,99 +31,78 @@ */ public class DatabaseError { - /** Internal use. */ - public static final int DATA_STALE = -1; - /** The server indicated that this operation failed. */ - public static final int OPERATION_FAILED = -2; - /** This client does not have permission to perform this operation. */ - public static final int PERMISSION_DENIED = -3; - /** The operation had to be aborted due to a network disconnect. */ - public static final int DISCONNECTED = -4; - - // Preempted was removed, this is for here for completeness and history - // public static final int PREEMPTED = -5; - - /** The supplied auth token has expired. */ - public static final int EXPIRED_TOKEN = -6; - /** - * The specified authentication token is invalid. This can occur when the token is malformed, - * expired, or the secret that was used to generate it has been revoked. - */ - public static final int INVALID_TOKEN = -7; - /** The transaction had too many retries */ - public static final int MAX_RETRIES = -8; - /** The transaction was overridden by a subsequent set */ - public static final int OVERRIDDEN_BY_SET = -9; - /** The service is unavailable. */ - public static final int UNAVAILABLE = -10; - /** An exception occurred in user code. */ - public static final int USER_CODE_EXCEPTION = -11; - - // client codes - /** The operation could not be performed due to a network error. */ - public static final int NETWORK_ERROR = -24; - - /** The write was canceled locally. */ - public static final int WRITE_CANCELED = -25; - - /** - * An unknown error occurred. Please refer to the error message and error details for more - * information. - */ - public static final int UNKNOWN_ERROR = -999; - - private static final Map errorReasons = new HashMap<>(); - private static final Map errorCodes = new HashMap<>(); - - static { - // Firebase Database error codes - errorReasons.put(DATA_STALE, "The transaction needs to be run again with current data"); - errorReasons.put(OPERATION_FAILED, "The server indicated that this operation failed"); - errorReasons.put( - PERMISSION_DENIED, "This client does not have permission to perform this operation"); - errorReasons.put(DISCONNECTED, "The operation had to be aborted due to a network disconnect"); - errorReasons.put(EXPIRED_TOKEN, "The supplied auth token has expired"); - errorReasons.put(INVALID_TOKEN, "The supplied auth token was invalid"); - errorReasons.put(MAX_RETRIES, "The transaction had too many retries"); - errorReasons.put(OVERRIDDEN_BY_SET, "The transaction was overridden by a subsequent set"); - errorReasons.put(UNAVAILABLE, "The service is unavailable"); - errorReasons.put( - USER_CODE_EXCEPTION, - "User code called from the Firebase Database runloop threw an exception:\n"); - - // client codes - errorReasons.put(NETWORK_ERROR, "The operation could not be performed due to a network error"); - errorReasons.put(WRITE_CANCELED, "The write was canceled by the user."); - errorReasons.put(UNKNOWN_ERROR, "An unknown error occurred"); - } + private static final Map errorReasons = + ImmutableMap.builder() + .put(DatabaseErrorCode.DATA_STALE, + "The transaction needs to be run again with current data") + .put(DatabaseErrorCode.OPERATION_FAILED, + "The server indicated that this operation failed") + .put(DatabaseErrorCode.PERMISSION_DENIED, + "This client does not have permission to perform this operation") + .put(DatabaseErrorCode.DISCONNECTED, + "The operation had to be aborted due to a network disconnect") + .put(DatabaseErrorCode.EXPIRED_TOKEN, "The supplied auth token has expired") + .put(DatabaseErrorCode.INVALID_TOKEN, "The supplied auth token was invalid") + .put(DatabaseErrorCode.MAX_RETRIES, "The transaction had too many retries") + .put(DatabaseErrorCode.OVERRIDDEN_BY_SET, + "The transaction was overridden by a subsequent set") + .put(DatabaseErrorCode.UNAVAILABLE, "The service is unavailable") + .put(DatabaseErrorCode.USER_CODE_EXCEPTION, + "User code called from the Firebase Database runloop threw an exception:\n") + // client codes + .put(DatabaseErrorCode.NETWORK_ERROR, + "The operation could not be performed due to a network error") + .put(DatabaseErrorCode.WRITE_CANCELED, "The write was canceled by the user.") + .put(DatabaseErrorCode.UNKNOWN_ERROR, "An unknown error occurred") + .build(); + + private static final Map platformCodes = + ImmutableMap.builder() + .put(DatabaseErrorCode.DATA_STALE, ErrorCode.FAILED_PRECONDITION) + .put(DatabaseErrorCode.OPERATION_FAILED, ErrorCode.INTERNAL) + .put(DatabaseErrorCode.PERMISSION_DENIED, ErrorCode.PERMISSION_DENIED) + .put(DatabaseErrorCode.DISCONNECTED, ErrorCode.UNKNOWN) + .put(DatabaseErrorCode.EXPIRED_TOKEN, ErrorCode.PERMISSION_DENIED) + .put(DatabaseErrorCode.INVALID_TOKEN, ErrorCode.PERMISSION_DENIED) + .put(DatabaseErrorCode.MAX_RETRIES, ErrorCode.DEADLINE_EXCEEDED) + .put(DatabaseErrorCode.OVERRIDDEN_BY_SET, ErrorCode.FAILED_PRECONDITION) + .put(DatabaseErrorCode.UNAVAILABLE, ErrorCode.UNAVAILABLE) + .put(DatabaseErrorCode.USER_CODE_EXCEPTION, ErrorCode.UNKNOWN) + // client codes + .put(DatabaseErrorCode.NETWORK_ERROR, ErrorCode.UNKNOWN) + .put(DatabaseErrorCode.WRITE_CANCELED, ErrorCode.CANCELLED) + .put(DatabaseErrorCode.UNKNOWN_ERROR, ErrorCode.UNKNOWN) + .build(); + + private static final Map errorCodes = new HashMap<>(); static { // Firebase Database error codes - errorCodes.put("datastale", DATA_STALE); - errorCodes.put("failure", OPERATION_FAILED); - errorCodes.put("permission_denied", PERMISSION_DENIED); - errorCodes.put("disconnected", DISCONNECTED); - errorCodes.put("expired_token", EXPIRED_TOKEN); - errorCodes.put("invalid_token", INVALID_TOKEN); - errorCodes.put("maxretries", MAX_RETRIES); - errorCodes.put("overriddenbyset", OVERRIDDEN_BY_SET); - errorCodes.put("unavailable", UNAVAILABLE); + errorCodes.put("datastale", DatabaseErrorCode.DATA_STALE); + errorCodes.put("failure", DatabaseErrorCode.OPERATION_FAILED); + errorCodes.put("permission_denied", DatabaseErrorCode.PERMISSION_DENIED); + errorCodes.put("disconnected", DatabaseErrorCode.DISCONNECTED); + errorCodes.put("expired_token", DatabaseErrorCode.EXPIRED_TOKEN); + errorCodes.put("invalid_token", DatabaseErrorCode.INVALID_TOKEN); + errorCodes.put("maxretries", DatabaseErrorCode.MAX_RETRIES); + errorCodes.put("overriddenbyset", DatabaseErrorCode.OVERRIDDEN_BY_SET); + errorCodes.put("unavailable", DatabaseErrorCode.UNAVAILABLE); // client codes - errorCodes.put("network_error", NETWORK_ERROR); - errorCodes.put("write_canceled", WRITE_CANCELED); + errorCodes.put("network_error", DatabaseErrorCode.NETWORK_ERROR); + errorCodes.put("write_canceled", DatabaseErrorCode.WRITE_CANCELED); } - private final int code; + private final DatabaseErrorCode code; private final String message; private final String details; - private DatabaseError(int code, String message) { + private DatabaseError(DatabaseErrorCode code, String message) { this(code, message, null); } - private DatabaseError(int code, String message, String details) { + private DatabaseError(DatabaseErrorCode code, String message, String details) { this.code = code; this.message = message; this.details = (details == null) ? "" : details; @@ -158,9 +141,9 @@ public static DatabaseError fromStatus(String status, String reason) { * @return An error corresponding the to the status */ public static DatabaseError fromStatus(String status, String reason, String details) { - Integer code = errorCodes.get(status.toLowerCase()); + DatabaseErrorCode code = errorCodes.get(status.toLowerCase()); if (code == null) { - code = UNKNOWN_ERROR; + code = DatabaseErrorCode.UNKNOWN_ERROR; } String message = reason == null ? errorReasons.get(code) : reason; @@ -174,7 +157,7 @@ public static DatabaseError fromStatus(String status, String reason, String deta * @param code The error code * @return An error corresponding the to the code */ - public static DatabaseError fromCode(int code) { + public static DatabaseError fromCode(DatabaseErrorCode code) { if (!errorReasons.containsKey(code)) { throw new IllegalArgumentException("Invalid Firebase Database error code: " + code); } @@ -186,14 +169,15 @@ public static DatabaseError fromException(Throwable e) { StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); e.printStackTrace(printWriter); - String reason = errorReasons.get(USER_CODE_EXCEPTION) + stringWriter.toString(); - return new DatabaseError(USER_CODE_EXCEPTION, reason); + String reason = errorReasons.get(DatabaseErrorCode.USER_CODE_EXCEPTION) + + stringWriter.toString(); + return new DatabaseError(DatabaseErrorCode.USER_CODE_EXCEPTION, reason); } /** * @return One of the defined status codes, depending on the error. */ - public int getCode() { + public DatabaseErrorCode getCode() { return code; } @@ -223,6 +207,6 @@ public String toString() { * @return An exception wrapping this error, with an appropriate message and no stack trace. */ public DatabaseException toException() { - return new DatabaseException("Firebase Database error: " + message); + return new DatabaseException(platformCodes.get(code), "Firebase Database error: " + message); } } diff --git a/src/main/java/com/google/firebase/database/DatabaseErrorCode.java b/src/main/java/com/google/firebase/database/DatabaseErrorCode.java new file mode 100644 index 000000000..3ba49754c --- /dev/null +++ b/src/main/java/com/google/firebase/database/DatabaseErrorCode.java @@ -0,0 +1,56 @@ +/* + * Copyright 2019 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.database; + +public enum DatabaseErrorCode { + + DATA_STALE, + /** The server indicated that this operation failed. */ + OPERATION_FAILED, + /** This client does not have permission to perform this operation. */ + PERMISSION_DENIED, + /** The operation had to be aborted due to a network disconnect. */ + DISCONNECTED, + + // Preempted was removed, this is for here for completeness and history + // public static final int PREEMPTED = -5; + + /** The supplied auth token has expired. */ + EXPIRED_TOKEN, + /** + * The specified authentication token is invalid. This can occur when the token is malformed, + * expired, or the secret that was used to generate it has been revoked. + */ + INVALID_TOKEN, + /** The transaction had too many retries */ + MAX_RETRIES, + /** The transaction was overridden by a subsequent set */ + OVERRIDDEN_BY_SET, + /** The service is unavailable. */ + UNAVAILABLE, + /** An exception occurred in user code. */ + USER_CODE_EXCEPTION, + + // client codes + /** The operation could not be performed due to a network error. */ + NETWORK_ERROR, + + /** The write was canceled locally. */ + WRITE_CANCELED, + UNKNOWN_ERROR, + +} diff --git a/src/main/java/com/google/firebase/database/DatabaseException.java b/src/main/java/com/google/firebase/database/DatabaseException.java index c85e93878..715441643 100644 --- a/src/main/java/com/google/firebase/database/DatabaseException.java +++ b/src/main/java/com/google/firebase/database/DatabaseException.java @@ -16,30 +16,22 @@ package com.google.firebase.database; +import com.google.firebase.ErrorCode; +import com.google.firebase.FirebaseException; + /** * This error is thrown when the Firebase Database library is unable to operate on the input it has * been given. */ -public class DatabaseException extends RuntimeException { - - /** - * For internal use - * - * @hide - * @param message A human readable description of the error - */ - public DatabaseException(String message) { - super(message); - } +public class DatabaseException extends FirebaseException { /** * For internal use * * @hide * @param message A human readable description of the error - * @param cause The underlying cause for this error */ - public DatabaseException(String message, Throwable cause) { - super(message, cause); + public DatabaseException(ErrorCode code, String message) { + super(code, message, null, null); } } diff --git a/src/main/java/com/google/firebase/database/DatabaseReference.java b/src/main/java/com/google/firebase/database/DatabaseReference.java index 10dfe40ba..9bef59da8 100644 --- a/src/main/java/com/google/firebase/database/DatabaseReference.java +++ b/src/main/java/com/google/firebase/database/DatabaseReference.java @@ -544,7 +544,7 @@ public String toString() { try { return parent.toString() + "/" + URLEncoder.encode(getKey(), "UTF-8").replace("+", "%20"); } catch (UnsupportedEncodingException e) { - throw new DatabaseException("Failed to URLEncode key: " + getKey(), e); + throw new IllegalArgumentException("Failed to URLEncode key: " + getKey(), e); } } } diff --git a/src/main/java/com/google/firebase/database/FirebaseDatabase.java b/src/main/java/com/google/firebase/database/FirebaseDatabase.java index 306ce9274..60ffc0bd8 100644 --- a/src/main/java/com/google/firebase/database/FirebaseDatabase.java +++ b/src/main/java/com/google/firebase/database/FirebaseDatabase.java @@ -79,7 +79,7 @@ private FirebaseDatabase(FirebaseApp app, RepoInfo repoInfo, DatabaseConfig conf public static FirebaseDatabase getInstance() { FirebaseApp instance = FirebaseApp.getInstance(); if (instance == null) { - throw new DatabaseException("You must call FirebaseApp.initialize() first."); + throw new IllegalStateException("You must call FirebaseApp.initialize() first."); } return getInstance(instance, instance.getOptions().getDatabaseUrl()); } @@ -93,7 +93,7 @@ public static FirebaseDatabase getInstance() { public static FirebaseDatabase getInstance(String url) { FirebaseApp instance = FirebaseApp.getInstance(); if (instance == null) { - throw new DatabaseException("You must call FirebaseApp.initialize() first."); + throw new IllegalStateException("You must call FirebaseApp.initialize() first."); } return getInstance(instance, url); } @@ -122,7 +122,7 @@ public static synchronized FirebaseDatabase getInstance(FirebaseApp app, String service = ImplFirebaseTrampolines.addService(app, new FirebaseDatabaseService()); } if (url == null || url.isEmpty()) { - throw new DatabaseException( + throw new IllegalArgumentException( "Failed to get FirebaseDatabase instance: Specify DatabaseURL within " + "FirebaseApp or from your getInstance() call."); } @@ -137,7 +137,7 @@ public static synchronized FirebaseDatabase getInstance(FirebaseApp app, String parsedUrl = Utilities.parseUrl(url); } if (!parsedUrl.path.isEmpty()) { - throw new DatabaseException( + throw new IllegalArgumentException( "Specified Database URL '" + url + "' is invalid. It should point to the root of a " @@ -232,7 +232,7 @@ public DatabaseReference getReferenceFromUrl(String url) { ParsedUrl parsedUrl = Utilities.parseUrl(url); Repo repo = ensureRepo(); if (!parsedUrl.repoInfo.host.equals(repo.getRepoInfo().host)) { - throw new DatabaseException( + throw new IllegalArgumentException( "Invalid URL (" + url + ") passed to getReference(). " @@ -323,7 +323,7 @@ private void assertUnfrozen(String methodCalled) { synchronized (lock) { checkNotDestroyed(); if (this.repo != null) { - throw new DatabaseException( + throw new IllegalStateException( "Calls to " + methodCalled + "() must be made before any " diff --git a/src/main/java/com/google/firebase/database/MutableData.java b/src/main/java/com/google/firebase/database/MutableData.java index 7a0e7983b..5604c6c30 100644 --- a/src/main/java/com/google/firebase/database/MutableData.java +++ b/src/main/java/com/google/firebase/database/MutableData.java @@ -298,7 +298,7 @@ public T getValue(Class valueType) { * * @param value The value to set at this location */ - public void setValue(Object value) throws DatabaseException { + public void setValue(Object value) { ValidationPath.validateWithObject(prefixPath, value); Object bouncedValue = CustomClassMapper.convertToPlainJavaTypes(value); Validation.validateWritableObject(bouncedValue); diff --git a/src/main/java/com/google/firebase/database/Query.java b/src/main/java/com/google/firebase/database/Query.java index e22a4931a..a9f5348a5 100644 --- a/src/main/java/com/google/firebase/database/Query.java +++ b/src/main/java/com/google/firebase/database/Query.java @@ -65,7 +65,7 @@ public class Query { // but we don't want to allow multiple orderByPriority calls, so track them here private final boolean orderByCalled; - Query(Repo repo, Path path, QueryParams params, boolean orderByCalled) throws DatabaseException { + Query(Repo repo, Path path, QueryParams params, boolean orderByCalled) { hardAssert(params.isValid(), "Validation of queries failed."); this.repo = repo; this.path = path; @@ -248,7 +248,7 @@ public void run() { */ public void keepSynced(final boolean keepSynced) { if (!this.path.isEmpty() && this.path.getFront().equals(ChildKey.getInfoKey())) { - throw new DatabaseException("Can't call keepSynced() on .info paths."); + throw new UnsupportedOperationException("Can't call keepSynced() on .info paths."); } repo.scheduleNow( diff --git a/src/main/java/com/google/firebase/database/core/Context.java b/src/main/java/com/google/firebase/database/core/Context.java index a0025f997..a2111a3e3 100644 --- a/src/main/java/com/google/firebase/database/core/Context.java +++ b/src/main/java/com/google/firebase/database/core/Context.java @@ -125,7 +125,7 @@ void stop() { void assertUnfrozen() { if (frozen) { - throw new DatabaseException( + throw new IllegalStateException( "Modifications to DatabaseConfig objects must occur before they are in use"); } } diff --git a/src/main/java/com/google/firebase/database/core/DatabaseConfig.java b/src/main/java/com/google/firebase/database/core/DatabaseConfig.java index 844f59266..af98c98b0 100644 --- a/src/main/java/com/google/firebase/database/core/DatabaseConfig.java +++ b/src/main/java/com/google/firebase/database/core/DatabaseConfig.java @@ -101,10 +101,10 @@ public synchronized void setPersistenceCacheSizeBytes(long cacheSizeInBytes) { assertUnfrozen(); if (cacheSizeInBytes < 1024 * 1024) { - throw new DatabaseException("The minimum cache size must be at least 1MB"); + throw new IllegalArgumentException("The minimum cache size must be at least 1MB"); } if (cacheSizeInBytes > 100 * 1024 * 1024) { - throw new DatabaseException( + throw new IllegalArgumentException( "Firebase Database currently doesn't support a cache size larger than 100MB"); } diff --git a/src/main/java/com/google/firebase/database/core/Path.java b/src/main/java/com/google/firebase/database/core/Path.java index b6fd451b9..4e5c91ff8 100644 --- a/src/main/java/com/google/firebase/database/core/Path.java +++ b/src/main/java/com/google/firebase/database/core/Path.java @@ -84,7 +84,7 @@ public static Path getRelative(Path from, Path to) { } else if (outerFront.equals(innerFront)) { return getRelative(from.popFront(), to.popFront()); } else { - throw new DatabaseException("INTERNAL ERROR: " + to + " is not contained in " + from); + throw new IllegalArgumentException("INTERNAL ERROR: " + to + " is not contained in " + from); } } diff --git a/src/main/java/com/google/firebase/database/core/Repo.java b/src/main/java/com/google/firebase/database/core/Repo.java index 98777d04d..0b6c761bc 100644 --- a/src/main/java/com/google/firebase/database/core/Repo.java +++ b/src/main/java/com/google/firebase/database/core/Repo.java @@ -20,6 +20,7 @@ import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseErrorCode; import com.google.firebase.database.DatabaseException; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; @@ -297,7 +298,7 @@ public void onDataUpdate( } postEvents(events); - } catch (DatabaseException e) { + } catch (Exception e) { logger.error("Firebase internal error", e); } } @@ -353,7 +354,7 @@ public void run() { } private void ackWriteAndRerunTransactions(long writeId, Path path, DatabaseError error) { - if (error != null && error.getCode() == DatabaseError.WRITE_CANCELED) { + if (error != null && error.getCode() == DatabaseErrorCode.WRITE_CANCELED) { // This write was already removed, we just need to ignore it... } else { boolean success = error == null; @@ -394,7 +395,7 @@ public void onRequestResult(String optErrorCode, String optErrorMessage) { } }); - Path affectedPath = abortTransactions(path, DatabaseError.OVERRIDDEN_BY_SET); + Path affectedPath = abortTransactions(path, DatabaseErrorCode.OVERRIDDEN_BY_SET); this.rerunTransactions(affectedPath); } @@ -437,7 +438,7 @@ public void onRequestResult(String optErrorCode, String optErrorMessage) { for (Entry update : updates) { Path pathFromRoot = path.child(update.getKey()); - Path affectedPath = abortTransactions(pathFromRoot, DatabaseError.OVERRIDDEN_BY_SET); + Path affectedPath = abortTransactions(pathFromRoot, DatabaseErrorCode.OVERRIDDEN_BY_SET); rerunTransactions(affectedPath); } } @@ -447,7 +448,7 @@ public void purgeOutstandingWrites() { List events = serverSyncTree.removeAllWrites(); postEvents(events); // Abort any transactions - abortTransactions(Path.getEmptyPath(), DatabaseError.WRITE_CANCELED); + abortTransactions(Path.getEmptyPath(), DatabaseErrorCode.WRITE_CANCELED); // Remove outstanding writes from connection connection.purgeOutstandingWrites(); } @@ -591,7 +592,7 @@ private void updateInfo(ChildKey childKey, Object value) { infoData.update(path, node); List events = this.infoSyncTree.applyServerOverwrite(path, node); this.postEvents(events); - } catch (DatabaseException e) { + } catch (Exception e) { logger.error("Failed to parse info update", e); } } @@ -612,7 +613,7 @@ private void runOnDisconnectEvents() { @Override public void visitTree(Path prefixPath, Node node) { events.addAll(serverSyncTree.applyServerOverwrite(prefixPath, node)); - Path affectedPath = abortTransactions(prefixPath, DatabaseError.OVERRIDDEN_BY_SET); + Path affectedPath = abortTransactions(prefixPath, DatabaseErrorCode.OVERRIDDEN_BY_SET); rerunTransactions(affectedPath); } }); @@ -623,8 +624,8 @@ public void visitTree(Path prefixPath, Node node) { private void warnIfWriteFailed(String writeType, Path path, DatabaseError error) { // DATA_STALE is a normal, expected error during transaction processing. if (error != null - && !(error.getCode() == DatabaseError.DATA_STALE - || error.getCode() == DatabaseError.WRITE_CANCELED)) { + && !(error.getCode() == DatabaseErrorCode.DATA_STALE + || error.getCode() == DatabaseErrorCode.WRITE_CANCELED)) { logger.warn(writeType + " at " + path.toString() + " failed: " + error.toString()); } } @@ -868,7 +869,7 @@ public void run() { } } else { // transactions are no longer sent. Update their status appropriately - if (error.getCode() == DatabaseError.DATA_STALE) { + if (error.getCode() == DatabaseErrorCode.DATA_STALE) { for (TransactionData transaction : queue) { if (transaction.status == TransactionStatus.SENT_NEEDS_ABORT) { transaction.status = TransactionStatus.NEEDS_ABORT; @@ -961,7 +962,7 @@ private void rerunTransactionQueue(List queue, Path path) { if (transaction.status == TransactionStatus.NEEDS_ABORT) { abortTransaction = true; abortReason = transaction.abortReason; - if (abortReason.getCode() != DatabaseError.WRITE_CANCELED) { + if (abortReason.getCode() != DatabaseErrorCode.WRITE_CANCELED) { events.addAll( serverSyncTree.ackUserWrite( transaction.currentWriteId, /*revert=*/ true, /*persist=*/ false, serverClock)); @@ -1108,7 +1109,7 @@ public void visitTree(Tree> tree) { }); } - private Path abortTransactions(Path path, final int reason) { + private Path abortTransactions(Path path, final DatabaseErrorCode reason) { Path affectedPath = getAncestorTransactionNode(path).getPath(); logger.debug("Aborting transactions for path: {}. Affected: {}", path, affectedPath); @@ -1135,19 +1136,20 @@ public void visitTree(Tree> tree) { return affectedPath; } - private void abortTransactionsAtNode(Tree> node, int reason) { + private void abortTransactionsAtNode(Tree> node, DatabaseErrorCode reason) { List queue = node.getValue(); List events = new ArrayList<>(); if (queue != null) { List callbacks = new ArrayList<>(); final DatabaseError abortError; - if (reason == DatabaseError.OVERRIDDEN_BY_SET) { + if (reason == DatabaseErrorCode.OVERRIDDEN_BY_SET) { abortError = DatabaseError.fromStatus(TRANSACTION_OVERRIDE_BY_SET); } else { hardAssert( - reason == DatabaseError.WRITE_CANCELED, "Unknown transaction abort reason: " + reason); - abortError = DatabaseError.fromCode(DatabaseError.WRITE_CANCELED); + reason == DatabaseErrorCode.WRITE_CANCELED, + "Unknown transaction abort reason: " + reason); + abortError = DatabaseError.fromCode(DatabaseErrorCode.WRITE_CANCELED); } int lastSent = -1; @@ -1170,13 +1172,13 @@ private void abortTransactionsAtNode(Tree> node, int reaso Repo.this, transaction.outstandingListener, QuerySpec.defaultQueryAtPath(transaction.path))); - if (reason == DatabaseError.OVERRIDDEN_BY_SET) { + if (reason == DatabaseErrorCode.OVERRIDDEN_BY_SET) { events.addAll( serverSyncTree.ackUserWrite( transaction.currentWriteId, /*revert=*/ true, /*persist=*/ false, serverClock)); } else { hardAssert( - reason == DatabaseError.WRITE_CANCELED, + reason == DatabaseErrorCode.WRITE_CANCELED, "Unknown transaction abort reason: " + reason); // If it was cancelled, it was already removed from the sync tree } diff --git a/src/main/java/com/google/firebase/database/core/RepoManager.java b/src/main/java/com/google/firebase/database/core/RepoManager.java index a1d0eafce..a7dad878e 100644 --- a/src/main/java/com/google/firebase/database/core/RepoManager.java +++ b/src/main/java/com/google/firebase/database/core/RepoManager.java @@ -41,12 +41,11 @@ public RepoManager() {} * Used for legacy unit tests. The public API should go through FirebaseDatabase which calls * createRepo. */ - public static Repo getRepo(Context ctx, RepoInfo info) throws DatabaseException { + public static Repo getRepo(Context ctx, RepoInfo info) { return instance.getLocalRepo(ctx, info); } - public static Repo createRepo(Context ctx, RepoInfo info, FirebaseDatabase database) - throws DatabaseException { + public static Repo createRepo(Context ctx, RepoInfo info, FirebaseDatabase database) { return instance.createLocalRepo(ctx, info, database); } @@ -89,7 +88,7 @@ public static void resume(Context ctx) { instance.resumeInternal(ctx); } - private Repo getLocalRepo(Context ctx, RepoInfo info) throws DatabaseException { + private Repo getLocalRepo(Context ctx, RepoInfo info) { ctx.freeze(); // No-op if it's already frozen String repoHash = "https://" + info.host + "/" + info.namespace; synchronized (repos) { @@ -102,8 +101,7 @@ private Repo getLocalRepo(Context ctx, RepoInfo info) throws DatabaseException { } } - private Repo createLocalRepo(Context ctx, RepoInfo info, FirebaseDatabase database) - throws DatabaseException { + private Repo createLocalRepo(Context ctx, RepoInfo info, FirebaseDatabase database) { ctx.freeze(); // No-op if it's already frozen String repoHash = "https://" + info.host + "/" + info.namespace; synchronized (repos) { diff --git a/src/main/java/com/google/firebase/database/core/ValidationPath.java b/src/main/java/com/google/firebase/database/core/ValidationPath.java index 3ea3d03c6..c039cf38e 100644 --- a/src/main/java/com/google/firebase/database/core/ValidationPath.java +++ b/src/main/java/com/google/firebase/database/core/ValidationPath.java @@ -38,7 +38,7 @@ public class ValidationPath { private final List parts = new ArrayList<>(); private int byteLength = 0; - private ValidationPath(Path path) throws DatabaseException { + private ValidationPath(Path path) { for (ChildKey key : path) { parts.add(key.asString()); } @@ -51,7 +51,7 @@ private ValidationPath(Path path) throws DatabaseException { checkValid(); } - public static void validateWithObject(Path path, Object value) throws DatabaseException { + public static void validateWithObject(Path path, Object value) { new ValidationPath(path).withObject(value); } @@ -89,7 +89,7 @@ private static int utf8Bytes(CharSequence sequence) { } @SuppressWarnings({"unchecked", "rawtypes"}) - private void withObject(Object value) throws DatabaseException { + private void withObject(Object value) { if (value instanceof Map) { Map mapValue = (Map) value; for (String key : mapValue.keySet()) { @@ -114,7 +114,7 @@ private void withObject(Object value) throws DatabaseException { } } - private void push(String child) throws DatabaseException { + private void push(String child) { // Count the '/' if (parts.size() > 0) { byteLength += 1; @@ -134,9 +134,9 @@ private String pop() { return last; } - private void checkValid() throws DatabaseException { + private void checkValid() { if (byteLength > MAX_PATH_LENGTH_BYTES) { - throw new DatabaseException( + throw new IllegalArgumentException( "Data has a key path longer than " + MAX_PATH_LENGTH_BYTES + " bytes (" @@ -144,7 +144,7 @@ private void checkValid() throws DatabaseException { + ")."); } if (parts.size() > MAX_PATH_DEPTH) { - throw new DatabaseException( + throw new IllegalArgumentException( "Path specified exceeds the maximum depth that can be written (" + MAX_PATH_DEPTH + ") or object contains a cycle " diff --git a/src/main/java/com/google/firebase/database/snapshot/NodeUtilities.java b/src/main/java/com/google/firebase/database/snapshot/NodeUtilities.java index 36e1e603c..efd25ac3a 100644 --- a/src/main/java/com/google/firebase/database/snapshot/NodeUtilities.java +++ b/src/main/java/com/google/firebase/database/snapshot/NodeUtilities.java @@ -29,11 +29,11 @@ public class NodeUtilities { // CSOFF: MethodName - public static Node NodeFromJSON(Object value) throws DatabaseException { + public static Node NodeFromJSON(Object value) { return NodeFromJSON(value, PriorityUtilities.NullPriority()); } - public static Node NodeFromJSON(Object value, Node priority) throws DatabaseException { + public static Node NodeFromJSON(Object value, Node priority) { try { if (value instanceof Map) { Map mapValue = (Map) value; @@ -104,11 +104,11 @@ public static Node NodeFromJSON(Object value, Node priority) throws DatabaseExce return new ChildrenNode(childSet, priority); } } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Failed to parse node with class " + value.getClass().toString()); } } catch (ClassCastException e) { - throw new DatabaseException("Failed to parse node", e); + throw new IllegalArgumentException("Failed to parse node", e); } } // CSON: MethodName diff --git a/src/main/java/com/google/firebase/database/snapshot/PriorityUtilities.java b/src/main/java/com/google/firebase/database/snapshot/PriorityUtilities.java index 14c758f67..5976950f5 100644 --- a/src/main/java/com/google/firebase/database/snapshot/PriorityUtilities.java +++ b/src/main/java/com/google/firebase/database/snapshot/PriorityUtilities.java @@ -47,7 +47,7 @@ public static Node parsePriority(Path nodePath, Object value) { Double.valueOf((Long) priority.getValue()), PriorityUtilities.NullPriority()); } if (!isValidPriority(priority)) { - throw new DatabaseException( + throw new IllegalArgumentException( (nodePath != null ? "Path '" + nodePath + "'" : "Node") + " contains invalid priority: Must be a string, double, ServerValue, or null"); } diff --git a/src/main/java/com/google/firebase/database/utilities/Utilities.java b/src/main/java/com/google/firebase/database/utilities/Utilities.java index 5a3c64822..a6928f24a 100644 --- a/src/main/java/com/google/firebase/database/utilities/Utilities.java +++ b/src/main/java/com/google/firebase/database/utilities/Utilities.java @@ -42,7 +42,7 @@ public class Utilities { private static final char[] HEX_CHARACTERS = "0123456789abcdef".toCharArray(); - public static ParsedUrl parseUrl(String url) throws DatabaseException { + public static ParsedUrl parseUrl(String url) { try { URI uri = URI.create(url); @@ -86,7 +86,7 @@ public static ParsedUrl parseUrl(String url) throws DatabaseException { return parsedUrl; } catch (Exception e) { - throw new DatabaseException("Invalid Firebase Database url specified: " + url, e); + throw new IllegalArgumentException("Invalid Firebase Database url specified: " + url, e); } } diff --git a/src/main/java/com/google/firebase/database/utilities/Validation.java b/src/main/java/com/google/firebase/database/utilities/Validation.java index 8a0bfcf12..573d1cc49 100644 --- a/src/main/java/com/google/firebase/database/utilities/Validation.java +++ b/src/main/java/com/google/firebase/database/utilities/Validation.java @@ -45,16 +45,16 @@ private static boolean isValidPathString(String pathString) { return !INVALID_PATH_REGEX.matcher(pathString).find(); } - public static void validatePathString(String pathString) throws DatabaseException { + public static void validatePathString(String pathString) { if (!isValidPathString(pathString)) { - throw new DatabaseException( + throw new IllegalArgumentException( "Invalid Firebase Database path: " + pathString + ". Firebase Database paths must not contain '.', '#', '$', '[', or ']'"); } } - public static void validateRootPathString(String pathString) throws DatabaseException { + public static void validateRootPathString(String pathString) { if (pathString.startsWith(".info")) { validatePathString(pathString.substring(5)); } else if (pathString.startsWith("/.info")) { @@ -76,16 +76,16 @@ private static boolean isValidKey(String key) { return key.equals(".info") || !INVALID_KEY_REGEX.matcher(key).find(); } - public static void validateNullableKey(String key) throws DatabaseException { + public static void validateNullableKey(String key) { if (!(key == null || isValidKey(key))) { - throw new DatabaseException( + throw new IllegalArgumentException( "Invalid key: " + key + ". Keys must not contain '/', '.', '#', '$', '[', or ']'"); } } private static void validateDoubleValue(double d) { if (Double.isInfinite(d) || Double.isNaN(d)) { - throw new DatabaseException("Invalid value: Value cannot be NaN, Inf or -Inf."); + throw new IllegalArgumentException("Invalid value: Value cannot be NaN, Inf or -Inf."); } } @@ -120,21 +120,20 @@ public static void validateWritableObject(Object object) { } } - public static void validateWritableKey(String key) throws DatabaseException { + public static void validateWritableKey(String key) { if (!isWritableKey(key)) { - throw new DatabaseException( + throw new IllegalArgumentException( "Invalid key: " + key + ". Keys must not contain '/', '.', '#', '$', '[', or ']'"); } } - public static void validateWritablePath(Path path) throws DatabaseException { + public static void validateWritablePath(Path path) throws IllegalArgumentException { if (!isWritablePath(path)) { - throw new DatabaseException("Invalid write location: " + path.toString()); + throw new IllegalArgumentException("Invalid write location: " + path.toString()); } } - public static Map parseAndValidateUpdate(Path path, Map update) - throws DatabaseException { + public static Map parseAndValidateUpdate(Path path, Map update) { final SortedMap parsedUpdate = new TreeMap<>(); for (Map.Entry entry : update.entrySet()) { Path updatePath = new Path(entry.getKey()); @@ -143,7 +142,7 @@ public static Map parseAndValidateUpdate(Path path, Map parseAndValidateUpdate(Path path, Map T convertToCustomClass(Object object, GenericTypeIndicator if (genericTypeIndicatorType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) genericTypeIndicatorType; if (!parameterizedType.getRawType().equals(GenericTypeIndicator.class)) { - throw new DatabaseException( + throw new IllegalArgumentException( "Not a direct subclass of GenericTypeIndicator: " + genericTypeIndicatorType); } // We are guaranteed to have exactly one type parameter Type type = parameterizedType.getActualTypeArguments()[0]; return deserializeToType(object, type); } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Not a direct subclass of GenericTypeIndicator: " + genericTypeIndicatorType); } } @@ -126,7 +126,7 @@ private static Object serialize(T obj) { } else if (obj instanceof Long || obj instanceof Integer) { return obj; } else { - throw new DatabaseException( + throw new IllegalArgumentException( String.format( "Numbers of type %s are not supported, please use an int, long, float or double", obj.getClass().getSimpleName())); @@ -136,7 +136,7 @@ private static Object serialize(T obj) { } else if (obj instanceof Boolean) { return obj; } else if (obj instanceof Character) { - throw new DatabaseException("Characters are not supported, please use Strings"); + throw new IllegalArgumentException("Characters are not supported, please use Strings"); } else if (obj instanceof Map) { Map result = new HashMap<>(); for (Map.Entry entry : ((Map) obj).entrySet()) { @@ -145,7 +145,7 @@ private static Object serialize(T obj) { String keyString = (String) key; result.put(keyString, serialize(entry.getValue())); } else { - throw new DatabaseException("Maps with non-string keys are not supported"); + throw new IllegalArgumentException("Maps with non-string keys are not supported"); } } return result; @@ -158,11 +158,11 @@ private static Object serialize(T obj) { } return result; } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Serializing Collections is not supported, please use Lists instead"); } } else if (obj.getClass().isArray()) { - throw new DatabaseException( + throw new IllegalArgumentException( "Serializing Arrays is not supported, please use Lists instead"); } else if (obj instanceof Enum) { return ((Enum) obj).name(); @@ -182,9 +182,9 @@ private static T deserializeToType(Object obj, Type type) { } else if (type instanceof Class) { return deserializeToClass(obj, (Class) type); } else if (type instanceof WildcardType) { - throw new DatabaseException("Generic wildcard types are not supported"); + throw new IllegalArgumentException("Generic wildcard types are not supported"); } else if (type instanceof GenericArrayType) { - throw new DatabaseException( + throw new IllegalArgumentException( "Generic Arrays are not supported, please use Lists instead"); } else { throw new IllegalStateException("Unknown type encountered: " + type); @@ -203,10 +203,10 @@ private static T deserializeToClass(Object obj, Class clazz) { } else if (String.class.isAssignableFrom(clazz)) { return (T) convertString(obj); } else if (clazz.isArray()) { - throw new DatabaseException( + throw new IllegalArgumentException( "Converting to Arrays is not supported, please use Lists instead"); } else if (clazz.getTypeParameters().length > 0) { - throw new DatabaseException( + throw new IllegalArgumentException( "Class " + clazz.getName() + " has generic type " @@ -234,14 +234,14 @@ private static T deserializeToParameterizedType(Object obj, ParameterizedTyp } return (T) result; } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Expected a List while deserializing, but got a " + obj.getClass()); } } else if (Map.class.isAssignableFrom(rawType)) { Type keyType = type.getActualTypeArguments()[0]; Type valueType = type.getActualTypeArguments()[1]; if (!keyType.equals(String.class)) { - throw new DatabaseException( + throw new IllegalArgumentException( "Only Maps with string keys are supported, " + "but found Map with key type " + keyType); @@ -253,7 +253,7 @@ private static T deserializeToParameterizedType(Object obj, ParameterizedTyp } return (T) result; } else if (Collection.class.isAssignableFrom(rawType)) { - throw new DatabaseException("Collections are not supported, please use Lists instead"); + throw new IllegalArgumentException("Collections are not supported, please use Lists instead"); } else { Map map = expectMap(obj); BeanMapper mapper = (BeanMapper) loadOrCreateBeanMapperForClass(rawType); @@ -283,7 +283,7 @@ private static T deserializeToPrimitive(Object obj, Class clazz) { } else if (Float.class.isAssignableFrom(clazz) || float.class.isAssignableFrom(clazz)) { return (T) (Float) convertDouble(obj).floatValue(); } else { - throw new DatabaseException( + throw new IllegalArgumentException( String.format("Deserializing values to %s is not supported", clazz.getSimpleName())); } } @@ -297,11 +297,11 @@ private static T deserializeToEnum(Object object, Class clazz) { try { return (T) Enum.valueOf((Class) clazz, value); } catch (IllegalArgumentException e) { - throw new DatabaseException( + throw new IllegalArgumentException( "Could not find enum value of " + clazz.getName() + " for value \"" + value + "\""); } } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Expected a String while deserializing to enum " + clazz + " but got a " @@ -327,7 +327,7 @@ private static Map expectMap(Object object) { // TODO: runtime validation of keys? return (Map) object; } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Expected a Map while deserializing, but got a " + object.getClass()); } } @@ -340,13 +340,13 @@ private static Integer convertInteger(Object obj) { if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) { return ((Number) obj).intValue(); } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Numeric value out of 32-bit integer range: " + value + ". Did you mean to use a long or double instead of an int?"); } } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Failed to convert a value of type " + obj.getClass().getName() + " to int"); } } @@ -361,13 +361,13 @@ private static Long convertLong(Object obj) { if (value >= Long.MIN_VALUE && value <= Long.MAX_VALUE) { return value.longValue(); } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Numeric value out of 64-bit long range: " + value + ". Did you mean to use a double instead of a long?"); } } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Failed to convert a value of type " + obj.getClass().getName() + " to long"); } } @@ -381,7 +381,7 @@ private static Double convertDouble(Object obj) { if (doubleValue.longValue() == value) { return doubleValue; } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Loss of precision while converting number to " + "double: " + obj @@ -390,7 +390,7 @@ private static Double convertDouble(Object obj) { } else if (obj instanceof Double) { return (Double) obj; } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Failed to convert a value of type " + obj.getClass().getName() + " to double"); } } @@ -399,7 +399,7 @@ private static Boolean convertBoolean(Object obj) { if (obj instanceof Boolean) { return (Boolean) obj; } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Failed to convert value of type " + obj.getClass().getName() + " to boolean"); } } @@ -408,7 +408,7 @@ private static String convertString(Object obj) { if (obj instanceof String) { return (String) obj; } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Failed to convert value of type " + obj.getClass().getName() + " to String"); } } @@ -418,7 +418,7 @@ private static T convertBean(Object obj, Class clazz) { if (obj instanceof Map) { return mapper.deserialize(expectMap(obj)); } else { - throw new DatabaseException( + throw new IllegalArgumentException( "Can't convert object of type " + obj.getClass().getName() + " to type " @@ -465,7 +465,8 @@ public BeanMapper(Class clazz) { addProperty(propertyName); method.setAccessible(true); if (getters.containsKey(propertyName)) { - throw new DatabaseException("Found conflicting getters for name: " + method.getName()); + throw new IllegalArgumentException( + "Found conflicting getters for name: " + method.getName()); } getters.put(propertyName, method); } @@ -492,7 +493,7 @@ public BeanMapper(Class clazz) { String existingPropertyName = properties.get(propertyName.toLowerCase()); if (existingPropertyName != null) { if (!existingPropertyName.equals(propertyName)) { - throw new DatabaseException( + throw new IllegalArgumentException( "Found setter with invalid case-sensitive name: " + method.getName()); } else { Method existingSetter = setters.get(propertyName); @@ -502,7 +503,7 @@ public BeanMapper(Class clazz) { } else if (!isSetterOverride(method, existingSetter)) { // We require that setters with conflicting property names are // overrides from a base class - throw new DatabaseException( + throw new IllegalArgumentException( "Found a conflicting setters " + "with name: " + method.getName() @@ -535,7 +536,8 @@ public BeanMapper(Class clazz) { } while (currentClass != null && !currentClass.equals(Object.class)); if (properties.isEmpty()) { - throw new DatabaseException("No properties to serialize found on class " + clazz.getName()); + throw new IllegalArgumentException( + "No properties to serialize found on class " + clazz.getName()); } } @@ -683,7 +685,7 @@ private static String serializedName(String methodName) { private void addProperty(String property) { String oldValue = this.properties.put(property.toLowerCase(), property); if (oldValue != null && !property.equals(oldValue)) { - throw new DatabaseException( + throw new IllegalArgumentException( "Found two getters or fields with conflicting case " + "sensitivity for property: " + property.toLowerCase()); @@ -696,7 +698,7 @@ public T deserialize(Map values) { public T deserialize(Map values, Map>, Type> types) { if (this.constructor == null) { - throw new DatabaseException( + throw new IllegalArgumentException( "Class " + this.clazz.getName() + " is missing a constructor with no arguments"); } T instance; @@ -740,7 +742,7 @@ public T deserialize(Map values, Map>, Typ message += " (fields/setters are case sensitive!)"; } if (this.throwOnUnknownProperties) { - throw new DatabaseException(message); + throw new IllegalArgumentException(message); } else if (this.warnOnUnknownProperties) { logger.warn(message); } diff --git a/src/main/java/com/google/firebase/internal/ApiClientUtils.java b/src/main/java/com/google/firebase/internal/ApiClientUtils.java index 723fc6d52..ee38e9dee 100644 --- a/src/main/java/com/google/firebase/internal/ApiClientUtils.java +++ b/src/main/java/com/google/firebase/internal/ApiClientUtils.java @@ -23,9 +23,18 @@ import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.common.collect.ImmutableList; +import com.google.firebase.ErrorCode; import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseException; import java.io.IOException; +import java.net.ConnectException; +import java.net.NoRouteToHostException; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.HashSet; +import java.util.Set; /** * A set of shared utilities for using the Google API client. @@ -56,13 +65,22 @@ public static HttpRequestFactory newAuthorizedRequestFactory(FirebaseApp app) { * automatic retries. * * @param app {@link FirebaseApp} from which to obtain authorization credentials. + <<<<<<< hkj-error-handling + * @param retryConfig {@link RetryConfig} which specifies how and when to retry errors. + ======= * @param retryConfig {@link RetryConfig} instance or null to disable retries. + >>>>>>> master * @return A new {@code HttpRequestFactory} instance. */ public static HttpRequestFactory newAuthorizedRequestFactory( FirebaseApp app, @Nullable RetryConfig retryConfig) { HttpTransport transport = app.getOptions().getHttpTransport(); + <<<<<<< hkj-error-handling + return transport.createRequestFactory( + new FirebaseRequestInitializer(app, retryConfig)); + ======= return transport.createRequestFactory(new FirebaseRequestInitializer(app, retryConfig)); + >>>>>>> master } public static HttpRequestFactory newUnauthorizedRequestFactory(FirebaseApp app) { @@ -80,6 +98,40 @@ public static void disconnectQuietly(HttpResponse response) { } } + <<<<<<< hkj-error-handling + public static FirebaseException newFirebaseException(IOException e) { + ErrorCode code = ErrorCode.UNKNOWN; + String message = "Unknown error while making a remote service call" ; + if (isInstance(e, SocketTimeoutException.class)) { + code = ErrorCode.DEADLINE_EXCEEDED; + message = "Timed out while making an API call"; + } + + if (isInstance(e, UnknownHostException.class) || isInstance(e, NoRouteToHostException.class)) { + code = ErrorCode.UNAVAILABLE; + message = "Failed to establish a connection"; + } + + return new FirebaseException(code, message + ": " + e.getMessage(), null, e); + } + + private static boolean isInstance(IOException t, Class type) { + Throwable current = t; + Set chain = new HashSet<>(); + while (current != null) { + if (!chain.add(current)) { + break; + } + + if (type.isInstance(current)) { + return true; + } + + current = current.getCause(); + } + + return false; + ======= public static JsonFactory getDefaultJsonFactory() { // Force using the Jackson2 parser for this project for now. Eventually we should switch // to Gson, but there are some issues that's preventing this migration at the moment. @@ -89,5 +141,6 @@ public static JsonFactory getDefaultJsonFactory() { public static HttpTransport getDefaultTransport() { return Utils.getDefaultTransport(); + >>>>>>> master } } diff --git a/src/main/java/com/google/firebase/internal/BaseHttpErrorHandler.java b/src/main/java/com/google/firebase/internal/BaseHttpErrorHandler.java new file mode 100644 index 000000000..642a54a72 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/BaseHttpErrorHandler.java @@ -0,0 +1,93 @@ +/* + * Copyright 2019 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 com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.HttpStatusCodes; +import com.google.common.collect.ImmutableMap; +import com.google.firebase.ErrorCode; +import com.google.firebase.FirebaseException; +import com.google.firebase.FirebaseHttpResponse; +import java.util.Map; + +public abstract class BaseHttpErrorHandler + implements HttpErrorHandler { + + private static final Map HTTP_ERROR_CODES = + ImmutableMap.builder() + .put(HttpStatusCodes.STATUS_CODE_BAD_REQUEST, ErrorCode.INVALID_ARGUMENT) + .put(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED, ErrorCode.UNAUTHENTICATED) + .put(HttpStatusCodes.STATUS_CODE_FORBIDDEN, ErrorCode.PERMISSION_DENIED) + .put(HttpStatusCodes.STATUS_CODE_NOT_FOUND, ErrorCode.NOT_FOUND) + .put(HttpStatusCodes.STATUS_CODE_CONFLICT, ErrorCode.CONFLICT) + .put(429, ErrorCode.RESOURCE_EXHAUSTED) + .put(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, ErrorCode.INTERNAL) + .put(HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE, ErrorCode.UNAVAILABLE) + .build(); + + @Override + public final T handleHttpResponseException( + HttpResponseException e, FirebaseHttpResponse response) { + ErrorParams params = this.getErrorParams(e, response); + return this.createException(params); + } + + protected ErrorParams getErrorParams(HttpResponseException e, FirebaseHttpResponse response) { + ErrorCode code = HTTP_ERROR_CODES.get(e.getStatusCode()); + if (code == null) { + code = ErrorCode.UNKNOWN; + } + + String message = String.format("Unexpected HTTP response with status: %d\n%s", + e.getStatusCode(), e.getContent()); + return new ErrorParams(code, message, e, response); + } + + protected abstract T createException(ErrorParams params); + + public static final class ErrorParams { + private final ErrorCode errorCode; + private final String message; + private final HttpResponseException exception; + private final FirebaseHttpResponse response; + + public ErrorParams( + ErrorCode errorCode, String message, + HttpResponseException e, FirebaseHttpResponse response) { + this.errorCode = errorCode; + this.message = message; + this.exception = e; + this.response = response; + } + + public ErrorCode getErrorCode() { + return errorCode; + } + + public String getMessage() { + return message; + } + + public HttpResponseException getException() { + return exception; + } + + public FirebaseHttpResponse getResponse() { + return response; + } + } +} diff --git a/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java b/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java index 5efdd0ec2..2a9bfb2f4 100644 --- a/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java +++ b/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java @@ -1,5 +1,9 @@ /* - * Copyright 2020 Google Inc. + <<<<<<< hkj-error-handling + * Copyright 2019 Google Inc. + ======= + * Copyright 2021 Google Inc. + >>>>>>> master * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +26,18 @@ import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpResponseException; + <<<<<<< hkj-error-handling +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.json.JsonParser; +import com.google.common.io.CharStreams; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseException; +import com.google.firebase.FirebaseHttpResponse; +import java.io.IOException; +import java.io.InputStreamReader; + + ======= import com.google.api.client.http.HttpResponseInterceptor; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonParser; @@ -36,23 +52,99 @@ * An HTTP client implementation that handles any errors that may occur during HTTP calls, and * converts them into an instance of FirebaseException. */ + >>>>>>> master public final class ErrorHandlingHttpClient { private final HttpRequestFactory requestFactory; private final JsonFactory jsonFactory; private final HttpErrorHandler errorHandler; + <<<<<<< hkj-error-handling + public ErrorHandlingHttpClient( + FirebaseApp app, HttpErrorHandler errorHandler, RetryConfig retryConfig) { + this( + ApiClientUtils.newAuthorizedRequestFactory(app, retryConfig), + app.getOptions().getJsonFactory(), + errorHandler); + } + + public ErrorHandlingHttpClient(HttpRequestFactory requestFactory, + JsonFactory jsonFactory, HttpErrorHandler errorHandler) { + ======= private HttpResponseInterceptor interceptor; public ErrorHandlingHttpClient( HttpRequestFactory requestFactory, JsonFactory jsonFactory, HttpErrorHandler errorHandler) { + >>>>>>> master this.requestFactory = checkNotNull(requestFactory, "requestFactory must not be null"); this.jsonFactory = checkNotNull(jsonFactory, "jsonFactory must not be null"); this.errorHandler = checkNotNull(errorHandler, "errorHandler must not be null"); } + <<<<<<< hkj-error-handling + public V sendAndParse(HttpRequestInfo requestInfo, Class responseType) throws T { + HttpResponseInfo responseInfo = this.sendRequest(requestInfo); + + try { + return this.parseResponse(responseInfo, responseType); + } finally { + responseInfo.disconnect(); + } + } + + private HttpResponseInfo sendRequest(HttpRequestInfo requestInfo) throws T { + HttpRequest request = newHttpRequest(requestInfo); + + try { + return new HttpResponseInfo(request.execute()); + } catch (HttpResponseException e) { + FirebaseHttpResponse response = new FirebaseHttpResponse(e, request); + throw errorHandler.handleHttpResponseException(e, response); + } catch (IOException e) { + throw errorHandler.handleIOException(e); + } + } + + private HttpRequest newHttpRequest(HttpRequestInfo requestInfo) throws T { + try { + HttpRequest request = requestInfo.newHttpRequest(requestFactory); + request.setParser(new JsonObjectParser(jsonFactory)); + return request; + } catch (IOException e) { + throw errorHandler.handleIOException(e); + } + } + + private V parseResponse(HttpResponseInfo responseInfo, Class responseType) throws T { + try { + JsonParser parser = jsonFactory.createJsonParser(responseInfo.content); + return parser.parseAndClose(responseType); + } catch (IOException e) { + throw errorHandler.handleParseException(e, responseInfo.toFirebaseHttpResponse()); + } + } + + private static class HttpResponseInfo { + private final HttpResponse response; + private final String content; + + HttpResponseInfo(HttpResponse response) throws IOException { + this.response = response; + // Read and buffer the content here. Otherwise if a parse error occurs, + // we lose the content. + this.content = CharStreams.toString( + new InputStreamReader(response.getContent(), response.getContentCharset())); + } + + void disconnect() { + ApiClientUtils.disconnectQuietly(response); + } + + FirebaseHttpResponse toFirebaseHttpResponse() { + return new FirebaseHttpResponse(response, content); + ======= public ErrorHandlingHttpClient setInterceptor(HttpResponseInterceptor interceptor) { this.interceptor = interceptor; return this; @@ -139,6 +231,7 @@ private HttpRequest createHttpRequest(HttpRequestInfo requestInfo) throws T { } catch (IOException e) { // Handle request initialization errors (credential loading and other config errors) throw errorHandler.handleIOException(e); + >>>>>>> master } } } diff --git a/src/main/java/com/google/firebase/internal/FirebaseService.java b/src/main/java/com/google/firebase/internal/FirebaseService.java index 2b802aee8..83deae4c8 100644 --- a/src/main/java/com/google/firebase/internal/FirebaseService.java +++ b/src/main/java/com/google/firebase/internal/FirebaseService.java @@ -65,4 +65,8 @@ public final T getInstance() { public void destroy() { // Child classes can override this method to implement any service-specific cleanup logic. } + <<<<<<< redacted-passwords + + ======= + >>>>>>> master } diff --git a/src/main/java/com/google/firebase/internal/HttpErrorHandler.java b/src/main/java/com/google/firebase/internal/HttpErrorHandler.java index 988b45a45..48e2f51f3 100644 --- a/src/main/java/com/google/firebase/internal/HttpErrorHandler.java +++ b/src/main/java/com/google/firebase/internal/HttpErrorHandler.java @@ -1,5 +1,9 @@ /* - * Copyright 2020 Google Inc. + <<<<<<< hkj-error-handling + * Copyright 2019 Google Inc. + ======= + * Copyright 2021 Google Inc. + >>>>>>> master * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +22,19 @@ import com.google.api.client.http.HttpResponseException; import com.google.firebase.FirebaseException; + <<<<<<< hkj-error-handling +import com.google.firebase.FirebaseHttpResponse; +import java.io.IOException; + +public interface HttpErrorHandler { + + T handleIOException(IOException e); + + T handleHttpResponseException(HttpResponseException e, FirebaseHttpResponse response); + + T handleParseException(IOException e, FirebaseHttpResponse response); + + ======= import com.google.firebase.IncomingHttpResponse; import java.io.IOException; @@ -41,4 +58,5 @@ public interface HttpErrorHandler { * Handle any errors that may occur while parsing the response payload. */ T handleParseException(IOException e, IncomingHttpResponse response); + >>>>>>> master } diff --git a/src/main/java/com/google/firebase/internal/HttpRequestInfo.java b/src/main/java/com/google/firebase/internal/HttpRequestInfo.java index 375e332fb..ae5904227 100644 --- a/src/main/java/com/google/firebase/internal/HttpRequestInfo.java +++ b/src/main/java/com/google/firebase/internal/HttpRequestInfo.java @@ -1,5 +1,9 @@ /* - * Copyright 2020 Google Inc. + <<<<<<< hkj-error-handling + * Copyright 2019 Google Inc. + ======= + * Copyright 2021 Google Inc. + >>>>>>> master * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,29 +21,49 @@ package com.google.firebase.internal; import static com.google.common.base.Preconditions.checkArgument; + <<<<<<< hkj-error-handling + ======= import static com.google.common.base.Preconditions.checkNotNull; + >>>>>>> master import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpContent; import com.google.api.client.http.HttpMethods; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; + <<<<<<< hkj-error-handling +import com.google.api.client.http.HttpResponseInterceptor; + ======= import com.google.api.client.http.json.JsonHttpContent; import com.google.api.client.json.JsonFactory; + >>>>>>> master import com.google.common.base.Strings; import java.io.IOException; import java.util.HashMap; import java.util.Map; + <<<<<<< hkj-error-handling + ======= /** * Internal API for configuring outgoing HTTP requests. To be used with the * {@link ErrorHandlingHttpClient} class. */ + >>>>>>> master public final class HttpRequestInfo { private final String method; private final GenericUrl url; private final HttpContent content; + <<<<<<< hkj-error-handling + private final Map headers = new HashMap<>(); + private HttpResponseInterceptor interceptor; + + private HttpRequestInfo(String method, String url, HttpContent content) { + checkArgument(!Strings.isNullOrEmpty(method), "method must not be null or empty"); + this.method = method; + this.url = new GenericUrl(url); + this.content = content; + ======= private final Object jsonContent; private final Map headers = new HashMap<>(); @@ -49,6 +73,7 @@ private HttpRequestInfo(String method, GenericUrl url, HttpContent content, Obje this.url = checkNotNull(url, "url must not be null"); this.content = content; this.jsonContent = jsonContent; + >>>>>>> master } public HttpRequestInfo addHeader(String name, String value) { @@ -61,6 +86,10 @@ public HttpRequestInfo addAllHeaders(Map headers) { return this; } + <<<<<<< hkj-error-handling + public HttpRequestInfo setResponseInterceptor(HttpResponseInterceptor interceptor) { + this.interceptor = interceptor; + ======= public HttpRequestInfo addParameter(String name, Object value) { this.url.put(name, value); return this; @@ -68,10 +97,29 @@ public HttpRequestInfo addParameter(String name, Object value) { public HttpRequestInfo addAllParameters(Map params) { this.url.putAll(params); + >>>>>>> master return this; } public static HttpRequestInfo buildGetRequest(String url) { + <<<<<<< hkj-error-handling + return new HttpRequestInfo(HttpMethods.GET, url, null); + } + + public static HttpRequestInfo buildPostRequest(String url, HttpContent content) { + return new HttpRequestInfo(HttpMethods.POST, url, content); + } + + HttpRequest newHttpRequest(HttpRequestFactory factory) throws IOException { + HttpRequest request = factory.buildRequest(method, url, content); + for (Map.Entry entry : headers.entrySet()) { + request.getHeaders().set(entry.getKey(), entry.getValue()); + } + request.setResponseInterceptor(interceptor); + + return request; + } + ======= return buildRequest(HttpMethods.GET, url, null); } @@ -129,4 +177,5 @@ private HttpContent getContent(JsonFactory jsonFactory) { return null; } + >>>>>>> master } diff --git a/src/main/java/com/google/firebase/internal/PlatformErrorHandler.java b/src/main/java/com/google/firebase/internal/PlatformErrorHandler.java new file mode 100644 index 000000000..60163f6f3 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/PlatformErrorHandler.java @@ -0,0 +1,105 @@ +/* + * Copyright 2019 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.common.base.Preconditions.checkNotNull; + +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.util.Key; +import com.google.common.base.Strings; +import com.google.firebase.ErrorCode; +import com.google.firebase.FirebaseException; +import com.google.firebase.FirebaseHttpResponse; +import java.io.IOException; + +public abstract class PlatformErrorHandler + extends BaseHttpErrorHandler { + + protected final JsonFactory jsonFactory; + + public PlatformErrorHandler(JsonFactory jsonFactory) { + this.jsonFactory = checkNotNull(jsonFactory, "jsonFactory must not be null"); + } + + @Override + protected final ErrorParams getErrorParams( + HttpResponseException e, FirebaseHttpResponse response) { + ErrorParams defaults = super.getErrorParams(e, response); + PlatformErrorResponse parsedError = this.parseErrorResponse(e.getContent()); + + ErrorCode code; + String status = parsedError.getStatus(); + if (!Strings.isNullOrEmpty(status)) { + code = Enum.valueOf(ErrorCode.class, parsedError.getStatus()); + } else { + code = defaults.getErrorCode(); + } + + String message = parsedError.getMessage(); + if (Strings.isNullOrEmpty(message)) { + message = defaults.getMessage(); + } + + return new ErrorParams(code, message, e, response); + } + + private PlatformErrorResponse parseErrorResponse(String content) { + PlatformErrorResponse response = new PlatformErrorResponse(); + if (content == null) { + return response; + } + + try { + jsonFactory.createJsonParser(content).parseAndClose(response); + } catch (IOException e) { + // Ignore any error that may occur while parsing the error response. The server + // may have responded with a non-json payload. Return an empty return value, and + // let the base class logic come into play. + } + return response; + } + + public static class PlatformErrorResponse { + @Key("error") + private PlatformError error; + + String getStatus() { + if (error != null) { + return error.status; + } + + return null; + } + + String getMessage() { + if (error != null) { + return error.message; + } + + return null; + } + } + + public static class PlatformError { + @Key("status") + private String status; + + @Key("message") + private String message; + } +} diff --git a/src/main/java/com/google/firebase/messaging/AndroidConfig.java b/src/main/java/com/google/firebase/messaging/AndroidConfig.java index 888f00f0d..4eb5e8644 100644 --- a/src/main/java/com/google/firebase/messaging/AndroidConfig.java +++ b/src/main/java/com/google/firebase/messaging/AndroidConfig.java @@ -207,7 +207,11 @@ public Builder setFcmOptions(AndroidFcmOptions androidFcmOptions) { } /** + <<<<<<< redacted-passwords + * Sets the direct_boot_ok flag, If set to true, messages will be allowed to be delivered to + ======= * Sets the {@code direct_boot_ok} flag. If set to true, messages are delivered to + >>>>>>> master * the app while the device is in direct boot mode. */ public Builder setDirectBootOk(boolean directBootOk) { diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java b/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java index 1271bf98b..82577c357 100644 --- a/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java +++ b/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java @@ -40,7 +40,12 @@ import com.google.common.collect.ImmutableMap; import com.google.firebase.ErrorCode; import com.google.firebase.FirebaseApp; + <<<<<<< hkj-error-handling +import com.google.firebase.FirebaseHttpRequest; +import com.google.firebase.FirebaseHttpResponse; + ======= import com.google.firebase.FirebaseException; + >>>>>>> master import com.google.firebase.ImplFirebaseTrampolines; import com.google.firebase.IncomingHttpResponse; import com.google.firebase.OutgoingHttpRequest; @@ -62,10 +67,42 @@ final class FirebaseMessagingClientImpl implements FirebaseMessagingClient { private static final String FCM_URL = "https://fcm.googleapis.com/v1/projects/%s/messages:send"; + <<<<<<< redacted-passwords + private static final String FCM_BATCH_URL = "https://fcm.googleapis.com/batch"; + + private static final String API_FORMAT_VERSION_HEADER = "X-GOOG-API-FORMAT-VERSION"; + + private static final String CLIENT_VERSION_HEADER = "X-Firebase-Client"; + + private static final Map FCM_ERROR_CODES = + ImmutableMap.builder() + // FCM v1 canonical error codes + .put("NOT_FOUND", "registration-token-not-registered") + .put("PERMISSION_DENIED", "mismatched-credential") + .put("RESOURCE_EXHAUSTED", "message-rate-exceeded") + .put("UNAUTHENTICATED", "third-party-auth-error") + + // FCM v1 new error codes + .put("APNS_AUTH_ERROR", "third-party-auth-error") + .put("INTERNAL", FirebaseMessaging.INTERNAL_ERROR) + .put("INVALID_ARGUMENT", "invalid-argument") + .put("QUOTA_EXCEEDED", "message-rate-exceeded") + .put("SENDER_ID_MISMATCH", "mismatched-credential") + .put("THIRD_PARTY_AUTH_ERROR", "third-party-auth-error") + .put("UNAVAILABLE", "server-unavailable") + .put("UNREGISTERED", "registration-token-not-registered") + .build(); + ======= private static final Map COMMON_HEADERS = ImmutableMap.of( "X-GOOG-API-FORMAT-VERSION", "2", "X-Firebase-Client", "fire-admin-java/" + SdkUtils.getVersion()); + >>>>>>> master + + private static final Map COMMON_HEADERS = + ImmutableMap.of( + API_FORMAT_VERSION_HEADER, "2", + CLIENT_VERSION_HEADER, "fire-admin-java/" + SdkUtils.getVersion()); private final String fcmSendUrl; private final HttpRequestFactory requestFactory; @@ -74,7 +111,10 @@ final class FirebaseMessagingClientImpl implements FirebaseMessagingClient { private final HttpResponseInterceptor responseInterceptor; private final MessagingErrorHandler errorHandler; private final ErrorHandlingHttpClient httpClient; + <<<<<<< hkj-error-handling + ======= private final MessagingBatchClient batchClient; + >>>>>>> master private FirebaseMessagingClientImpl(Builder builder) { checkArgument(!Strings.isNullOrEmpty(builder.projectId)); @@ -83,10 +123,17 @@ private FirebaseMessagingClientImpl(Builder builder) { this.childRequestFactory = checkNotNull(builder.childRequestFactory); this.jsonFactory = checkNotNull(builder.jsonFactory); this.responseInterceptor = builder.responseInterceptor; + <<<<<<< hkj-error-handling + + this.errorHandler = new MessagingErrorHandler(builder.jsonFactory); + this.httpClient = new ErrorHandlingHttpClient<>( + builder.requestFactory, builder.jsonFactory, errorHandler); + ======= this.errorHandler = new MessagingErrorHandler(this.jsonFactory); this.httpClient = new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, errorHandler) .setInterceptor(responseInterceptor); this.batchClient = new MessagingBatchClient(requestFactory.getTransport(), jsonFactory); + >>>>>>> master } @VisibleForTesting @@ -121,9 +168,16 @@ public BatchResponse sendAll( private String sendSingleRequest( Message message, boolean dryRun) throws FirebaseMessagingException { HttpRequestInfo request = + <<<<<<< hkj-error-handling + HttpRequestInfo.buildPostRequest( + fcmSendUrl, new JsonHttpContent(jsonFactory, message.wrapForTransport(dryRun))) + .addAllHeaders(COMMON_HEADERS) + .setResponseInterceptor(responseInterceptor); + ======= HttpRequestInfo.buildJsonPostRequest( fcmSendUrl, message.wrapForTransport(dryRun)) .addAllHeaders(COMMON_HEADERS); + >>>>>>> master MessagingServiceResponse parsed = httpClient.sendAndParse( request, MessagingServiceResponse.class); return parsed.getMessageId(); @@ -133,6 +187,20 @@ private BatchResponse sendBatchRequest( List messages, boolean dryRun) throws FirebaseMessagingException { MessagingBatchCallback callback = new MessagingBatchCallback(); + <<<<<<< hkj-error-handling + try { + BatchRequest batch = newBatchRequest(messages, dryRun, callback); + batch.execute(); + return new BatchResponse(callback.getResponses()); + } catch (HttpResponseException e) { + FirebaseHttpRequest req = new FirebaseHttpRequest("POST", FCM_BATCH_URL); + FirebaseHttpResponse resp = new FirebaseHttpResponse(e, req); + ======= + <<<<<<< redacted-passwords + BatchRequest batch = newBatchRequest(messages, dryRun, callback); + batch.execute(); + return new BatchResponseImpl(callback.getResponses()); + ======= try { BatchRequest batch = newBatchRequest(messages, dryRun, callback); batch.execute(); @@ -141,10 +209,15 @@ private BatchResponse sendBatchRequest( OutgoingHttpRequest req = new OutgoingHttpRequest( HttpMethods.POST, MessagingBatchClient.FCM_BATCH_URL); IncomingHttpResponse resp = new IncomingHttpResponse(e, req); + >>>>>>> master throw errorHandler.handleHttpResponseException(e, resp); } catch (IOException e) { throw errorHandler.handleIOException(e); } + <<<<<<< hkj-error-handling + ======= + >>>>>>> master + >>>>>>> master } private BatchRequest newBatchRequest( @@ -240,6 +313,22 @@ FirebaseMessagingClientImpl build() { } } + <<<<<<< hkj-error-handling + private static FirebaseMessagingException newException(MessagingServiceErrorResponse response) { + + String code = response.getStatus(); + ErrorCode c = Enum.valueOf(ErrorCode.class, code); + + String msg = response.getErrorMessage(); + if (Strings.isNullOrEmpty(msg)) { + msg = String.format("Unexpected HTTP response: %s", response.toString()); + } + + return new FirebaseMessagingException(c, msg, null, null, null); + } + + ======= + >>>>>>> master private static class MessagingBatchCallback implements BatchCallback { @@ -307,7 +396,11 @@ private MessagingServiceErrorResponse safeParse(String response) { try { return jsonFactory.createJsonParser(response) .parseAndClose(MessagingServiceErrorResponse.class); + <<<<<<< v7 + } catch (IOException ignore) { + ======= } catch (Exception ignore) { + >>>>>>> master // Ignore any error that may occur while parsing the error response. The server // may have responded with a non-json payload. } diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessagingException.java b/src/main/java/com/google/firebase/messaging/FirebaseMessagingException.java index a3d92788a..1b9db324c 100644 --- a/src/main/java/com/google/firebase/messaging/FirebaseMessagingException.java +++ b/src/main/java/com/google/firebase/messaging/FirebaseMessagingException.java @@ -16,6 +16,12 @@ package com.google.firebase.messaging; + <<<<<<< hkj-error-handling +import com.google.firebase.ErrorCode; +import com.google.firebase.FirebaseException; +import com.google.firebase.FirebaseHttpResponse; +import com.google.firebase.internal.Nullable; + ======= import com.google.common.annotations.VisibleForTesting; import com.google.firebase.ErrorCode; import com.google.firebase.FirebaseException; @@ -24,9 +30,30 @@ import com.google.firebase.internal.Nullable; public final class FirebaseMessagingException extends FirebaseException { + >>>>>>> master + + private final MessagingErrorCode errorCode; + <<<<<<< hkj-error-handling private final MessagingErrorCode errorCode; + FirebaseMessagingException( + ErrorCode code, String message, MessagingErrorCode errorCode, + Throwable cause, FirebaseHttpResponse response) { + super(code, message, response, cause); + this.errorCode = errorCode; + } + + FirebaseMessagingException( + ErrorCode code, String message, Throwable cause) { + super(code, message, null, cause); + this.errorCode = null; + } + + FirebaseMessagingException( + MessagingErrorCode errorCode, String message, Throwable cause) { + super(ErrorCode.UNKNOWN, message, null, cause); + ======= @VisibleForTesting FirebaseMessagingException(@NonNull ErrorCode code, @NonNull String message) { this(code, message, null, null, null); @@ -39,6 +66,7 @@ private FirebaseMessagingException( @Nullable IncomingHttpResponse response, @Nullable MessagingErrorCode errorCode) { super(code, message, cause, response); + >>>>>>> master this.errorCode = errorCode; } diff --git a/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java b/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java index 5648fcf0c..6eef50a59 100644 --- a/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java +++ b/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java @@ -23,6 +23,7 @@ import com.google.api.client.util.Key; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; +import com.google.firebase.ErrorCode; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseException; import com.google.firebase.internal.AbstractHttpErrorHandler; @@ -69,12 +70,34 @@ static InstanceIdClientImpl fromApp(FirebaseApp app) { public TopicManagementResponse subscribeToTopic( String topic, List registrationTokens) throws FirebaseMessagingException { + <<<<<<< hkj-error-handling + try { + return sendInstanceIdRequest(topic, registrationTokens, IID_SUBSCRIBE_PATH); + } catch (HttpResponseException e) { + throw createExceptionFromResponse(e); + } catch (IOException e) { + throw new FirebaseMessagingException( + ErrorCode.UNKNOWN, "Error while calling IID backend service", e); + } + ======= return sendInstanceIdRequest(topic, registrationTokens, IID_SUBSCRIBE_PATH); + >>>>>>> master } public TopicManagementResponse unsubscribeFromTopic( String topic, List registrationTokens) throws FirebaseMessagingException { + <<<<<<< hkj-error-handling + try { + return sendInstanceIdRequest(topic, registrationTokens, IID_UNSUBSCRIBE_PATH); + } catch (HttpResponseException e) { + throw createExceptionFromResponse(e); + } catch (IOException e) { + throw new FirebaseMessagingException( + ErrorCode.UNKNOWN, "Error while calling IID backend service", e); + } + ======= return sendInstanceIdRequest(topic, registrationTokens, IID_UNSUBSCRIBE_PATH); + >>>>>>> master } private TopicManagementResponse sendInstanceIdRequest( @@ -103,6 +126,24 @@ private String getPrefixedTopic(String topic) { } } + <<<<<<< hkj-error-handling + private static FirebaseMessagingException newException( + InstanceIdServiceErrorResponse response, HttpResponseException e) { + // Infer error code from HTTP status + String code = IID_ERROR_CODES.get(e.getStatusCode()); + if (code == null) { + code = FirebaseMessaging.UNKNOWN_ERROR; + } + String msg = response.error; + if (Strings.isNullOrEmpty(msg)) { + msg = String.format("Unexpected HTTP response with status: %d; body: %s", + e.getStatusCode(), e.getContent()); + } + return new FirebaseMessagingException(ErrorCode.UNKNOWN, msg, e); + } + + ======= + >>>>>>> master private static class InstanceIdServiceResponse { @Key("results") private List results; diff --git a/src/main/java/com/google/firebase/messaging/LightSettings.java b/src/main/java/com/google/firebase/messaging/LightSettings.java index e0e898374..0345d4f07 100644 --- a/src/main/java/com/google/firebase/messaging/LightSettings.java +++ b/src/main/java/com/google/firebase/messaging/LightSettings.java @@ -61,7 +61,11 @@ private Builder() {} /** * Sets the lightSettingsColor value with a string. * + <<<<<<< redacted-passwords + * @param lightSettingsColor LightSettingsColor specified in the {@code #rrggbb} format. + ======= * @param color LightSettingsColor specified in the {@code #rrggbb} format. + >>>>>>> master * @return This builder. */ public Builder setColorFromString(String color) { @@ -72,7 +76,11 @@ public Builder setColorFromString(String color) { /** * Sets the lightSettingsColor value in the light settings. * + <<<<<<< redacted-passwords + * @param lightSettingsColor Color to be used in the light settings. + ======= * @param color Color to be used in the light settings. + >>>>>>> master * @return This builder. */ public Builder setColor(LightSettingsColor color) { diff --git a/src/main/java/com/google/firebase/messaging/MessagingErrorCode.java b/src/main/java/com/google/firebase/messaging/MessagingErrorCode.java index b566befbd..6cb56a7f7 100644 --- a/src/main/java/com/google/firebase/messaging/MessagingErrorCode.java +++ b/src/main/java/com/google/firebase/messaging/MessagingErrorCode.java @@ -1,3 +1,37 @@ + <<<<<<< hkj-error-handling +/* + * Copyright 2019 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.messaging; + +public enum MessagingErrorCode { + + THIRD_PARTY_AUTH_ERROR, + + INVALID_ARGUMENT, + + INTERNAL, + + QUOTA_EXCEEDED, + + SENDER_ID_MISMATCH, + + UNAVAILABLE, + + ======= package com.google.firebase.messaging; /** @@ -39,5 +73,6 @@ public enum MessagingErrorCode { * App instance was unregistered from FCM. This usually means that the token used is no longer * valid and a new one must be used. */ + >>>>>>> master UNREGISTERED, } diff --git a/src/main/java/com/google/firebase/messaging/MessagingErrorHandler.java b/src/main/java/com/google/firebase/messaging/MessagingErrorHandler.java new file mode 100644 index 000000000..e7a484e7c --- /dev/null +++ b/src/main/java/com/google/firebase/messaging/MessagingErrorHandler.java @@ -0,0 +1,131 @@ +/* + * Copyright 2019 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.messaging; + +import com.google.api.client.json.JsonFactory; +import com.google.api.client.util.Key; +import com.google.common.collect.ImmutableMap; +import com.google.firebase.ErrorCode; +import com.google.firebase.FirebaseException; +import com.google.firebase.FirebaseHttpResponse; +import com.google.firebase.internal.ApiClientUtils; +import com.google.firebase.internal.PlatformErrorHandler; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +final class MessagingErrorHandler extends PlatformErrorHandler { + + private static final String MESSAGING_ERROR_TYPE = + "type.googleapis.com/google.firebase.fcm.v1.FcmError"; + + private static final Map MESSAGING_ERROR_CODES = + ImmutableMap.builder() + .put("APNS_AUTH_ERROR", MessagingErrorCode.THIRD_PARTY_AUTH_ERROR) + .put("INTERNAL", MessagingErrorCode.INTERNAL) + .put("INVALID_ARGUMENT", MessagingErrorCode.INVALID_ARGUMENT) + .put("QUOTA_EXCEEDED", MessagingErrorCode.QUOTA_EXCEEDED) + .put("SENDER_ID_MISMATCH", MessagingErrorCode.SENDER_ID_MISMATCH) + .put("THIRD_PARTY_AUTH_ERROR", MessagingErrorCode.THIRD_PARTY_AUTH_ERROR) + .put("UNAVAILABLE", MessagingErrorCode.UNAVAILABLE) + .put("UNREGISTERED", MessagingErrorCode.UNREGISTERED) + .build(); + + MessagingErrorHandler(JsonFactory jsonFactory) { + super(jsonFactory); + } + + @Override + protected FirebaseMessagingException createException(ErrorParams params) { + String content = params.getResponse().getContent(); + return new FirebaseMessagingException( + params.getErrorCode(), + params.getMessage(), + getMessagingErrorCode(content), + params.getException(), + params.getResponse()); + } + + @Override + public FirebaseMessagingException handleIOException(IOException e) { + FirebaseException error = ApiClientUtils.newFirebaseException(e); + return new FirebaseMessagingException( + error.getCode(), + error.getMessage(), + null, + e, + null); + } + + @Override + public FirebaseMessagingException handleParseException(IOException e, + FirebaseHttpResponse response) { + return new FirebaseMessagingException( + ErrorCode.UNKNOWN, + "Error parsing response from FCM: " + e.getMessage(), + null, + e, + response); + } + + private MessagingErrorCode getMessagingErrorCode(String content) { + if (content == null) { + return null; + } + + MessagingErrorResponse response; + try { + response = jsonFactory.createJsonParser(content).parseAndClose(MessagingErrorResponse.class); + } catch (IOException e) { + // Ignore any error that may occur while parsing the error response. The server + // may have responded with a non-json payload. Return an empty return value, and + // let the base class logic come into play. + return null; + } + + if (response.error == null || response.error.details == null) { + return null; + } + + List details = response.error.details; + for (MessagingErrorDetail detail : details) { + if (MESSAGING_ERROR_TYPE.equals(detail.type)) { + return MESSAGING_ERROR_CODES.get(detail.errorCode); + } + } + + return null; + } + + public static class MessagingErrorResponse { + @Key("error") + private MessagingError error; + } + + public static class MessagingError extends PlatformErrorHandler.PlatformError { + @Key("details") + private List details; + } + + public static class MessagingErrorDetail { + @Key("@type") + private String type; + + @Key("errorCode") + private String errorCode; + } +} diff --git a/src/main/java/com/google/firebase/messaging/internal/MessagingServiceErrorResponse.java b/src/main/java/com/google/firebase/messaging/internal/MessagingServiceErrorResponse.java index 7c21199cc..4c2958a06 100644 --- a/src/main/java/com/google/firebase/messaging/internal/MessagingServiceErrorResponse.java +++ b/src/main/java/com/google/firebase/messaging/internal/MessagingServiceErrorResponse.java @@ -39,7 +39,10 @@ public String getStatus() { return (String) error.get("status"); } + <<<<<<< hkj-error-handling + ======= + >>>>>>> master @Nullable public MessagingErrorCode getMessagingErrorCode() { if (error == null) { diff --git a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagement.java b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagement.java index 9e294e099..bb0e3c168 100644 --- a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagement.java +++ b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagement.java @@ -36,6 +36,7 @@ * modify, or retrieve information about the Android or iOS Apps in your Firebase project. */ public class FirebaseProjectManagement { + private static final String SERVICE_ID = FirebaseProjectManagement.class.getName(); private static final Object GET_INSTANCE_LOCK = new Object(); diff --git a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java index 2bf927008..e7f3db8fc 100644 --- a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java +++ b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java @@ -34,7 +34,10 @@ import com.google.firebase.ErrorCode; import com.google.firebase.FirebaseApp; import com.google.firebase.ImplFirebaseTrampolines; + <<<<<<< redacted-passwords + ======= import com.google.firebase.IncomingHttpResponse; + >>>>>>> master import com.google.firebase.internal.ApiClientUtils; import com.google.firebase.internal.CallableOperation; import java.nio.charset.StandardCharsets; @@ -58,6 +61,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 = @@ -79,14 +83,26 @@ class FirebaseProjectManagementServiceImpl implements AndroidAppService, IosAppS this.app = checkNotNull(app); this.sleeper = checkNotNull(sleeper); this.scheduler = checkNotNull(scheduler); + <<<<<<< redacted-passwords + this.requestFactory = checkNotNull(requestFactory); + ======= + >>>>>>> master this.httpHelper = new HttpHelper(app.getOptions().getJsonFactory(), requestFactory); } + @VisibleForTesting + HttpRequestFactory getRequestFactory() { + return requestFactory; + } + + <<<<<<< redacted-passwords @VisibleForTesting void setInterceptor(HttpResponseInterceptor interceptor) { httpHelper.setInterceptor(interceptor); } + ======= + >>>>>>> master /* getAndroidApp */ @Override diff --git a/src/test/java/com/google/firebase/FirebaseAppTest.java b/src/test/java/com/google/firebase/FirebaseAppTest.java index 56e025d22..64678c8d0 100644 --- a/src/test/java/com/google/firebase/FirebaseAppTest.java +++ b/src/test/java/com/google/firebase/FirebaseAppTest.java @@ -171,7 +171,6 @@ public void testRehydratingDeletedInstanceThrows() { final String name = "myApp"; FirebaseApp firebaseApp = FirebaseApp.initializeApp(OPTIONS, name); firebaseApp.delete(); - TestOnlyImplFirebaseTrampolines.clearInstancesForTest(); FirebaseApp.getInstance(name); } diff --git a/src/test/java/com/google/firebase/OutgoingHttpRequestTest.java b/src/test/java/com/google/firebase/OutgoingHttpRequestTest.java index ffd17f057..f71b94de9 100644 --- a/src/test/java/com/google/firebase/OutgoingHttpRequestTest.java +++ b/src/test/java/com/google/firebase/OutgoingHttpRequestTest.java @@ -21,14 +21,21 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; + <<<<<<< v7 +import com.google.api.client.googleapis.util.Utils; + ======= + >>>>>>> master import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpMethods; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.json.JsonHttpContent; import com.google.api.client.testing.http.MockHttpTransport; import com.google.common.collect.ImmutableMap; + <<<<<<< v7 + ======= import com.google.firebase.internal.ApiClientUtils; + >>>>>>> master import java.io.IOException; import org.junit.Test; @@ -74,7 +81,11 @@ public void testOutgoingHttpRequest() { @Test public void testOutgoingHttpRequestWithContent() throws IOException { JsonHttpContent streamingContent = new JsonHttpContent( + <<<<<<< v7 + Utils.getDefaultJsonFactory(), + ======= ApiClientUtils.getDefaultJsonFactory(), + >>>>>>> master ImmutableMap.of("key", "value")); HttpRequest httpRequest = new MockHttpTransport().createRequestFactory() .buildPostRequest(new GenericUrl(TEST_URL), streamingContent); diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java index 954850100..63f9b52bb 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java @@ -51,7 +51,10 @@ import com.google.firebase.auth.UserTestUtils.RandomUser; import com.google.firebase.auth.UserTestUtils.TemporaryUser; import com.google.firebase.auth.hash.Scrypt; + <<<<<<< v7 + ======= import com.google.firebase.internal.ApiClientUtils; + >>>>>>> master import com.google.firebase.internal.Nullable; import com.google.firebase.testing.IntegrationTestUtils; import java.io.IOException; @@ -434,10 +437,17 @@ public void testLastRefreshTime() throws Exception { // New users should not have a lastRefreshTimestamp set. assertEquals(0, newUserRecord.getUserMetadata().getLastRefreshTimestamp()); + <<<<<<< v7 // Login to cause the lastRefreshTimestamp to be set. signInWithPassword(newUserRecord.getEmail(), "password"); + ======= + + // Login to cause the lastRefreshTimestamp to be set. + signInWithPassword(newUserRecord.getEmail(), "password"); + + >>>>>>> master // Attempt to retrieve the user 3 times (with a small delay between each // attempt). Occasionally, this call retrieves the user data without the // lastLoginTime/lastRefreshTime set; possibly because it's hitting a @@ -461,15 +471,81 @@ public void testLastRefreshTime() throws Exception { assertTrue(lastRefreshTimestamp <= now + tolerance); } + @Test + public void testLastRefreshTime() throws Exception { + RandomUser user = RandomUser.create(); + UserRecord newUserRecord = auth.createUser(new CreateRequest() + .setUid(user.uid) + .setEmail(user.email) + .setEmailVerified(false) + .setPassword("password")); + + try { + // New users should not have a lastRefreshTimestamp set. + assertEquals(0, newUserRecord.getUserMetadata().getLastRefreshTimestamp()); + + // Login to cause the lastRefreshTimestamp to be set. + signInWithPassword(newUserRecord.getEmail(), "password"); + + // Attempt to retrieve the user 3 times (with a small delay between each + // attempt). Occassionally, this call retrieves the user data without the + // lastLoginTime/lastRefreshTime set; possibly because it's hitting a + // different server than the login request uses. + UserRecord userRecord = null; + for (int i = 0; i < 3; i++) { + userRecord = auth.getUser(newUserRecord.getUid()); + + if (userRecord.getUserMetadata().getLastRefreshTimestamp() != 0) { + break; + } + + TimeUnit.SECONDS.sleep((long)Math.pow(2, i)); + } + + // Ensure the lastRefreshTimestamp is approximately "now" (with a tollerance of 10 minutes). + long now = System.currentTimeMillis(); + long tollerance = TimeUnit.MINUTES.toMillis(10); + long lastRefreshTimestamp = userRecord.getUserMetadata().getLastRefreshTimestamp(); + assertTrue(now - tollerance <= lastRefreshTimestamp); + assertTrue(lastRefreshTimestamp <= now + tollerance); + } finally { + auth.deleteUser(newUserRecord.getUid()); + } + } + @Test public void testListUsers() throws Exception { final List uids = new ArrayList<>(); + <<<<<<< redacted-passwords + try { + uids.add(auth.createUserAsync(new CreateRequest().setPassword("password")).get().getUid()); + uids.add(auth.createUserAsync(new CreateRequest().setPassword("password")).get().getUid()); + uids.add(auth.createUserAsync(new CreateRequest().setPassword("password")).get().getUid()); + + // Test list by batches + final AtomicInteger collected = new AtomicInteger(0); + ListUsersPage page = auth.listUsersAsync(null).get(); + while (page != null) { + for (ExportedUserRecord user : page.getValues()) { + if (uids.contains(user.getUid())) { + collected.incrementAndGet(); + assertNotNull("Missing passwordHash field. A common cause would be " + + "forgetting to add the \"Firebase Authentication Admin\" permission. See " + + "instructions in CONTRIBUTING.md", user.getPasswordHash()); + assertNotNull(user.getPasswordSalt()); + } + } + page = page.getNextPage(); + } + assertEquals(uids.size(), collected.get()); + ======= for (int i = 0; i < 3; i++) { UserRecord.CreateRequest createRequest = new UserRecord.CreateRequest().setPassword("password"); uids.add(temporaryUser.create(createRequest).getUid()); } + >>>>>>> master // Test list by batches final AtomicInteger collected = new AtomicInteger(0); @@ -1098,8 +1174,18 @@ private Map parseLinkParameters(String link) throws Exception { return result; } + <<<<<<< redacted-passwords + static String randomPhoneNumber() { + Random random = new Random(); + StringBuilder builder = new StringBuilder("+1"); + for (int i = 0; i < 10; i++) { + builder.append(random.nextInt(10)); + } + return builder.toString(); + ======= private String signInWithCustomToken(String customToken) throws IOException { return signInWithCustomToken(customToken, null); + >>>>>>> master } private String signInWithCustomToken( @@ -1194,11 +1280,21 @@ private void checkRecreateUser(String uid) throws Exception { } } + <<<<<<< redacted-passwords + static class RandomUser { + final String uid; + final String email; + + private RandomUser(String uid, String email) { + this.uid = uid; + this.email = email; + ======= private boolean checkOidcProviderConfig(List providerIds, OidcProviderConfig config) { if (providerIds.contains(config.getProviderId())) { assertEquals("CLIENT_ID", config.getClientId()); assertEquals("https://oidc.com/issuer", config.getIssuer()); return true; + >>>>>>> master } return false; } @@ -1231,5 +1327,22 @@ static UserRecord newUserWithParams(FirebaseAuth auth) throws Exception { .setPhotoUrl("https://example.com/photo.png") .setPassword("password")); } + + static UserRecord newUserWithParams() throws Exception { + return newUserWithParams(auth); + } + + static UserRecord newUserWithParams(FirebaseAuth auth) throws Exception { + // TODO(rsgowman): This function could be used throughout this file (similar to the other + // ports). + RandomUser randomUser = RandomUser.create(); + return auth.createUser(new CreateRequest() + .setUid(randomUser.uid) + .setEmail(randomUser.email) + .setPhoneNumber(randomPhoneNumber()) + .setDisplayName("Random User") + .setPhotoUrl("https://example.com/photo.png") + .setPassword("password")); + } } diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java index d385e7e4e..daa5f47cc 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java @@ -31,7 +31,10 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableMap; + <<<<<<< v7 + ======= import com.google.common.collect.ImmutableSet; + >>>>>>> master import com.google.firebase.ErrorCode; import com.google.firebase.FirebaseApp; @@ -57,7 +60,10 @@ public class FirebaseAuthTest { ErrorCode.INVALID_ARGUMENT, "Test error message", null, null, null); private static final long VALID_SINCE = 1494364393; private static final String TEST_USER = "testUser"; + <<<<<<< v7 + ======= private static final String AUTH_EMULATOR = "localhost:9099"; + >>>>>>> master @After public void cleanup() { @@ -97,10 +103,9 @@ public void testAppDelete() { } @Test - public void testInvokeAfterAppDelete() throws Exception { + public void testInvokeAfterAppDelete() throws FirebaseAuthException { FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testInvokeAfterAppDelete"); FirebaseAuth auth = FirebaseAuth.getInstance(app); - assertNotNull(auth); app.delete(); String message = "FirebaseApp 'testInvokeAfterAppDelete' was deleted"; @@ -113,21 +118,33 @@ public void testInvokeAfterAppDelete() throws Exception { try { auth.createCustomToken("uid"); + <<<<<<< redacted-passwords + fail("No error thrown from token factory for deleted app"); + ======= fail("No error thrown when invoking auth after deleting app"); + >>>>>>> master } catch (IllegalStateException ex) { assertEquals(message, ex.getMessage()); } try { auth.verifyIdToken("idToken"); + <<<<<<< redacted-passwords + fail("No error thrown from token verifier for deleted app"); + ======= fail("No error thrown when invoking auth after deleting app"); + >>>>>>> master } catch (IllegalStateException ex) { assertEquals(message, ex.getMessage()); } try { auth.getUser("uid"); + <<<<<<< redacted-passwords + fail("No error thrown from user manager for deleted app"); + ======= fail("No error thrown when invoking auth after deleting app"); + >>>>>>> master } catch (IllegalStateException ex) { assertEquals(message, ex.getMessage()); } @@ -267,6 +284,8 @@ public void testVerifyIdTokenWithRevocationCheckFailure() { } @Test + <<<<<<< v7 + ======= public void testVerifyIdTokenWithEmulator() throws Exception { // Enable emulator mode TestUtils.setEnvironmentVariables( @@ -306,6 +325,7 @@ public void testVerifyIdTokenFailureWithEmulator() { } @Test + >>>>>>> master public void testVerifyIdTokenFailure() { MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(testException); FirebaseAuth auth = getAuthForIdTokenVerification(tokenVerifier); @@ -431,6 +451,7 @@ public void testVerifySessionCookieFailure() { fail("No error thrown for invalid token"); } catch (FirebaseAuthException authException) { assertSame(testException, authException); + <<<<<<< v7 } } @@ -446,6 +467,40 @@ public void testVerifySessionCookieWithRevocationCheck() throws Exception { assertEquals("cookie", tokenVerifier.getLastTokenString()); } + @Test + public void testVerifySessionCookieWithRevocationCheckFailure() { + MockTokenVerifier tokenVerifier = MockTokenVerifier.fromResult( + getFirebaseToken(VALID_SINCE - 1000)); + FirebaseAuth auth = getAuthForSessionCookieVerificationWithRevocationCheck(tokenVerifier); + + try { + auth.verifySessionCookie("cookie", true); + fail("No error thrown for revoked session cookie"); + } catch (FirebaseAuthException e) { + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); + assertEquals("Firebase session cookie is revoked.", e.getMessage()); + assertNull(e.getCause()); + assertNull(e.getHttpResponse()); + assertEquals(AuthErrorCode.REVOKED_SESSION_COOKIE, e.getAuthErrorCode()); + ======= + >>>>>>> master + } + + assertEquals("cookie", tokenVerifier.getLastTokenString()); + } + + @Test + public void testVerifySessionCookieWithRevocationCheck() throws Exception { + MockTokenVerifier tokenVerifier = MockTokenVerifier.fromResult( + getFirebaseToken(VALID_SINCE + 1000)); + FirebaseAuth auth = getAuthForSessionCookieVerificationWithRevocationCheck(tokenVerifier); + + FirebaseToken firebaseToken = auth.verifySessionCookie("cookie", true); + + assertEquals("testUser", firebaseToken.getUid()); + assertEquals("cookie", tokenVerifier.getLastTokenString()); + } + @Test public void testVerifySessionCookieWithRevocationCheckFailure() { MockTokenVerifier tokenVerifier = MockTokenVerifier.fromResult( @@ -505,6 +560,13 @@ public void testVerifySessionCookieWithCheckRevokedAsyncFailure() throws Interru } } + <<<<<<< v7 + private FirebaseToken getFirebaseToken(String subject) { + return new FirebaseToken(ImmutableMap.of("sub", subject)); + } + + ======= + >>>>>>> master private FirebaseToken getFirebaseToken(long issuedAt) { return new FirebaseToken(ImmutableMap.of("sub", TEST_USER, "iat", issuedAt)); } @@ -522,6 +584,9 @@ private FirebaseAuth getAuthForIdTokenVerification(FirebaseTokenVerifier tokenVe private FirebaseAuth getAuthForIdTokenVerification( Supplier tokenVerifierSupplier) { FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions); + <<<<<<< redacted-passwords + FirebaseUserManager userManager = new FirebaseUserManager(app); + ======= return getAuthForIdTokenVerification(app, tokenVerifierSupplier); } @@ -529,6 +594,7 @@ private FirebaseAuth getAuthForIdTokenVerification( FirebaseApp app, Supplier tokenVerifierSupplier) { FirebaseUserManager userManager = FirebaseUserManager.createUserManager(app, null); + >>>>>>> master return FirebaseAuth.builder() .setFirebaseApp(app) .setIdTokenVerifier(tokenVerifierSupplier) @@ -549,6 +615,9 @@ private FirebaseAuth getAuthForSessionCookieVerification(FirebaseTokenVerifier t private FirebaseAuth getAuthForSessionCookieVerification( Supplier tokenVerifierSupplier) { FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions); + <<<<<<< redacted-passwords + FirebaseUserManager userManager = new FirebaseUserManager(app); + ======= return getAuthForSessionCookieVerification(app, tokenVerifierSupplier); } @@ -556,6 +625,7 @@ private FirebaseAuth getAuthForSessionCookieVerification( FirebaseApp app, Supplier tokenVerifierSupplier) { FirebaseUserManager userManager = FirebaseUserManager.createUserManager(app, null); + >>>>>>> master return FirebaseAuth.builder() .setFirebaseApp(app) .setCookieVerifier(tokenVerifierSupplier) diff --git a/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java index d02d2b5da..4adca1567 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java @@ -33,6 +33,11 @@ import com.google.api.client.json.webtoken.JsonWebToken.Payload; import com.google.api.client.testing.http.MockHttpTransport; import com.google.common.base.Strings; + <<<<<<< v7 +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + ======= + >>>>>>> master import com.google.firebase.ErrorCode; import com.google.firebase.testing.ServiceAccount; import java.io.IOException; @@ -54,6 +59,11 @@ public class FirebaseTokenVerifierImplTest { + "-P1wie318In0sInYiOjAsImlhdCI6MTQ4MDk4Mj" + "U2NH0.ZWEpoHgIPCAz8Q-cNFBS8jiqClTJ3j27yuRkQo-QxyI"; + <<<<<<< v7 + private static final String TEST_TOKEN_ISSUER = "https://test.token.issuer"; + + ======= + >>>>>>> master private FirebaseTokenVerifier tokenVerifier; private TestTokenFactory tokenFactory; @@ -81,7 +91,10 @@ public void testVerifyTokenWithoutKeyId() { try { tokenVerifier.verifyToken(token); + <<<<<<< v7 + ======= fail("No error thrown for missing kid"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Firebase test token has no \"kid\" claim. " + "See https://test.doc.url for details on how to retrieve a test token."; @@ -95,7 +108,10 @@ public void testVerifyTokenFirebaseCustomToken() { try { tokenVerifier.verifyToken(token); + <<<<<<< v7 + ======= fail("No error thrown for passing custom token"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "verifyTestToken() expects a test token, but was given a custom token. " + "See https://test.doc.url for details on how to retrieve a test token."; @@ -109,7 +125,10 @@ public void testVerifyTokenIncorrectAlgorithm() { try { tokenVerifier.verifyToken(token); + <<<<<<< v7 + ======= fail("No error thrown for incorrect alg"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Firebase test token has incorrect algorithm. " + "Expected \"RS256\" but got \"HSA\". " @@ -119,6 +138,8 @@ public void testVerifyTokenIncorrectAlgorithm() { } @Test + <<<<<<< v7 + ======= public void testVerifyTokenWithoutAlgorithm() { String token = createTokenWithoutAlgorithm(); @@ -134,12 +155,16 @@ public void testVerifyTokenWithoutAlgorithm() { } @Test + >>>>>>> master public void testVerifyTokenIncorrectAudience() { String token = createTokenWithIncorrectAudience(); try { tokenVerifier.verifyToken(token); + <<<<<<< v7 + ======= fail("No error thrown for incorrect audience"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Firebase test token has incorrect \"aud\" (audience) claim. " + "Expected \"proj-test-101\" but got \"invalid-audience\". " @@ -156,7 +181,10 @@ public void testVerifyTokenIncorrectIssuer() { try { tokenVerifier.verifyToken(token); + <<<<<<< v7 + ======= fail("No error thrown for incorrect issuer"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Firebase test token has incorrect \"iss\" (issuer) claim. " + "Expected \"https://test.token.issuer\" but got " @@ -173,7 +201,10 @@ public void testVerifyTokenMissingSubject() { try { tokenVerifier.verifyToken(token); + <<<<<<< v7 + ======= fail("No error thrown for missing subject"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Firebase test token has no \"sub\" (subject) claim. " + "See https://test.doc.url for details on how to retrieve a test token."; @@ -187,7 +218,10 @@ public void testVerifyTokenEmptySubject() { try { tokenVerifier.verifyToken(token); + <<<<<<< v7 + ======= fail("No error thrown for empty subject"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Firebase test token has an empty string \"sub\" (subject) claim. " + "See https://test.doc.url for details on how to retrieve a test token."; @@ -201,7 +235,10 @@ public void testVerifyTokenLongSubject() { try { tokenVerifier.verifyToken(token); + <<<<<<< v7 + ======= fail("No error thrown for very long subject"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Firebase test token has \"sub\" (subject) claim longer " + "than 128 characters. " @@ -220,7 +257,10 @@ public void testVerifyTokenIssuedAtInFuture() { try { tokenVerifier.verifyToken(token); + <<<<<<< v7 + ======= fail("No error thrown for token issued in the future"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Firebase test token is not yet valid. " + "See https://test.doc.url for details on how to retrieve a test token."; @@ -238,7 +278,10 @@ public void testVerifyTokenExpired() { try { tokenVerifier.verifyToken(token); + <<<<<<< v7 + ======= fail("No error thrown for expired token"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Firebase test token has expired. " + "Get a fresh test token and try again. " @@ -256,7 +299,10 @@ public void testVerifyTokenSignatureMismatch() { try { tokenVerifier.verifyToken(token); + <<<<<<< v7 + ======= fail("No error thrown for mismatched signature"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Failed to verify the signature of Firebase test token. " + "See https://test.doc.url for details on how to retrieve a test token."; @@ -272,7 +318,10 @@ public void testMalformedCert() { try { tokenVerifier.verifyToken(token); + <<<<<<< v7 + ======= fail("No error thrown for malformed cert"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Error while fetching public key certificates: Could not parse certificate"; assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); @@ -297,7 +346,11 @@ public LowLevelHttpRequest buildRequest(String method, String url) throws IOExce try { idTokenVerifier.verifyToken(token); + <<<<<<< v7 + Assert.fail("No exception thrown"); + ======= fail("No error thrown for failing to fetch certificate"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Error while fetching public key certificates: Expected error"; assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); @@ -316,7 +369,10 @@ public void testMalformedSignature() { try { tokenVerifier.verifyToken(token); + <<<<<<< v7 + ======= fail("No error thrown for malformed signature"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Failed to verify the signature of Firebase test token. " + "See https://test.doc.url for details on how to retrieve a test token."; @@ -328,7 +384,10 @@ public void testMalformedSignature() { public void testLegacyCustomToken() { try { tokenVerifier.verifyToken(LEGACY_CUSTOM_TOKEN); + <<<<<<< v7 + ======= fail("No error thrown for passing legacy token"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "verifyTestToken() expects a test token, but was given a " + "legacy custom token. " @@ -341,7 +400,10 @@ public void testLegacyCustomToken() { public void testMalformedToken() { try { tokenVerifier.verifyToken("not.a.jwt"); + <<<<<<< v7 + ======= fail("No error thrown for malformed token"); + >>>>>>> master } catch (FirebaseAuthException e) { String message = "Failed to parse Firebase test token. " + "Make sure you passed a string that represents a complete and valid JWT. " @@ -382,8 +444,20 @@ public void testVerifyTokenWithMatchingTenantId() throws FirebaseAuthException { public void testVerifyTokenDifferentTenantIds() { FirebaseTokenVerifierImpl verifier = fullyPopulatedBuilder() .setTenantId("TENANT_1") + <<<<<<< v7 + .build() + .verifyToken(createTokenWithTenantId("TENANT_2")); + } catch (FirebaseAuthException e) { + assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, e.getAuthErrorCode()); + assertEquals( + "The tenant ID ('TENANT_2') of the token did not match the expected value ('TENANT_1')", + e.getMessage()); + } + } + ======= .build(); String token = createTokenWithTenantId("TENANT_2"); + >>>>>>> master try { verifier.verifyToken(token); @@ -444,6 +518,53 @@ public void testBuilderNoDocUrl() { fullyPopulatedBuilder().setDocUrl(null).build(); } + <<<<<<< v7 + private GooglePublicKeysManager newPublicKeysManager(String certificate) { + String serviceAccountCertificates = + String.format("{\"%s\" : \"%s\"}", TestTokenFactory.PRIVATE_KEY_ID, certificate); + HttpTransport transport = new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setContent(serviceAccountCertificates)) + .build(); + return newPublicKeysManager(transport); + } + + private GooglePublicKeysManager newPublicKeysManager(HttpTransport transport) { + return new GooglePublicKeysManager.Builder( + transport, FirebaseTokenUtils.UNQUOTED_CTRL_CHAR_JSON_FACTORY) + .setClock(TestTokenFactory.CLOCK) + .setPublicCertsEncodedUrl("https://test.cert.url") + .build(); + } + + private FirebaseTokenVerifier newTestTokenVerifier(GooglePublicKeysManager publicKeysManager) { + return fullyPopulatedBuilder() + .setPublicKeysManager(publicKeysManager) + .build(); + } + + private FirebaseTokenVerifierImpl.Builder fullyPopulatedBuilder() { + return FirebaseTokenVerifierImpl.builder() + .setShortName("test token") + .setMethod("verifyTestToken()") + .setDocUrl("https://test.doc.url") + .setJsonFactory(TestTokenFactory.JSON_FACTORY) + .setPublicKeysManager(newPublicKeysManager(ServiceAccount.EDITOR.getCert())) + .setInvalidTokenErrorCode(AuthErrorCode.INVALID_ID_TOKEN) + .setExpiredTokenErrorCode(AuthErrorCode.EXPIRED_ID_TOKEN) + .setIdTokenVerifier(newIdTokenVerifier()); + } + + private IdTokenVerifier newIdTokenVerifier() { + return new IdTokenVerifier.Builder() + .setClock(TestTokenFactory.CLOCK) + .setAudience(ImmutableList.of(TestTokenFactory.PROJECT_ID)) + .setIssuer(TEST_TOKEN_ISSUER) + .build(); + } + + ======= + >>>>>>> master private String createTokenWithoutKeyId() { return tokenFactory.createTokenWithoutKeyId(false); } @@ -480,6 +601,18 @@ private String createTokenWithTimestamps(long issuedAtSeconds, long expirationSe return tokenFactory.createTokenWithTimestamps(issuedAtSeconds, expirationSeconds, false); } + private void checkInvalidTokenException(FirebaseAuthException e, String message) { + checkException(e, message, AuthErrorCode.INVALID_ID_TOKEN); + } + + private void checkException(FirebaseAuthException e, String message, AuthErrorCode errorCode) { + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + assertNull(e.getHttpResponse()); + assertEquals(errorCode, e.getAuthErrorCode()); + } + private String createTokenWithTenantId(String tenantId) { return tokenFactory.createTokenWithTenantId(tenantId, false); } diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java index 83b98068d..1c65b6efb 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java @@ -45,9 +45,16 @@ import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.FirebaseUserManager.EmailLinkType; + <<<<<<< redacted-passwords +import com.google.firebase.auth.UidIdentifier; +import com.google.firebase.auth.UserIdentifier; +import com.google.firebase.auth.UserRecord.CreateRequest; +import com.google.firebase.auth.UserRecord.UpdateRequest; + ======= import com.google.firebase.auth.multitenancy.TenantAwareFirebaseAuth; import com.google.firebase.auth.multitenancy.TenantManager; import com.google.firebase.internal.ApiClientUtils; + >>>>>>> master import com.google.firebase.internal.SdkUtils; import com.google.firebase.testing.MultiRequestMockHttpTransport; import com.google.firebase.testing.TestResponseInterceptor; @@ -98,10 +105,13 @@ public class FirebaseUserManagerTest { private static final String TENANTS_BASE_URL = PROJECT_BASE_URL + "/tenants"; + <<<<<<< v7 + ======= private static final String AUTH_EMULATOR = "localhost:8000"; private static final String PROJECT_BASE_URL_EMULATOR = "http://" + AUTH_EMULATOR + "/identitytoolkit.googleapis.com/v2/projects/test-project-id"; + >>>>>>> master private static final String SAML_RESPONSE = TestUtils.loadResource("saml.json"); private static final String OIDC_RESPONSE = TestUtils.loadResource("oidc.json"); @@ -336,16 +346,119 @@ public void testInvalidEmailIdentifier() { } @Test + <<<<<<< redacted-passwords + public void testGetUsersExceeds100() throws Exception { + FirebaseApp.initializeApp(new FirebaseOptions.Builder() + .setCredentials(credentials) + .build()); + List identifiers = new ArrayList<>(); + for (int i = 0; i < 101; i++) { + identifiers.add(new UidIdentifier("uid_" + i)); + } + + try { + FirebaseAuth.getInstance().getUsers(identifiers); + fail("No error thrown for too many supplied identifiers"); + ======= public void testInvalidPhoneIdentifier() { try { new PhoneIdentifier("invalid phone number"); fail("No error thrown for invalid phone number"); + >>>>>>> master } catch (IllegalArgumentException expected) { // expected } } @Test + <<<<<<< redacted-passwords + public void testGetUsersNull() throws Exception { + FirebaseApp.initializeApp(new FirebaseOptions.Builder() + .setCredentials(credentials) + .build()); + try { + FirebaseAuth.getInstance().getUsers(null); + fail("No error thrown for null identifiers"); + } catch (NullPointerException expected) { + // expected + } + } + + @Test + public void testGetUsersEmpty() throws Exception { + initializeAppForUserManagement(); + GetUsersResult result = FirebaseAuth.getInstance().getUsers(new ArrayList()); + assertTrue(result.getUsers().isEmpty()); + assertTrue(result.getNotFound().isEmpty()); + } + + @Test + public void testGetUsersAllNonExisting() throws Exception { + initializeAppForUserManagement("{ \"users\": [] }"); + List ids = ImmutableList.of( + new UidIdentifier("id-that-doesnt-exist")); + GetUsersResult result = FirebaseAuth.getInstance().getUsers(ids); + assertTrue(result.getUsers().isEmpty()); + assertEquals(ids.size(), result.getNotFound().size()); + assertTrue(result.getNotFound().containsAll(ids)); + } + + @Test + public void testGetUsersMultipleIdentifierTypes() throws Exception { + initializeAppForUserManagement(("" + + "{ " + + " 'users': [{ " + + " 'localId': 'uid1', " + + " 'email': 'user1@example.com', " + + " 'phoneNumber': '+15555550001' " + + " }, { " + + " 'localId': 'uid2', " + + " 'email': 'user2@example.com', " + + " 'phoneNumber': '+15555550002' " + + " }, { " + + " 'localId': 'uid3', " + + " 'email': 'user3@example.com', " + + " 'phoneNumber': '+15555550003' " + + " }, { " + + " 'localId': 'uid4', " + + " 'email': 'user4@example.com', " + + " 'phoneNumber': '+15555550004', " + + " 'providerUserInfo': [{ " + + " 'providerId': 'google.com', " + + " 'rawId': 'google_uid4' " + + " }] " + + " }] " + + "} " + ).replace("'", "\"")); + + UidIdentifier doesntExist = new UidIdentifier("this-uid-doesnt-exist"); + List ids = ImmutableList.of( + new UidIdentifier("uid1"), + new EmailIdentifier("user2@example.com"), + new PhoneIdentifier("+15555550003"), + new ProviderIdentifier("google.com", "google_uid4"), + doesntExist); + GetUsersResult result = FirebaseAuth.getInstance().getUsers(ids); + Collection uids = userRecordsToUids(result.getUsers()); + assertTrue(uids.containsAll(ImmutableList.of("uid1", "uid2", "uid3", "uid4"))); + assertEquals(1, result.getNotFound().size()); + assertTrue(result.getNotFound().contains(doesntExist)); + } + + private Collection userRecordsToUids(Collection userRecords) { + Collection uids = new HashSet<>(); + for (UserRecord userRecord : userRecords) { + uids.add(userRecord.getUid()); + } + return uids; + } + + @Test + public void testInvalidUidIdentifier() throws Exception { + try { + new UidIdentifier("too long " + Strings.repeat(".", 128)); + fail("No error thrown for invalid uid"); + ======= public void testInvalidProviderIdentifier() { try { new ProviderIdentifier("", "valid-uid"); @@ -357,32 +470,67 @@ public void testInvalidProviderIdentifier() { try { new ProviderIdentifier("valid-id", ""); fail("No error thrown for invalid provider uid"); + >>>>>>> master } catch (IllegalArgumentException expected) { // expected } } @Test + <<<<<<< redacted-passwords + public void testInvalidEmailIdentifier() throws Exception { + try { + new EmailIdentifier("invalid email addr"); + fail("No error thrown for invalid email"); + } catch (IllegalArgumentException expected) { + // expected + ======= public void testGetUserByProviderUidWithInvalidProviderId() throws Exception { initializeAppForUserManagement(); try { FirebaseAuth.getInstance().getUserByProviderUidAsync("", "uid").get(); fail("No error thrown for invalid request"); } catch (IllegalArgumentException expected) { + >>>>>>> master } } @Test + <<<<<<< redacted-passwords + public void testInvalidPhoneIdentifier() throws Exception { + try { + new PhoneIdentifier("invalid phone number"); + fail("No error thrown for invalid phone number"); + } catch (IllegalArgumentException expected) { + // expected + ======= public void testGetUserByProviderUidWithInvalidProviderUid() throws Exception { initializeAppForUserManagement(); try { FirebaseAuth.getInstance().getUserByProviderUidAsync("id", "").get(); fail("No error thrown for invalid request"); } catch (IllegalArgumentException expected) { + >>>>>>> master } } @Test + <<<<<<< redacted-passwords + public void testInvalidProviderIdentifier() throws Exception { + try { + new ProviderIdentifier("", "valid-uid"); + fail("No error thrown for invalid provider id"); + } catch (IllegalArgumentException expected) { + // expected + } + + try { + new ProviderIdentifier("valid-id", ""); + fail("No error thrown for invalid provider uid"); + } catch (IllegalArgumentException expected) { + // expected + } + ======= public void testGetUserByProviderUidWithValidInput() throws Exception { TestResponseInterceptor interceptor = initializeAppForUserManagement( TestUtils.loadResource("getUser.json")); @@ -410,6 +558,7 @@ public void testGetUserByProviderUidWithEmail() throws Exception { .getUserByProviderUidAsync("email", "testuser@example.com").get(); checkUserRecord(userRecord); checkRequestHeaders(interceptor); + >>>>>>> master } @Test @@ -519,8 +668,13 @@ public void testDeleteUser() throws Exception { } @Test + <<<<<<< redacted-passwords + public void testDeleteUsersExceeds1000() throws Exception { + FirebaseApp.initializeApp(new FirebaseOptions.Builder() + ======= public void testDeleteUsersExceeds1000() { FirebaseApp.initializeApp(FirebaseOptions.builder() + >>>>>>> master .setCredentials(credentials) .build()); List ids = new ArrayList<>(); @@ -536,8 +690,13 @@ public void testDeleteUsersExceeds1000() { } @Test + <<<<<<< redacted-passwords + public void testDeleteUsersInvalidId() throws Exception { + FirebaseApp.initializeApp(new FirebaseOptions.Builder() + ======= public void testDeleteUsersInvalidId() { FirebaseApp.initializeApp(FirebaseOptions.builder() + >>>>>>> master .setCredentials(credentials) .build()); try { @@ -855,12 +1014,15 @@ public void call(FirebaseAuth auth) throws Exception { MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); FirebaseAuth auth = getRetryDisabledAuth(response); + <<<<<<< redacted-passwords + ======= Map codes = ImmutableMap.of( 302, ErrorCode.UNKNOWN, 400, ErrorCode.INVALID_ARGUMENT, 401, ErrorCode.UNAUTHENTICATED, 404, ErrorCode.NOT_FOUND, 500, ErrorCode.INTERNAL); + >>>>>>> master // Test for common HTTP error codes for (int code : codes.keySet()) { @@ -890,6 +1052,8 @@ public void call(FirebaseAuth auth) throws Exception { response.setStatusCode(500); try { operation.call(auth); + <<<<<<< redacted-passwords + ======= fail("No error thrown for HTTP error"); } catch (ExecutionException e) { assertThat(e.getCause().toString(), e.getCause(), instanceOf(FirebaseAuthException.class)); @@ -910,6 +1074,7 @@ public void call(FirebaseAuth auth) throws Exception { response.setStatusCode(500); try { operation.call(auth); + >>>>>>> master fail("No error thrown for HTTP error"); } catch (ExecutionException e) { assertTrue(e.getCause().toString(), e.getCause() instanceof FirebaseAuthException); @@ -1568,10 +1733,16 @@ public void testHttpErrorWithCode() { } @Test + <<<<<<< redacted-passwords + public void testUnexpectedHttpError() { + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() + .setContent("{}") + ======= public void testHttpErrorWithUnknownCode() { String content = "{\"error\": {\"message\": \"SOMETHING_NEW\"}}"; MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() .setContent(content) + >>>>>>> master .setStatusCode(500); FirebaseAuth auth = getRetryDisabledAuth(response); FirebaseUserManager userManager = auth.getUserManager(); @@ -1584,6 +1755,8 @@ public void testHttpErrorWithUnknownCode() { assertNull(e.getAuthErrorCode()); assertTrue(e.getCause() instanceof HttpResponseException); assertNotNull(e.getHttpResponse()); + <<<<<<< v7 + ======= } } @@ -1605,6 +1778,7 @@ public void testHttpErrorWithEmailNotFoundCode() { assertEquals(AuthErrorCode.EMAIL_NOT_FOUND, e.getAuthErrorCode()); assertTrue(e.getCause() instanceof HttpResponseException); assertNotNull(e.getHttpResponse()); + >>>>>>> master } } @@ -2903,6 +3077,8 @@ private static TestResponseInterceptor initializeAppForTenantAwareUserManagement return interceptor; } + <<<<<<< redacted-passwords + ======= private static TestResponseInterceptor initializeAppForUserManagement(String... responses) { initializeAppWithResponses(responses); TestResponseInterceptor interceptor = new TestResponseInterceptor(); @@ -2930,12 +3106,19 @@ private static GenericJson parseRequestContent(TestResponseInterceptor intercept return JSON_FACTORY.fromString(new String(out.toByteArray()), GenericJson.class); } + >>>>>>> master private static FirebaseAuth getRetryDisabledAuth(MockLowLevelHttpResponse response) { final MockHttpTransport transport = new MockHttpTransport.Builder() .setLowLevelHttpResponse(response) .build(); + <<<<<<< redacted-passwords + final FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder() + .setCredentials(credentials) + .setProjectId("test-project-id") + ======= final FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder() .setCredentials(credentials) + >>>>>>> master .setHttpTransport(transport) .build()); return FirebaseAuth.builder() @@ -2943,13 +3126,23 @@ private static FirebaseAuth getRetryDisabledAuth(MockLowLevelHttpResponse respon .setUserManager(new Supplier() { @Override public FirebaseUserManager get() { + <<<<<<< redacted-passwords + return new FirebaseUserManager(app, transport.createRequestFactory()); + } + }) + ======= return FirebaseUserManager.builder() .setProjectId("test-project-id") .setHttpRequestFactory(transport.createRequestFactory()) + <<<<<<< v7 + .setJsonFactory(Utils.getDefaultJsonFactory()) + ======= .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) + >>>>>>> master .build(); } }) + >>>>>>> master .build(); } diff --git a/src/test/java/com/google/firebase/auth/GetUsersIT.java b/src/test/java/com/google/firebase/auth/GetUsersIT.java index a0bdcb6c6..94e29459a 100644 --- a/src/test/java/com/google/firebase/auth/GetUsersIT.java +++ b/src/test/java/com/google/firebase/auth/GetUsersIT.java @@ -21,7 +21,10 @@ import com.google.common.collect.ImmutableList; import com.google.firebase.FirebaseApp; + <<<<<<< redacted-passwords + ======= import com.google.firebase.auth.UserTestUtils.RandomUser; + >>>>>>> master import com.google.firebase.testing.IntegrationTestUtils; import java.util.Collection; @@ -45,6 +48,20 @@ public static void setUpClass() throws Exception { testUser2 = FirebaseAuthIT.newUserWithParams(auth); testUser3 = FirebaseAuthIT.newUserWithParams(auth); + <<<<<<< redacted-passwords + FirebaseAuthIT.RandomUser randomUser = FirebaseAuthIT.RandomUser.create(); + importUserUid = randomUser.uid; + String phone = FirebaseAuthIT.randomPhoneNumber(); + UserImportResult result = auth.importUsers(ImmutableList.of( + ImportUserRecord.builder() + .setUid(randomUser.uid) + .setEmail(randomUser.email) + .setPhoneNumber(phone) + .addUserProvider( + UserProvider.builder() + .setProviderId("google.com") + .setUid("google_" + randomUser.uid) + ======= RandomUser randomUser = UserTestUtils.generateRandomUserInfo(); importUserUid = randomUser.getUid(); UserImportResult result = auth.importUsers(ImmutableList.of( @@ -56,6 +73,7 @@ public static void setUpClass() throws Exception { UserProvider.builder() .setProviderId("google.com") .setUid("google_" + randomUser.getUid()) + >>>>>>> master .build()) .build() )); diff --git a/src/test/java/com/google/firebase/auth/ListUsersPageTest.java b/src/test/java/com/google/firebase/auth/ListUsersPageTest.java index db490fb6a..3bc3a6411 100644 --- a/src/test/java/com/google/firebase/auth/ListUsersPageTest.java +++ b/src/test/java/com/google/firebase/auth/ListUsersPageTest.java @@ -69,7 +69,11 @@ public void testRedactedPasswords() throws FirebaseAuthException, IOException { newUser("user2", REDACTED_BASE64)), ListUsersPage.END_OF_LIST); TestUserSource source = new TestUserSource(result); + <<<<<<< redacted-passwords + ListUsersPage page = new ListUsersPage.PageFactory(source).create(); + ======= ListUsersPage page = new ListUsersPage.Factory(source).create(); + >>>>>>> master assertFalse(page.hasNextPage()); assertEquals(ListUsersPage.END_OF_LIST, page.getNextPageToken()); assertNull(page.getNextPage()); @@ -356,7 +360,11 @@ private static ExportedUserRecord newUser(String uid) throws IOException { } private static ExportedUserRecord newUser(String uid, String passwordHash) throws IOException { + <<<<<<< redacted-passwords + JsonFactory jsonFactory = Utils.getDefaultJsonFactory(); + ======= JsonFactory jsonFactory = ApiClientUtils.getDefaultJsonFactory(); + >>>>>>> master DownloadAccountResponse.User parsed = jsonFactory.fromString( String.format("{\"localId\":\"%s\", \"passwordHash\":\"%s\"}", uid, passwordHash), DownloadAccountResponse.User.class); diff --git a/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java b/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java index 679fe5a3c..b1a6522fa 100644 --- a/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java +++ b/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java @@ -23,6 +23,10 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; + <<<<<<< v7 +import com.google.api.client.googleapis.util.Utils; + ======= + >>>>>>> master import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpStatusCodes; import com.google.api.client.testing.http.MockHttpTransport; @@ -99,7 +103,11 @@ public void testIAMCryptoSignerHttpError() { .build(); CryptoSigners.IAMCryptoSigner signer = new CryptoSigners.IAMCryptoSigner( transport.createRequestFactory(), + <<<<<<< v7 + Utils.getDefaultJsonFactory(), + ======= ApiClientUtils.getDefaultJsonFactory(), + >>>>>>> master "test-service-account@iam.gserviceaccount.com"); try { signer.sign("foo".getBytes()); diff --git a/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java b/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java index b94038925..2fd26aa5a 100644 --- a/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java +++ b/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java @@ -45,7 +45,10 @@ import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuthException; import com.google.firebase.auth.MockGoogleCredentials; + <<<<<<< v7 + ======= import com.google.firebase.internal.ApiClientUtils; + >>>>>>> master import com.google.firebase.internal.SdkUtils; import com.google.firebase.testing.MultiRequestMockHttpTransport; import com.google.firebase.testing.TestResponseInterceptor; @@ -398,9 +401,13 @@ private static TenantManager createRetryDisabledTenantManager(MockLowLevelHttpRe .setCredentials(credentials) .build()); FirebaseTenantClient tenantClient = new FirebaseTenantClient( + <<<<<<< v7 + "test-project-id", Utils.getDefaultJsonFactory(), transport.createRequestFactory()); + ======= "test-project-id", ApiClientUtils.getDefaultJsonFactory(), transport.createRequestFactory()); + >>>>>>> master return new TenantManager(app, tenantClient); } diff --git a/src/test/java/com/google/firebase/database/DatabaseErrorTest.java b/src/test/java/com/google/firebase/database/DatabaseErrorTest.java index 13a46d6f8..af64ed0d0 100644 --- a/src/test/java/com/google/firebase/database/DatabaseErrorTest.java +++ b/src/test/java/com/google/firebase/database/DatabaseErrorTest.java @@ -10,24 +10,17 @@ public class DatabaseErrorTest { @Test public void testFromCode() { - DatabaseError error = DatabaseError.fromCode(DatabaseError.DATA_STALE); - assertEquals(DatabaseError.DATA_STALE, error.getCode()); + DatabaseError error = DatabaseError.fromCode(DatabaseErrorCode.DATA_STALE); + assertEquals(DatabaseErrorCode.DATA_STALE, error.getCode()); assertEquals("The transaction needs to be run again with current data", error.getMessage()); assertEquals("", error.getDetails()); assertEquals("DatabaseError: " + error.getMessage(), error.toString()); - - try { - DatabaseError.fromCode(19191); - fail("No error thrown for unknown error code"); - } catch (IllegalArgumentException expected) { - // expected - } } @Test public void testFromException() { DatabaseError error = DatabaseError.fromException(new RuntimeException("_test_")); - assertEquals(DatabaseError.USER_CODE_EXCEPTION, error.getCode()); + assertEquals(DatabaseErrorCode.USER_CODE_EXCEPTION, error.getCode()); assertTrue(error.getMessage().contains("_test_")); assertEquals("", error.getDetails()); } diff --git a/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java b/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java index 2bdc6185e..af241f0ac 100644 --- a/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java +++ b/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java @@ -39,14 +39,24 @@ import org.junit.Test; public class FirebaseDatabaseTest { + <<<<<<< redacted-passwords + + private static final FirebaseOptions firebaseOptions = + new FirebaseOptions.Builder() + ======= private static final FirebaseOptions firebaseOptions = FirebaseOptions.builder() + >>>>>>> master .setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream())) .setDatabaseUrl("https://firebase-db-test.firebaseio.com") .build(); private static final FirebaseOptions firebaseOptionsWithoutDatabaseUrl = + <<<<<<< redacted-passwords + new FirebaseOptions.Builder() + ======= FirebaseOptions.builder() + >>>>>>> master .setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream())) .build(); @@ -87,7 +97,7 @@ public void testInvalidUrl() { try { FirebaseDatabase.getInstance(url); fail("No error thrown for URL: " + url); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // expected } } @@ -152,7 +162,7 @@ public void testReferenceFromUrl() { try { defaultDatabase.getReferenceFromUrl("https://other-db-test.firebaseio.com/foo/bar"); fail("No error thrown for invalid URL"); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // expected } } finally { diff --git a/src/test/java/com/google/firebase/database/MapperTest.java b/src/test/java/com/google/firebase/database/MapperTest.java index 941fbde9d..17e609ded 100644 --- a/src/test/java/com/google/firebase/database/MapperTest.java +++ b/src/test/java/com/google/firebase/database/MapperTest.java @@ -63,28 +63,28 @@ public void primitiveDeserializeString() { try { deserialize("{'value': 1.1}", StringBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } // Int try { deserialize("{'value': 1}", StringBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } // Long try { deserialize("{'value': 1234567890123}", StringBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } // Boolean try { deserialize("{'value': true}", StringBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } } @@ -97,28 +97,28 @@ public void primitiveDeserializeBoolean() { try { deserialize("{'value': 1.1}", BooleanBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } // Long try { deserialize("{'value': 1234567890123}", BooleanBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } // Int try { deserialize("{'value': 1}", BooleanBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } // String try { deserialize("{'value': 'foo'}", BooleanBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } } @@ -138,14 +138,14 @@ public void primitiveDeserializeDouble() { try { deserialize("{'value': true}", DoubleBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } // String try { deserialize("{'value': 'foo'}", DoubleBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } } @@ -165,14 +165,14 @@ public void primitiveDeserializeFloat() { try { deserialize("{'value': true}", FloatBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } // String try { deserialize("{'value': 'foo'}", FloatBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } } @@ -189,28 +189,28 @@ public void primitiveDeserializeInt() { try { deserialize("{'value': 1e10}", IntBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } // Long try { deserialize("{'value': 1234567890123}", IntBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } // Boolean try { deserialize("{'value': true}", IntBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } // String try { deserialize("{'value': 'foo'}", IntBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } } @@ -231,30 +231,30 @@ public void primitiveDeserializeLong() { try { deserialize("{'value': 1e300}", LongBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } // Boolean try { deserialize("{'value': true}", LongBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } // String try { deserialize("{'value': 'foo'}", LongBean.class); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void primitiveDeserializeWrongTypeMap() { deserialize("{'value': {'foo': 'bar'}}", StringBean.class); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void primitiveDeserializeWrongTypeList() { deserialize("{'value': ['foo']}", StringBean.class); } @@ -275,12 +275,12 @@ public void publicPrivateFieldDeserialze() { assertEquals(null, bean.value3); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void packageFieldDeserialze() { deserialize("{'value': 'foo'}", PackageFieldBean.class); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void privateFieldDeserialize() { deserialize("{'value': 'foo'}", PrivateFieldBean.class); } @@ -307,12 +307,12 @@ public void ignoreExtraProperties() { assertEquals("foo", bean.value); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void throwOnUnknownProperties() { deserialize("{'value': 'foo', 'unknown': 'bar'}", ThrowOnUnknownPropertiesBean.class); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void twoSetterBean() { deserialize("{'value': 'foo'}", TwoSetterBean.class); } @@ -337,12 +337,12 @@ public void privateSetterIsCalledWhenPresent() { assertEquals("setter:foo", bean.value); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void setterIsCaseSensitive1() { deserialize("{'value': 'foo'}", CaseSensitiveSetterBean1.class); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void setterIsCaseSensitive2() { deserialize("{'value': 'foo'}", CaseSensitiveSetterBean2.class); } @@ -365,7 +365,7 @@ public void caseSensitiveSetterIsCalledWhenPresent3() { assertEquals("foo", bean.value); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void caseSensitiveSetterMustHaveSameCaseAsSetter() { deserialize("{'value': 'foo'}", CaseSensitiveSetterBean6.class); } @@ -410,7 +410,7 @@ public void beansCanContainBeanMaps() { assertEquals("foo", bean.values.get("key").value); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void beanMapsMustHaveStringKeys() { deserialize("{'values': {'1': 'bar'}}", IllegalKeyMapBean.class); } @@ -474,14 +474,14 @@ public void serializePublicFieldBean() { assertJson("{'value': 'foo'}", serialize(bean)); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void serializePrivateFieldBean() { PrivateFieldBean bean = new PrivateFieldBean(); bean.value = "foo"; serialize(bean); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void serializePackageFieldBean() { PackageFieldBean bean = new PackageFieldBean(); bean.value = "foo"; @@ -511,7 +511,7 @@ public void getterOverridesPublicField() { assertJson("{'value': 'getter:foo'}", serialize(bean)); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void getterAndPublicFieldsConflictOnCaseSensitivity() { GetterPublicFieldBeanCaseSensitive bean = new GetterPublicFieldBeanCaseSensitive(); bean.valueCase = "foo"; @@ -601,7 +601,7 @@ public void serializeMapOfBeansWorks() { assertJson("{'values': {'key': {'value': 'foo'}}}", serialize(bean)); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void beanMapsMustHaveStringKeysForSerializing() { IllegalKeyMapBean bean = new IllegalKeyMapBean(); bean.values = @@ -615,7 +615,7 @@ public void beanMapsMustHaveStringKeysForSerializing() { serialize(bean); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void twoGettersThrows() { TwoGetterBean bean = new TwoGetterBean(); bean.value = "foo"; @@ -686,62 +686,62 @@ public void roundTripUnicodeBean() { assertEquals("foo", deserialized.漢字); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void shortsCantBeSerialized() { ShortBean bean = new ShortBean(); bean.value = 1; serialize(bean); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void bytesCantBeSerialized() { ByteBean bean = new ByteBean(); bean.value = 1; serialize(bean); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void charsCantBeSerialized() { CharBean bean = new CharBean(); bean.value = 1; serialize(bean); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void intArraysCantBeSerialized() { IntArrayBean bean = new IntArrayBean(); bean.values = new int[] {1}; serialize(bean); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void objectArraysCantBeSerialized() { StringArrayBean bean = new StringArrayBean(); bean.values = new String[] {"foo"}; serialize(bean); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void shortsCantBeDeserialized() { deserialize("{'value': 1}", ShortBean.class); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void bytesCantBeDeserialized() { deserialize("{'value': 1}", ByteBean.class); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void charsCantBeDeserialized() { deserialize("{'value': '1'}", CharBean.class); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void intArraysCantBeDeserialized() { deserialize("{'values': [1]}", IntArrayBean.class); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void objectArraysCantBeDeserialized() { deserialize("{'values': ['foo']}", StringArrayBean.class); } @@ -758,7 +758,7 @@ public void privateConstructorCanBeDeserialized() { assertEquals("foo", bean.value); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void argConstructorCantBeDeserialized() { deserialize("{'value': 'foo'}", ArgConstructorBean.class); } @@ -811,32 +811,32 @@ public void primitiveClassesCanBePassedInTopLevel() { assertEquals((Double) 1.1, CustomClassMapper.convertToCustomClass(1.1, Double.class)); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void passingInListTopLevelThrows() { CustomClassMapper.convertToCustomClass(Collections.singletonList("foo"), List.class); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void passingInMapTopLevelThrows() { CustomClassMapper.convertToCustomClass(Collections.singletonMap("foo", "bar"), Map.class); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void passingInCharacterTopLevelThrows() { CustomClassMapper.convertToCustomClass('1', Character.class); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void passingInShortTopLevelThrows() { CustomClassMapper.convertToCustomClass(1, Short.class); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void passingInByteTopLevelThrows() { CustomClassMapper.convertToCustomClass(1, Byte.class); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void passingInGenericBeanTopLevelThrows() { deserialize("{'value': 'foo'}", GenericBean.class); } @@ -848,14 +848,14 @@ public void collectionsCanBeSerializedWhenList() { assertJson("{'values': ['foo']}", serialize(bean)); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void collectionsCantBeSerializedWhenSet() { CollectionBean bean = new CollectionBean(); bean.values = Collections.singleton("foo"); serialize(bean); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void collectionsCantBeDeserialized() { deserialize("{'values': ['foo']}", CollectionBean.class); } @@ -930,7 +930,7 @@ public void serializingGenericBeansSupported() { assertJson("{'valueA': 'foo', 'valueB': 1}", serialize(doubleBean)); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void deserializingWrongTypeThrows() { deserialize("{'value': 'foo'}", WrongTypeBean.class); } @@ -942,17 +942,17 @@ public void serializingWrongTypeWorks() { assertJson("{'value': '1'}", serialize(bean)); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void extendingGenericTypeIndicatorIsForbidden1() { deserialize("{'value': 'foo'}", new GenericTypeIndicatorSubclass>() {}); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void extendingGenericTypeIndicatorIsForbidden2() { deserialize("{'value': 'foo'}", new NonGenericTypeIndicatorSubclass() {}); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void extendingGenericTypeIndicatorIsForbidden3() { deserialize("{'value': 'foo'}", new NonGenericTypeIndicatorSubclassConcreteSubclass()); } @@ -964,12 +964,12 @@ public void subclassingGenericTypeIndicatorIsAllowed() { assertEquals("foo", bean.value); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void unknownTypeParametersNotSupported() { deserialize("{'value': 'foo'}", new GenericTypeIndicatorSubclass>() {}); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void unknownTypeParametersSupportedIfBoundedByKnownType() { GenericBean bean = deserialize( @@ -1075,7 +1075,7 @@ public void enumsCanBeParsedToNull() { assertNull(bean.complexEnum); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void throwsOnUnmatchedEnums() { String json = "{'enumField': 'Unavailable', 'enumValue': 'Foo', 'complexEnum': 'One'}"; deserialize(json, EnumBean.class); @@ -1146,14 +1146,14 @@ public void inheritedFieldsAndSettersAreParsed() { assertEquals("private-value", baseBean.classPrivateValue); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void settersFromSubclassConflictsWithBaseClass() { ConflictingSetterSubBean bean = new ConflictingSetterSubBean(); bean.value = 1; serialize(bean); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void settersFromSubclassConflictsWithBaseClass2() { ConflictingSetterSubBean2 bean = new ConflictingSetterSubBean2(); bean.value = 1; @@ -1175,7 +1175,7 @@ public void settersCanOverridePrimitiveSettersParsing() { assertEquals(-2, bean.value); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void genericSettersFromSubclassConflictsWithBaseClass() { ConflictingGenericSetterSubBean bean = new ConflictingGenericSetterSubBean<>(); bean.value = "hello"; @@ -1184,7 +1184,7 @@ public void genericSettersFromSubclassConflictsWithBaseClass() { // This should work, but generics and subclassing are tricky to get right. For now we will just // throw and we can add support for generics & subclassing if it becomes a high demand feature - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void settersCanOverrideGenericSettersParsingNot() { NonConflictingGenericSetterSubBean bean = deserialize("{'value': 'value'}", NonConflictingGenericSetterSubBean.class); diff --git a/src/test/java/com/google/firebase/database/MutableDataTest.java b/src/test/java/com/google/firebase/database/MutableDataTest.java index d47d8aa97..7cfd957c6 100644 --- a/src/test/java/com/google/firebase/database/MutableDataTest.java +++ b/src/test/java/com/google/firebase/database/MutableDataTest.java @@ -214,14 +214,14 @@ public void validatesPaths() { try { dataFor(TestHelpers.buildObjFromPath(path, "test_value")); fail("Invalid path did not throw exception."); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // expected } try { dataForPath("scalar_value", key); fail("Invalid path did not throw exception."); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // expected } } diff --git a/src/test/java/com/google/firebase/database/ObjectMapTest.java b/src/test/java/com/google/firebase/database/ObjectMapTest.java index bd1cdcd97..6da1473e0 100644 --- a/src/test/java/com/google/firebase/database/ObjectMapTest.java +++ b/src/test/java/com/google/firebase/database/ObjectMapTest.java @@ -52,7 +52,7 @@ public void basicObjectMapping() throws IOException { try { data.getValue(Author.class); fail("Should throw"); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // No-op, expected to throw } } @@ -80,7 +80,7 @@ public void nativeToObject() { try { m = data.getValue(Message.class); fail("Should throw"); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // No-op, expected to throw } } diff --git a/src/test/java/com/google/firebase/database/QueryTest.java b/src/test/java/com/google/firebase/database/QueryTest.java index 58128f452..8533827de 100644 --- a/src/test/java/com/google/firebase/database/QueryTest.java +++ b/src/test/java/com/google/firebase/database/QueryTest.java @@ -451,7 +451,7 @@ public void testKeepSynced() { try { query.keepSynced(true); fail("No error thrown for keepSynced() call on .info node"); - } catch (DatabaseException expected) { + } catch (UnsupportedOperationException expected) { // expected } } diff --git a/src/test/java/com/google/firebase/database/UtilitiesTest.java b/src/test/java/com/google/firebase/database/UtilitiesTest.java index be4aaad99..d7266a52e 100644 --- a/src/test/java/com/google/firebase/database/UtilitiesTest.java +++ b/src/test/java/com/google/firebase/database/UtilitiesTest.java @@ -52,12 +52,12 @@ public void settingValidCacheSizeSucceeds() { new DatabaseConfig().setPersistenceCacheSizeBytes(5 * 1024 * 1024); // works fine. } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void settingCacheSizeTooLowFails() { new DatabaseConfig().setPersistenceCacheSizeBytes(1024 * 1024 - 1); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void settingCacheSizeTooHighFails() { new DatabaseConfig().setPersistenceCacheSizeBytes(100 * 1024 * 1024 + 1); } diff --git a/src/test/java/com/google/firebase/database/core/RepoTest.java b/src/test/java/com/google/firebase/database/core/RepoTest.java index 9c2281d4c..e93acad91 100644 --- a/src/test/java/com/google/firebase/database/core/RepoTest.java +++ b/src/test/java/com/google/firebase/database/core/RepoTest.java @@ -18,6 +18,7 @@ import com.google.firebase.database.ChildEventListener; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseErrorCode; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.database.MutableData; @@ -594,7 +595,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { assertNull(errorResult.get()); assertEquals("foo", refResult.get().getKey()); - DatabaseError ex = DatabaseError.fromCode(DatabaseError.WRITE_CANCELED); + DatabaseError ex = DatabaseError.fromCode(DatabaseErrorCode.WRITE_CANCELED); repo.callOnComplete(listener, ex, new Path("/bar")); assertEquals(ex, errorResult.get()); assertEquals("bar", refResult.get().getKey()); @@ -629,7 +630,7 @@ public void run() { }); waitFor(semaphore); assertNotNull(errorResult.get()); - assertEquals(DatabaseError.DATA_STALE, errorResult.get().getCode()); + assertEquals(DatabaseErrorCode.DATA_STALE, errorResult.get().getCode()); assertEquals("failure", refResult.get().getKey()); } @@ -666,7 +667,7 @@ public void run() { }); waitFor(semaphore); assertNotNull(errorResult.get()); - assertEquals(DatabaseError.DATA_STALE, errorResult.get().getCode()); + assertEquals(DatabaseErrorCode.DATA_STALE, errorResult.get().getCode()); assertEquals("failure", refResult.get().getKey()); } @@ -699,7 +700,7 @@ public void run() { }); waitFor(semaphore); assertNotNull(errorResult.get()); - assertEquals(DatabaseError.DATA_STALE, errorResult.get().getCode()); + assertEquals(DatabaseErrorCode.DATA_STALE, errorResult.get().getCode()); assertEquals("failure", refResult.get().getKey()); } diff --git a/src/test/java/com/google/firebase/database/future/WriteFuture.java b/src/test/java/com/google/firebase/database/future/WriteFuture.java index 84f5ffbfe..7b4e2b6ad 100644 --- a/src/test/java/com/google/firebase/database/future/WriteFuture.java +++ b/src/test/java/com/google/firebase/database/future/WriteFuture.java @@ -35,7 +35,7 @@ public class WriteFuture implements Future { private Semaphore semaphore; private boolean done = false; - public WriteFuture(DatabaseReference ref, Object value) throws DatabaseException { + public WriteFuture(DatabaseReference ref, Object value) { semaphore = new Semaphore(0); ref.setValue( value, @@ -48,8 +48,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { }); } - public WriteFuture(DatabaseReference ref, Object value, double priority) - throws DatabaseException { + public WriteFuture(DatabaseReference ref, Object value, double priority) { semaphore = new Semaphore(0); ref.setValue( value, @@ -63,8 +62,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { }); } - public WriteFuture(DatabaseReference ref, Object value, String priority) - throws DatabaseException { + public WriteFuture(DatabaseReference ref, Object value, String priority) { semaphore = new Semaphore(0); ref.setValue( value, diff --git a/src/test/java/com/google/firebase/database/integration/DataTestIT.java b/src/test/java/com/google/firebase/database/integration/DataTestIT.java index d832f995d..698455e3b 100644 --- a/src/test/java/com/google/firebase/database/integration/DataTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/DataTestIT.java @@ -465,14 +465,14 @@ public void testVerifyNodesStartingWithPeriod() { try { ref.child(".foo"); fail("Should fail"); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // No-op } try { ref.child("foo/.foo"); fail("Should fail"); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // No-op } } @@ -662,7 +662,7 @@ public boolean isComplete(List events) { // removedTwo did equal false, now equals true try { ref.child("two").removeValueAsync(); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Should not fail"); } } @@ -704,7 +704,7 @@ public boolean isComplete(List events) { if (sawJson.compareAndSet(false, true)) { try { writer.setValueAsync(primitive); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Shouldn't happen: " + e.toString()); } } else { @@ -712,7 +712,7 @@ public boolean isComplete(List events) { if (sawPrimitive.compareAndSet(false, true)) { try { writer.setValueAsync(json); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Shouldn't happen: " + e.toString()); } } @@ -970,7 +970,7 @@ public boolean isComplete(List events) { if (priority != null && priority.equals(100.0)) { try { ref2.setValueAsync("whatever"); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Shouldn't happen: " + e.toString()); } return true; @@ -1091,35 +1091,35 @@ public void testInvalidPaths() throws InterruptedException { try { ref.child(path); fail("Should not be a valid path: " + path); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // No-op, expected } try { root.child(path); fail("Should not be a valid path: " + path); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // No-op, expected } try { root.child(IntegrationTestUtils.getDatabaseUrl() + "/tests/" + path); fail("Should not be a valid path: " + path); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // No-op, expected } try { snap.child(path); fail("Should not be a valid path: " + path); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // No-op, expected } try { snap.hasChild(path); fail("Should not be a valid path: " + path); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // No-op, expected } } @@ -1144,14 +1144,14 @@ public void testInvalidKeys() throws DatabaseException { try { ref.setValueAsync(badObject); fail("Should not be a valid object: " + badObject); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // No-op, expected } try { ref.onDisconnect().setValueAsync(badObject); fail("Should not be a valid object: " + badObject); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // No-op, expected } @@ -1160,7 +1160,7 @@ public void testInvalidKeys() throws DatabaseException { } @Test - public void testInvalidUpdates() throws DatabaseException { + public void testInvalidUpdates() { DatabaseReference ref = IntegrationTestUtils.getRandomNode(masterApp); List> badUpdates = ImmutableList.of( @@ -1176,21 +1176,21 @@ public void testInvalidUpdates() throws DatabaseException { try { ref.updateChildrenAsync(badUpdate); fail("Should not be a valid update: " + badUpdate); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // No-op, expected } try { ref.onDisconnect().updateChildrenAsync(badUpdate); fail("Should not be a valid object: " + badUpdate); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // No-op, expected } } } @Test - public void testAsciiControlCharacters() throws DatabaseException { + public void testAsciiControlCharacters() { DatabaseReference node = IntegrationTestUtils.getRandomNode(masterApp); // Test all controls characters PLUS 0x7F (127). for (int i = 0; i <= 32; i++) { @@ -1199,15 +1199,14 @@ public void testAsciiControlCharacters() throws DatabaseException { try { node.setValueAsync(obj); fail("Ascii control character should not be allowed in path."); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // expected } } } @Test - public void invalidDoubleValues() - throws DatabaseException, TestFailure, TimeoutException, InterruptedException { + public void invalidDoubleValues() { DatabaseReference node = IntegrationTestUtils.getRandomNode(masterApp); Object[] invalidValues = new Object[] { @@ -1222,7 +1221,7 @@ public void invalidDoubleValues() try { node.setValueAsync(invalidValue); fail("NaN or Inf are not allowed as values."); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { assertEquals("Invalid value: Value cannot be NaN, Inf or -Inf.", expected.getMessage()); } } @@ -1291,26 +1290,26 @@ private BadGroup(String expectedError, List keys) { try { nodeChild.setValueAsync(obj); fail("Too-long path for setValue should throw exception."); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // expected } try { nodeChild.child(key).setValueAsync("another_value"); fail("Too-long path before setValue should throw exception."); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // expected } try { nodeChild.updateChildrenAsync(obj); fail("Too-long path for updateChildren should throw exception."); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // expected } try { Map deepUpdate = MapBuilder.of(key, "test_value"); nodeChild.updateChildrenAsync(deepUpdate); fail("Too-long path in deep update for updateChildren should throw exception."); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { // expected } } @@ -1321,38 +1320,38 @@ private BadGroup(String expectedError, List keys) { try { node.setValueAsync(obj); fail("Expected setValueAsync(bad key) to throw exception: " + key); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { TestHelpers.assertContains(e.getMessage(), badGroup.expectedError); } try { node.child(key).setValueAsync("another_value"); fail("Expected child(\"" + key + "\").setValueAsync() to throw exception: " + key); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { TestHelpers.assertContains(e.getMessage(), badGroup.expectedError); } try { node.updateChildrenAsync(obj); fail("Expected updateChildrenAsync(bad key) to throw exception: " + key); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { TestHelpers.assertContains(e.getMessage(), badGroup.expectedError); } try { Map deepUpdate = MapBuilder.of(key, "test_value"); node.updateChildrenAsync(deepUpdate); fail("Expected updateChildrean(bad deep update key) to throw exception: " + key); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { TestHelpers.assertContains(e.getMessage(), badGroup.expectedError); } try { node.onDisconnect().setValueAsync(obj); fail("Expected onDisconnect.setValueAsync(bad key) to throw exception: " + key); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { TestHelpers.assertContains(e.getMessage(), badGroup.expectedError); } try { node.onDisconnect().updateChildrenAsync(obj); fail("Expected onDisconnect.updateChildrenAsync(bad key) to throw exception: " + key); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { TestHelpers.assertContains(e.getMessage(), badGroup.expectedError); } try { @@ -1360,7 +1359,7 @@ private BadGroup(String expectedError, List keys) { node.onDisconnect().updateChildrenAsync(deepUpdate); fail("Expected onDisconnect.updateChildrenAsync(bad deep update key) to throw exception: " + key); - } catch (DatabaseException e) { + } catch (IllegalArgumentException e) { TestHelpers.assertContains(e.getMessage(), badGroup.expectedError); } } @@ -2445,7 +2444,7 @@ public boolean isComplete(List events) { public Transaction.Result doTransaction(MutableData currentData) { try { currentData.setValue(ServerValue.TIMESTAMP); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Should not fail"); } return Transaction.success(currentData); diff --git a/src/test/java/com/google/firebase/database/integration/EventHelper.java b/src/test/java/com/google/firebase/database/integration/EventHelper.java index 2dc20e381..4da1c81e2 100644 --- a/src/test/java/com/google/firebase/database/integration/EventHelper.java +++ b/src/test/java/com/google/firebase/database/integration/EventHelper.java @@ -80,7 +80,7 @@ public EventHelper addValueExpectation(DatabaseReference ref, T expectedValu } EventHelper addChildExpectation( - DatabaseReference ref, Event.EventType eventType, String childName) throws DatabaseException { + DatabaseReference ref, Event.EventType eventType, String childName) { if (!locations.contains(ref)) { toListen.add(ref); } diff --git a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java index 84ec321a5..09db50fb4 100644 --- a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java @@ -84,7 +84,7 @@ public void testNullDatabaseUrl() { try { FirebaseDatabase.getInstance(app); fail("no error thrown for getInstance() with null URL"); - } catch (DatabaseException expected) { // ignore + } catch (IllegalArgumentException expected) { // ignore } } @@ -94,7 +94,7 @@ public void testMalformedDatabaseUrlInOptions() { try { FirebaseDatabase.getInstance(app); fail("no error thrown for getInstance() with malformed URL"); - } catch (DatabaseException expected) { // ignore + } catch (IllegalArgumentException expected) { // ignore } } @@ -104,7 +104,7 @@ public void testMalformedDatabaseUrlInGetInstance() { try { FirebaseDatabase.getInstance(app, "not-a-url"); fail("no error thrown for getInstance() with malformed URL"); - } catch (DatabaseException expected) { // ignore + } catch (IllegalArgumentException expected) { // ignore } } @@ -115,7 +115,7 @@ public void testDatabaseUrlWithPathInOptions() { try { FirebaseDatabase.getInstance(app); fail("no error thrown for DB URL with path"); - } catch (DatabaseException expected) { // ignore + } catch (IllegalArgumentException expected) { // ignore } } @@ -126,7 +126,7 @@ public void testDatabaseUrlWithPathInGetInstance() { FirebaseDatabase.getInstance(app, IntegrationTestUtils.getDatabaseUrl() + "/paths/are/not/allowed"); fail("no error thrown for DB URL with path"); - } catch (DatabaseException expected) { // ignore + } catch (IllegalArgumentException expected) { // ignore } } @@ -153,7 +153,7 @@ public void testGetReferenceFromURLWithPath() { assertEquals(dbUrl + "/foo/bar", ref.toString()); } - @Test(expected = DatabaseException.class) + @Test(expected = IllegalArgumentException.class) public void testGetReferenceThrowsWithBadUrl() { FirebaseDatabase db = FirebaseDatabase.getInstance(); db.getReferenceFromUrl("https://tests2.fake-firebaseio.com:9000"); diff --git a/src/test/java/com/google/firebase/database/integration/InfoTestIT.java b/src/test/java/com/google/firebase/database/integration/InfoTestIT.java index 5acd581a2..5fd39c29b 100644 --- a/src/test/java/com/google/firebase/database/integration/InfoTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/InfoTestIT.java @@ -62,7 +62,7 @@ public void testInfoNodeSetValue() { try { ref.setValueAsync("hi"); fail("Should not be allowed"); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // No-op, expected } } @@ -73,7 +73,7 @@ public void testInfoNodeSetValueWithPriority() { try { ref.setValueAsync("hi", 5); fail("Should not be allowed"); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // No-op, expected } } @@ -84,7 +84,7 @@ public void testInfoNodeSetPriority() { try { ref.setPriorityAsync("hi"); fail("Should not be allowed"); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // No-op, expected } } @@ -108,7 +108,7 @@ public void onComplete( } }); fail("Should not be allowed"); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // No-op, expected } } @@ -119,7 +119,7 @@ public void testInfoNodeRemoveValue() { try { ref.removeValueAsync(); fail("Should not be allowed"); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // No-op, expected } } @@ -130,7 +130,7 @@ public void testInfoNodeChildSetValue() { try { ref.child("test").setValueAsync("hi"); fail("Should not be allowed"); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // No-op, expected } } diff --git a/src/test/java/com/google/firebase/database/integration/QueryTestIT.java b/src/test/java/com/google/firebase/database/integration/QueryTestIT.java index 9a5d50334..7f2afeb87 100644 --- a/src/test/java/com/google/firebase/database/integration/QueryTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/QueryTestIT.java @@ -106,7 +106,7 @@ public void testInvalidPathsToOrderBy() { try { ref.orderByChild(key); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } } } @@ -118,174 +118,174 @@ public void testInvalidQueries() { try { ref.orderByKey().orderByPriority(); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByKey().orderByValue(); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByKey().orderByChild("foo"); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByValue().orderByPriority(); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByValue().orderByKey(); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByValue().orderByValue(); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByValue().orderByChild("foo"); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByChild("foo").orderByPriority(); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByChild("foo").orderByKey(); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByChild("foo").orderByValue(); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByKey().startAt(1); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByKey().startAt(null); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByKey().endAt(null); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByKey().equalTo(null); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByKey().startAt("test", "test"); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByKey().endAt(1); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByKey().endAt("test", "test"); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByKey().orderByPriority(); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByPriority().orderByKey(); fail("Should throw"); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByPriority().orderByValue(); fail("Should throw"); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByPriority().orderByPriority(); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.limitToLast(1).limitToLast(1); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.limitToFirst(1).limitToLast(1); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.limitToLast(1).limitToFirst(1); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.equalTo(true).endAt("test", "test"); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.equalTo(true).startAt("test", "test"); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.equalTo(true).equalTo("test", "test"); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.equalTo("test").equalTo(true); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByChild("foo").orderByKey(); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.limitToFirst(5).limitToLast(10); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.startAt(5).equalTo(10); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByPriority().startAt(false); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByPriority().endAt(true); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.orderByPriority().equalTo(true); fail("Should throw"); - } catch (DatabaseException | IllegalArgumentException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } } @@ -299,14 +299,14 @@ public void testInvalidKeysInStartAtOrEndAtQueries() { try { ref.startAt(null, key); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } try { ref.endAt(null, key); fail("Should throw"); - } catch (DatabaseException e) { // ignore + } catch (IllegalArgumentException e) { // ignore } } @@ -2458,7 +2458,7 @@ public boolean isComplete(List events) { TestHelpers.assertDeepEquals(toSet, events.get(0).getSnapshot().getValue()); try { writer.child("d").setValueAsync(4); - } catch (DatabaseException e) { // ignore + } catch (Exception e) { // ignore fail("Should not fail"); } return false; diff --git a/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java b/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java index fe65d8686..3f0802029 100644 --- a/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/TransactionTestIT.java @@ -29,6 +29,7 @@ import com.google.firebase.database.ChildEventListener; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseErrorCode; import com.google.firebase.database.DatabaseException; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.DatabaseReference.CompletionListener; @@ -98,7 +99,7 @@ public void testNewValueIsImmediatelyVisible() throws Exception { public Transaction.Result doTransaction(MutableData currentData) { try { currentData.setValue(42); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Should not fail"); } return Transaction.success(currentData); @@ -128,7 +129,7 @@ public void testEventRaisedForNewValue() throws InterruptedException { public Transaction.Result doTransaction(MutableData currentData) { try { currentData.setValue(42); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Should not throw"); } return Transaction.success(currentData); @@ -154,7 +155,7 @@ public void testNonAbortedTransactionSetsCommittedToTrue() throws InterruptedExc public Transaction.Result doTransaction(MutableData currentData) { try { currentData.setValue(42); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Should not fail"); } return Transaction.success(currentData); @@ -233,7 +234,7 @@ public Transaction.Result doTransaction(MutableData currentData) { if (currentData.getValue() == null) { try { currentData.setValue("temp value"); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Exception thrown: " + e.toString()); } return Transaction.success(currentData); @@ -285,7 +286,7 @@ public void onCancelled(DatabaseError error) { public Transaction.Result doTransaction(MutableData currentData) { try { currentData.setValue(42); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Should not fail"); } return Transaction.success(currentData); @@ -330,7 +331,7 @@ public Transaction.Result doTransaction(MutableData currentData) { currentData.child("a").setValue(42); currentData.child("b").setValue(87); return Transaction.success(currentData); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Should not throw"); return Transaction.abort(); } @@ -367,7 +368,7 @@ public Transaction.Result doTransaction(MutableData currentData) { currentData.child("a").setValue(5); currentData.child("b").setValue(6); return Transaction.success(currentData); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Should not throw"); return Transaction.abort(); } @@ -399,7 +400,7 @@ public Transaction.Result doTransaction(MutableData currentData) { try { currentData.setValue(42); return Transaction.success(currentData); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Should not throw"); return Transaction.abort(); } @@ -421,7 +422,7 @@ public Transaction.Result doTransaction(MutableData currentData) { try { currentData.setValue(84); return Transaction.success(currentData); - } catch (DatabaseException e) { + } catch (Exception e) { fail("Should not throw"); return Transaction.abort(); } @@ -737,7 +738,7 @@ public Transaction.Result doTransaction(MutableData currentData) { @Override public void onComplete(DatabaseError error, boolean committed, DataSnapshot currentData) { assertNotNull(error); - assertEquals(DatabaseError.MAX_RETRIES, error.getCode()); + assertEquals(DatabaseErrorCode.MAX_RETRIES, error.getCode()); assertFalse(committed); semaphore.release(1); } @@ -770,7 +771,7 @@ public Transaction.Result doTransaction(MutableData currentData) { @Override public void onComplete(DatabaseError error, boolean committed, DataSnapshot currentData) { - assertEquals(DatabaseError.OVERRIDDEN_BY_SET, error.getCode()); + assertEquals(DatabaseErrorCode.OVERRIDDEN_BY_SET, error.getCode()); assertFalse(committed); semaphore.release(1); } @@ -813,7 +814,7 @@ public Transaction.Result doTransaction(MutableData currentData) { @Override public void onComplete(DatabaseError error, boolean committed, DataSnapshot currentData) { - assertEquals(DatabaseError.OVERRIDDEN_BY_SET, error.getCode()); + assertEquals(DatabaseErrorCode.OVERRIDDEN_BY_SET, error.getCode()); assertFalse(committed); fooTransaction.release(); } @@ -1022,7 +1023,7 @@ public Transaction.Result doTransaction(MutableData currentData) { @Override public void onComplete(DatabaseError error, boolean committed, DataSnapshot currentData) { assertFalse(committed); - assertEquals(DatabaseError.DISCONNECTED, error.getCode()); + assertEquals(DatabaseErrorCode.DISCONNECTED, error.getCode()); semaphore.release(1); } }); @@ -1377,7 +1378,7 @@ public Transaction.Result doTransaction(MutableData currentData) { @Override public void onComplete(DatabaseError error, boolean committed, DataSnapshot currentData) { assertFalse(committed); - assertEquals(DatabaseError.USER_CODE_EXCEPTION, error.getCode()); + assertEquals(DatabaseErrorCode.USER_CODE_EXCEPTION, error.getCode()); done.release(1); } }); @@ -1394,7 +1395,7 @@ public Transaction.Result doTransaction(MutableData currentData) { @Override public void onComplete(DatabaseError error, boolean committed, DataSnapshot currentData) { assertFalse(committed); - assertEquals(DatabaseError.USER_CODE_EXCEPTION, error.getCode()); + assertEquals(DatabaseErrorCode.USER_CODE_EXCEPTION, error.getCode()); done.release(1); } }); diff --git a/src/test/java/com/google/firebase/database/util/EmulatorHelperTest.java b/src/test/java/com/google/firebase/database/util/EmulatorHelperTest.java index bffd8a5e3..5a5231237 100644 --- a/src/test/java/com/google/firebase/database/util/EmulatorHelperTest.java +++ b/src/test/java/com/google/firebase/database/util/EmulatorHelperTest.java @@ -54,7 +54,7 @@ public void testExtractingEmulatorUrlsThrowsException() { "localhost:9000" ); for (String invalidFormedUrl : invalidFormedUrls) { - thrown.expect(DatabaseException.class); + thrown.expect(IllegalArgumentException.class); EmulatorHelper.isEmulatorUrl(invalidFormedUrl); } } diff --git a/src/test/java/com/google/firebase/database/utilities/UtilitiesTest.java b/src/test/java/com/google/firebase/database/utilities/UtilitiesTest.java index 0dcc1cbd0..bb1d931d4 100644 --- a/src/test/java/com/google/firebase/database/utilities/UtilitiesTest.java +++ b/src/test/java/com/google/firebase/database/utilities/UtilitiesTest.java @@ -95,7 +95,7 @@ public void testParseInvalidUrl() { try { Utilities.parseUrl(url); fail("No error thrown for URL: '" + url + "'"); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // expected } } diff --git a/src/test/java/com/google/firebase/database/utilities/ValidationTest.java b/src/test/java/com/google/firebase/database/utilities/ValidationTest.java index 3293f5d6d..151a995a5 100644 --- a/src/test/java/com/google/firebase/database/utilities/ValidationTest.java +++ b/src/test/java/com/google/firebase/database/utilities/ValidationTest.java @@ -49,7 +49,7 @@ public void testInvalidPathString() { try { Validation.validatePathString(path); fail("No error thrown for invalid path: " + path); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // expected } } @@ -75,7 +75,7 @@ public void testInvalidRootPathString() { try { Validation.validateRootPathString(path); fail("No error thrown for invalid path: " + path); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // expected } } @@ -96,7 +96,7 @@ public void testInvalidNullableKey() { try { Validation.validateNullableKey(key); fail("No error thrown for invalid nullable key: " + key); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // expected } } @@ -111,7 +111,7 @@ public void testWritableKey() { try { Validation.validateWritableKey(key); fail("No error thrown for non-writable key: " + key); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // expected } } @@ -138,7 +138,7 @@ public void testNonWritablePath() { try { Validation.validateWritablePath(path); fail("No error thrown for non-writable path: " + pathString); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // expected } } @@ -175,7 +175,7 @@ public void testInvalidUpdate() { try { Validation.parseAndValidateUpdate(path, map); fail("No error thrown for invalid update: " + map); - } catch (DatabaseException expected) { + } catch (IllegalArgumentException expected) { // expected } } diff --git a/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java b/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java index 27864d992..5743fcbff 100644 --- a/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java +++ b/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java @@ -50,11 +50,20 @@ public class FirebaseInstanceIdTest { + <<<<<<< redacted-passwords + private static final FirebaseOptions APP_OPTIONS = new FirebaseOptions.Builder() + ======= + <<<<<<< v7 + ======= private static final FirebaseOptions APP_OPTIONS = FirebaseOptions.builder() + >>>>>>> master .setCredentials(new MockGoogleCredentials("test-token")) .setProjectId("test-project") .build(); + <<<<<<< redacted-passwords + ======= + >>>>>>> master private static final Map ERROR_MESSAGES = ImmutableMap.of( 404, "Instance ID \"test-iid\": Failed to find the instance ID.", 409, "Instance ID \"test-iid\": Already deleted.", @@ -74,6 +83,7 @@ public class FirebaseInstanceIdTest { private static final String TEST_URL = "https://console.firebase.google.com/v1/project/test-project/instanceId/test-iid"; + >>>>>>> master @After public void tearDown() { TestOnlyImplFirebaseTrampolines.clearInstancesForTest(); @@ -111,7 +121,19 @@ public void testInvokeAfterAppDelete() { @Test public void testInvalidInstanceId() { + <<<<<<< redacted-passwords FirebaseApp.initializeApp(APP_OPTIONS); + ======= + <<<<<<< v7 + FirebaseOptions options = FirebaseOptions.builder() + .setCredentials(new MockGoogleCredentials("test-token")) + .setProjectId("test-project") + .build(); + FirebaseApp.initializeApp(options); + ======= + FirebaseApp.initializeApp(APP_OPTIONS); + >>>>>>> master + >>>>>>> master FirebaseInstanceId instanceId = FirebaseInstanceId.getInstance(); TestResponseInterceptor interceptor = new TestResponseInterceptor(); @@ -139,7 +161,17 @@ public void testDeleteInstanceId() throws Exception { MockHttpTransport transport = new MockHttpTransport.Builder() .setLowLevelHttpResponse(response) .build(); + <<<<<<< redacted-passwords + FirebaseOptions options = new FirebaseOptions.Builder(APP_OPTIONS) + ======= + <<<<<<< v7 + FirebaseOptions options = FirebaseOptions.builder() + .setCredentials(new MockGoogleCredentials("test-token")) + .setProjectId("test-project") + ======= FirebaseOptions options = APP_OPTIONS.toBuilder() + >>>>>>> master + >>>>>>> master .setHttpTransport(transport) .build(); FirebaseApp app = FirebaseApp.initializeApp(options); @@ -183,15 +215,48 @@ public void testDeleteInstanceIdError() throws Exception { MockHttpTransport transport = new MockHttpTransport.Builder() .setLowLevelHttpResponse(response) .build(); + <<<<<<< v7 + FirebaseOptions options = FirebaseOptions.builder() + .setCredentials(new MockGoogleCredentials("test-token")) + .setProjectId("test-project") + ======= FirebaseOptions options = APP_OPTIONS.toBuilder() + >>>>>>> master .setHttpTransport(transport) .build(); FirebaseApp app = FirebaseApp.initializeApp(options); + <<<<<<< redacted-passwords + String url = "https://console.firebase.google.com/v1/project/test-project/instanceId/test-iid"; + for (Map.Entry entry : errors.entrySet()) { + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() + .setStatusCode(entry.getKey()) + .setContent("test error"); + MockHttpTransport transport = new MockHttpTransport.Builder() + .setLowLevelHttpResponse(response) + .build(); + FirebaseOptions options = new FirebaseOptions.Builder(APP_OPTIONS) + .setHttpTransport(transport) + .build(); + final FirebaseApp app = FirebaseApp.initializeApp(options); + + FirebaseInstanceId instanceId = FirebaseInstanceId.getInstance(); + TestResponseInterceptor interceptor = new TestResponseInterceptor(); + instanceId.setInterceptor(interceptor); + try { + instanceId.deleteInstanceIdAsync("test-iid").get(); + fail("No error thrown for HTTP error"); + } catch (ExecutionException e) { + assertTrue(e.getCause() instanceof FirebaseInstanceIdException); + assertEquals(entry.getValue(), e.getCause().getMessage()); + assertTrue(e.getCause().getCause() instanceof HttpResponseException); + } + ======= // Disable retries by passing a regular HttpRequestFactory. FirebaseInstanceId instanceId = new FirebaseInstanceId(app, transport.createRequestFactory()); TestResponseInterceptor interceptor = new TestResponseInterceptor(); instanceId.setInterceptor(interceptor); + >>>>>>> master try { for (int statusCode : ERROR_CODES.keySet()) { diff --git a/src/test/java/com/google/firebase/internal/AbstractPlatformErrorHandlerTest.java b/src/test/java/com/google/firebase/internal/AbstractPlatformErrorHandlerTest.java index ca4b063f0..fa03bf33b 100644 --- a/src/test/java/com/google/firebase/internal/AbstractPlatformErrorHandlerTest.java +++ b/src/test/java/com/google/firebase/internal/AbstractPlatformErrorHandlerTest.java @@ -251,7 +251,11 @@ private ErrorHandlingHttpClient createHttpClient( .build(); return new ErrorHandlingHttpClient<>( transport.createRequestFactory(), + <<<<<<< v7 + Utils.getDefaultJsonFactory(), + ======= ApiClientUtils.getDefaultJsonFactory(), + >>>>>>> master new TestPlatformErrorHandler()); } @@ -265,7 +269,11 @@ public LowLevelHttpRequest buildRequest(String method, String url) throws IOExce }; return new ErrorHandlingHttpClient<>( transport.createRequestFactory(), + <<<<<<< v7 + Utils.getDefaultJsonFactory(), + ======= ApiClientUtils.getDefaultJsonFactory(), + >>>>>>> master new TestPlatformErrorHandler()); } @@ -281,7 +289,11 @@ private static class TestPlatformErrorHandler extends AbstractPlatformErrorHandler { TestPlatformErrorHandler() { + <<<<<<< v7 + super(Utils.getDefaultJsonFactory()); + ======= super(ApiClientUtils.getDefaultJsonFactory()); + >>>>>>> master } @Override diff --git a/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java b/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java index c19f5f567..ad8dc6c98 100644 --- a/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java +++ b/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import com.google.api.client.http.GenericUrl; @@ -29,12 +30,16 @@ import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.common.collect.ImmutableList; +import com.google.firebase.ErrorCode; import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseException; import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; import com.google.firebase.internal.RetryInitializer.RetryHandlerDecorator; import java.io.IOException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; import org.junit.After; import org.junit.Test; @@ -128,4 +133,54 @@ public void disconnect() throws IOException { assertTrue(lowLevelResponse.isDisconnected()); } + + @Test + public void testTimeoutException() { + IOException cause = new SocketTimeoutException("test"); + FirebaseException exception = ApiClientUtils.newFirebaseException(cause); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, exception.getCode()); + assertEquals("Timed out while making an API call: test", exception.getMessage()); + assertNull(exception.getHttpResponse()); + assertSame(cause, exception.getCause()); + } + + @Test + public void testNestedTimeoutException() { + IOException cause = new IOException("test", new SocketTimeoutException("nested")); + FirebaseException exception = ApiClientUtils.newFirebaseException(cause); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, exception.getCode()); + assertEquals("Timed out while making an API call: test", exception.getMessage()); + assertNull(exception.getHttpResponse()); + assertSame(cause, exception.getCause()); + } + + @Test + public void testNetworkException() { + IOException cause = new UnknownHostException("test"); + FirebaseException exception = ApiClientUtils.newFirebaseException(cause); + assertEquals(ErrorCode.UNAVAILABLE, exception.getCode()); + assertEquals("Failed to establish a connection: test", exception.getMessage()); + assertNull(exception.getHttpResponse()); + assertSame(cause, exception.getCause()); + } + + @Test + public void testNestedNetworkException() { + IOException cause = new IOException("test", new UnknownHostException("nested")); + FirebaseException exception = ApiClientUtils.newFirebaseException(cause); + assertEquals(ErrorCode.UNAVAILABLE, exception.getCode()); + assertEquals("Failed to establish a connection: test", exception.getMessage()); + assertNull(exception.getHttpResponse()); + assertSame(cause, exception.getCause()); + } + + @Test + public void testUnknownTransportException() { + IOException cause = new IOException("test"); + FirebaseException exception = ApiClientUtils.newFirebaseException(cause); + assertEquals(ErrorCode.UNKNOWN, exception.getCode()); + assertEquals("Unknown error while making a remote service call: test", exception.getMessage()); + assertNull(exception.getHttpResponse()); + assertSame(cause, exception.getCause()); + } } diff --git a/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java b/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java index 8b3e75efd..4113cb5ec 100644 --- a/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java +++ b/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java @@ -1,5 +1,9 @@ /* + <<<<<<< hkj-error-handling + * Copyright 2019 Google Inc. + ======= * Copyright 2020 Google Inc. + >>>>>>> master * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +26,16 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; + <<<<<<< hkj-error-handling +import com.google.api.client.googleapis.util.Utils; +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.HttpStatusCodes; +import com.google.api.client.http.LowLevelHttpRequest; + ======= + <<<<<<< v7 +import com.google.api.client.googleapis.util.Utils; + ======= + >>>>>>> master import com.google.api.client.http.ByteArrayContent; import com.google.api.client.http.HttpContent; import com.google.api.client.http.HttpMethods; @@ -31,10 +45,20 @@ import com.google.api.client.http.HttpStatusCodes; import com.google.api.client.http.LowLevelHttpRequest; import com.google.api.client.json.JsonFactory; + >>>>>>> master import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.api.client.testing.util.MockSleeper; import com.google.api.client.util.GenericData; + <<<<<<< hkj-error-handling +import com.google.common.collect.ImmutableList; +import com.google.firebase.ErrorCode; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseException; +import com.google.firebase.FirebaseHttpResponse; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.auth.MockGoogleCredentials; + ======= import com.google.auth.oauth2.AccessToken; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -47,28 +71,48 @@ import com.google.firebase.auth.MockGoogleCredentials; import com.google.firebase.testing.TestResponseInterceptor; import java.io.ByteArrayOutputStream; + >>>>>>> master import java.io.IOException; import org.junit.Test; public class ErrorHandlingHttpClientTest { + <<<<<<< hkj-error-handling + ======= + <<<<<<< v7 + private static final JsonFactory DEFAULT_JSON_FACTORY = Utils.getDefaultJsonFactory(); + ======= private static final JsonFactory DEFAULT_JSON_FACTORY = ApiClientUtils.getDefaultJsonFactory(); + >>>>>>> master private static final HttpRequestInfo TEST_REQUEST = HttpRequestInfo.buildGetRequest( "https://firebase.google.com"); + >>>>>>> master @Test(expected = NullPointerException.class) public void testNullRequestFactory() { new ErrorHandlingHttpClient<>( null, + <<<<<<< hkj-error-handling + Utils.getDefaultJsonFactory(), + ======= DEFAULT_JSON_FACTORY, + >>>>>>> master new TestHttpErrorHandler()); } @Test(expected = NullPointerException.class) public void testNullJsonFactory() { new ErrorHandlingHttpClient<>( + <<<<<<< hkj-error-handling + Utils.getDefaultTransport().createRequestFactory(), + ======= + <<<<<<< v7 + Utils.getDefaultTransport().createRequestFactory(), + ======= ApiClientUtils.getDefaultTransport().createRequestFactory(), + >>>>>>> master + >>>>>>> master null, new TestHttpErrorHandler()); } @@ -76,8 +120,17 @@ public void testNullJsonFactory() { @Test(expected = NullPointerException.class) public void testNullErrorHandler() { new ErrorHandlingHttpClient<>( + <<<<<<< hkj-error-handling + Utils.getDefaultTransport().createRequestFactory(), + Utils.getDefaultJsonFactory(), + ======= + <<<<<<< v7 + Utils.getDefaultTransport().createRequestFactory(), + ======= ApiClientUtils.getDefaultTransport().createRequestFactory(), + >>>>>>> master DEFAULT_JSON_FACTORY, + >>>>>>> master null); } @@ -85,6 +138,20 @@ public void testNullErrorHandler() { public void testSuccessfulRequest() throws FirebaseException { MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() .setContent("{\"foo\": \"bar\"}"); + <<<<<<< hkj-error-handling + MockHttpTransport transport = new MockHttpTransport.Builder() + .setLowLevelHttpResponse(response) + .build(); + ErrorHandlingHttpClient client = new ErrorHandlingHttpClient<>( + transport.createRequestFactory(), + Utils.getDefaultJsonFactory(), + new TestHttpErrorHandler()); + + HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest("https://firebase.google.com"); + GenericData body = client.sendAndParse(requestInfo, GenericData.class); + assertEquals(1, body.size()); + assertEquals("bar", body.get("foo")); + ======= ErrorHandlingHttpClient client = createHttpClient(response); GenericData body = client.sendAndParse(TEST_REQUEST, GenericData.class); @@ -180,6 +247,7 @@ public void testUnsupportedMethod() throws FirebaseException, IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); last.getContent().writeTo(out); assertEquals("{\"key\":\"value\"}", out.toString()); + >>>>>>> master } @Test @@ -193,6 +261,18 @@ public LowLevelHttpRequest buildRequest(String method, String url) throws IOExce }; ErrorHandlingHttpClient client = new ErrorHandlingHttpClient<>( transport.createRequestFactory(), + <<<<<<< hkj-error-handling + Utils.getDefaultJsonFactory(), + new TestHttpErrorHandler()); + + HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest("https://firebase.google.com"); + try { + client.sendAndParse(requestInfo, GenericData.class); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getCode()); + assertEquals("Network error: Test", e.getMessage()); + ======= DEFAULT_JSON_FACTORY, new TestHttpErrorHandler()); @@ -202,6 +282,7 @@ public LowLevelHttpRequest buildRequest(String method, String url) throws IOExce } catch (FirebaseException e) { assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); assertEquals("IO error: Test", e.getMessage()); + >>>>>>> master assertNull(e.getHttpResponse()); assertSame(exception, e.getCause()); } @@ -213,6 +294,30 @@ public void testErrorResponse() { .setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR) .addHeader("Custom-Header", "value") .setContent("{}"); + <<<<<<< hkj-error-handling + MockHttpTransport transport = new MockHttpTransport.Builder() + .setLowLevelHttpResponse(response) + .build(); + ErrorHandlingHttpClient client = new ErrorHandlingHttpClient<>( + transport.createRequestFactory(), + Utils.getDefaultJsonFactory(), + new TestHttpErrorHandler()); + + HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest("https://firebase.google.com"); + try { + client.sendAndParse(requestInfo, GenericData.class); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.INTERNAL, e.getCode()); + assertEquals("Example error message: {}", e.getMessage()); + FirebaseHttpResponse httpResponse = e.getHttpResponse(); + assertNotNull(httpResponse); + assertEquals(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, httpResponse.getStatusCode()); + assertEquals("{}", httpResponse.getContent()); + assertEquals(1, httpResponse.getHeaders().size()); + assertEquals(ImmutableList.of("value"), httpResponse.getHeaders().get("custom-header")); + assertEquals("GET", httpResponse.getRequest().getMethod()); + ======= ErrorHandlingHttpClient client = createHttpClient(response); try { @@ -225,12 +330,37 @@ public void testErrorResponse() { IncomingHttpResponse httpResponse = e.getHttpResponse(); assertEquals(1, httpResponse.getHeaders().size()); assertEquals(ImmutableList.of("value"), httpResponse.getHeaders().get("custom-header")); + >>>>>>> master assertNotNull(e.getCause()); } } @Test public void testParseError() { + <<<<<<< hkj-error-handling + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() + .setContent("not json"); + MockHttpTransport transport = new MockHttpTransport.Builder() + .setLowLevelHttpResponse(response) + .build(); + ErrorHandlingHttpClient client = new ErrorHandlingHttpClient<>( + transport.createRequestFactory(), + Utils.getDefaultJsonFactory(), + new TestHttpErrorHandler()); + + HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest("https://firebase.google.com"); + try { + client.sendAndParse(requestInfo, GenericData.class); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getCode()); + assertEquals("Parse error", e.getMessage()); + FirebaseHttpResponse httpResponse = e.getHttpResponse(); + assertNotNull(httpResponse); + assertEquals(HttpStatusCodes.STATUS_CODE_OK, httpResponse.getStatusCode()); + assertEquals("not json", httpResponse.getContent()); + assertEquals("GET", httpResponse.getRequest().getMethod()); + ======= String payload = "not json"; MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() .setContent(payload); @@ -243,6 +373,7 @@ public void testParseError() { assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); assertEquals("Parse error", e.getMessage()); assertHttpResponse(e, HttpStatusCodes.STATUS_CODE_OK, payload); + >>>>>>> master assertNotNull(e.getCause()); } } @@ -254,19 +385,43 @@ public void testRetryOnError() { .setLowLevelHttpRequest(request) .build(); + <<<<<<< hkj-error-handling + ======= FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder() .setCredentials(new MockGoogleCredentials("token")) .setHttpTransport(transport) .build()); + >>>>>>> master RetryConfig retryConfig = RetryConfig.builder() .setMaxRetries(4) .setRetryStatusCodes(ImmutableList.of(503)) .setSleeper(new MockSleeper()) .build(); + <<<<<<< hkj-error-handling + HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest("https://firebase.google.com"); + + FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder() + .setCredentials(new MockGoogleCredentials("token")) + .setHttpTransport(transport) + .build()); + ErrorHandlingHttpClient client = new ErrorHandlingHttpClient<>( + app, new TestHttpErrorHandler(), retryConfig); + try { + client.sendAndParse(requestInfo, GenericData.class); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.INTERNAL, e.getCode()); + assertEquals("Example error message: null", e.getMessage()); + assertNotNull(e.getHttpResponse()); + ======= HttpRequestFactory requestFactory = ApiClientUtils.newAuthorizedRequestFactory( app, retryConfig); ErrorHandlingHttpClient client = new ErrorHandlingHttpClient<>( + <<<<<<< v7 + requestFactory, Utils.getDefaultJsonFactory(), new TestHttpErrorHandler()); + ======= requestFactory, ApiClientUtils.getDefaultJsonFactory(), new TestHttpErrorHandler()); + >>>>>>> master try { client.sendAndParse(TEST_REQUEST, GenericData.class); @@ -275,6 +430,7 @@ public void testRetryOnError() { assertEquals(ErrorCode.INTERNAL, e.getErrorCode()); assertEquals("Example error message: null", e.getMessage()); assertHttpResponse(e, HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE, null); + >>>>>>> master assertNotNull(e.getCause()); assertEquals(5, request.getCount()); @@ -283,6 +439,14 @@ public void testRetryOnError() { } } + <<<<<<< hkj-error-handling + private static class TestHttpErrorHandler implements HttpErrorHandler { + + @Override + public FirebaseException handleIOException(IOException e) { + return new FirebaseException( + ErrorCode.UNKNOWN, "Network error: " + e.getMessage(), null, e); + ======= @Test public void testRequestInitializationError() { CountingLowLevelHttpRequest request = CountingLowLevelHttpRequest.fromStatus(503); @@ -301,7 +465,11 @@ public AccessToken refreshAccessToken() throws IOException { .build()); HttpRequestFactory requestFactory = ApiClientUtils.newAuthorizedRequestFactory(app); ErrorHandlingHttpClient client = new ErrorHandlingHttpClient<>( + <<<<<<< v7 + requestFactory, Utils.getDefaultJsonFactory(), new TestHttpErrorHandler()); + ======= requestFactory, ApiClientUtils.getDefaultJsonFactory(), new TestHttpErrorHandler()); + >>>>>>> master try { client.sendAndParse(TEST_REQUEST, GenericData.class); @@ -340,10 +508,21 @@ private static class TestHttpErrorHandler implements HttpErrorHandler>>>>>> master } @Override public FirebaseException handleHttpResponseException( + <<<<<<< hkj-error-handling + HttpResponseException e, FirebaseHttpResponse response) { + return new FirebaseException( + ErrorCode.INTERNAL, "Example error message: " + e.getContent(), response, e); + } + + @Override + public FirebaseException handleParseException(IOException e, FirebaseHttpResponse response) { + return new FirebaseException(ErrorCode.UNKNOWN, "Parse error", response, e); + ======= HttpResponseException e, IncomingHttpResponse response) { return new FirebaseException( ErrorCode.INTERNAL, "Example error message: " + e.getContent(), e, response); @@ -352,6 +531,7 @@ public FirebaseException handleHttpResponseException( @Override public FirebaseException handleParseException(IOException e, IncomingHttpResponse response) { return new FirebaseException(ErrorCode.UNKNOWN, "Parse error", e, response); + >>>>>>> master } } } diff --git a/src/test/java/com/google/firebase/internal/PlatformErrorHandlerTest.java b/src/test/java/com/google/firebase/internal/PlatformErrorHandlerTest.java new file mode 100644 index 000000000..70227e96a --- /dev/null +++ b/src/test/java/com/google/firebase/internal/PlatformErrorHandlerTest.java @@ -0,0 +1,208 @@ +/* + * Copyright 2019 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 org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import com.google.api.client.googleapis.util.Utils; +import com.google.api.client.http.HttpStatusCodes; +import com.google.api.client.testing.http.MockHttpTransport; +import com.google.api.client.testing.http.MockLowLevelHttpResponse; +import com.google.api.client.util.GenericData; +import com.google.firebase.ErrorCode; +import com.google.firebase.FirebaseException; +import com.google.firebase.FirebaseHttpResponse; +import java.io.IOException; +import org.junit.Test; + +public class PlatformErrorHandlerTest { + + @Test + public void testPlatformError() { + String payload = "{\"error\": {\"status\": \"UNAVAILABLE\", \"message\": \"Test error\"}}"; + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() + .setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR) + .setContent(payload); + MockHttpTransport transport = new MockHttpTransport.Builder() + .setLowLevelHttpResponse(response) + .build(); + ErrorHandlingHttpClient client = new ErrorHandlingHttpClient<>( + transport.createRequestFactory(), + Utils.getDefaultJsonFactory(), + new TestPlatformErrorHandler()); + + HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest("https://firebase.google.com"); + try { + client.sendAndParse(requestInfo, GenericData.class); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNAVAILABLE, e.getCode()); + assertEquals("Test error", e.getMessage()); + FirebaseHttpResponse httpResponse = e.getHttpResponse(); + assertNotNull(httpResponse); + assertEquals(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, httpResponse.getStatusCode()); + assertEquals(payload, httpResponse.getContent()); + assertEquals("GET", httpResponse.getRequest().getMethod()); + assertNotNull(e.getCause()); + } + } + + @Test + public void testNonJsonError() { + String payload = "not json"; + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() + .setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR) + .setContent(payload); + MockHttpTransport transport = new MockHttpTransport.Builder() + .setLowLevelHttpResponse(response) + .build(); + ErrorHandlingHttpClient client = new ErrorHandlingHttpClient<>( + transport.createRequestFactory(), + Utils.getDefaultJsonFactory(), + new TestPlatformErrorHandler()); + + HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest("https://firebase.google.com"); + try { + client.sendAndParse(requestInfo, GenericData.class); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.INTERNAL, e.getCode()); + assertEquals("Unexpected HTTP response with status: 500\nnot json", e.getMessage()); + FirebaseHttpResponse httpResponse = e.getHttpResponse(); + assertNotNull(httpResponse); + assertEquals(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, httpResponse.getStatusCode()); + assertEquals(payload, httpResponse.getContent()); + assertEquals("GET", httpResponse.getRequest().getMethod()); + assertNotNull(e.getCause()); + } + } + + @Test + public void testPlatformErrorWithoutCode() { + String payload = "{\"error\": {\"message\": \"Test error\"}}"; + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() + .setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR) + .setContent(payload); + MockHttpTransport transport = new MockHttpTransport.Builder() + .setLowLevelHttpResponse(response) + .build(); + ErrorHandlingHttpClient client = new ErrorHandlingHttpClient<>( + transport.createRequestFactory(), + Utils.getDefaultJsonFactory(), + new TestPlatformErrorHandler()); + + HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest("https://firebase.google.com"); + try { + client.sendAndParse(requestInfo, GenericData.class); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.INTERNAL, e.getCode()); + assertEquals("Test error", e.getMessage()); + FirebaseHttpResponse httpResponse = e.getHttpResponse(); + assertNotNull(httpResponse); + assertEquals(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, httpResponse.getStatusCode()); + assertEquals(payload, httpResponse.getContent()); + assertEquals("GET", httpResponse.getRequest().getMethod()); + assertNotNull(e.getCause()); + } + } + + @Test + public void testPlatformErrorWithoutMessage() { + String payload = "{\"error\": {\"status\": \"INVALID_ARGUMENT\"}}"; + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() + .setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR) + .setContent(payload); + MockHttpTransport transport = new MockHttpTransport.Builder() + .setLowLevelHttpResponse(response) + .build(); + ErrorHandlingHttpClient client = new ErrorHandlingHttpClient<>( + transport.createRequestFactory(), + Utils.getDefaultJsonFactory(), + new TestPlatformErrorHandler()); + + HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest("https://firebase.google.com"); + try { + client.sendAndParse(requestInfo, GenericData.class); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getCode()); + assertEquals("Unexpected HTTP response with status: 500\n" + payload, e.getMessage()); + FirebaseHttpResponse httpResponse = e.getHttpResponse(); + assertNotNull(httpResponse); + assertEquals(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, httpResponse.getStatusCode()); + assertEquals(payload, httpResponse.getContent()); + assertEquals("GET", httpResponse.getRequest().getMethod()); + assertNotNull(e.getCause()); + } + } + + @Test + public void testPlatformErrorWithoutCodeOrMessage() { + String payload = "{}"; + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse() + .setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR) + .setContent(payload); + MockHttpTransport transport = new MockHttpTransport.Builder() + .setLowLevelHttpResponse(response) + .build(); + ErrorHandlingHttpClient client = new ErrorHandlingHttpClient<>( + transport.createRequestFactory(), + Utils.getDefaultJsonFactory(), + new TestPlatformErrorHandler()); + + HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest("https://firebase.google.com"); + try { + client.sendAndParse(requestInfo, GenericData.class); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.INTERNAL, e.getCode()); + assertEquals("Unexpected HTTP response with status: 500\n" + payload, e.getMessage()); + FirebaseHttpResponse httpResponse = e.getHttpResponse(); + assertNotNull(httpResponse); + assertEquals(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, httpResponse.getStatusCode()); + assertEquals(payload, httpResponse.getContent()); + assertEquals("GET", httpResponse.getRequest().getMethod()); + assertNotNull(e.getCause()); + } + } + + private static class TestPlatformErrorHandler extends PlatformErrorHandler { + + TestPlatformErrorHandler() { + super(Utils.getDefaultJsonFactory()); + } + + @Override + protected FirebaseException createException(ErrorParams params) { + return new FirebaseException(params.getErrorCode(), params.getMessage(), + params.getResponse(), params.getException()); + } + + @Override + public FirebaseException handleIOException(IOException e) { + return new FirebaseException(ErrorCode.UNKNOWN, "IO error", null, e); + } + + @Override + public FirebaseException handleParseException(IOException e, FirebaseHttpResponse response) { + return new FirebaseException(ErrorCode.UNKNOWN, "Parse error", response, e); + } + } +} diff --git a/src/test/java/com/google/firebase/internal/TestApiClientUtils.java b/src/test/java/com/google/firebase/internal/TestApiClientUtils.java index 510e08cd5..c9081dc2b 100644 --- a/src/test/java/com/google/firebase/internal/TestApiClientUtils.java +++ b/src/test/java/com/google/firebase/internal/TestApiClientUtils.java @@ -18,14 +18,25 @@ import static com.google.firebase.internal.ApiClientUtils.DEFAULT_RETRY_CONFIG; import static org.junit.Assert.assertEquals; + <<<<<<< redacted-passwords +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import com.google.api.client.http.GenericUrl; + ======= +import static org.junit.Assert.assertTrue; + + >>>>>>> master 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; + <<<<<<< redacted-passwords +import java.io.IOException; + ======= + >>>>>>> master public class TestApiClientUtils { @@ -36,6 +47,11 @@ public class TestApiClientUtils { .setSleeper(new MockSleeper()) .build(); + <<<<<<< redacted-passwords + private static final GenericUrl TEST_URL = new GenericUrl("https://firebase.google.com"); + + ======= + >>>>>>> master /** * Creates a new {@code HttpRequestFactory} which provides authorization (OAuth2), timeouts and * automatic retries. Bypasses exponential backoff between consecutive retries for faster @@ -60,12 +76,29 @@ public static HttpRequestFactory retryDisabledRequestFactory(FirebaseApp app) { } /** + <<<<<<< redacted-passwords + * 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); + } + + ======= * Checks whether the given HttpRequest has been configured for authorization and * automatic retries. * * @param request The HttpRequest to check. */ public static void assertAuthAndRetrySupport(HttpRequest request) { + >>>>>>> master // Verify authorization assertTrue(request.getHeaders().getAuthorization().startsWith("Bearer ")); @@ -76,7 +109,11 @@ public static void assertAuthAndRetrySupport(HttpRequest request) { .getRetryConfig(); assertEquals(DEFAULT_RETRY_CONFIG.getMaxRetries(), retryConfig.getMaxRetries()); assertEquals(DEFAULT_RETRY_CONFIG.getMaxIntervalMillis(), retryConfig.getMaxIntervalMillis()); + <<<<<<< redacted-passwords + assertFalse(retryConfig.isRetryOnIOExceptions()); + ======= assertEquals(DEFAULT_RETRY_CONFIG.isRetryOnIOExceptions(), retryConfig.isRetryOnIOExceptions()); + >>>>>>> master assertEquals(DEFAULT_RETRY_CONFIG.getRetryStatusCodes(), retryConfig.getRetryStatusCodes()); } } diff --git a/src/test/java/com/google/firebase/messaging/BatchResponseTest.java b/src/test/java/com/google/firebase/messaging/BatchResponseTest.java index 441822220..e64473041 100644 --- a/src/test/java/com/google/firebase/messaging/BatchResponseTest.java +++ b/src/test/java/com/google/firebase/messaging/BatchResponseTest.java @@ -44,8 +44,13 @@ public void testSomeResponse() { ImmutableList responses = ImmutableList.of( SendResponse.fromMessageId("message1"), SendResponse.fromMessageId("message2"), + <<<<<<< hkj-error-handling + SendResponse.fromException(new FirebaseMessagingException(ErrorCode.UNKNOWN, + "error-message", null)) + ======= SendResponse.fromException( new FirebaseMessagingException(ErrorCode.INTERNAL, "error-message")) + >>>>>>> master ); BatchResponse batchResponse = new BatchResponseImpl(responses); diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java index 17848cc65..1525993d3 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java @@ -65,6 +65,10 @@ public class FirebaseMessagingClientImplTest { "https://fcm.googleapis.com/v1/projects/test-project/messages:send"; private static final List HTTP_ERRORS = ImmutableList.of(401, 404, 500); + private static final Map HTTP_2_ERROR = ImmutableMap.of( + 401, ErrorCode.UNAUTHENTICATED, + 404, ErrorCode.NOT_FOUND, + 500, ErrorCode.INTERNAL); private static final Map HTTP_2_ERROR = ImmutableMap.of( 401, ErrorCode.UNAUTHENTICATED, @@ -152,7 +156,11 @@ public void testSendTransportError() { client.send(EMPTY_MESSAGE, DRY_RUN_DISABLED); fail("No error thrown for HTTP error"); } catch (FirebaseMessagingException error) { + <<<<<<< hkj-error-handling + assertEquals(ErrorCode.UNKNOWN, error.getCode()); + ======= assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); + >>>>>>> master assertEquals("Unknown error while making a remote service call: transport error", error.getMessage()); assertTrue(error.getCause() instanceof IOException); @@ -172,11 +180,16 @@ public void testSendSuccessResponseWithUnexpectedPayload() { client.send(entry.getKey(), DRY_RUN_DISABLED); fail("No error thrown for malformed response"); } catch (FirebaseMessagingException error) { + <<<<<<< hkj-error-handling + assertEquals(ErrorCode.UNKNOWN, error.getCode()); + assertTrue(error.getMessage().startsWith("Error parsing response from FCM: ")); + ======= assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); assertTrue(error.getMessage().startsWith("Error while parsing HTTP response: ")); assertNotNull(error.getCause()); assertNotNull(error.getHttpResponse()); assertNull(error.getMessagingErrorCode()); + >>>>>>> master } checkRequestHeader(interceptor.getLastRequest()); } @@ -208,7 +221,15 @@ public void testSendErrorWithMalformedResponse() { fail("No error thrown for HTTP error"); } catch (FirebaseMessagingException error) { checkExceptionFromHttpResponse(error, HTTP_2_ERROR.get(code), null, + <<<<<<< hkj-error-handling + "Unexpected HTTP response with status: " + code + "\nnot json"); + ======= + <<<<<<< v7 + "Unexpected HTTP response with status: " + code + "\nnot json"); + ======= "Unexpected HTTP response with status: " + code + "\n[not json]"); + >>>>>>> master + >>>>>>> master } checkRequestHeader(interceptor.getLastRequest()); } @@ -260,6 +281,8 @@ public void testSendErrorWithFcmError() { } catch (FirebaseMessagingException error) { checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, MessagingErrorCode.UNREGISTERED); + <<<<<<< hkj-error-handling + ======= } checkRequestHeader(interceptor.getLastRequest()); } @@ -320,6 +343,25 @@ public void testSendErrorWithDetailsAndNoCode() { } } + @Test + public void testSendErrorWithThirdPartyError() { + for (int code : HTTP_ERRORS) { + response.setStatusCode(code).setContent( + "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\", " + + "\"details\":[{\"@type\": \"type.googleapis.com/google.firebase.fcm" + + ".v1.FcmError\", \"errorCode\": \"THIRD_PARTY_AUTH_ERROR\"}]}}"); + + try { + client.send(EMPTY_MESSAGE, DRY_RUN_DISABLED); + fail("No error thrown for HTTP error"); + } catch (FirebaseMessagingException error) { + checkExceptionFromHttpResponse(error, "third-party-auth-error"); + >>>>>>> master + } + checkRequestHeader(interceptor.getLastRequest()); + } + } + @Test public void testSendAll() throws Exception { final TestResponseInterceptor interceptor = new TestResponseInterceptor(); @@ -406,9 +448,15 @@ public void testSendAllTransportError() { client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); fail("No error thrown for HTTP error"); } catch (FirebaseMessagingException error) { + <<<<<<< hkj-error-handling + assertEquals(ErrorCode.UNKNOWN, error.getCode()); + assertEquals("Unknown error while making a remote service call: transport error", + error.getMessage()); + ======= assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); assertEquals( "Unknown error while making a remote service call: transport error", error.getMessage()); + >>>>>>> master assertTrue(error.getCause() instanceof IOException); assertNull(error.getHttpResponse()); assertNull(error.getMessagingErrorCode()); @@ -626,10 +674,14 @@ private void assertBatchResponse( FirebaseMessagingException exception = sendResponse.getException(); assertNotNull(exception); + <<<<<<< hkj-error-handling + assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getCode()); + ======= assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode()); assertNull(exception.getCause()); assertNull(exception.getHttpResponse()); assertEquals(MessagingErrorCode.INVALID_ARGUMENT, exception.getMessagingErrorCode()); + >>>>>>> master } checkBatchRequestHeader(interceptor.getLastRequest()); @@ -681,8 +733,13 @@ private void checkExceptionFromHttpResponse( ErrorCode expectedCode, MessagingErrorCode expectedMessagingCode, String expectedMessage) { + <<<<<<< hkj-error-handling + assertEquals(expectedCode, error.getCode()); + ======= assertEquals(expectedCode, error.getErrorCode()); + >>>>>>> master assertEquals(expectedMessage, error.getMessage()); + assertEquals(expectedMessagingCode, error.getMessagingErrorCode()); assertTrue(error.getCause() instanceof HttpResponseException); assertEquals(expectedMessagingCode, error.getMessagingErrorCode()); diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java index 9ae7bba24..a7d04dafd 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java @@ -143,7 +143,11 @@ public void testSendAll() throws Exception { assertNull(responses.get(2).getMessageId()); FirebaseMessagingException exception = responses.get(2).getException(); assertNotNull(exception); + <<<<<<< hkj-error-handling + assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getCode()); + ======= assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode()); + >>>>>>> master } @Test diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java index 8e8e79e07..726ad8706 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java @@ -51,7 +51,11 @@ public class FirebaseMessagingTest { .addToken("test-fcm-token2") .build(); private static final FirebaseMessagingException TEST_EXCEPTION = + <<<<<<< hkj-error-handling + new FirebaseMessagingException(ErrorCode.UNKNOWN, "Test error message", new Exception()); + ======= new FirebaseMessagingException(ErrorCode.INTERNAL, "Test error message"); + >>>>>>> master private static final ImmutableList.Builder TOO_MANY_IDS = ImmutableList.builder(); diff --git a/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java b/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java index e7222ccb5..da13b7111 100644 --- a/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java +++ b/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java @@ -32,7 +32,10 @@ import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.common.collect.ImmutableList; + <<<<<<< hkj-error-handling + ======= import com.google.common.collect.ImmutableMap; + >>>>>>> master import com.google.firebase.ErrorCode; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; @@ -187,6 +190,10 @@ public void testSubscribeTransportError() { client.subscribeToTopic("test-topic", ImmutableList.of("id1", "id2")); fail("No error thrown for HTTP error"); } catch (FirebaseMessagingException error) { + <<<<<<< hkj-error-handling + assertEquals(ErrorCode.UNKNOWN, error.getCode()); + assertEquals("Error while calling IID backend service", error.getMessage()); + ======= assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); assertEquals( "Unknown error while making a remote service call: transport error", error.getMessage()); @@ -207,6 +214,7 @@ public void testSubscribeParseError() { } catch (FirebaseMessagingException error) { assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); assertTrue(error.getMessage().startsWith("Error while parsing HTTP response: ")); + >>>>>>> master assertTrue(error.getCause() instanceof IOException); } } @@ -327,6 +335,10 @@ public void testUnsubscribeTransportError() { client.unsubscribeFromTopic("test-topic", ImmutableList.of("id1", "id2")); fail("No error thrown for HTTP error"); } catch (FirebaseMessagingException error) { + <<<<<<< hkj-error-handling + assertEquals(ErrorCode.UNKNOWN, error.getCode()); + assertEquals("Error while calling IID backend service", error.getMessage()); + ======= assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); assertEquals( "Unknown error while making a remote service call: transport error", error.getMessage()); @@ -347,6 +359,7 @@ public void testUnsubscribeParseError() { } catch (FirebaseMessagingException error) { assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); assertTrue(error.getMessage().startsWith("Error while parsing HTTP response: ")); + >>>>>>> master assertTrue(error.getCause() instanceof IOException); } } @@ -449,9 +462,15 @@ private void checkTopicManagementRequestHeader( assertEquals(expectedUrl, request.getUrl().toString()); } + <<<<<<< hkj-error-handling + private void checkExceptionFromHttpResponse(FirebaseMessagingException error, + int expectedCode, String expectedMessage) { + assertEquals(ErrorCode.UNKNOWN, error.getCode()); + ======= private void checkExceptionFromHttpResponse( FirebaseMessagingException error, int statusCode, String expectedMessage) { assertEquals(HTTP_2_ERROR.get(statusCode), error.getErrorCode()); + >>>>>>> master assertEquals(expectedMessage, error.getMessage()); assertTrue(error.getCause() instanceof HttpResponseException); assertNull(error.getMessagingErrorCode()); @@ -468,6 +487,10 @@ private void checkExceptionFromHttpResponse( private InstanceIdClient initClientWithFaultyTransport() { return new InstanceIdClientImpl( TestUtils.createFaultyHttpTransport().createRequestFactory(), + <<<<<<< v7 + Utils.getDefaultJsonFactory()); + ======= ApiClientUtils.getDefaultJsonFactory()); + >>>>>>> master } } diff --git a/src/test/java/com/google/firebase/messaging/MessageTest.java b/src/test/java/com/google/firebase/messaging/MessageTest.java index 992644b35..c97429ff1 100644 --- a/src/test/java/com/google/firebase/messaging/MessageTest.java +++ b/src/test/java/com/google/firebase/messaging/MessageTest.java @@ -30,8 +30,14 @@ import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; + <<<<<<< redacted-passwords +import java.text.SimpleDateFormat; +import java.util.Date; + ======= + >>>>>>> master import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.TimeUnit; import org.junit.Test; @@ -187,7 +193,11 @@ public void testAndroidMessageWithNotification() throws IOException { .put("body_loc_args", ImmutableList.of("body-arg1", "body-arg2", "body-arg3")) .put("channel_id", "channel-id") // There is a problem with the JsonParser assignment to BigDecimal takes priority over + <<<<<<< redacted-passwords + // all other number types and so this integer value is interpreted as a BigDecimal + ======= // all other number types and so this integer value is interpreted as a BigDecimal + >>>>>>> master // rather than an Integer. .put("notification_count", BigDecimal.valueOf(4L)) .build(); @@ -228,7 +238,11 @@ public void testAndroidMessageWithDirectBootOk() throws IOException { public void testAndroidNotificationWithNegativeCount() throws IllegalArgumentException { AndroidNotification.builder().setNotificationCount(-1).build(); } + <<<<<<< redacted-passwords + + ======= + >>>>>>> master @Test public void testAndroidMessageWithoutLocalization() throws IOException { Message message = Message.builder() @@ -848,7 +862,11 @@ public void testImageInApnsNotification() throws IOException { } @Test + <<<<<<< redacted-passwords + public void testInvalidColorInAndroidNotificationLightSettings() throws IOException { + ======= public void testInvalidColorInAndroidNotificationLightSettings() { + >>>>>>> master try { LightSettings.Builder lightSettingsBuilder = LightSettings.builder() .setColorFromString("#01020K") @@ -866,10 +884,14 @@ public void testInvalidColorInAndroidNotificationLightSettings() { public void testExtendedAndroidNotificationParameters() throws IOException { long[] vibrateTimings = {1000L, 1001L}; Message message = Message.builder() + <<<<<<< redacted-passwords + .setNotification(new Notification("title", "body")) + ======= .setNotification(Notification.builder() .setTitle("title") .setBody("body") .build()) + >>>>>>> master .setAndroidConfig(AndroidConfig.builder() .setNotification(AndroidNotification.builder() .setTitle("android-title") diff --git a/src/test/java/com/google/firebase/messaging/MulticastMessageTest.java b/src/test/java/com/google/firebase/messaging/MulticastMessageTest.java index f26d0816f..e45dcc955 100644 --- a/src/test/java/com/google/firebase/messaging/MulticastMessageTest.java +++ b/src/test/java/com/google/firebase/messaging/MulticastMessageTest.java @@ -38,10 +38,14 @@ public class MulticastMessageTest { private static final WebpushConfig WEBPUSH = WebpushConfig.builder() .putData("key", "value") .build(); + <<<<<<< redacted-passwords + private static final Notification NOTIFICATION = new Notification("title", "body"); + ======= private static final Notification NOTIFICATION = Notification.builder() .setTitle("title") .setBody("body") .build(); + >>>>>>> master private static final FcmOptions FCM_OPTIONS = FcmOptions.withAnalyticsLabel("analytics_label"); @Test diff --git a/src/test/java/com/google/firebase/messaging/SendResponseTest.java b/src/test/java/com/google/firebase/messaging/SendResponseTest.java index d90f1e6b6..64305e5b1 100644 --- a/src/test/java/com/google/firebase/messaging/SendResponseTest.java +++ b/src/test/java/com/google/firebase/messaging/SendResponseTest.java @@ -38,8 +38,13 @@ public void testSuccessfulResponse() { @Test public void testFailureResponse() { + <<<<<<< hkj-error-handling + FirebaseMessagingException exception = new FirebaseMessagingException(ErrorCode.UNKNOWN, + "error-message", null); + ======= FirebaseMessagingException exception = new FirebaseMessagingException( ErrorCode.INTERNAL, "error-message"); + >>>>>>> master SendResponse response = SendResponse.fromException(exception); assertNull(response.getMessageId()); diff --git a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java index 853074e65..1d6c08ad6 100644 --- a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java +++ b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java @@ -43,8 +43,14 @@ import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; + <<<<<<< redacted-passwords + ======= + <<<<<<< v7 + ======= import com.google.firebase.internal.ApiClientUtils; + >>>>>>> master import com.google.firebase.internal.SdkUtils; + >>>>>>> master import com.google.firebase.internal.TestApiClientUtils; import com.google.firebase.testing.MultiRequestMockHttpTransport; import com.google.firebase.testing.TestUtils; @@ -1033,6 +1039,12 @@ public void deleteShaCertificateAsync() throws Exception { } @Test + <<<<<<< redacted-passwords + public void testAuthAndRetriesSupport() { + FirebaseOptions options = new FirebaseOptions.Builder() + .setCredentials(new MockGoogleCredentials("test-token")) + .setProjectId(PROJECT_ID) + ======= public void testAuthAndRetriesSupport() throws Exception { List mockResponses = ImmutableList.of( new MockLowLevelHttpResponse().setContent("{}")); @@ -1041,17 +1053,23 @@ public void testAuthAndRetriesSupport() throws Exception { .setCredentials(new MockGoogleCredentials("test-token")) .setProjectId(PROJECT_ID) .setHttpTransport(transport) + >>>>>>> master .build(); FirebaseApp app = FirebaseApp.initializeApp(options); FirebaseProjectManagementServiceImpl serviceImpl = new FirebaseProjectManagementServiceImpl(app); + <<<<<<< redacted-passwords + + TestApiClientUtils.assertAuthAndRetrySupport(serviceImpl.getRequestFactory()); + ======= serviceImpl.setInterceptor(interceptor); serviceImpl.deleteShaCertificate(SHA1_RESOURCE_NAME); assertEquals(1, interceptor.getNumberOfResponses()); TestApiClientUtils.assertAuthAndRetrySupport(interceptor.getResponse(0).getRequest()); + >>>>>>> master } @Test @@ -1060,7 +1078,11 @@ public void testHttpRetries() throws Exception { firstRpcResponse.setStatusCode(503).setContent("{}"), new MockLowLevelHttpResponse().setContent("{}")); MockHttpTransport transport = new MultiRequestMockHttpTransport(mockResponses); + <<<<<<< redacted-passwords + FirebaseOptions options = new FirebaseOptions.Builder() + ======= FirebaseOptions options = FirebaseOptions.builder() + >>>>>>> master .setCredentials(new MockGoogleCredentials("test-token")) .setProjectId(PROJECT_ID) .setHttpTransport(transport)