From b025377c4e9f58ca60f806d9f972bfc2f50698cf Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Sun, 5 Mar 2023 18:26:01 -0800 Subject: [PATCH 1/3] checkpoint --- FirebaseAuth.podspec | 1 - FirebaseAuth/Sources/Auth/FIRAuth.m | 6 +- FirebaseAuth/Sources/Backend/FIRAuthBackend.h | 25 - FirebaseAuth/Sources/Backend/FIRAuthBackend.m | 533 -------- .../AuthProvider/PhoneAuthProvider.swift | 2 +- .../Sources/Swift/Backend/AuthBackend.swift | 65 +- .../Swift/Utilities/AuthErrorUtils.swift | 26 +- .../SystemService/FIRSecureTokenService.m | 1 - FirebaseAuth/Sources/User/FIRUser.m | 1 - .../AuthBackendRPCImplentationTests.swift | 638 +++++++++ .../FIRAuthBackendRPCImplementationTests.m | 1176 ----------------- FirebaseAuth/Tests/Unit/FIRAuthTests.m | 5 +- .../Tests/Unit/FIRFakeBackendRPCIssuer.h | 105 -- .../Tests/Unit/FIRFakeBackendRPCIssuer.m | 101 -- .../Tests/Unit/FIRPhoneAuthProviderTests.m | 6 +- FirebaseAuth/Tests/Unit/FIRUserTests.m | 4 +- .../Tests/Unit/FakeBackendRPCIssuer.swift | 3 +- .../Unit/OCMStubRecorder+FIRAuthUnitTests.h | 110 -- .../Unit/OCMStubRecorder+FIRAuthUnitTests.m | 102 -- FirebaseAuth/Tests/Unit/RPCBaseTests.swift | 2 + Package.swift | 1 - 21 files changed, 709 insertions(+), 2204 deletions(-) create mode 100644 FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift delete mode 100644 FirebaseAuth/Tests/Unit/FIRAuthBackendRPCImplementationTests.m delete mode 100644 FirebaseAuth/Tests/Unit/FIRFakeBackendRPCIssuer.h delete mode 100644 FirebaseAuth/Tests/Unit/FIRFakeBackendRPCIssuer.m delete mode 100644 FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.h delete mode 100644 FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.m diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 1eff7efdc59..64fc73f2fbb 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -114,7 +114,6 @@ supports email and password accounts, as well as several 3rd party authenticatio ] # app_host is needed for tests with keychain unit_tests.requires_app_host = true - unit_tests.dependency 'OCMock' unit_tests.dependency 'HeartbeatLoggingTestUtils' # This pre-processor directive is used to selectively disable keychain diff --git a/FirebaseAuth/Sources/Auth/FIRAuth.m b/FirebaseAuth/Sources/Auth/FIRAuth.m index 2a3327a725c..76bbbad0047 100644 --- a/FirebaseAuth/Sources/Auth/FIRAuth.m +++ b/FirebaseAuth/Sources/Auth/FIRAuth.m @@ -32,7 +32,6 @@ #import "FirebaseAuth/Sources/Auth/FIRAuthDataResult_Internal.h" #import "FirebaseAuth/Sources/Auth/FIRAuthDispatcher.h" #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h" -#import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h" #import "FirebaseAuth/Sources/SystemService/FIRAuthStoredUserManager.h" #import "FirebaseAuth/Sources/User/FIRUser_Internal.h" #import "FirebaseAuth/Sources/Utilities/FIRAuthExceptionUtils.h" @@ -1620,6 +1619,9 @@ - (void)scene:(UIScene *)scene @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked asynchronously on the global auth work queue in the future. */ +typedef void (^FIRVerifyPhoneNumberResponseCallback)( + FIRVerifyPhoneNumberResponse *_Nullable response, NSError *_Nullable error); + - (void)signInWithPhoneCredential:(FIRPhoneAuthCredential *)credential operation:(FIRAuthOperationType)operation callback:(FIRVerifyPhoneNumberResponseCallback)callback { @@ -1707,6 +1709,8 @@ - (void)internalSignInAndRetrieveDataWithCustomToken:(NSString *)token @param password The password used to create the new Firebase user. @param completion Optionally; a block which is invoked when the request finishes. */ +typedef void (^FIRSignupNewUserCallback)(FIRSignUpNewUserResponse *_Nullable response, + NSError *_Nullable error); - (void)internalCreateUserWithEmail:(NSString *)email password:(NSString *)password completion:(nullable FIRSignupNewUserCallback)completion { diff --git a/FirebaseAuth/Sources/Backend/FIRAuthBackend.h b/FirebaseAuth/Sources/Backend/FIRAuthBackend.h index ac5d39d1ef2..9d9dcf45dfc 100644 --- a/FirebaseAuth/Sources/Backend/FIRAuthBackend.h +++ b/FirebaseAuth/Sources/Backend/FIRAuthBackend.h @@ -113,29 +113,4 @@ typedef void (^FIRVerifyPhoneNumberResponseCallback)( @end -/** @protocol FIRAuthBackendImplementation - @brief Used to make FIRAuthBackend provide a layer of indirection to an actual RPC-based backend - or a mock backend. - */ -@protocol FIRAuthBackendImplementation - -/** @fn postWithRequest:response:callback: - @brief Calls the RPC using HTTP POST. - @remarks Possible error responses: - @see FIRAuthInternalErrorCodeRPCRequestEncodingError - @see FIRAuthInternalErrorCodeJSONSerializationError - @see FIRAuthInternalErrorCodeNetworkError - @see FIRAuthInternalErrorCodeUnexpectedErrorResponse - @see FIRAuthInternalErrorCodeUnexpectedResponse - @see FIRAuthInternalErrorCodeRPCResponseDecodingError - @param request The request. - @param response The empty response to be filled. - @param callback The callback for both success and failure. -*/ -- (void)postWithRequest:(id)request - response:(id)response - callback:(void (^)(NSError *_Nullable error))callback; - -@end - NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Sources/Backend/FIRAuthBackend.m b/FirebaseAuth/Sources/Backend/FIRAuthBackend.m index dbc6a2acb58..79fb856d442 100644 --- a/FirebaseAuth/Sources/Backend/FIRAuthBackend.m +++ b/FirebaseAuth/Sources/Backend/FIRAuthBackend.m @@ -523,542 +523,9 @@ + (NSMutableURLRequest *)requestWithURL:(NSURL *)URL @interface FIRAuthBackendRPCIssuerImplementation : NSObject @end -@implementation FIRAuthBackendRPCIssuerImplementation { - /** @var The session fetcher service. - */ - GTMSessionFetcherService *_fetcherService; -} - -- (instancetype)init { - self = [super init]; - if (self) { - _fetcherService = [[GTMSessionFetcherService alloc] init]; - _fetcherService.userAgent = [FIRAuthBackend authUserAgent]; - _fetcherService.callbackQueue = FIRAuthGlobalWorkQueue(); - - // Avoid reusing the session to prevent - // https://github.com/firebase/firebase-ios-sdk/issues/1261 - _fetcherService.reuseSession = NO; - } - return self; -} - -- (void)asyncPostToURLWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration - URL:(NSURL *)URL - body:(nullable NSData *)body - contentType:(NSString *)contentType - completionHandler: - (void (^)(NSData *_Nullable, NSError *_Nullable))handler { - NSMutableURLRequest *request = [FIRAuthBackend requestWithURL:URL - contentType:contentType - requestConfiguration:requestConfiguration]; - GTMSessionFetcher *fetcher = [_fetcherService fetcherWithRequest:request]; - NSString *emulatorHostAndPort = requestConfiguration.emulatorHostAndPort; - if (emulatorHostAndPort) { - fetcher.allowLocalhostRequest = YES; - fetcher.allowedInsecureSchemes = @[ @"http" ]; - } - fetcher.bodyData = body; - [fetcher beginFetchWithCompletionHandler:handler]; -} - -@end @implementation FIRAuthBackendRPCImplementation -- (instancetype)init { - self = [super init]; - if (self) { - _RPCIssuer = [[FIRAuthBackendRPCIssuerImplementation alloc] init]; - } - return self; -} - -#pragma mark - Generic RPC handling methods - -/** @fn postWithRequest:response:callback: - @brief Calls the RPC using HTTP POST. - @remarks Possible error responses: - @see FIRAuthInternalErrorCodeRPCRequestEncodingError - @see FIRAuthInternalErrorCodeJSONSerializationError - @see FIRAuthInternalErrorCodeNetworkError - @see FIRAuthInternalErrorCodeUnexpectedErrorResponse - @see FIRAuthInternalErrorCodeUnexpectedResponse - @see FIRAuthInternalErrorCodeRPCResponseDecodingError - @param request The request. - @param response The empty response to be filled. - @param callback The callback for both success and failure. - */ -- (void)postWithRequest:(id)request - response:(id)response - callback:(void (^)(NSError *_Nullable error))callback { - NSError *error; - NSData *bodyData; - if ([request containsPostBody]) { - id postBody = [request unencodedHTTPRequestBodyWithError:&error]; - if (!postBody) { - callback([FIRAuthErrorUtils RPCRequestEncodingErrorWithUnderlyingError:error]); - return; - } - - NSJSONWritingOptions JSONWritingOptions = 0; -#if DEBUG - JSONWritingOptions |= NSJSONWritingPrettyPrinted; -#endif - - if ([NSJSONSerialization isValidJSONObject:postBody]) { - bodyData = [NSJSONSerialization dataWithJSONObject:postBody - options:JSONWritingOptions - error:&error]; - if (!bodyData) { - // This is an untested case. This happens exclusively when there is an error in the - // framework implementation of dataWithJSONObject:options:error:. This shouldn't normally - // occur as isValidJSONObject: should return NO in any case we should encounter an error. - error = [FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:error]; - } - } else { - error = [FIRAuthErrorUtils JSONSerializationErrorForUnencodableType]; - } - if (!bodyData) { - callback(error); - return; - } - } - - [_RPCIssuer - asyncPostToURLWithRequestConfiguration:[request requestConfiguration] - URL:[request requestURL] - body:bodyData - contentType:kJSONContentType - completionHandler:^(NSData *data, NSError *error) { - // If there is an error with no body data at all, then this must be a - // network error. - if (error && !data) { - callback([FIRAuthErrorUtils networkErrorWithUnderlyingError:error]); - return; - } - - // Try to decode the HTTP response data which may contain either a - // successful response or error message. - NSError *jsonError; - NSDictionary *dictionary = - [NSJSONSerialization JSONObjectWithData:data - options:NSJSONReadingMutableLeaves - error:&jsonError]; - if (!dictionary) { - if (error) { - // We have an error, but we couldn't decode the body, so we have no - // additional information other than the raw response and the - // original NSError (the jsonError is infered by the error code - // (FIRAuthErrorCodeUnexpectedHTTPResponse, and is irrelevant.) - callback([FIRAuthErrorUtils - unexpectedErrorResponseWithData:data - underlyingError:error]); - } else { - // This is supposed to be a "successful" response, but we couldn't - // deserialize the body. - callback([FIRAuthErrorUtils unexpectedResponseWithData:data - underlyingError:jsonError]); - } - return; - } - if (![dictionary isKindOfClass:[NSDictionary class]]) { - if (error) { - callback([FIRAuthErrorUtils - unexpectedErrorResponseWithDeserializedResponse:dictionary - underlyingError:error]); - } else { - callback([FIRAuthErrorUtils - unexpectedResponseWithDeserializedResponse:dictionary]); - } - return; - } - - // At this point we either have an error with successfully decoded - // details in the body, or we have a response which must pass further - // validation before we know it's truly successful. We deal with the - // case where we have an error with successfully decoded error details - // first: - if (error) { - NSDictionary *errorDictionary = dictionary[kErrorKey]; - if ([errorDictionary isKindOfClass:[NSDictionary class]]) { - id errorMessage = errorDictionary[kErrorMessageKey]; - if ([errorMessage isKindOfClass:[NSString class]]) { - NSString *errorMessageString = (NSString *)errorMessage; - - // Contruct client error. - NSError *clientError = [[self class] - clientErrorWithServerErrorMessage:errorMessageString - errorDictionary:errorDictionary - response:response]; - if (clientError) { - callback(clientError); - return; - } - } - // Not a message we know, return the message directly. - if (errorMessage) { - NSError *unexpecterErrorResponse = [FIRAuthErrorUtils - unexpectedErrorResponseWithDeserializedResponse: - errorDictionary - underlyingError:error]; - callback(unexpecterErrorResponse); - return; - } - } - // No error message at all, return the decoded response. - callback([FIRAuthErrorUtils - unexpectedErrorResponseWithDeserializedResponse:dictionary - underlyingError:error]); - return; - } - - // Finally, we try to populate the response object with the JSON - // values. - if (![response setWithDictionary:dictionary error:&error]) { - callback([FIRAuthErrorUtils - RPCResponseDecodingErrorWithDeserializedResponse:dictionary - underlyingError:error]); - return; - } - // In case returnIDPCredential of a verifyAssertion request is set to - // @YES, the server may return a 200 with a response that may contain a - // server error. - if ([request isKindOfClass:[FIRVerifyAssertionRequest class]]) { - FIRVerifyAssertionRequest *verifyAssertionRequest = - (FIRVerifyAssertionRequest *)request; - if (verifyAssertionRequest.returnIDPCredential) { - NSString *errorMessage = - dictionary[kReturnIDPCredentialErrorMessageKey]; - if ([errorMessage isKindOfClass:[NSString class]]) { - NSString *errorString = (NSString *)errorMessage; - NSError *clientError = - [[self class] clientErrorWithServerErrorMessage:errorString - errorDictionary:@{} - response:response]; - if (clientError) { - callback(clientError); - return; - } - } - } - } - // Success! The response object originally passed in can be used by the - // caller. - callback(nil); - }]; -} - -/** @fn clientErrorWithServerErrorMessage:errorDictionary: - @brief Translates known server errors to client errors. - @param serverErrorMessage The error message from the server. - @param errorDictionary The error part of the response from the server. - @param response The response from the server RPC. - @return A client error, if any. - */ -+ (nullable NSError *)clientErrorWithServerErrorMessage:(NSString *)serverErrorMessage - errorDictionary:(NSDictionary *)errorDictionary - response:(id)response { - NSString *shortErrorMessage = serverErrorMessage; - NSString *serverDetailErrorMessage; - NSRange colonRange = [serverErrorMessage rangeOfString:@":"]; - if (colonRange.location != NSNotFound) { - shortErrorMessage = [serverErrorMessage substringToIndex:colonRange.location]; - shortErrorMessage = - [shortErrorMessage stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - serverDetailErrorMessage = [serverErrorMessage substringFromIndex:colonRange.location + 1]; - serverDetailErrorMessage = [serverDetailErrorMessage - stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - } - - // Delegate the responsibility for constructing the client error to the response object, - // if possible. - SEL clientErrorWithServerErrorMessageSelector = @selector(clientErrorWithShortErrorMessage: - detailErrorMessage:); - if ([response respondsToSelector:clientErrorWithServerErrorMessageSelector]) { - NSError *error = [response clientErrorWithShortErrorMessage:shortErrorMessage - detailErrorMessage:serverDetailErrorMessage]; - if (error) { - return error; - } - } - - if ([shortErrorMessage isEqualToString:kUserNotFoundErrorMessage]) { - return [FIRAuthErrorUtils userNotFoundErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kUserDeletedErrorMessage]) { - return [FIRAuthErrorUtils userNotFoundErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidLocalIDErrorMessage]) { - // This case shouldn't be necessary but it is for now: b/27908364 . - return [FIRAuthErrorUtils userNotFoundErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kUserTokenExpiredErrorMessage]) { - return [FIRAuthErrorUtils userTokenExpiredErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kTooManyRequestsErrorMessage]) { - return [FIRAuthErrorUtils tooManyRequestsErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidCustomTokenErrorMessage]) { - return [FIRAuthErrorUtils invalidCustomTokenErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kCustomTokenMismatch]) { - return [FIRAuthErrorUtils customTokenMismatchErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidCredentialErrorMessage] || - [shortErrorMessage isEqualToString:kInvalidPendingToken]) { - return [FIRAuthErrorUtils invalidCredentialErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kUserDisabledErrorMessage]) { - return [FIRAuthErrorUtils userDisabledErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kOperationNotAllowedErrorMessage]) { - return [FIRAuthErrorUtils operationNotAllowedErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kPasswordLoginDisabledErrorMessage]) { - return [FIRAuthErrorUtils operationNotAllowedErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kEmailAlreadyInUseErrorMessage]) { - return [FIRAuthErrorUtils emailAlreadyInUseErrorWithEmail:nil]; - } - - if ([shortErrorMessage isEqualToString:kInvalidEmailErrorMessage]) { - return [FIRAuthErrorUtils invalidEmailErrorWithMessage:serverDetailErrorMessage]; - } - - // "INVALID_IDENTIFIER" can be returned by createAuthURI RPC. Considering email addresses are - // currently the only identifiers, we surface the FIRAuthErrorCodeInvalidEmail error code in this - // case. - if ([shortErrorMessage isEqualToString:kInvalidIdentifierErrorMessage]) { - return [FIRAuthErrorUtils invalidEmailErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kWrongPasswordErrorMessage]) { - return [FIRAuthErrorUtils wrongPasswordErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kCredentialTooOldErrorMessage]) { - return [FIRAuthErrorUtils requiresRecentLoginErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidUserTokenErrorMessage]) { - return [FIRAuthErrorUtils invalidUserTokenErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kFederatedUserIDAlreadyLinkedMessage]) { - FIROAuthCredential *credential; - NSString *email; - if ([response isKindOfClass:[FIRVerifyAssertionResponse class]]) { - FIRVerifyAssertionResponse *verifyAssertion = (FIRVerifyAssertionResponse *)response; - credential = [[FIROAuthCredential alloc] initWithVerifyAssertionResponse:verifyAssertion]; - email = verifyAssertion.email; - } - return [FIRAuthErrorUtils credentialAlreadyInUseErrorWithMessage:serverDetailErrorMessage - credential:credential - email:email]; - } - - if ([shortErrorMessage isEqualToString:kWeakPasswordErrorMessagePrefix]) { - return [FIRAuthErrorUtils weakPasswordErrorWithServerResponseReason:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kExpiredActionCodeErrorMessage]) { - return [FIRAuthErrorUtils expiredActionCodeErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidActionCodeErrorMessage]) { - return [FIRAuthErrorUtils invalidActionCodeErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kMissingEmailErrorMessage]) { - return [FIRAuthErrorUtils missingEmailErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidSenderEmailErrorMessage]) { - return [FIRAuthErrorUtils invalidSenderErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidMessagePayloadErrorMessage]) { - return [FIRAuthErrorUtils invalidMessagePayloadErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidRecipientEmailErrorMessage]) { - return [FIRAuthErrorUtils invalidRecipientEmailErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kMissingIosBundleIDErrorMessage]) { - return [FIRAuthErrorUtils missingIosBundleIDErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kMissingAndroidPackageNameErrorMessage]) { - return [FIRAuthErrorUtils missingAndroidPackageNameErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kUnauthorizedDomainErrorMessage]) { - return [FIRAuthErrorUtils unauthorizedDomainErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidContinueURIErrorMessage]) { - return [FIRAuthErrorUtils invalidContinueURIErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidProviderIDErrorMessage]) { - return [FIRAuthErrorUtils invalidProviderIDErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidDynamicLinkDomainErrorMessage]) { - return [FIRAuthErrorUtils invalidDynamicLinkDomainErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kMissingContinueURIErrorMessage]) { - return [FIRAuthErrorUtils missingContinueURIErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidPhoneNumberErrorMessage]) { - return [FIRAuthErrorUtils invalidPhoneNumberErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidSessionInfoErrorMessage]) { - return [FIRAuthErrorUtils invalidVerificationIDErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidVerificationCodeErrorMessage]) { - return [FIRAuthErrorUtils invalidVerificationCodeErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kSessionExpiredErrorMessage]) { - return [FIRAuthErrorUtils sessionExpiredErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kMissingAppTokenErrorMessage]) { - return [FIRAuthErrorUtils missingAppTokenErrorWithUnderlyingError:nil]; - } - - if ([shortErrorMessage isEqualToString:kMissingAppCredentialErrorMessage]) { - return [FIRAuthErrorUtils missingAppCredentialWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidAppCredentialErrorMessage]) { - return [FIRAuthErrorUtils invalidAppCredentialWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kQuoutaExceededErrorMessage]) { - return [FIRAuthErrorUtils quotaExceededErrorWithMessage:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kAppNotVerifiedErrorMessage]) { - return [FIRAuthErrorUtils appNotVerifiedErrorWithMessage:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kMissingClientIdentifier]) { - return [FIRAuthErrorUtils missingClientIdentifierErrorWithMessage:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kCaptchaCheckFailedErrorMessage]) { - return [FIRAuthErrorUtils captchaCheckFailedErrorWithMessage:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kMissingOrInvalidNonceErrorMessage]) { - return [FIRAuthErrorUtils missingOrInvalidNonceErrorWithMessage:serverDetailErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kMissingMFAPendingCredentialErrorMessage]) { - return [FIRAuthErrorUtils errorWithCode:FIRAuthErrorCodeMissingMultiFactorSession - message:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kMissingMFAEnrollmentIDErrorMessage]) { - return [FIRAuthErrorUtils errorWithCode:FIRAuthErrorCodeMissingMultiFactorInfo - message:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kInvalidMFAPendingCredentialErrorMessage]) { - return [FIRAuthErrorUtils errorWithCode:FIRAuthErrorCodeInvalidMultiFactorSession - message:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kMFAEnrollmentNotFoundErrorMessage]) { - return [FIRAuthErrorUtils errorWithCode:FIRAuthErrorCodeMultiFactorInfoNotFound - message:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kAdminOnlyOperationErrorMessage]) { - return [FIRAuthErrorUtils errorWithCode:FIRAuthErrorCodeAdminRestrictedOperation - message:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kUnverifiedEmailErrorMessage]) { - return [FIRAuthErrorUtils errorWithCode:FIRAuthErrorCodeUnverifiedEmail - message:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kSecondFactorExistsErrorMessage]) { - return [FIRAuthErrorUtils errorWithCode:FIRAuthErrorCodeSecondFactorAlreadyEnrolled - message:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kSecondFactorLimitExceededErrorMessage]) { - return [FIRAuthErrorUtils errorWithCode:FIRAuthErrorCodeMaximumSecondFactorCountExceeded - message:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kUnsupportedFirstFactorErrorMessage]) { - return [FIRAuthErrorUtils errorWithCode:FIRAuthErrorCodeUnsupportedFirstFactor - message:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kEmailChangeNeedsVerificationErrorMessage]) { - return [FIRAuthErrorUtils errorWithCode:FIRAuthErrorCodeEmailChangeNeedsVerification - message:serverErrorMessage]; - } - - if ([shortErrorMessage isEqualToString:kTenantIDMismatch]) { - return [FIRAuthErrorUtils tenantIDMismatchError]; - } - - if ([shortErrorMessage isEqualToString:kUnsupportedTenantOperation]) { - return [FIRAuthErrorUtils unsupportedTenantOperationError]; - } - - if ([shortErrorMessage isEqualToString:kBlockingCloudFunctionErrorResponse]) { - return - [FIRAuthErrorUtils blockingCloudFunctionServerResponseWithMessage:serverDetailErrorMessage]; - } - - // In this case we handle an error that might be specified in the underlying errors dictionary, - // the error message in determined based on the @c reason key in the dictionary. - if (errorDictionary[kErrorsKey]) { - // Check for underlying error with reason = keyInvalid; - id underlyingErrors = errorDictionary[kErrorsKey]; - if ([underlyingErrors isKindOfClass:[NSArray class]]) { - NSArray *underlyingErrorsArray = (NSArray *)underlyingErrors; - for (id underlyingError in underlyingErrorsArray) { - if ([underlyingError isKindOfClass:[NSDictionary class]]) { - NSDictionary *underlyingErrorDictionary = (NSDictionary *)underlyingError; - NSString *reason = underlyingErrorDictionary[kReasonKey]; - if ([reason hasPrefix:kInvalidKeyReasonValue]) { - return [FIRAuthErrorUtils invalidAPIKeyError]; - } - if ([reason isEqualToString:kAppNotAuthorizedReasonValue]) { - return [FIRAuthErrorUtils appNotAuthorizedError]; - } - } - } - } - } - return nil; -} @end diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift index bbd65d5cf5f..66e43ca4634 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift @@ -381,7 +381,7 @@ import Foundation var queryItems = [URLQueryItem(name: "apiKey", value: apiKey), URLQueryItem(name: "authType", value: self.kAuthTypeVerifyApp), URLQueryItem(name: "ibi", value: bundleID ?? ""), - URLQueryItem(name: "v", value: FIRAuthBackend.authUserAgent()), + URLQueryItem(name: "v", value: AuthBackend.authUserAgent()), URLQueryItem(name: "eventID", value: eventID)] if self.usingClientIDScheme { queryItems.append(URLQueryItem(name: "clientID", value: clientID)) diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index 6c1616ec95c..341885e7597 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -84,7 +84,7 @@ public class AuthBackendRPCIssuerImplementation: NSObject, AuthBackendRPCIssuer gBackendImplementation = defaultImplementation } - private class func implementation() -> AuthBackendImplementation { + class func implementation() -> AuthBackendImplementation { if gBackendImplementation == nil { gBackendImplementation = AuthBackendRPCImplementation() } @@ -255,7 +255,7 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation // TODO: Can unencodedHTTPRequestBody ever throw? // They don't today, but there are a few fatalErrors that might better be implemented as // thrown errors.. Although perhaps the case of 'containsPostBody' returning false could - // perhaps be modelled differently so that the failing unencodedHTTPRequestBody could only + // perhaps be modeled differently so that the failing unencodedHTTPRequestBody could only // be called when a body exists... let postBody = try request.unencodedHTTPRequestBody() var JSONWritingOptions: JSONSerialization.WritingOptions = .init(rawValue: 0) @@ -308,7 +308,7 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation .mutableLeaves) guard let decodedDictionary = rawDecode as? [String: Any] else { if error != nil { - callback(AuthErrorUtils.unexpectedResponse(deserializedResponse: rawDecode, + callback(AuthErrorUtils.unexpectedErrorResponse(deserializedResponse: rawDecode, underlyingError: error)) } else { callback(AuthErrorUtils.unexpectedResponse(deserializedResponse: rawDecode)) @@ -322,7 +322,7 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation // additional information other than the raw response and the // original NSError (the jsonError is inferred by the error code // (AuthErrorCodeUnexpectedHTTPResponse, and is irrelevant.) - callback(AuthErrorUtils.unexpectedResponse(data: data, underlyingError: error)) + callback(AuthErrorUtils.unexpectedErrorResponse(data: data, underlyingError: error)) return } else { // This is supposed to be a "successful" response, but we couldn't @@ -340,14 +340,14 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation if error != nil { if let errorDictionary = dictionary["error"] as? [String: Any] { if let errorMessage = errorDictionary["message"] as? String { - let clientError = AuthBackendRPCImplementation.clientError( + if let clientError = AuthBackendRPCImplementation.clientError( withServerErrorMessage: errorMessage, errorDictionary: errorDictionary, response: response, - error: error - ) - callback(clientError) - return + error: error) { + callback(clientError) + return + } } // Not a message we know, return the message directly. callback(AuthErrorUtils.unexpectedErrorResponse( @@ -395,7 +395,7 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation private class func clientError(withServerErrorMessage serverErrorMessage: String, errorDictionary: [String: Any], response: AuthRPCResponse, - error: Error?) -> Error { + error: Error?) -> Error? { let split = serverErrorMessage.split(separator: ":") let shortErrorMessage = split.first?.trimmingCharacters(in: .whitespacesAndNewlines) let serverDetailErrorMessage = String(split.count > 1 ? split[1] : "") @@ -414,7 +414,6 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation .invalidUserTokenError(message: serverDetailErrorMessage) case "CREDENTIAL_TOO_OLD_LOGIN_AGAIN": return AuthErrorUtils .requiresRecentLoginError(message: serverDetailErrorMessage) - case "EMAIL_EXISTS": return AuthErrorUtils .emailAlreadyInUseError(email: nil) case "OPERATION_NOT_ALLOWED": return AuthErrorUtils @@ -479,6 +478,42 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation .invalidVerificationIDError(message: serverDetailErrorMessage) case "SESSION_EXPIRED": return AuthErrorUtils .sessionExpiredError(message: serverDetailErrorMessage) + case "ADMIN_ONLY_OPERATION": return AuthErrorUtils + .error(code: AuthErrorCode.adminRestrictedOperation, message: serverDetailErrorMessage) + case "BLOCKING_FUNCTION_ERROR_RESPONSE": return AuthErrorUtils + .blockingCloudFunctionServerResponse(message: serverDetailErrorMessage) + case "EMAIL_CHANGE_NEEDS_VERIFICATION": return AuthErrorUtils + .error(code: AuthErrorCode.emailChangeNeedsVerification, message: serverDetailErrorMessage) + case "INVALID_MFA_PENDING_CREDENTIAL": return AuthErrorUtils + .error(code: AuthErrorCode.invalidMultiFactorSession, message: serverDetailErrorMessage) + case "INVALID_PROVIDER_ID": return AuthErrorUtils + .invalidProviderIDError(message: serverDetailErrorMessage) + case "MFA_ENROLLMENT_NOT_FOUND": return AuthErrorUtils + .error(code: AuthErrorCode.multiFactorInfoNotFound, message: serverDetailErrorMessage) + case "MISSING_CLIENT_IDENTIFIER": return AuthErrorUtils + .missingClientIdentifierError(message: serverDetailErrorMessage) + case "MISSING_IOS_APP_TOKEN": return AuthErrorUtils + .missingAppTokenError(underlyingError: nil) + case "MISSING_MFA_ENROLLMENT_ID": return AuthErrorUtils + .error(code: AuthErrorCode.missingMultiFactorInfo, message: serverDetailErrorMessage) + case "MISSING_MFA_PENDING_CREDENTIAL": return AuthErrorUtils + .error(code: AuthErrorCode.missingMultiFactorSession, message: serverDetailErrorMessage) + case "MISSING_OR_INVALID_NONCE": return AuthErrorUtils + .missingOrInvalidNonceError(message: serverDetailErrorMessage) + case "SECOND_FACTOR_EXISTS": return AuthErrorUtils + .error(code: AuthErrorCode.secondFactorAlreadyEnrolled, message: serverDetailErrorMessage) + case "SECOND_FACTOR_LIMIT_EXCEEDED": return AuthErrorUtils + .error(code: AuthErrorCode.maximumSecondFactorCountExceeded, message: serverDetailErrorMessage) + case "TENANT_ID_MISMATCH": return AuthErrorUtils + .tenantIDMismatchError() + case "TOKEN_EXPIRED": return AuthErrorUtils + .userTokenExpiredError(message: serverDetailErrorMessage) + case "UNSUPPORTED_FIRST_FACTOR": return AuthErrorUtils + .error(code: AuthErrorCode.unsupportedFirstFactor, message: serverDetailErrorMessage) + case "UNSUPPORTED_TENANT_OPERATION": return AuthErrorUtils + .unsupportedTenantOperationError() + case "UNVERIFIED_EMAIL": return AuthErrorUtils + .error(code: AuthErrorCode.unverifiedEmail, message: serverDetailErrorMessage) case "FEDERATED_USER_ID_ALREADY_LINKED": guard let verifyAssertion = response as? VerifyAssertionResponse else { return AuthErrorUtils.credentialAlreadyInUseError( @@ -491,11 +526,6 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation message: serverDetailErrorMessage, credential: credential, email: email ) - // TODO: MISSING_API_KEY should go away and its code should be generated in the "keyInvalid". - case "MISSING_API_KEY": return AuthErrorUtils.unexpectedResponse( - deserializedResponse: errorDictionary, - underlyingError: error - ) default: if let underlyingErrors = errorDictionary["errors"] as? [[String: String]] { for underlyingError in underlyingErrors { @@ -510,7 +540,6 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation } } } - // TODO: Old code returns nil on fall through. - fatalError("Implement missing message for: \(shortErrorMessage ?? "missing value")") + return nil } } diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift index 1739788bfa1..b1b4dde40f6 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift @@ -41,24 +41,14 @@ private let kFIRAuthErrorMessageMalformedJWT = "Failed to parse JWT. Check the userInfo dictionary for the full token." @objc(FIRAuthErrorUtils) public class AuthErrorUtils: NSObject { - private static let errorDomain = "FIRAuthErrorDomain" - - private static let internalErrorDomain = "FIRAuthInternalErrorDomain" - - static let userInfoDeserializedResponseKey = - "FIRAuthErrorUserInfoDeserializedResponseKey" - - private static let userInfoDataKey = "FIRAuthErrorUserInfoDataKey" - - private static let userInfoEmailKey = "FIRAuthErrorUserInfoEmailKey" - - private static let userInfoUpdatedCredentialKey = - "FIRAuthErrorUserInfoUpdatedCredentialKey" - - private static let userInfoNameKey = "FIRAuthErrorUserInfoNameKey" - - private static let userInfoMultiFactorResolverKey = - "FIRAuthErrorUserInfoMultiFactorResolverKey" + static let errorDomain = "FIRAuthErrorDomain" + static let internalErrorDomain = "FIRAuthInternalErrorDomain" + static let userInfoDeserializedResponseKey = "FIRAuthErrorUserInfoDeserializedResponseKey" + static let userInfoDataKey = "FIRAuthErrorUserInfoDataKey" + static let userInfoEmailKey = "FIRAuthErrorUserInfoEmailKey" + static let userInfoUpdatedCredentialKey = "FIRAuthErrorUserInfoUpdatedCredentialKey" + static let userInfoNameKey = "FIRAuthErrorUserInfoNameKey" + static let userInfoMultiFactorResolverKey = "FIRAuthErrorUserInfoMultiFactorResolverKey" /** @var kServerErrorDetailMarker @brief This marker indicates that the server error message contains a detail error message which diff --git a/FirebaseAuth/Sources/SystemService/FIRSecureTokenService.m b/FirebaseAuth/Sources/SystemService/FIRSecureTokenService.m index f36985da115..d35467a5284 100644 --- a/FirebaseAuth/Sources/SystemService/FIRSecureTokenService.m +++ b/FirebaseAuth/Sources/SystemService/FIRSecureTokenService.m @@ -20,7 +20,6 @@ #import "FirebaseAuth/Sources/Auth/FIRAuthSerialTaskQueue.h" #import "FirebaseAuth/Sources/Auth/FIRAuth_Internal.h" -#import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h" #import "FirebaseAuth-Swift.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h" diff --git a/FirebaseAuth/Sources/User/FIRUser.m b/FirebaseAuth/Sources/User/FIRUser.m index 0b5bcaed512..b3844a51d41 100644 --- a/FirebaseAuth/Sources/User/FIRUser.m +++ b/FirebaseAuth/Sources/User/FIRUser.m @@ -22,7 +22,6 @@ #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h" #import "FirebaseAuth/Sources/Auth/FIRAuthSerialTaskQueue.h" #import "FirebaseAuth/Sources/Auth/FIRAuth_Internal.h" -#import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h" #import "FirebaseAuth/Sources/MultiFactor/FIRMultiFactor+Internal.h" #import "FirebaseAuth/Sources/SystemService/FIRSecureTokenService.h" #import "FirebaseAuth/Sources/User/FIRUserInfoImpl.h" diff --git a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift new file mode 100644 index 00000000000..97c838318b4 --- /dev/null +++ b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift @@ -0,0 +1,638 @@ +// Copyright 2023 Google LLC +// +// 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. + +import Foundation +import XCTest + +@testable import FirebaseAuth + +private class FakeRequest : NSObject, AuthRPCRequest { + let kFakeRequestURL = "https://www.google.com/" + func requestURL() -> URL { + return try! XCTUnwrap(URL(string:kFakeRequestURL)) + } + + func unencodedHTTPRequestBody() throws -> Any { + if let encodingError { + throw encodingError + } + return requestBody + } + + func requestConfiguration() -> FirebaseAuth.AuthRequestConfiguration { + return AuthRequestConfiguration( + APIKey: "kTestAPIKey", + appID: "kTestFirebaseAppID" + ) + } + + func containsPostBody() -> Bool { + return true + } + + var response: FirebaseAuth.AuthRPCResponse + + let encodingError:NSError? + let requestBody: [String: AnyHashable] + + init(withEncodingError error: NSError) { + encodingError = error + requestBody = [:] + response = FakeResponse() + } + + init(withDecodingError error: NSError) { + encodingError = nil + requestBody = [:] + response = FakeResponse(withDecodingError: error) + } + + init(withRequestBody body: [String: AnyHashable]) { + encodingError = nil + requestBody = body + response = FakeResponse() + } +} + +private class FakeResponse: NSObject, AuthRPCResponse { + let decodingError: NSError? + var receivedDictionary : [String : Any] = [:] + init(withDecodingError error: NSError? = nil) { + decodingError = error + } + func setFields(dictionary: [String : Any]) throws { + if let decodingError { + throw decodingError + } + receivedDictionary = dictionary + } +} + +class AuthBackendRPCImplementationTests: RPCBaseTests { + let kFakeErrorDomain = "fakeDomain" + let kFakeErrorCode = -1 + + + /** @fn testRequestEncodingError + @brief This test checks the behaviour of @c postWithRequest:response:callback: when the + request passed returns an error during it's unencodedHTTPRequestBodyWithError: method. + The error returned should be delivered to the caller without any change. + */ + func testRequestEncodingError() throws { + let encodingError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode) + let request = FakeRequest(withEncodingError: encodingError) + + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue) + + let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) + XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.RPCRequestEncodingError.rawValue) + + let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) + XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) + + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]) + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) + } + + /** @fn testBodyDataSerializationError + @brief This test checks the behaviour of @c postWithRequest:response:callback: when the + request returns an object which isn't serializable by @c NSJSONSerialization. + The error from @c NSJSONSerialization should be returned as the underlyingError for an + @c NSError with the code @c FIRAuthErrorCodeJSONSerializationError. + */ + func testBodyDataSerializationError() throws { + let request = FakeRequest(withRequestBody: ["unencodable": self]) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue) + + let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.JSONSerializationError.rawValue) + XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) + + XCTAssertNil(underlyingError.userInfo[NSUnderlyingErrorKey]) + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]) + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) + } + + /** @fn testNetworkError + @brief This test checks to make sure a network error is properly wrapped and forwarded with the + correct code (FIRAuthErrorCodeNetworkError). + */ + func testNetworkError() throws { + let request = FakeRequest(withRequestBody: [:]) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode) + try RPCIssuer?.respond(withData: nil, error: responseError) + + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.networkError.rawValue) + + let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingError.domain, kFakeErrorDomain) + XCTAssertEqual(underlyingError.code, kFakeErrorCode) + + XCTAssertNil(underlyingError.userInfo[NSUnderlyingErrorKey]) + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]) + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) + } + + /** @fn testUnparsableErrorResponse + @brief This test checks the behaviour of @c postWithRequest:response:callback: when the + response isn't deserializable by @c NSJSONSerialization and an error + condition (with an associated error response message) was expected. We are expecting to + receive the original network error wrapped in an @c NSError with the code + @c FIRAuthErrorCodeUnexpectedHTTPResponse. + */ + func testUnparsableErrorResponse() throws { + let request = FakeRequest(withRequestBody: [:]) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + let data = "An error occurred.".data(using: .utf8) + let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode) + try RPCIssuer?.respond(withData: data, error: responseError) + + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue) + + let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) + XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue) + + let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) + XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) + + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]) + XCTAssertEqual(data, + try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey] as? Data)) + } + + /** @fn testUnparsableSuccessResponse + @brief This test checks the behaviour of @c postWithRequest:response:callback: when the + response isn't deserializable by @c NSJSONSerialization and no error + condition was indicated. We are expecting to + receive the @c NSJSONSerialization error wrapped in an @c NSError with the code + @c FIRAuthErrorCodeUnexpectedServerResponse. + */ + func testUnparsableSuccessResponse() throws { + let request = FakeRequest(withRequestBody: [:]) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + let data = "Some non-JSON value.".data(using: .utf8) + try RPCIssuer?.respond(withData: data, error: nil) + + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue) + + let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) + XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedResponse.rawValue) + + let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingUnderlying.domain, NSCocoaErrorDomain) + + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]) + XCTAssertEqual(data, + try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey] as? Data)) + } + + /** @fn testNonDictionaryErrorResponse + @brief This test checks the behaviour of @c postWithRequest:response:callback: when the + response deserialized by @c NSJSONSerialization is not a dictionary, and an error was + expected. We are expecting to receive the original network error wrapped in an @c NSError + with the code @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded response + in the @c NSError.userInfo dictionary associated with the key + @c FIRAuthErrorUserInfoDeserializedResponseKey. + */ + func testNonDictionaryErrorResponse() throws { + let request = FakeRequest(withRequestBody: [:]) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + // We are responding with a JSON-encoded string value representing an array - which is unexpected. + // It should normally be a dictionary, and we need to check for this sort of thing. Because we can + // successfully decode this value, however, we do return it in the error results. We check for + // this array later in the test. + let data = "[]".data(using: .utf8) + let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode) + try RPCIssuer?.respond(withData: data, error: responseError) + + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue) + + let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) + XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue) + + let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) + XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) + + XCTAssertNotNil(try XCTUnwrap( + underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]) as? [Int]) + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) + } + + /** @fn testNonDictionarySuccessResponse + @brief This test checks the behaviour of @c postWithRequest:response:callback: when the + response deserialized by @c NSJSONSerialization is not a dictionary, and no error was + expected. We are expecting to receive an @c NSError with the code + @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded response in the + @c NSError.userInfo dictionary associated with the key + @c FIRAuthErrorUserInfoDecodedResponseKey. + */ + func testNonDictionarySuccessResponse() throws { + let request = FakeRequest(withRequestBody: [:]) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + // We are responding with a JSON-encoded string value representing an array - which is unexpected. + // It should normally be a dictionary, and we need to check for this sort of thing. Because we can + // successfully decode this value, however, we do return it in the error results. We check for + // this array later in the test. + let data = "[]".data(using: .utf8) + try RPCIssuer?.respond(withData: data, error: nil) + + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue) + + let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) + XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedResponse.rawValue) + XCTAssertNil(underlyingError.userInfo[NSUnderlyingErrorKey]) + + XCTAssertNotNil(try XCTUnwrap( + underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]) as? [Int]) + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) + } + + /** @fn testCaptchaRequiredResponse + @brief This test checks the behaviour of @c postWithRequest:response:callback: when the + we get an error message indicating captcha is required. The backend should not be returning + this error to mobile clients. If it does, we should wrap it in an @c NSError with the code + @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded error message in the + @c NSError.userInfo dictionary associated with the key + @c FIRAuthErrorUserInfoDeserializedResponseKey. + */ + func testCaptchaRequiredResponse() throws { + let kErrorMessageCaptchaRequired = "CAPTCHA_REQUIRED" + let request = FakeRequest(withRequestBody: [:]) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode) + try RPCIssuer?.respond(serverErrorMessage: kErrorMessageCaptchaRequired, error: responseError) + + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue) + + let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) + XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue) + let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) + XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) + + let dictionary = try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) + XCTAssertEqual(dictionary["message"], kErrorMessageCaptchaRequired) + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) + } + + /** @fn testCaptchaCheckFailedResponse + @brief This test checks the behaviour of @c postWithRequest:response:callback: when the + we get an error message indicating captcha check failed. The backend should not be returning + this error to mobile clients. If it does, we should wrap it in an @c NSError with the code + @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded error message in the + @c NSError.userInfo dictionary associated with the key + @c FIRAuthErrorUserInfoDecodedErrorResponseKey. + */ + func testCaptchaCheckFailedResponse() throws { + let kErrorMessageCaptchaCheckFailed = "CAPTCHA_CHECK_FAILED" + let request = FakeRequest(withRequestBody: [:]) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode) + try RPCIssuer?.respond(serverErrorMessage: kErrorMessageCaptchaCheckFailed, error: responseError) + + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.captchaCheckFailed.rawValue) + } + + /** @fn testCaptchaRequiredInvalidPasswordResponse + @brief This test checks the behaviour of @c postWithRequest:response:callback: when the + we get an error message indicating captcha is required and an invalid password was entered. + The backend should not be returning this error to mobile clients. If it does, we should wrap + it in an @c NSError with the code + @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded error message in the + @c NSError.userInfo dictionary associated with the key + @c FIRAuthErrorUserInfoDeserializedResponseKey. + */ + func testCaptchaRequiredInvalidPasswordResponse() throws { + let kErrorMessageCaptchaRequiredInvalidPassword = "CAPTCHA_REQUIRED_INVALID_PASSWORD" + let request = FakeRequest(withRequestBody: [:]) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode) + try RPCIssuer?.respond(serverErrorMessage: kErrorMessageCaptchaRequiredInvalidPassword, + error: responseError) + + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue) + + let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) + XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue) + let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) + XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) + + let dictionary = try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) + XCTAssertEqual(dictionary["message"], kErrorMessageCaptchaRequiredInvalidPassword) + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) + } + + /** @fn testDecodableErrorResponseWithUnknownMessage + @brief This test checks the behaviour of @c postWithRequest:response:callback: when the + response deserialized by @c NSJSONSerialization represents a valid error response (and an + error was indicated) but we didn't receive an error message we know about. We are expecting + to receive the original network error wrapped in an @c NSError with the code + @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded + error message in the @c NSError.userInfo dictionary associated with the key + @c FIRAuthErrorUserInfoDeserializedResponseKey. + */ + func testDecodableErrorResponseWithUnknownMessage() throws { + let kUnknownServerErrorMessage = "UNKNOWN_MESSAGE" + let request = FakeRequest(withRequestBody: [:]) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + // We need to return a valid "error" response here, but we are going to intentionally use a bogus + // error message. + let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode) + try RPCIssuer?.respond(serverErrorMessage: kUnknownServerErrorMessage, error: responseError) + + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue) + + let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) + XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue) + let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) + XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) + + let dictionary = try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) + XCTAssertEqual(dictionary["message"], kUnknownServerErrorMessage) + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) + } + + /** @fn testErrorResponseWithNoErrorMessage + @brief This test checks the behaviour of @c postWithRequest:response:callback: when the + response deserialized by @c NSJSONSerialization is a dictionary, and an error was indicated, + but no error information was present in the decoded response. We are expecting to receive + the original network error wrapped in an @c NSError with the code + @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded + response message in the @c NSError.userInfo dictionary associated with the key + @c FIRAuthErrorUserInfoDeserializedResponseKey. + */ + func testErrorResponseWithNoErrorMessage() throws { + let request = FakeRequest(withRequestBody: [:]) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode) + try RPCIssuer?.respond(withJSON: [:], error: responseError) + + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue) + + let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) + XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue) + let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) + XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) + + let dictionary = try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) + XCTAssertEqual(dictionary, [:]) + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) + } + + /** @fn testClientErrorResponse + @brief This test checks the behaviour of @c postWithRequest:response:callback: when the + response contains a client error specified by an error messsage sent from the backend. + */ + func testClientErrorResponse() throws { + let request = FakeRequest(withRequestBody: [:]) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode) + let kUserDisabledErrorMessage = "USER_DISABLED" + let kServerErrorDetailMarker = " : " + let kFakeUserDisabledCustomErrorMessage = "The user has been disabled." + let customErrorMessage = "\(kUserDisabledErrorMessage)" + + "\(kServerErrorDetailMarker)\(kFakeUserDisabledCustomErrorMessage)" + try RPCIssuer?.respond(serverErrorMessage: customErrorMessage, error: responseError) + + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.userDisabled.rawValue) + + let customMessage = try XCTUnwrap(rpcError?.userInfo[NSLocalizedDescriptionKey] as? String) + XCTAssertEqual(customMessage, kFakeUserDisabledCustomErrorMessage) + } + + /** @fn testUndecodableSuccessResponse + @brief This test checks the behaviour of @c postWithRequest:response:callback: when the + response isn't decodable by the response class but no error condition was expected. We are + expecting to receive an @c NSError with the code + @c FIRAuthErrorCodeUnexpectedServerResponse and the error from @c setWithDictionary:error: + as the value of the underlyingError. + */ + func testUndecodableSuccessResponse() throws { + let request = FakeRequest(withDecodingError: NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + // It doesn't matter what we respond with here, as long as it's not an error response. The fake + // response will deterministicly simulate a decoding error regardless of the response value it was + // given. + try RPCIssuer?.respond(withJSON: [:]) + + XCTAssert(callbackInvoked) + XCTAssertNil(rpcResponse) + + XCTAssertEqual(rpcError?.domain, AuthErrors.AuthErrorDomain) + XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue) + + let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) + XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) + XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.RPCResponseDecodingError.rawValue) + + let dictionary = try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) + XCTAssertEqual(dictionary, [:]) + XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) + } + + /** @fn testSuccessfulResponse + @brief Tests that a decoded dictionary is handed to the response instance. + */ + func testSuccessfulResponse() throws { + let request = FakeRequest(withRequestBody: [:]) + var callbackInvoked = false + var rpcResponse: FakeResponse? + var rpcError: NSError? + let kTestKey = "TestKey" + let kTestValue = "TestValue" + + rpcImplementation?.post(withRequest: request) { response, error in + callbackInvoked = true + rpcResponse = response as? FakeResponse + rpcError = error as? NSError + } + // It doesn't matter what we respond with here, as long as it's not an error response. The fake + // response will deterministicly simulate a decoding error regardless of the response value it was + // given. + try RPCIssuer?.respond(withJSON: [kTestKey : kTestValue]) + + XCTAssert(callbackInvoked) + XCTAssertNil(rpcError) + XCTAssertEqual(try XCTUnwrap(rpcResponse?.receivedDictionary[kTestKey] as? String), kTestValue) + } +} diff --git a/FirebaseAuth/Tests/Unit/FIRAuthBackendRPCImplementationTests.m b/FirebaseAuth/Tests/Unit/FIRAuthBackendRPCImplementationTests.m deleted file mode 100644 index 161c9294004..00000000000 --- a/FirebaseAuth/Tests/Unit/FIRAuthBackendRPCImplementationTests.m +++ /dev/null @@ -1,1176 +0,0 @@ -/* - * Copyright 2017 Google - * - * 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. - */ - -#import - -@import HeartbeatLoggingTestUtils; - -@import FirebaseAuth; -#import "FirebaseAuth/Tests/Unit/FIRFakeBackendRPCIssuer.h" - -#import "FirebaseCore/Extension/FirebaseCoreInternal.h" - -// XXX TODO, can't declare globals in Swift for Obj-C consumption -static NSString *const FIRAuthErrorDomain = @"FIRAuthErrorDomain"; - -static NSString *const FIRAuthInternalErrorDomain = @"FIRAuthInternalErrorDomain"; - -static NSString *const FIRAuthErrorUserInfoDeserializedResponseKey = - @"FIRAuthErrorUserInfoDeserializedResponseKey"; - -static NSString *const FIRAuthErrorUserInfoDataKey = @"FIRAuthErrorUserInfoDataKey"; - -static NSString *const FIRAuthErrorUserInfoEmailKey = @"FIRAuthErrorUserInfoEmailKey"; - -static NSString *const FIRAuthErrorUserInfoUpdatedCredentialKey = - @"FIRAuthErrorUserInfoUpdatedCredentialKey"; - -static NSString *const FIRAuthErrorUserInfoNameKey = @"FIRAuthErrorUserInfoNameKey"; - -static NSString *const FIRAuthErrorUserInfoMultiFactorResolverKey = - @"FIRAuthErrorUserInfoMultiFactorResolverKey"; - -/** @var kFakeRequestURL - @brief Used as a fake URL for a fake RPC request. We don't test this here, since it's tested - for the specific RPC requests in their various unit tests. - */ -static NSString *const kFakeRequestURL = @"https://www.google.com/"; - -/** @var kFakeAPIkey - @brief Used as a fake APIKey for a fake RPC request. We don't test this here. - */ -static NSString *const kFakeAPIkey = @"FAKE_API_KEY"; - -/** @var kFakeFirebaseAppID - @brief Used as a fake Firebase app ID for a fake RPC request. We don't test this here. - */ -static NSString *const kFakeFirebaseAppID = @"FAKE_APP_ID"; - -/** @var kFakeErrorDomain - @brief A value to use for fake @c NSErrors. - */ -static NSString *const kFakeErrorDomain = @"fakeDomain"; - -/** @var kFakeErrorCode - @brief A value to use for fake @c NSErrors. - */ -static const NSUInteger kFakeErrorCode = -1; - -/** @var kUnknownServerErrorMessage - @brief A value to use for fake server errors with an unknown message. - */ -static NSString *const kUnknownServerErrorMessage = @"UNKNOWN_MESSAGE"; - -/** @var kErrorMessageCaptchaRequired - @brief The error message in JSON responses from the server for CAPTCHA required. - */ -static NSString *const kErrorMessageCaptchaRequired = @"CAPTCHA_REQUIRED"; - -/** @var kErrorMessageCaptchaRequiredInvalidPassword - @brief The error message in JSON responses from the server for CAPTCHA required with invalid - password. - */ -static NSString *const kErrorMessageCaptchaRequiredInvalidPassword = - @"CAPTCHA_REQUIRED_INVALID_PASSWORD"; - -/** @var kErrorMessageCaptchaCheckFailed - @brief The error message in JSON responses from the server for CAPTCHA check failed. - */ -static NSString *const kErrorMessageCaptchaCheckFailed = @"CAPTCHA_CHECK_FAILED"; - -/** @var kErrorMessageEmailExists - @brief The error message in JSON responses from the server for user's email already exists. - */ -static NSString *const kErrorMessageEmailExists = @"EMAIL_EXISTS"; - -/** @var kErrorMessageKey - @brief The key for the error message in an error response. - */ -static NSString *const kErrorMessageKey = @"message"; - -/** @var kTestKey - @brief A key to use for a successful response dictionary. - */ -static NSString *const kTestKey = @"TestKey"; - -/** @var kUserDisabledErrorMessage - @brief This is the base error message the server will respond with if the user's account has - been disabled. - */ -static NSString *const kUserDisabledErrorMessage = @"USER_DISABLED"; - -/** @var kFakeUserDisabledCustomErrorMessage - @brief This is a fake custom error message the server can respond with if the user's account has - been disabled. - */ -static NSString *const kFakeUserDisabledCustomErrorMessage = @"The user has been disabled."; - -/** @var kServerErrorDetailMarker - @brief This marker indicates that the server error message contains a detail error message which - should be used instead of the hardcoded client error message. - */ -static NSString *const kServerErrorDetailMarker = @" : "; - -/** @var kTestValue - @brief A value to use for a successful response dictionary. - */ -static NSString *const kTestValue = @"TestValue"; - -#pragma mark - FIRAuthBackendRPCImplementation - -/** @class FIRAuthBackendRPCImplementation - @brief Exposes an otherwise private class to these tests. See the real implementation for - documentation. - */ -@interface FIRAuthBackendRPCImplementation : NSObject - -/** @fn postWithRequest:response:callback: - @brief Calls the RPC using HTTP POST. - @remarks Possible error responses: - @see FIRAuthInternalErrorCodeRPCRequestEncodingError - @see FIRAuthInternalErrorCodeJSONSerializationError - @see FIRAuthInternalErrorCodeNetworkError - @see FIRAuthInternalErrorCodeUnexpectedErrorResponse - @see FIRAuthInternalErrorCodeUnexpectedResponse - @see FIRAuthInternalErrorCodeRPCResponseDecodingError - @param request The request. - @param response The empty response to be filled. - @param callback The callback for both success and failure. - */ -- (void)postWithRequest:(id)request - response:(id)response - callback:(void (^)(NSError *error))callback; - -@end - -#pragma mark - FIRFakeHeartbeatLogger - -/// A fake heartbeat logger used for dependency injection during testing. -@interface FIRFakeHeartbeatLogger : NSObject -@property(nonatomic, copy, nullable) FIRHeartbeatsPayload * (^onFlushHeartbeatsIntoPayloadHandler) - (void); -@property(nonatomic, copy, nullable) FIRDailyHeartbeatCode (^onHeartbeatCodeForTodayHandler)(void); -@end - -@implementation FIRFakeHeartbeatLogger - -- (nonnull FIRHeartbeatsPayload *)flushHeartbeatsIntoPayload { - if (self.onFlushHeartbeatsIntoPayloadHandler) { - return self.onFlushHeartbeatsIntoPayloadHandler(); - } else { - return nil; - } -} - -- (FIRDailyHeartbeatCode)heartbeatCodeForToday { - // This API should not be used by the below tests because the Auth - // SDK uses only the V2 heartbeat API (`flushHeartbeatsIntoPayload`) for - // getting heartbeats. - [self doesNotRecognizeSelector:_cmd]; - return FIRDailyHeartbeatCodeNone; -} - -- (void)log { - // This API should not be used by the below tests because the Auth - // SDK does not log heartbeats in it's networking context. - [self doesNotRecognizeSelector:_cmd]; -} - -@end - -#pragma mark - FIRFakeRequest - -/** @class FIRFakeRequest - @brief Allows us to fake a request with deterministic request bodies and encoding errors - returned from the @c FIRAuthRPCRequest-specified @c unencodedHTTPRequestBodyWithError: - method. - */ -@interface FIRFakeRequest : NSObject - -/** @fn fakeRequest - @brief A "normal" request which returns an encodable request object with no error. - */ -+ (nullable instancetype)fakeRequest; - -/** @fn fakeRequestWithEncodingError - @brief A request which returns a fake error during the encoding process. - */ -+ (nullable instancetype)fakeRequestWithEncodingError:(NSError *)error; - -/** @fn fakeRequestWithUnserializableRequestBody - @brief A request which returns a request object which can not be properly serialized by - @c NSJSONSerialization. - */ -+ (nullable instancetype)fakeRequestWithUnserializableRequestBody; - -/** @fn fakeRequestWithNoBody - @brief A request which returns a nil request body but no error. - */ -+ (nullable instancetype)fakeRequestWithNoBody; - -/** @fn init - @brief Please use initWithRequestBody:encodingError: - */ -- (nullable instancetype)init NS_UNAVAILABLE; - -/** @fn initWithRequestBody:encodingError: - @brief Designated initializer. - @param requestBody The fake request body to return when @c unencodedHTTPRequestBodyWithError: is - invoked. - @param encodingError The fake error to return when @c unencodedHTTPRequestBodyWithError is - invoked. - @param requestConfiguration The request configuration associated with the fake request. - */ -- (nullable instancetype)initWithRequestBody:(nullable id)requestBody - encodingError:(nullable NSError *)encodingError - requestConfiguration: - (nullable FIRAuthRequestConfiguration *)requestConfiguration - NS_DESIGNATED_INITIALIZER; - -@end - -@implementation FIRFakeRequest { - /** @var _requestBody - @brief The fake request body object we will return when @c unencodedHTTPRequestBodyWithError: - is invoked. - */ - id _Nullable _requestBody; - - /** @var _requestEncodingError - @brief The fake error object we will return when @c unencodedHTTPRequestBodyWithError: - is invoked. - */ - NSError *_Nullable _requestEncodingError; - - /** @var _requestConfiguration - @brief The request configuration to return. - */ - FIRAuthRequestConfiguration *_Nullable _requestConfiguration; -} - -+ (nullable instancetype)fakeRequest { - return [[self alloc] initWithRequestBody:@{} encodingError:nil requestConfiguration:nil]; -} - -+ (nullable instancetype)fakeRequestWithRequestConfiguration: - (FIRAuthRequestConfiguration *)requestConfiguration { - return [[self alloc] initWithRequestBody:@{} - encodingError:nil - requestConfiguration:requestConfiguration]; -} - -+ (nullable instancetype)fakeRequestWithEncodingError:(NSError *)error { - return [[self alloc] initWithRequestBody:nil encodingError:error requestConfiguration:nil]; -} - -+ (nullable instancetype)fakeRequestWithUnserializableRequestBody { - return [[self alloc] initWithRequestBody:@{@"unencodableValue" : self} - encodingError:nil - requestConfiguration:nil]; -} - -+ (nullable instancetype)fakeRequestWithNoBody { - return [[self alloc] initWithRequestBody:nil encodingError:nil requestConfiguration:nil]; -} - -- (nullable instancetype)initWithRequestBody:(nullable id)requestBody - encodingError:(nullable NSError *)encodingError - requestConfiguration: - (nullable FIRAuthRequestConfiguration *)requestConfiguration { - self = [super init]; - if (self) { - _requestBody = requestBody; - _requestEncodingError = encodingError; - _requestConfiguration = requestConfiguration; - } - return self; -} - -- (NSURL *)requestURL { - return [NSURL URLWithString:kFakeRequestURL]; -} - -- (BOOL)containsPostBody { - return YES; -} - -- (FIRAuthRequestConfiguration *)requestConfiguration { - if (!_requestConfiguration) { - _requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:kFakeAPIkey - appID:kFakeFirebaseAppID - auth:nil]; - } - return _requestConfiguration; -} - -- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { - if (error) { - *error = _requestEncodingError; - } - return _requestBody; -} - -@end - -#pragma mark - FIRFakeResponse - -/** @class FIRFakeResponse - @brief Allows us to inspect the dictionaries received by @c FIRAuthRPCResponse classes, and - provide deterministic responses to the @c setWithDictionary:error: - methods. - */ -@interface FIRFakeResponse : NSObject - -/** @property receivedDictionary - @brief The dictionary passed to the @c setWithDictionary:error: method. - */ -@property(nonatomic, strong, readonly, nullable) NSDictionary *receivedDictionary; - -/** @fn fakeResponse - @brief A "normal" sucessful response (no error, no expected kind.) - */ -+ (nullable instancetype)fakeResponse; - -/** @fn fakeResponseWithDecodingError - @brief A response which returns a fake error during the decoding process. - */ -+ (nullable instancetype)fakeResponseWithDecodingError; - -/** @fn init - @brief Please use initWithDecodingError: - */ -- (nullable instancetype)init NS_UNAVAILABLE; - -- (nullable instancetype)initWithDecodingError:(nullable NSError *)decodingError - NS_DESIGNATED_INITIALIZER; - -@end - -@implementation FIRFakeResponse { - /** @var _responseDecodingError - @brief The value to return for an error when the @c setWithDictionary:error: method is - invoked. - */ - NSError *_Nullable _responseDecodingError; -} - -+ (nullable instancetype)fakeResponse { - return [[self alloc] initWithDecodingError:nil]; -} - -+ (nullable instancetype)fakeResponseWithDecodingError { - NSError *decodingError = [FIRAuthErrorUtils unexpectedErrorResponseWithDeserializedResponse:self]; - return [[self alloc] initWithDecodingError:decodingError]; -} - -- (nullable instancetype)initWithDecodingError:(nullable NSError *)decodingError { - self = [super init]; - if (self) { - _responseDecodingError = decodingError; - } - return self; -} - -- (BOOL)setWithDictionary:(NSDictionary *)dictionary error:(NSError *_Nullable *_Nullable)error { - if (_responseDecodingError) { - if (error) { - *error = _responseDecodingError; - } - return NO; - } - _receivedDictionary = dictionary; - return YES; -} - -@end - -#pragma mark - FIRAuthBackendRPCImplementationTests - -/** @class FIRAuthBackendRPCImplementationTests - @brief This set of unit tests is designed primarily to test the possible outcomes of the - @c FIRAuthBackendRPCImplementation.postWithRequest:response:callback: method. - */ -@interface FIRAuthBackendRPCImplementationTests : XCTestCase -@end -@implementation FIRAuthBackendRPCImplementationTests { - /** @var _RPCIssuer - @brief This backend RPC issuer is used to fake network responses for each test in the suite. - In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it. - */ - FIRFakeBackendRPCIssuer *_RPCIssuer; - - /** @var _RPCImplementation - @brief This backend RPC implementation is used to make fake network requests for each test in - the suite. - */ - FIRAuthBackendRPCImplementation *_RPCImplementation; -} - -- (void)setUp { - FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init]; - [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer]; - _RPCIssuer = RPCIssuer; - _RPCImplementation = [FIRAuthBackend implementation]; -} - -- (void)tearDown { - [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil]; - _RPCIssuer = nil; - _RPCImplementation = nil; -} - -#ifdef TODO -// Restore Heartbeats for CocoaPods. - -/** @fn testRequest_IncludesHeartbeatPayload_WhenHeartbeatsNeedSending - @brief This test checks the behavior of @c postWithRequest:response:callback: - to verify that a heartbeats payload is attached as a header to an - outgoing request when there are stored heartbeats that need sending. - */ -- (void)testRequest_IncludesHeartbeatPayload_WhenHeartbeatsNeedSending { - // Given - FIRFakeHeartbeatLogger *fakeHeartbeatLogger = [[FIRFakeHeartbeatLogger alloc] init]; - FIRAuthRequestConfiguration *requestConfiguration = - [[FIRAuthRequestConfiguration alloc] initWithAPIKey:kFakeAPIkey - appID:kFakeFirebaseAppID - heartbeatLogger:fakeHeartbeatLogger]; - FIRFakeRequest *request = - [FIRFakeRequest fakeRequestWithRequestConfiguration:requestConfiguration]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - // When - FIRHeartbeatsPayload *nonEmptyHeartbeatsPayload = - [FIRHeartbeatLoggingTestUtils nonEmptyHeartbeatsPayload]; - - fakeHeartbeatLogger.onFlushHeartbeatsIntoPayloadHandler = ^FIRHeartbeatsPayload * { - return nonEmptyHeartbeatsPayload; - }; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - // Then - NSString *expectedHeader = FIRHeaderValueFromHeartbeatsPayload(nonEmptyHeartbeatsPayload); - XCTAssertEqualObjects([_RPCIssuer.completeRequest valueForHTTPHeaderField:@"X-Firebase-Client"], - expectedHeader); -} - -/** @fn testRequest_DoesNotIncludeAHeartbeatPayload_WhenNoHeartbeatsNeedSending - @brief This test checks the behavior of @c postWithRequest:response:callback: - to verify that a request header does not contain heartbeat data in the - case that there are no stored heartbeats that need sending. - */ -- (void)testRequest_DoesNotIncludeAHeartbeatPayload_WhenNoHeartbeatsNeedSending { - // Given - FIRFakeHeartbeatLogger *fakeHeartbeatLogger = [[FIRFakeHeartbeatLogger alloc] init]; - FIRAuthRequestConfiguration *requestConfiguration = - [[FIRAuthRequestConfiguration alloc] initWithAPIKey:kFakeAPIkey - appID:kFakeFirebaseAppID - heartbeatLogger:fakeHeartbeatLogger]; - FIRFakeRequest *request = - [FIRFakeRequest fakeRequestWithRequestConfiguration:requestConfiguration]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - // When - FIRHeartbeatsPayload *emptyHeartbeatsPayload = - [FIRHeartbeatLoggingTestUtils emptyHeartbeatsPayload]; - - fakeHeartbeatLogger.onFlushHeartbeatsIntoPayloadHandler = ^FIRHeartbeatsPayload * { - return emptyHeartbeatsPayload; - }; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - // Then - XCTAssertNil([_RPCIssuer.completeRequest valueForHTTPHeaderField:@"X-Firebase-Client"]); -} -#endif - -/** @fn testRequestEncodingError - @brief This test checks the behaviour of @c postWithRequest:response:callback: when the - request passed returns an error during it's unencodedHTTPRequestBodyWithError: method. - The error returned should be delivered to the caller without any change. - */ -- (void)testRequestEncodingError { - NSError *encodingError = [NSError errorWithDomain:kFakeErrorDomain - code:kFakeErrorCode - userInfo:@{}]; - FIRFakeRequest *request = [FIRFakeRequest fakeRequestWithEncodingError:encodingError]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - // There is no need to call [_RPCIssuer respondWithError:...] in this test because a request - // should never have been tried - and we we know that's the case when we test @c callbackInvoked. - - XCTAssert(callbackInvoked); - - XCTAssertNotNil(callbackError); - XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError); - - NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingError); - XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain); - XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeRPCRequestEncodingError); - - NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingUnderlyingError); - XCTAssertEqualObjects(underlyingUnderlyingError.domain, kFakeErrorDomain); - XCTAssertEqual(underlyingUnderlyingError.code, kFakeErrorCode); - - id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]; - XCTAssertNil(deserializedResponse); - - id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey]; - XCTAssertNil(dataResponse); -} - -/** @fn testBodyDataSerializationError - @brief This test checks the behaviour of @c postWithRequest:response:callback: when the - request returns an object which isn't serializable by @c NSJSONSerialization. - The error from @c NSJSONSerialization should be returned as the underlyingError for an - @c NSError with the code @c FIRAuthErrorCodeJSONSerializationError. - */ -- (void)testBodyDataSerializationError { - FIRFakeRequest *request = [FIRFakeRequest fakeRequestWithUnserializableRequestBody]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - // There is no need to call [_RPCIssuer respondWithError:...] in this test because a request - // should never have been tried - and we we know that's the case when we test @c callbackInvoked. - - XCTAssert(callbackInvoked); - - XCTAssertNotNil(callbackError); - XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError); - - NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingError); - XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain); - XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeJSONSerializationError); - - NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNil(underlyingUnderlyingError); - - id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]; - XCTAssertNil(deserializedResponse); - - id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey]; - XCTAssertNil(dataResponse); -} - -/** @fn testNetworkError - @brief This test checks to make sure a network error is properly wrapped and forwarded with the - correct code (FIRAuthErrorCodeNetworkError). - */ -- (void)testNetworkError { - FIRFakeRequest *request = [FIRFakeRequest fakeRequest]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - // It shouldn't matter what the error domain/code/userInfo are, any junk values are suitable. The - // implementation should treat any error with no response data as a network error. - NSError *responseError = [NSError errorWithDomain:kFakeErrorDomain - code:kFakeErrorCode - userInfo:nil]; - [_RPCIssuer respondWithError:responseError]; - - XCTAssert(callbackInvoked); - - XCTAssertNotNil(callbackError); - XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeNetworkError); - - NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingError); - XCTAssertEqualObjects(underlyingError.domain, kFakeErrorDomain); - XCTAssertEqual(underlyingError.code, kFakeErrorCode); - - NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNil(underlyingUnderlyingError); - - id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]; - XCTAssertNil(deserializedResponse); - - id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey]; - XCTAssertNil(dataResponse); -} - -/** @fn testUnparsableErrorResponse - @brief This test checks the behaviour of @c postWithRequest:response:callback: when the - response isn't deserializable by @c NSJSONSerialization and an error - condition (with an associated error response message) was expected. We are expecting to - receive the original network error wrapped in an @c NSError with the code - @c FIRAuthErrorCodeUnexpectedHTTPResponse. - */ -- (void)testUnparsableErrorResponse { - FIRFakeRequest *request = [FIRFakeRequest fakeRequest]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - NSData *data = - [@"An error occurred." dataUsingEncoding:NSUTF8StringEncoding]; - NSError *error = [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{}]; - [_RPCIssuer respondWithData:data error:error]; - - XCTAssert(callbackInvoked); - - XCTAssertNotNil(callbackError); - XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError); - - NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingError); - XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain); - XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse); - - NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingUnderlyingError); - XCTAssertEqualObjects(underlyingUnderlyingError.domain, kFakeErrorDomain); - XCTAssertEqual(underlyingUnderlyingError.code, kFakeErrorCode); - - id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]; - XCTAssertNil(deserializedResponse); - - id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey]; - XCTAssertNotNil(dataResponse); - XCTAssertEqualObjects(dataResponse, data); -} - -/** @fn testUnparsableSuccessResponse - @brief This test checks the behaviour of @c postWithRequest:response:callback: when the - response isn't deserializable by @c NSJSONSerialization and no error - condition was indicated. We are expecting to - receive the @c NSJSONSerialization error wrapped in an @c NSError with the code - @c FIRAuthErrorCodeUnexpectedServerResponse. - */ -- (void)testUnparsableSuccessResponse { - FIRFakeRequest *request = [FIRFakeRequest fakeRequest]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - NSData *data = [@"Some non-JSON value." dataUsingEncoding:NSUTF8StringEncoding]; - [_RPCIssuer respondWithData:data error:nil]; - - XCTAssert(callbackInvoked); - - XCTAssertNotNil(callbackError); - XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError); - - NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingError); - XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain); - XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedResponse); - - NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingUnderlyingError); - XCTAssertEqualObjects(underlyingUnderlyingError.domain, NSCocoaErrorDomain); - - id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]; - XCTAssertNil(deserializedResponse); - - id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey]; - XCTAssertNotNil(dataResponse); - XCTAssertEqualObjects(dataResponse, data); -} - -/** @fn testNonDictionaryErrorResponse - @brief This test checks the behaviour of @c postWithRequest:response:callback: when the - response deserialized by @c NSJSONSerialization is not a dictionary, and an error was - expected. We are expecting to receive the original network error wrapped in an @c NSError - with the code @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded response - in the @c NSError.userInfo dictionary associated with the key - @c FIRAuthErrorUserInfoDeserializedResponseKey. - */ -- (void)testNonDictionaryErrorResponse { - FIRFakeRequest *request = [FIRFakeRequest fakeRequest]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - // We are responding with a JSON-encoded string value representing an array - which is unexpected. - // It should normally be a dictionary, and we need to check for this sort of thing. Because we can - // successfully decode this value, however, we do return it in the error results. We check for - // this array later in the test. - NSData *data = [@"[]" dataUsingEncoding:NSUTF8StringEncoding]; - NSError *error = [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{}]; - [_RPCIssuer respondWithData:data error:error]; - - XCTAssert(callbackInvoked); - - XCTAssertNotNil(callbackError); - XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError); - - NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingError); - XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain); - XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse); - - NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingUnderlyingError); - XCTAssertEqualObjects(underlyingUnderlyingError.domain, kFakeErrorDomain); - XCTAssertEqual(underlyingUnderlyingError.code, kFakeErrorCode); - - id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]; - XCTAssertNotNil(deserializedResponse); - XCTAssert([deserializedResponse isKindOfClass:[NSArray class]]); - - id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey]; - XCTAssertNil(dataResponse); -} - -/** @fn testNonDictionarySuccessResponse - @brief This test checks the behaviour of @c postWithRequest:response:callback: when the - response deserialized by @c NSJSONSerialization is not a dictionary, and no error was - expected. We are expecting to receive an @c NSError with the code - @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded response in the - @c NSError.userInfo dictionary associated with the key - @c FIRAuthErrorUserInfoDecodedResponseKey. - */ -- (void)testNonDictionarySuccessResponse { - FIRFakeRequest *request = [FIRFakeRequest fakeRequest]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - // We are responding with a JSON-encoded string value representing an array - which is unexpected. - // It should normally be a dictionary, and we need to check for this sort of thing. Because we can - // successfully decode this value, however, we do return it in the error results. We check for - // this array later in the test. - NSData *data = [@"[]" dataUsingEncoding:NSUTF8StringEncoding]; - [_RPCIssuer respondWithData:data error:nil]; - - XCTAssert(callbackInvoked); - - XCTAssertNotNil(callbackError); - XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError); - - NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingError); - XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain); - XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedResponse); - - NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNil(underlyingUnderlyingError); - - id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]; - XCTAssertNotNil(deserializedResponse); - XCTAssert([deserializedResponse isKindOfClass:[NSArray class]]); - - id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey]; - XCTAssertNil(dataResponse); -} - -/** @fn testCaptchaRequiredResponse - @brief This test checks the behaviour of @c postWithRequest:response:callback: when the - we get an error message indicating captcha is required. The backend should not be returning - this error to mobile clients. If it does, we should wrap it in an @c NSError with the code - @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded error message in the - @c NSError.userInfo dictionary associated with the key - @c FIRAuthErrorUserInfoDeserializedResponseKey. - */ -- (void)testCaptchaRequiredResponse { - FIRFakeRequest *request = [FIRFakeRequest fakeRequest]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - NSError *error = [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{}]; - [_RPCIssuer respondWithServerErrorMessage:kErrorMessageCaptchaRequired error:error]; - - XCTAssert(callbackInvoked); - - XCTAssertNotNil(callbackError); - XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError); - - NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingError); - XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain); - XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse); - - NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingUnderlyingError); - XCTAssertEqualObjects(underlyingUnderlyingError.domain, kFakeErrorDomain); - XCTAssertEqual(underlyingUnderlyingError.code, kFakeErrorCode); - - id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]; - XCTAssertNotNil(deserializedResponse); - XCTAssert([deserializedResponse isKindOfClass:[NSDictionary class]]); - XCTAssertNotNil(deserializedResponse[@"message"]); - - id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey]; - XCTAssertNil(dataResponse); -} - -/** @fn testCaptchaCheckFailedResponse - @brief This test checks the behaviour of @c postWithRequest:response:callback: when the - we get an error message indicating captcha check failed. The backend should not be returning - this error to mobile clients. If it does, we should wrap it in an @c NSError with the code - @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded error message in the - @c NSError.userInfo dictionary associated with the key - @c FIRAuthErrorUserInfoDecodedErrorResponseKey. - */ -- (void)testCaptchaCheckFailedResponse { - FIRFakeRequest *request = [FIRFakeRequest fakeRequest]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - NSError *error = [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{}]; - [_RPCIssuer respondWithServerErrorMessage:kErrorMessageCaptchaCheckFailed error:error]; - - XCTAssert(callbackInvoked); - - XCTAssertNotNil(callbackError); - XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeCaptchaCheckFailed); -} - -/** @fn testCaptchaRequiredInvalidPasswordResponse - @brief This test checks the behaviour of @c postWithRequest:response:callback: when the - we get an error message indicating captcha is required and an invalid password was entered. - The backend should not be returning this error to mobile clients. If it does, we should wrap - it in an @c NSError with the code - @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded error message in the - @c NSError.userInfo dictionary associated with the key - @c FIRAuthErrorUserInfoDeserializedResponseKey. - */ -- (void)testCaptchaRequiredInvalidPasswordResponse { - FIRFakeRequest *request = [FIRFakeRequest fakeRequest]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - NSError *error = [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{}]; - [_RPCIssuer respondWithServerErrorMessage:kErrorMessageCaptchaRequiredInvalidPassword - error:error]; - - XCTAssert(callbackInvoked); - - XCTAssertNotNil(callbackError); - XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError); - - NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingError); - XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain); - XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse); - - NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingUnderlyingError); - XCTAssertEqualObjects(underlyingUnderlyingError.domain, kFakeErrorDomain); - XCTAssertEqual(underlyingUnderlyingError.code, kFakeErrorCode); - - id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]; - XCTAssertNotNil(deserializedResponse); - XCTAssert([deserializedResponse isKindOfClass:[NSDictionary class]]); - XCTAssertNotNil(deserializedResponse[@"message"]); - - id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey]; - XCTAssertNil(dataResponse); -} - -/** @fn testDecodableErrorResponseWithUnknownMessage - @brief This test checks the behaviour of @c postWithRequest:response:callback: when the - response deserialized by @c NSJSONSerialization represents a valid error response (and an - error was indicated) but we didn't receive an error message we know about. We are expecting - to receive the original network error wrapped in an @c NSError with the code - @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded - error message in the @c NSError.userInfo dictionary associated with the key - @c FIRAuthErrorUserInfoDeserializedResponseKey. - */ -- (void)testDecodableErrorResponseWithUnknownMessage { - FIRFakeRequest *request = [FIRFakeRequest fakeRequest]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - // We need to return a valid "error" response here, but we are going to intentionally use a bogus - // error message. - NSError *error = [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{}]; - [_RPCIssuer respondWithServerErrorMessage:kUnknownServerErrorMessage error:error]; - - XCTAssert(callbackInvoked); - - XCTAssertNotNil(callbackError); - XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError); - - NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingError); - XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain); - XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse); - - NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingUnderlyingError); - XCTAssertEqualObjects(underlyingUnderlyingError.domain, kFakeErrorDomain); - XCTAssertEqual(underlyingUnderlyingError.code, kFakeErrorCode); - - id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]; - XCTAssertNotNil(deserializedResponse); - XCTAssert([deserializedResponse isKindOfClass:[NSDictionary class]]); - XCTAssertNotNil(deserializedResponse[@"message"]); - - id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey]; - XCTAssertNil(dataResponse); -} - -/** @fn testErrorResponseWithNoErrorMessage - @brief This test checks the behaviour of @c postWithRequest:response:callback: when the - response deserialized by @c NSJSONSerialization is a dictionary, and an error was indicated, - but no error information was present in the decoded response. We are expecting to receive - the original network error wrapped in an @c NSError with the code - @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded - response message in the @c NSError.userInfo dictionary associated with the key - @c FIRAuthErrorUserInfoDeserializedResponseKey. - */ -- (void)testErrorResponseWithNoErrorMessage { - FIRFakeRequest *request = [FIRFakeRequest fakeRequest]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - NSError *error = [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{}]; - [_RPCIssuer respondWithJSON:@{} error:error]; - - XCTAssert(callbackInvoked); - - XCTAssertNotNil(callbackError); - XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError); - - NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingError); - XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain); - XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse); - - NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingUnderlyingError); - XCTAssertEqualObjects(underlyingUnderlyingError.domain, kFakeErrorDomain); - XCTAssertEqual(underlyingUnderlyingError.code, kFakeErrorCode); - - id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]; - XCTAssertNotNil(deserializedResponse); - XCTAssert([deserializedResponse isKindOfClass:[NSDictionary class]]); - - id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey]; - XCTAssertNil(dataResponse); -} - -/** @fn testClientErrorResponse - @brief This test checks the behaviour of @c postWithRequest:response:callback: when the - response contains a client error specified by an error messsage sent from the backend. - */ -- (void)testClientErrorResponse { - FIRFakeRequest *request = [FIRFakeRequest fakeRequest]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackerror; - __block BOOL callBackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callBackInvoked = YES; - callbackerror = error; - }]; - NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil]; - NSString *customErrorMessage = - [NSString stringWithFormat:@"%@%@%@", kUserDisabledErrorMessage, kServerErrorDetailMarker, - kFakeUserDisabledCustomErrorMessage]; - [_RPCIssuer respondWithServerErrorMessage:customErrorMessage error:error]; - XCTAssertNotNil(callbackerror, @"An error should be returned from callback."); - XCTAssert(callBackInvoked); - XCTAssertEqual(callbackerror.code, FIRAuthErrorCodeUserDisabled); - NSString *customMessage = callbackerror.userInfo[NSLocalizedDescriptionKey]; - XCTAssertEqualObjects(customMessage, kFakeUserDisabledCustomErrorMessage); -} - -/** @fn testUndecodableSuccessResponse - @brief This test checks the behaviour of @c postWithRequest:response:callback: when the - response isn't decodable by the response class but no error condition was expected. We are - expecting to receive an @c NSError with the code - @c FIRAuthErrorCodeUnexpectedServerResponse and the error from @c setWithDictionary:error: - as the value of the underlyingError. - */ -- (void)testUndecodableSuccessResponse { - FIRFakeRequest *request = [FIRFakeRequest fakeRequest]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponseWithDecodingError]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - // It doesn't matter what we respond with here, as long as it's not an error response. The fake - // response will deterministicly simulate a decoding error regardless of the response value it was - // given. - [_RPCIssuer respondWithJSON:@{}]; - - XCTAssert(callbackInvoked); - - XCTAssertNotNil(callbackError); - XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain); - XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError); - - NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey]; - XCTAssertNotNil(underlyingError); - XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain); - XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeRPCResponseDecodingError); - - id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]; - XCTAssertNotNil(deserializedResponse); - XCTAssert([deserializedResponse isKindOfClass:[NSDictionary class]]); - - id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey]; - XCTAssertNil(dataResponse); -} - -/** @fn testSuccessfulResponse - @brief Tests that a decoded dictionary is handed to the response instance. - */ -- (void)testSuccessfulResponse { - FIRFakeRequest *request = [FIRFakeRequest fakeRequest]; - FIRFakeResponse *response = [FIRFakeResponse fakeResponse]; - - __block NSError *callbackError; - __block BOOL callbackInvoked; - [_RPCImplementation postWithRequest:request - response:response - callback:^(NSError *error) { - callbackInvoked = YES; - callbackError = error; - }]; - - [_RPCIssuer respondWithJSON:@{kTestKey : kTestValue}]; - - XCTAssert(callbackInvoked); - XCTAssertNil(callbackError); - XCTAssertNotNil(response.receivedDictionary); - XCTAssertEqualObjects(response.receivedDictionary[kTestKey], kTestValue); -} - -@end diff --git a/FirebaseAuth/Tests/Unit/FIRAuthTests.m b/FirebaseAuth/Tests/Unit/FIRAuthTests.m index 6e9afc41ac6..4245e595cf7 100644 --- a/FirebaseAuth/Tests/Unit/FIRAuthTests.m +++ b/FirebaseAuth/Tests/Unit/FIRAuthTests.m @@ -26,7 +26,6 @@ #import "FirebaseAuth/Sources/Auth/FIRAuthDispatcher.h" #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h" #import "FirebaseAuth/Sources/Auth/FIRAuth_Internal.h" -#import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h" @import FirebaseAuth; #import "FirebaseAuth/Sources/User/FIRUser_Internal.h" #import "FirebaseAuth/Tests/Unit/FIRApp+FIRAuthUnitTests.h" @@ -300,7 +299,7 @@ - (void)setUp { #endif // TARGET_OS_IOS _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation)); - [FIRAuthBackend setBackendImplementation:_mockBackend]; + [FIRAuthBackend2 setBackendImplementation:_mockBackend]; [FIRApp resetAppForAuthUnitTests]; // Set FIRAuthDispatcher implementation in order to save the token refresh task for later @@ -324,7 +323,7 @@ - (void)setUp { } - (void)tearDown { - [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil]; + [FIRAuthBackend2 setDefaultBackendImplementationWithRPCIssuer:nil]; [[FIRAuthDispatcher sharedInstance] setDispatchAfterImplementation:nil]; #if TARGET_OS_IOS diff --git a/FirebaseAuth/Tests/Unit/FIRFakeBackendRPCIssuer.h b/FirebaseAuth/Tests/Unit/FIRFakeBackendRPCIssuer.h deleted file mode 100644 index ff35c6ab0f1..00000000000 --- a/FirebaseAuth/Tests/Unit/FIRFakeBackendRPCIssuer.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2017 Google - * - * 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. - */ - -#import - -#import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h" - -NS_ASSUME_NONNULL_BEGIN - -/** @class FIRFakeBackendRPCIssuer - @brief An implementation of @c FIRAuthBackendRPCIssuer which is used to test backend request, - response, and glue logic. - */ -@interface FIRFakeBackendRPCIssuer : NSObject - -/** @property requestURL - @brief The URL which was requested. - */ -@property(nonatomic, readonly) NSURL *requestURL; - -/** @property requestData - @brief The raw data in the POST body. - */ -@property(nonatomic, readonly) NSData *requestData; - -/** @property decodedRequest - @brief The raw data in the POST body decoded as JSON. - */ -@property(nonatomic, readonly) NSDictionary *decodedRequest; - -/** @property contentType - @brief The value of the content type HTTP header in the request. - */ -@property(nonatomic, readonly) NSString *contentType; - -/** @property completeRequest - @brief The last request to be processed by the backend. - */ -@property(atomic, readonly) NSURLRequest *completeRequest; - -/** @fn respondWithData:error: - @brief Responds to a pending RPC request with data and an error. - @remarks This is useful for simulating an error response with bogus data or unexpected data - (like unexpectedly receiving an HTML body.) - @param data The data to return as the body of an HTTP response. - @param error The simulated error to return from GTM. - */ -- (void)respondWithData:(nullable NSData *)data error:(nullable NSError *)error; - -/** @fn respondWithJSON:error: - @brief Responds to a pending RPC request with JSON and an error. - @remarks This is useful for simulating an error response with error JSON. - @param JSON The JSON to return. - @param error The simulated error to return from GTM. - */ -- (nullable NSData *)respondWithJSON:(nullable NSDictionary *)JSON error:(nullable NSError *)error; - -/** @fn respondWithJSONError: - @brief Responds to a pending RPC request with a JSON server error. - @param JSON A dictionary which should be a server error encoded as JSON for fake response. - */ -- (NSData *)respondWithJSONError:(NSDictionary *)JSON; - -/** @fn respondWithError: - @brief Responds to a pending RPC request with an error. This is useful for simulating things - like a network timeout or unreachable host. - @param error The simulated error to return from GTM. - */ -- (NSData *)respondWithError:(NSError *)error; - -/** @fn respondWithServerErrorMessage:error: - @brief Responds to a pending RPC request with a server error message. - @param errorMessage The simulated error message to return from the server. - @param error The simulated error to return from GTM. - */ -- (NSData *)respondWithServerErrorMessage:(NSString *)errorMessage error:(NSError *)error; - -/** @fn respondWithServerErrorMessage: - @brief Responds to a pending RPC request with a server error message. - @param errorMessage The simulated error message to return from the server. - */ -- (NSData *)respondWithServerErrorMessage:(NSString *)errorMessage; - -/** @fn respondWithJSON: - @brief Responds to a pending RPC request with JSON. - @param JSON A dictionary which should be encoded as JSON for a fake response. - */ -- (NSData *)respondWithJSON:(NSDictionary *)JSON; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Tests/Unit/FIRFakeBackendRPCIssuer.m b/FirebaseAuth/Tests/Unit/FIRFakeBackendRPCIssuer.m deleted file mode 100644 index 23beda492ae..00000000000 --- a/FirebaseAuth/Tests/Unit/FIRFakeBackendRPCIssuer.m +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2017 Google - * - * 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. - */ - -#import "FirebaseAuth/Tests/Unit/FIRFakeBackendRPCIssuer.h" -#import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h" - -@interface FIRAuthBackend (Internal) -+ (NSMutableURLRequest *)requestWithURL:(NSURL *)URL - contentType:(NSString *)contentType - requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration; -@end - -/** @var kFakeErrorDomain - @brief Fake error domain used for testing. - */ -static NSString *const kFakeErrorDomain = @"fake domain"; - -@implementation FIRFakeBackendRPCIssuer { - /** @var _handler - @brief A block we must invoke when @c respondWithError or @c respondWithJSON are called. - */ - FIRAuthBackendRPCIssuerCompletionHandler _handler; -} - -- (void)asyncPostToURLWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration - URL:(NSURL *)URL - body:(NSData *)body - contentType:(NSString *)contentType - completionHandler:(FIRAuthBackendRPCIssuerCompletionHandler)handler { - _requestURL = [URL copy]; - if (body) { - _requestData = body; - // Use the real implementation so that the complete request can - // be verified during testing. - _completeRequest = [FIRAuthBackend requestWithURL:URL - contentType:contentType - requestConfiguration:requestConfiguration]; - NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:body options:0 error:nil]; - _decodedRequest = JSON; - } - _contentType = contentType; - _handler = handler; -} - -- (void)respondWithData:(NSData *)data error:(NSError *)error { - NSAssert(_handler, @"There is no pending RPC request."); - NSAssert(data || error, @"At least one of: data or error should be been non-nil."); - FIRAuthBackendRPCIssuerCompletionHandler handler = _handler; - _handler = nil; - handler(data, error); -} - -- (NSData *)respondWithServerErrorMessage:(NSString *)errorMessage error:(NSError *)error { - return [self respondWithJSON:@{@"error" : @{@"message" : errorMessage}} error:error]; -} - -- (NSData *)respondWithServerErrorMessage:(NSString *)errorMessage { - NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil]; - return [self respondWithServerErrorMessage:errorMessage error:error]; -} - -- (nullable NSData *)respondWithJSON:(NSDictionary *)JSON error:(NSError *)error { - NSError *JSONEncodingError; - NSData *data; - if (JSON) { - data = [NSJSONSerialization dataWithJSONObject:JSON - options:NSJSONWritingPrettyPrinted - error:&JSONEncodingError]; - } - NSAssert(!JSONEncodingError, @"An error occurred encoding the JSON response."); - [self respondWithData:data error:error]; - return data; -} - -- (NSData *)respondWithJSONError:(NSDictionary *)JSONError { - return [self respondWithJSON:JSONError - error:[NSError errorWithDomain:kFakeErrorDomain code:0 userInfo:nil]]; -} - -- (NSData *)respondWithError:(NSError *)error { - return [self respondWithJSON:nil error:error]; -} - -- (NSData *)respondWithJSON:(NSDictionary *)JSON { - return [self respondWithJSON:JSON error:nil]; -} - -@end diff --git a/FirebaseAuth/Tests/Unit/FIRPhoneAuthProviderTests.m b/FirebaseAuth/Tests/Unit/FIRPhoneAuthProviderTests.m index 12e523b7d60..78f8707838e 100644 --- a/FirebaseAuth/Tests/Unit/FIRPhoneAuthProviderTests.m +++ b/FirebaseAuth/Tests/Unit/FIRPhoneAuthProviderTests.m @@ -27,13 +27,11 @@ #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h" #import "FirebaseAuth/Sources/Auth/FIRAuth_Internal.h" -#import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h" @import FirebaseAuth; #import "FirebaseAuth/Sources/SystemService/FIRAuthAPNSToken.h" #import "FirebaseAuth/Sources/SystemService/FIRAuthAPNSTokenManager.h" #import "FirebaseAuth/Sources/SystemService/FIRAuthAppCredentialManager.h" #import "FirebaseAuth/Sources/SystemService/FIRAuthNotificationManager.h" -#import "FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.h" NS_ASSUME_NONNULL_BEGIN @@ -256,7 +254,7 @@ @implementation FIRPhoneAuthProviderTests { */ id _mockURLPresenter; } - +#ifdef TODO_SWIFT - (void)setUp { [super setUp]; _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation)); @@ -305,7 +303,7 @@ - (void)testCredentialWithVerificationID { XCTAssertNil(credential.temporaryProof); XCTAssertNil(credential.phoneNumber); } -#ifdef TODO_SWIFT + /** @fn testVerifyEmptyPhoneNumber @brief Tests a failed invocation @c verifyPhoneNumber:completion: because an empty phone number was provided. diff --git a/FirebaseAuth/Tests/Unit/FIRUserTests.m b/FirebaseAuth/Tests/Unit/FIRUserTests.m index d2a6d8bc17b..9d2557c82c7 100644 --- a/FirebaseAuth/Tests/Unit/FIRUserTests.m +++ b/FirebaseAuth/Tests/Unit/FIRUserTests.m @@ -24,7 +24,6 @@ #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h" #import "FirebaseAuth/Sources/Auth/FIRAuth_Internal.h" -#import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h" @import FirebaseAuth; #import "FirebaseAuth/Sources/SystemService/FIRSecureTokenService.h" #import "FirebaseAuth/Sources/User/FIRAdditionalUserInfo_Internal.h" @@ -410,6 +409,7 @@ + (NSDictionary *)googleProfile { return kGoogleProfile; } +#ifdef TODO_SWIFT - (void)setUp { [super setUp]; _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation)); @@ -3408,7 +3408,7 @@ - (void)expectVerifyPhoneNumberRequestWithPhoneNumber:(nullable NSString *)phone }); } #endif - +#endif @end NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Tests/Unit/FakeBackendRPCIssuer.swift b/FirebaseAuth/Tests/Unit/FakeBackendRPCIssuer.swift index 7ede93907e3..cf5f2bd5437 100644 --- a/FirebaseAuth/Tests/Unit/FakeBackendRPCIssuer.swift +++ b/FirebaseAuth/Tests/Unit/FakeBackendRPCIssuer.swift @@ -91,6 +91,7 @@ class FakeBackendRPCIssuer: NSObject, AuthBackendRPCIssuer { return try respond(serverErrorMessage: errorMessage, error: error) } + @discardableResult func respond(serverErrorMessage errorMessage: String, error: NSError) throws -> Data { return try respond(withJSON: ["error": ["message": errorMessage]], error: error) } @@ -102,7 +103,7 @@ class FakeBackendRPCIssuer: NSObject, AuthBackendRPCIssuer { "errors": [["reason": errorMessage]]]], error: error) } - func respond(withJSON json: [String: Any], error: NSError? = nil) throws -> Data { + @discardableResult func respond(withJSON json: [String: Any], error: NSError? = nil) throws -> Data { let data = try JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions.prettyPrinted) try respond(withData: data, error: error) diff --git a/FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.h b/FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.h deleted file mode 100644 index 1aa8f3e80a8..00000000000 --- a/FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2017 Google - * - * 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. - */ - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** @typedef FIRAuthGeneralBlock1 - @brief A general block that takes one id and returns nothing. - */ -typedef void (^FIRAuthGeneralBlock1)(id); - -/** @typedef FIRAuthGeneralBlock2 - @brief A general block that takes two nullable ids and returns nothing. - */ -typedef void (^FIRAuthGeneralBlock2)(id _Nullable, id _Nullable); - -/** @typedef FIRAuthIdDoubleIdBlock - @brief A block that takes third parameters with types @c id, @c double, and @c id . - */ -typedef void (^FIRAuthIdDoubleIdBlock)(id, double, id); - -/** @category OCMStubRecorder(FIRAuthUnitTests) - @brief Utility methods and properties use by Firebase Auth unit tests. - */ -@interface OCMStubRecorder (FIRAuthUnitTests) - -/** @fn andCallBlock1 - @brief Calls a general block that takes one parameter as the action of the stub. - @param block1 A block that takes exactly one 'id'-compatible parameter. - @remarks The method being stubbed must take exactly one parameter, which must be - compatible with type 'id'. - */ -- (id)andCallBlock1:(FIRAuthGeneralBlock1)block1; - -/** @fn andCallBlock2 - @brief Calls a general block that takes two parameters as the action of the stub. - @param block2 A block that takes exactly two 'id'-compatible parameters. - @remarks The method being stubbed must take exactly two parameters, both of which must be - compatible with type 'id'. - */ -- (id)andCallBlock2:(FIRAuthGeneralBlock2)block2; - -/** @fn andDispatchError2 - @brief Dispatchs an error to the second callback parameter in the global auth work queue. - @param error The error to call back as the second argument to the second parameter block. - @remarks The method being stubbed must take exactly two parameters, the first of which must be - compatible with type 'id' and the second of which must be a block that takes an - 'id'-compatible parameter and an NSError* parameter. - */ -- (id)andDispatchError2:(NSError *)error; - -/** @fn andCallIdDoubleIdBlock: - @brief Calls a block that takes three parameters as the action of the stub. - @param block A block that takes exactly three parameters as described. - @remarks The method being stubbed must take exactly three parameters. Its first and the third - parameters must be compatible with type 'id' and its second parameter must be a 'double'. - */ -- (id)andCallIdDoubleIdBlock:(FIRAuthIdDoubleIdBlock)block; - -// This macro allows .andCallBlock1 shorthand to match established style of OCMStubRecorder. -#define andCallBlock1(block1) _andCallBlock1(block1) - -// This macro allows .andCallBlock2 shorthand to match established style of OCMStubRecorder. -#define andCallBlock2(block2) _andCallBlock2(block2) - -// This macro allows .andDispatchError2 shorthand to match established style of OCMStubRecorder. -#define andDispatchError2(block2) _andDispatchError2(block2) - -// This macro allows .andCallIdDoubleIdBlock shorthand to match established style of -// OCMStubRecorder. -#define andCallIdDoubleIdBlock(block) _andCallIdDoubleIdBlock(block) - -/** @property _andCallBlock1 - @brief A block that calls @c andCallBlock1: method on self. - */ -@property(nonatomic, readonly) OCMStubRecorder * (^_andCallBlock1)(FIRAuthGeneralBlock1); - -/** @property _andCallBlock2 - @brief A block that calls @c andCallBlock2: method on self. - */ -@property(nonatomic, readonly) OCMStubRecorder * (^_andCallBlock2)(FIRAuthGeneralBlock2); - -/** @property _andDispatchError2 - @brief A block that calls @c andDispatchError2: method on self. - */ -@property(nonatomic, readonly) OCMStubRecorder * (^_andDispatchError2)(NSError *); - -/** @property _andCallIdDoubleIdBlock - @brief A block that calls @c andCallBlock2: method on self. - */ -@property(nonatomic, readonly) OCMStubRecorder * (^_andCallIdDoubleIdBlock)(FIRAuthIdDoubleIdBlock); - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.m b/FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.m deleted file mode 100644 index 55c4e069a0b..00000000000 --- a/FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.m +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2017 Google - * - * 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. - */ - -#import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h" -#import "FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.h" - -/** @fn argumentOf - @brief Retrieves a specific argument from a method invocation. - @param invocation The Objective-C method invocation. - @param position The position of the argument to retrieve, starting from 0. - @return The argument at the given position that the method has been invoked with. - @remarks The argument type must be compatible with @c id . - */ -static id argumentOf(NSInvocation *invocation, int position) { - __unsafe_unretained id unretainedArgument; - // Indices 0 and 1 indicate the hidden arguments self and _cmd. Actual arguments starts from 2. - [invocation getArgument:&unretainedArgument atIndex:position + 2]; - // The argument needs to be retained, or it will be released along with the invocation object. - id argument = unretainedArgument; - return argument; -} - -/** @fn doubleArgumentOf - @brief Retrieves a specific argument of type 'double' from a method invocation. - @param invocation The Objective-C method invocation. - @param position The position of the argument to retrieve, starting from 0. - @return The argument at the given position that the method has been invoked with. - @remarks The argument type must be @c double . - */ -static double doubleArgumentOf(NSInvocation *invocation, int position) { - double argument; - // Indices 0 and 1 indicate the hidden arguments self and _cmd. Actual arguments starts from 2. - [invocation getArgument:&argument atIndex:position + 2]; - return argument; -} - -@implementation OCMStubRecorder (FIRAuthUnitTests) - -- (id)andCallBlock1:(FIRAuthGeneralBlock1)block1 { - return [self andDo:^(NSInvocation *invocation) { - block1(argumentOf(invocation, 0)); - }]; -} - -- (id)andCallBlock2:(FIRAuthGeneralBlock2)block2 { - return [self andDo:^(NSInvocation *invocation) { - block2(argumentOf(invocation, 0), argumentOf(invocation, 1)); - }]; -} - -- (id)andDispatchError2:(NSError *)error { - return [self andCallBlock2:^(id request, FIRAuthGeneralBlock2 callback) { - dispatch_async(FIRAuthGlobalWorkQueue(), ^() { - callback(nil, error); - }); - }]; -} - -- (id)andCallIdDoubleIdBlock:(FIRAuthIdDoubleIdBlock)block { - return [self andDo:^(NSInvocation *invocation) { - block(argumentOf(invocation, 0), doubleArgumentOf(invocation, 2), argumentOf(invocation, 2)); - }]; -} - -- (OCMStubRecorder * (^)(FIRAuthGeneralBlock1))_andCallBlock1 { - return ^(FIRAuthGeneralBlock1 block1) { - return [self andCallBlock1:block1]; - }; -} - -- (OCMStubRecorder * (^)(FIRAuthGeneralBlock2))_andCallBlock2 { - return ^(FIRAuthGeneralBlock2 block2) { - return [self andCallBlock2:block2]; - }; -} - -- (OCMStubRecorder * (^)(NSError *))_andDispatchError2 { - return ^(NSError *error) { - return [self andDispatchError2:error]; - }; -} - -- (OCMStubRecorder * (^)(FIRAuthIdDoubleIdBlock))_andCallIdDoubleIdBlock { - return ^(FIRAuthIdDoubleIdBlock block) { - return [self andCallIdDoubleIdBlock:block]; - }; -} - -@end diff --git a/FirebaseAuth/Tests/Unit/RPCBaseTests.swift b/FirebaseAuth/Tests/Unit/RPCBaseTests.swift index 8343cd3e491..d921cacd45d 100644 --- a/FirebaseAuth/Tests/Unit/RPCBaseTests.swift +++ b/FirebaseAuth/Tests/Unit/RPCBaseTests.swift @@ -34,10 +34,12 @@ class RPCBaseTests: XCTestCase { let kTestIdentifier = "Identifier" var RPCIssuer: FakeBackendRPCIssuer? + var rpcImplementation: AuthBackendImplementation? override func setUp() { RPCIssuer = FakeBackendRPCIssuer() AuthBackend.setDefaultBackendImplementationWithRPCIssuer(issuer: RPCIssuer) + rpcImplementation = AuthBackend.implementation() } override func tearDown() { diff --git a/Package.swift b/Package.swift index fad6b8601ae..87b14c57646 100644 --- a/Package.swift +++ b/Package.swift @@ -460,7 +460,6 @@ let package = Package( name: "AuthUnit", dependencies: [ "FirebaseAuth", - .product(name: "OCMock", package: "OCMock"), "HeartbeatLoggingTestUtils", ], path: "FirebaseAuth/Tests/Unit", From 53c89aa55607df9a064749e2d1c86ef5a5bee0c4 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Sun, 5 Mar 2023 18:29:53 -0800 Subject: [PATCH 2/3] Two more deletes --- FirebaseAuth/Sources/Backend/FIRAuthBackend.h | 116 ---- FirebaseAuth/Sources/Backend/FIRAuthBackend.m | 532 ------------------ 2 files changed, 648 deletions(-) delete mode 100644 FirebaseAuth/Sources/Backend/FIRAuthBackend.h delete mode 100644 FirebaseAuth/Sources/Backend/FIRAuthBackend.m diff --git a/FirebaseAuth/Sources/Backend/FIRAuthBackend.h b/FirebaseAuth/Sources/Backend/FIRAuthBackend.h deleted file mode 100644 index 9d9dcf45dfc..00000000000 --- a/FirebaseAuth/Sources/Backend/FIRAuthBackend.h +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2017 Google - * - * 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. - */ - -#import - -@protocol FIRAuthRPCRequest; -@protocol FIRAuthRPCResponse; -@class FIRAuthRequestConfiguration; -@class FIRVerifyPhoneNumberResponse; -// TODO: FIRSignUpNewUserResponse Used in extra internal functions in FIRAuth.m -@class FIRSignUpNewUserResponse; - -@protocol FIRAuthBackendImplementation; -@protocol FIRAuthBackendRPCIssuer; - -NS_ASSUME_NONNULL_BEGIN - -/** @typedef FIRAuthBackendRPCIssuerCompletionHandler - @brief The type of block used to return the result of a call to an endpoint. - @param data The HTTP response body. - @param error The error which occurred, if any. - @remarks One of response or error will be non-nil. - */ -typedef void (^FIRAuthBackendRPCIssuerCompletionHandler)(NSData *_Nullable data, - NSError *_Nullable error); - -/** @typedef FIRSignupNewUserCallback - @brief The type of block used to return the result of a call to the signupNewUser endpoint. - @param response The received response, if any. - @param error The error which occurred, if any. - @remarks One of response or error will be non-nil. - */ -typedef void (^FIRSignupNewUserCallback)(FIRSignUpNewUserResponse *_Nullable response, - NSError *_Nullable error); - -/** @typedef FIRVerifyPhoneNumberResponseCallback - @brief The type of block used to return the result of a call to the verifyPhoneNumber endpoint. - @param response The received response, if any. - @param error The error which occurred, if any. - @remarks One of response or error will be non-nil. - */ -typedef void (^FIRVerifyPhoneNumberResponseCallback)( - FIRVerifyPhoneNumberResponse *_Nullable response, NSError *_Nullable error); - -/** @class FIRAuthBackend - @brief Simple static class with methods representing the backend RPCs. - @remarks All callback blocks passed as method parameters are invoked asynchronously on the - global work queue in the future. See - https://github.com/firebase/firebase-ios-sdk/tree/master/FirebaseAuth/Docs/threading.md - */ -@interface FIRAuthBackend : NSObject - -/** @fn authUserAgent - @brief Retrieves the Firebase Auth user agent. - @return The Firebase Auth user agent. - */ -+ (NSString *)authUserAgent; - -+ (id)implementation; - -/** @fn setBackendImplementation: - @brief Changes the default backend implementation to something else. - @param backendImplementation The backend implementation to use. - @remarks This is not, generally, safe to call in a scenario where other backend requests may - be occuring. This is specifically to help mock the backend for testing purposes. - */ -+ (void)setBackendImplementation:(id)backendImplementation; - -/** @fn setDefaultBackendImplementationWithRPCIssuer: - @brief Uses the default backend implementation, but with a custom RPC issuer. - @param RPCIssuer The RPC issuer to use. If @c nil, will use the default implementation. - @remarks This is not, generally, safe to call in a scenario where other backend requests may - be occuring. This is specifically to help test the backend interfaces (requests, responses, - and shared FIRAuthBackend logic.) - */ -+ (void)setDefaultBackendImplementationWithRPCIssuer: - (nullable id)RPCIssuer; - -@end - -/** @protocol FIRAuthBackendRPCIssuer - @brief Used to make FIRAuthBackend - */ -@protocol FIRAuthBackendRPCIssuer - -/** @fn asyncPostToURLWithRequestConfiguration:URL:body:contentType:completionHandler: - @brief Asynchronously seXnds a POST request. - @param requestConfiguration The request to be made. - @param URL The request URL. - @param body Request body. - @param contentType Content type of the body. - @param handler provided that handles POST response. Invoked asynchronously on the auth global - work queue in the future. - */ -- (void)asyncPostToURLWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration - URL:(NSURL *)URL - body:(nullable NSData *)body - contentType:(NSString *)contentType - completionHandler:(FIRAuthBackendRPCIssuerCompletionHandler)handler; - -@end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Sources/Backend/FIRAuthBackend.m b/FirebaseAuth/Sources/Backend/FIRAuthBackend.m deleted file mode 100644 index 79fb856d442..00000000000 --- a/FirebaseAuth/Sources/Backend/FIRAuthBackend.m +++ /dev/null @@ -1,532 +0,0 @@ -/* - * Copyright 2017 Google - * - * 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. - */ - -#import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h" - -#if SWIFT_PACKAGE -@import GTMSessionFetcherCore; -#else -#import -#import -#endif - -#import "FirebaseAuth/Sources/Public/FirebaseAuth/FirebaseAuth.h" - -#import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h" -#import "FirebaseCore/Extension/FirebaseCoreInternal.h" - -#import "FirebaseAuth-Swift.h" - -NS_ASSUME_NONNULL_BEGIN - -/** @var kClientVersionHeader - @brief HTTP header name for the client version. - */ -static NSString *const kClientVersionHeader = @"X-Client-Version"; - -/** @var kIosBundleIdentifierHeader - @brief HTTP header name for iOS bundle ID. - */ -static NSString *const kIosBundleIdentifierHeader = @"X-Ios-Bundle-Identifier"; - -/** @var kFirebaseLocalHeader - @brief HTTP header name for the firebase locale. - */ -static NSString *const kFirebaseLocalHeader = @"X-Firebase-Locale"; - -/** @var kFirebaseAppIDHeader - @brief HTTP header name for the Firebase app ID. - */ -static NSString *const kFirebaseAppIDHeader = @"X-Firebase-GMPID"; - -/** @var kFirebaseHeartbeatHeader - @brief HTTP header name for a Firebase heartbeats payload. - */ -static NSString *const kFirebaseHeartbeatHeader = @"X-Firebase-Client"; - -/** @var kFirebaseAuthCoreFrameworkMarker - @brief The marker in the HTTP header that indicates the request comes from Firebase Auth Core. - */ -static NSString *const kFirebaseAuthCoreFrameworkMarker = @"FirebaseCore-iOS"; - -/** @var kJSONContentType - @brief The value of the HTTP content-type header for JSON payloads. - */ -static NSString *const kJSONContentType = @"application/json"; - -/** @var kErrorDataKey - @brief Key for error data in NSError returned by @c GTMSessionFetcher. - */ -static NSString *const kErrorDataKey = @"data"; - -/** @var kErrorKey - @brief The key for the "error" value in JSON responses from the server. - */ -static NSString *const kErrorKey = @"error"; - -/** @var kErrorsKey - @brief The key for the "errors" value in JSON responses from the server. - */ -static NSString *const kErrorsKey = @"errors"; - -/** @var kReasonKey - @brief The key for the "reason" value in JSON responses from the server. - */ -static NSString *const kReasonKey = @"reason"; - -/** @var kInvalidKeyReasonValue - @brief The value for the "reason" key indicating an invalid API Key was received by the server. - */ -static NSString *const kInvalidKeyReasonValue = @"keyInvalid"; - -/** @var kAppNotAuthorizedReasonValue - @brief The value for the "reason" key indicating the App is not authorized to use Firebase - Authentication. - */ -static NSString *const kAppNotAuthorizedReasonValue = @"ipRefererBlocked"; - -/** @var kErrorMessageKey - @brief The key for an error's "message" value in JSON responses from the server. - */ -static NSString *const kErrorMessageKey = @"message"; - -/** @var kReturnIDPCredentialErrorMessageKey - @brief The key for "errorMessage" value in JSON responses from the server, In case - returnIDPCredential of a verifyAssertion request is set to @YES. - */ -static NSString *const kReturnIDPCredentialErrorMessageKey = @"errorMessage"; - -/** @var kUserNotFoundErrorMessage - @brief This is the error message returned when the user is not found, which means the user - account has been deleted given the token was once valid. - */ -static NSString *const kUserNotFoundErrorMessage = @"USER_NOT_FOUND"; - -/** @var kUserDeletedErrorMessage - @brief This is the error message the server will respond with if the user entered an invalid - email address. - */ -static NSString *const kUserDeletedErrorMessage = @"EMAIL_NOT_FOUND"; - -/** @var kInvalidLocalIDErrorMessage - @brief This is the error message the server responds with if the user local id in the id token - does not exit. - */ -static NSString *const kInvalidLocalIDErrorMessage = @"INVALID_LOCAL_ID"; - -/** @var kUserTokenExpiredErrorMessage - @brief The error returned by the server if the token issue time is older than the account's - valid_since time. - */ -static NSString *const kUserTokenExpiredErrorMessage = @"TOKEN_EXPIRED"; - -/** @var kTooManyRequestsErrorMessage - @brief This is the error message the server will respond with if too many requests were made to - a server method. - */ -static NSString *const kTooManyRequestsErrorMessage = @"TOO_MANY_ATTEMPTS_TRY_LATER"; - -/** @var kInvalidCustomTokenErrorMessage - @brief This is the error message the server will respond with if there is a validation error - with the custom token. - */ -static NSString *const kInvalidCustomTokenErrorMessage = @"INVALID_CUSTOM_TOKEN"; - -/** @var kCustomTokenMismatch - @brief This is the error message the server will respond with if the service account and API key - belong to different projects. - */ -static NSString *const kCustomTokenMismatch = @"CREDENTIAL_MISMATCH"; - -/** @var kInvalidCredentialErrorMessage - @brief This is the error message the server responds with if the IDP token or requestUri is - invalid. - */ -static NSString *const kInvalidCredentialErrorMessage = @"INVALID_IDP_RESPONSE"; - -/** @var kUserDisabledErrorMessage - @brief The error returned by the server if the user account is diabled. - */ -static NSString *const kUserDisabledErrorMessage = @"USER_DISABLED"; - -/** @var kOperationNotAllowedErrorMessage - @brief This is the error message the server will respond with if Admin disables IDP specified by - provider. - */ -static NSString *const kOperationNotAllowedErrorMessage = @"OPERATION_NOT_ALLOWED"; - -/** @var kPasswordLoginDisabledErrorMessage - @brief This is the error message the server responds with if password login is disabled. - */ -static NSString *const kPasswordLoginDisabledErrorMessage = @"PASSWORD_LOGIN_DISABLED"; - -/** @var kEmailAlreadyInUseErrorMessage - @brief This is the error message the server responds with if the email address already exists. - */ -static NSString *const kEmailAlreadyInUseErrorMessage = @"EMAIL_EXISTS"; - -/** @var kInvalidEmailErrorMessage - @brief The error returned by the server if the email is invalid. - */ -static NSString *const kInvalidEmailErrorMessage = @"INVALID_EMAIL"; - -/** @var kInvalidIdentifierErrorMessage - @brief The error returned by the server if the identifier is invalid. - */ -static NSString *const kInvalidIdentifierErrorMessage = @"INVALID_IDENTIFIER"; - -/** @var kWrongPasswordErrorMessage - @brief This is the error message the server will respond with if the user entered a wrong - password. - */ -static NSString *const kWrongPasswordErrorMessage = @"INVALID_PASSWORD"; - -/** @var kCredentialTooOldErrorMessage - @brief This is the error message the server responds with if account change is attempted 5 - minutes after signing in. - */ -static NSString *const kCredentialTooOldErrorMessage = @"CREDENTIAL_TOO_OLD_LOGIN_AGAIN"; - -/** @var kFederatedUserIDAlreadyLinkedMessage - @brief This is the error message the server will respond with if the federated user ID has been - already linked with another account. - */ -static NSString *const kFederatedUserIDAlreadyLinkedMessage = @"FEDERATED_USER_ID_ALREADY_LINKED"; - -/** @var kInvalidUserTokenErrorMessage - @brief This is the error message the server responds with if user's saved auth credential is - invalid, and the user needs to sign in again. - */ -static NSString *const kInvalidUserTokenErrorMessage = @"INVALID_ID_TOKEN"; - -/** @var kWeakPasswordErrorMessagePrefix - @brief This is the prefix for the error message the server responds with if user's new password - to be set is too weak. - */ -static NSString *const kWeakPasswordErrorMessagePrefix = @"WEAK_PASSWORD"; - -/** @var kExpiredActionCodeErrorMessage - @brief This is the error message the server will respond with if the action code is expired. - */ -static NSString *const kExpiredActionCodeErrorMessage = @"EXPIRED_OOB_CODE"; - -/** @var kInvalidActionCodeErrorMessage - @brief This is the error message the server will respond with if the action code is invalid. - */ -static NSString *const kInvalidActionCodeErrorMessage = @"INVALID_OOB_CODE"; - -/** @var kMissingEmailErrorMessage - @brief This is the error message the server will respond with if the email address is missing - during a "send password reset email" attempt. - */ -static NSString *const kMissingEmailErrorMessage = @"MISSING_EMAIL"; - -/** @var kInvalidSenderEmailErrorMessage - @brief This is the error message the server will respond with if the sender email is invalid - during a "send password reset email" attempt. - */ -static NSString *const kInvalidSenderEmailErrorMessage = @"INVALID_SENDER"; - -/** @var kInvalidMessagePayloadErrorMessage - @brief This is the error message the server will respond with if there are invalid parameters in - the payload during a "send password reset email" attempt. - */ -static NSString *const kInvalidMessagePayloadErrorMessage = @"INVALID_MESSAGE_PAYLOAD"; - -/** @var kInvalidRecipientEmailErrorMessage - @brief This is the error message the server will respond with if the recipient email is invalid. - */ -static NSString *const kInvalidRecipientEmailErrorMessage = @"INVALID_RECIPIENT_EMAIL"; - -/** @var kMissingIosBundleIDErrorMessage - @brief This is the error message the server will respond with if iOS bundle ID is missing but - the iOS App store ID is provided. - */ -static NSString *const kMissingIosBundleIDErrorMessage = @"MISSING_IOS_BUNDLE_ID"; - -/** @var kMissingAndroidPackageNameErrorMessage - @brief This is the error message the server will respond with if Android Package Name is missing - but the flag indicating the app should be installed is set to true. - */ -static NSString *const kMissingAndroidPackageNameErrorMessage = @"MISSING_ANDROID_PACKAGE_NAME"; - -/** @var kUnauthorizedDomainErrorMessage - @brief This is the error message the server will respond with if the domain of the continue URL - specified is not allowlisted in the Firebase console. - */ -static NSString *const kUnauthorizedDomainErrorMessage = @"UNAUTHORIZED_DOMAIN"; - -/** @var kInvalidProviderIDErrorMessage - @brief This is the error message the server will respond with if the provider id given for the - web operation is invalid. - */ -static NSString *const kInvalidProviderIDErrorMessage = @"INVALID_PROVIDER_ID"; - -/** @var kInvalidDynamicLinkDomainErrorMessage - @brief This is the error message the server will respond with if the dynamic link domain - provided in the request is invalid. - */ -static NSString *const kInvalidDynamicLinkDomainErrorMessage = @"INVALID_DYNAMIC_LINK_DOMAIN"; - -/** @var kInvalidContinueURIErrorMessage - @brief This is the error message the server will respond with if the continue URL provided in - the request is invalid. - */ -static NSString *const kInvalidContinueURIErrorMessage = @"INVALID_CONTINUE_URI"; - -/** @var kMissingContinueURIErrorMessage - @brief This is the error message the server will respond with if there was no continue URI - present in a request that required one. - */ -static NSString *const kMissingContinueURIErrorMessage = @"MISSING_CONTINUE_URI"; - -/** @var kInvalidPhoneNumberErrorMessage - @brief This is the error message the server will respond with if an incorrectly formatted phone - number is provided. - */ -static NSString *const kInvalidPhoneNumberErrorMessage = @"INVALID_PHONE_NUMBER"; - -/** @var kInvalidVerificationCodeErrorMessage - @brief This is the error message the server will respond with if an invalid verification code is - provided. - */ -static NSString *const kInvalidVerificationCodeErrorMessage = @"INVALID_CODE"; - -/** @var kInvalidSessionInfoErrorMessage - @brief This is the error message the server will respond with if an invalid session info - (verification ID) is provided. - */ -static NSString *const kInvalidSessionInfoErrorMessage = @"INVALID_SESSION_INFO"; - -/** @var kSessionExpiredErrorMessage - @brief This is the error message the server will respond with if the SMS code has expired before - it is used. - */ -static NSString *const kSessionExpiredErrorMessage = @"SESSION_EXPIRED"; - -/** @var kMissingOrInvalidNonceErrorMessage - @brief This is the error message the server will respond with if the nonce is missing or - invalid. - */ -static NSString *const kMissingOrInvalidNonceErrorMessage = @"MISSING_OR_INVALID_NONCE"; - -/** @var kMissingAppTokenErrorMessage - @brief This is the error message the server will respond with if the APNS token is missing in a - verifyClient request. - */ -static NSString *const kMissingAppTokenErrorMessage = @"MISSING_IOS_APP_TOKEN"; - -/** @var kMissingAppCredentialErrorMessage - @brief This is the error message the server will respond with if the app token is missing in a - sendVerificationCode request. - */ -static NSString *const kMissingAppCredentialErrorMessage = @"MISSING_APP_CREDENTIAL"; - -/** @var kInvalidAppCredentialErrorMessage - @brief This is the error message the server will respond with if the app credential in a - sendVerificationCode request is invalid. - */ -static NSString *const kInvalidAppCredentialErrorMessage = @"INVALID_APP_CREDENTIAL"; - -/** @var kQuoutaExceededErrorMessage - @brief This is the error message the server will respond with if the quota for SMS text messages - has been exceeded for the project. - */ -static NSString *const kQuoutaExceededErrorMessage = @"QUOTA_EXCEEDED"; - -/** @var kAppNotVerifiedErrorMessage - @brief This is the error message the server will respond with if Firebase could not verify the - app during a phone authentication flow. - */ -static NSString *const kAppNotVerifiedErrorMessage = @"APP_NOT_VERIFIED"; - -/** @var kMissingClientIdentifier - @brief This is the error message the server will respond with if Firebase could not verify the - app during a phone authentication flow when a real phone number is used and app verification - is disabled for testing. - */ -static NSString *const kMissingClientIdentifier = @"MISSING_CLIENT_IDENTIFIER"; - -/** @var kCaptchaCheckFailedErrorMessage - @brief This is the error message the server will respond with if the reCAPTCHA token provided is - invalid. - */ -static NSString *const kCaptchaCheckFailedErrorMessage = @"CAPTCHA_CHECK_FAILED"; - -/** @var kTenantIDMismatch - @brief This is the error message the server will respond with if the tenant id mismatches. - */ -static NSString *const kTenantIDMismatch = @"TENANT_ID_MISMATCH"; - -/** @var kUnsupportedTenantOperation - @brief This is the error message the server will respond with if the operation does not support - multi-tenant. - */ -static NSString *const kUnsupportedTenantOperation = @"UNSUPPORTED_TENANT_OPERATION"; - -/** @var kMissingMFAPendingCredentialErrorMessage - @brief This is the error message the server will respond with if the MFA pending credential is - missing. - */ -static NSString *const kMissingMFAPendingCredentialErrorMessage = @"MISSING_MFA_PENDING_CREDENTIAL"; - -/** @var kMissingMFAEnrollmentIDErrorMessage - @brief This is the error message the server will respond with if the MFA enrollment ID is missing. - */ -static NSString *const kMissingMFAEnrollmentIDErrorMessage = @"MISSING_MFA_ENROLLMENT_ID"; - -/** @var kInvalidMFAPendingCredentialErrorMessage - @brief This is the error message the server will respond with if the MFA pending credential is - invalid. - */ -static NSString *const kInvalidMFAPendingCredentialErrorMessage = @"INVALID_MFA_PENDING_CREDENTIAL"; - -/** @var kMFAEnrollmentNotFoundErrorMessage - @brief This is the error message the server will respond with if the MFA enrollment info is not - found. - */ -static NSString *const kMFAEnrollmentNotFoundErrorMessage = @"MFA_ENROLLMENT_NOT_FOUND"; - -/** @var kAdminOnlyOperationErrorMessage - @brief This is the error message the server will respond with if the operation is admin only. - */ -static NSString *const kAdminOnlyOperationErrorMessage = @"ADMIN_ONLY_OPERATION"; - -/** @var kUnverifiedEmailErrorMessage - @brief This is the error message the server will respond with if the email is unverified. - */ -static NSString *const kUnverifiedEmailErrorMessage = @"UNVERIFIED_EMAIL"; - -/** @var kSecondFactorExistsErrorMessage - @brief This is the error message the server will respond with if the second factor already exsists. - */ -static NSString *const kSecondFactorExistsErrorMessage = @"SECOND_FACTOR_EXISTS"; - -/** @var kSecondFactorLimitExceededErrorMessage - @brief This is the error message the server will respond with if the number of second factor - reaches the limit. - */ -static NSString *const kSecondFactorLimitExceededErrorMessage = @"SECOND_FACTOR_LIMIT_EXCEEDED"; - -/** @var kUnsupportedFirstFactorErrorMessage - @brief This is the error message the server will respond with if the first factor doesn't support - MFA. - */ -static NSString *const kUnsupportedFirstFactorErrorMessage = @"UNSUPPORTED_FIRST_FACTOR"; - -/** @var kBlockingCloudFunctionErrorResponse - @brief This is the error message blocking Cloud Functions. - */ -static NSString *const kBlockingCloudFunctionErrorResponse = @"BLOCKING_FUNCTION_ERROR_RESPONSE"; - -/** @var kEmailChangeNeedsVerificationErrorMessage - @brief This is the error message the server will respond with if changing an unverified email. - */ -static NSString *const kEmailChangeNeedsVerificationErrorMessage = - @"EMAIL_CHANGE_NEEDS_VERIFICATION"; - -/** @var kInvalidPendingToken - @brief Generic IDP error codes. - */ -static NSString *const kInvalidPendingToken = @"INVALID_PENDING_TOKEN"; - -/** @var gBackendImplementation - @brief The singleton FIRAuthBackendImplementation instance to use. - */ -static id gBackendImplementation; - -/** @class FIRAuthBackendRPCImplementation - @brief The default RPC-based backend implementation. - */ -@interface FIRAuthBackendRPCImplementation : NSObject - -/** @property RPCIssuer - @brief An instance of FIRAuthBackendRPCIssuer for making RPC requests. Allows the RPC - requests/responses to be easily faked. - */ -@property(nonatomic, strong) id RPCIssuer; - -@end - -@implementation FIRAuthBackend - -+ (id)implementation { - if (!gBackendImplementation) { - gBackendImplementation = [[FIRAuthBackendRPCImplementation alloc] init]; - } - return gBackendImplementation; -} - -+ (void)setBackendImplementation:(id)backendImplementation { - gBackendImplementation = backendImplementation; -} - -+ (void)setDefaultBackendImplementationWithRPCIssuer: - (nullable id)RPCIssuer { - FIRAuthBackendRPCImplementation *defaultImplementation = - [[FIRAuthBackendRPCImplementation alloc] init]; - if (RPCIssuer) { - defaultImplementation.RPCIssuer = RPCIssuer; - } - gBackendImplementation = defaultImplementation; -} - -+ (NSString *)authUserAgent { - return [NSString stringWithFormat:@"FirebaseAuth.iOS/%@ %@", FIRFirebaseVersion(), - GTMFetcherStandardUserAgentString(nil)]; -} - -+ (NSMutableURLRequest *)requestWithURL:(NSURL *)URL - contentType:(NSString *)contentType - requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; - [request setValue:contentType forHTTPHeaderField:@"Content-Type"]; - NSString *additionalFrameworkMarker = - requestConfiguration.additionalFrameworkMarker ?: kFirebaseAuthCoreFrameworkMarker; - NSString *clientVersion = [NSString - stringWithFormat:@"iOS/FirebaseSDK/%@/%@", FIRFirebaseVersion(), additionalFrameworkMarker]; - [request setValue:clientVersion forHTTPHeaderField:kClientVersionHeader]; - NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; - [request setValue:bundleID forHTTPHeaderField:kIosBundleIdentifierHeader]; - NSString *appID = requestConfiguration.appID; - [request setValue:appID forHTTPHeaderField:kFirebaseAppIDHeader]; - [request setValue:FIRHeaderValueFromHeartbeatsPayload( - [requestConfiguration.heartbeatLogger flushHeartbeatsIntoPayload]) - forHTTPHeaderField:kFirebaseHeartbeatHeader]; - NSArray *preferredLocalizations = [NSBundle mainBundle].preferredLocalizations; - if (preferredLocalizations.count) { - NSString *acceptLanguage = preferredLocalizations.firstObject; - [request setValue:acceptLanguage forHTTPHeaderField:@"Accept-Language"]; - } - NSString *languageCode = requestConfiguration.languageCode; - if (languageCode.length) { - [request setValue:languageCode forHTTPHeaderField:kFirebaseLocalHeader]; - } - return request; -} - -@end - -@interface FIRAuthBackendRPCIssuerImplementation : NSObject -@end - - -@implementation FIRAuthBackendRPCImplementation - - -@end - -NS_ASSUME_NONNULL_END From 52571e75a83f02b3f5f3a2325779814f0e2d3387 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 6 Mar 2023 09:26:57 -0800 Subject: [PATCH 3/3] finish --- FirebaseAuth.podspec | 1 + FirebaseAuth/Sources/Auth/FIRAuth.m | 10 +- .../Sources/Swift/Backend/AuthBackend.swift | 44 +-- .../AuthBackendRPCImplentationTests.swift | 282 ++++++++++++------ FirebaseAuth/Tests/Unit/FIRAuthTests.m | 5 +- FirebaseAuth/Tests/Unit/FIRUserTests.m | 3 +- .../Tests/Unit/FakeBackendRPCIssuer.swift | 3 +- Package.swift | 1 + 8 files changed, 235 insertions(+), 114 deletions(-) diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 64fc73f2fbb..1eff7efdc59 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -114,6 +114,7 @@ supports email and password accounts, as well as several 3rd party authenticatio ] # app_host is needed for tests with keychain unit_tests.requires_app_host = true + unit_tests.dependency 'OCMock' unit_tests.dependency 'HeartbeatLoggingTestUtils' # This pre-processor directive is used to selectively disable keychain diff --git a/FirebaseAuth/Sources/Auth/FIRAuth.m b/FirebaseAuth/Sources/Auth/FIRAuth.m index 76bbbad0047..6cf7f54845c 100644 --- a/FirebaseAuth/Sources/Auth/FIRAuth.m +++ b/FirebaseAuth/Sources/Auth/FIRAuth.m @@ -1612,6 +1612,9 @@ - (void)scene:(UIScene *)scene #pragma mark - Internal Methods #if TARGET_OS_IOS +typedef void (^FIRVerifyPhoneNumberResponseCallback)( + FIRVerifyPhoneNumberResponse *_Nullable response, NSError *_Nullable error); + /** @fn signInWithPhoneCredential:callback: @brief Signs in using a phone credential. @param credential The Phone Auth credential used to sign in. @@ -1619,9 +1622,6 @@ - (void)scene:(UIScene *)scene @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked asynchronously on the global auth work queue in the future. */ -typedef void (^FIRVerifyPhoneNumberResponseCallback)( - FIRVerifyPhoneNumberResponse *_Nullable response, NSError *_Nullable error); - - (void)signInWithPhoneCredential:(FIRPhoneAuthCredential *)credential operation:(FIRAuthOperationType)operation callback:(FIRVerifyPhoneNumberResponseCallback)callback { @@ -1702,6 +1702,8 @@ - (void)internalSignInAndRetrieveDataWithCustomToken:(NSString *)token }]; } +typedef void (^FIRSignupNewUserCallback)(FIRSignUpNewUserResponse *_Nullable response, + NSError *_Nullable error); /** @fn internalCreateUserWithEmail:password:completion: @brief Makes a backend request attempting to create a new Firebase user given an email address and password. @@ -1709,8 +1711,6 @@ - (void)internalSignInAndRetrieveDataWithCustomToken:(NSString *)token @param password The password used to create the new Firebase user. @param completion Optionally; a block which is invoked when the request finishes. */ -typedef void (^FIRSignupNewUserCallback)(FIRSignUpNewUserResponse *_Nullable response, - NSError *_Nullable error); - (void)internalCreateUserWithEmail:(NSString *)email password:(NSString *)password completion:(nullable FIRSignupNewUserCallback)completion { diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index 341885e7597..d50e3663c21 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -309,7 +309,7 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation guard let decodedDictionary = rawDecode as? [String: Any] else { if error != nil { callback(AuthErrorUtils.unexpectedErrorResponse(deserializedResponse: rawDecode, - underlyingError: error)) + underlyingError: error)) } else { callback(AuthErrorUtils.unexpectedResponse(deserializedResponse: rawDecode)) } @@ -344,7 +344,8 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation withServerErrorMessage: errorMessage, errorDictionary: errorDictionary, response: response, - error: error) { + error: error + ) { callback(clientError) return } @@ -479,41 +480,44 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation case "SESSION_EXPIRED": return AuthErrorUtils .sessionExpiredError(message: serverDetailErrorMessage) case "ADMIN_ONLY_OPERATION": return AuthErrorUtils - .error(code: AuthErrorCode.adminRestrictedOperation, message: serverDetailErrorMessage) + .error(code: AuthErrorCode.adminRestrictedOperation, message: serverDetailErrorMessage) case "BLOCKING_FUNCTION_ERROR_RESPONSE": return AuthErrorUtils - .blockingCloudFunctionServerResponse(message: serverDetailErrorMessage) + .blockingCloudFunctionServerResponse(message: serverDetailErrorMessage) case "EMAIL_CHANGE_NEEDS_VERIFICATION": return AuthErrorUtils - .error(code: AuthErrorCode.emailChangeNeedsVerification, message: serverDetailErrorMessage) + .error(code: AuthErrorCode.emailChangeNeedsVerification, message: serverDetailErrorMessage) case "INVALID_MFA_PENDING_CREDENTIAL": return AuthErrorUtils - .error(code: AuthErrorCode.invalidMultiFactorSession, message: serverDetailErrorMessage) + .error(code: AuthErrorCode.invalidMultiFactorSession, message: serverDetailErrorMessage) case "INVALID_PROVIDER_ID": return AuthErrorUtils - .invalidProviderIDError(message: serverDetailErrorMessage) + .invalidProviderIDError(message: serverDetailErrorMessage) case "MFA_ENROLLMENT_NOT_FOUND": return AuthErrorUtils - .error(code: AuthErrorCode.multiFactorInfoNotFound, message: serverDetailErrorMessage) + .error(code: AuthErrorCode.multiFactorInfoNotFound, message: serverDetailErrorMessage) case "MISSING_CLIENT_IDENTIFIER": return AuthErrorUtils - .missingClientIdentifierError(message: serverDetailErrorMessage) + .missingClientIdentifierError(message: serverDetailErrorMessage) case "MISSING_IOS_APP_TOKEN": return AuthErrorUtils - .missingAppTokenError(underlyingError: nil) + .missingAppTokenError(underlyingError: nil) case "MISSING_MFA_ENROLLMENT_ID": return AuthErrorUtils - .error(code: AuthErrorCode.missingMultiFactorInfo, message: serverDetailErrorMessage) + .error(code: AuthErrorCode.missingMultiFactorInfo, message: serverDetailErrorMessage) case "MISSING_MFA_PENDING_CREDENTIAL": return AuthErrorUtils - .error(code: AuthErrorCode.missingMultiFactorSession, message: serverDetailErrorMessage) + .error(code: AuthErrorCode.missingMultiFactorSession, message: serverDetailErrorMessage) case "MISSING_OR_INVALID_NONCE": return AuthErrorUtils - .missingOrInvalidNonceError(message: serverDetailErrorMessage) + .missingOrInvalidNonceError(message: serverDetailErrorMessage) case "SECOND_FACTOR_EXISTS": return AuthErrorUtils - .error(code: AuthErrorCode.secondFactorAlreadyEnrolled, message: serverDetailErrorMessage) + .error(code: AuthErrorCode.secondFactorAlreadyEnrolled, message: serverDetailErrorMessage) case "SECOND_FACTOR_LIMIT_EXCEEDED": return AuthErrorUtils - .error(code: AuthErrorCode.maximumSecondFactorCountExceeded, message: serverDetailErrorMessage) + .error( + code: AuthErrorCode.maximumSecondFactorCountExceeded, + message: serverDetailErrorMessage + ) case "TENANT_ID_MISMATCH": return AuthErrorUtils - .tenantIDMismatchError() + .tenantIDMismatchError() case "TOKEN_EXPIRED": return AuthErrorUtils - .userTokenExpiredError(message: serverDetailErrorMessage) + .userTokenExpiredError(message: serverDetailErrorMessage) case "UNSUPPORTED_FIRST_FACTOR": return AuthErrorUtils - .error(code: AuthErrorCode.unsupportedFirstFactor, message: serverDetailErrorMessage) + .error(code: AuthErrorCode.unsupportedFirstFactor, message: serverDetailErrorMessage) case "UNSUPPORTED_TENANT_OPERATION": return AuthErrorUtils - .unsupportedTenantOperationError() + .unsupportedTenantOperationError() case "UNVERIFIED_EMAIL": return AuthErrorUtils - .error(code: AuthErrorCode.unverifiedEmail, message: serverDetailErrorMessage) + .error(code: AuthErrorCode.unverifiedEmail, message: serverDetailErrorMessage) case "FEDERATED_USER_ID_ALREADY_LINKED": guard let verifyAssertion = response as? VerifyAssertionResponse else { return AuthErrorUtils.credentialAlreadyInUseError( diff --git a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift index 97c838318b4..66b1e8dcc74 100644 --- a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift +++ b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift @@ -16,74 +16,16 @@ import Foundation import XCTest @testable import FirebaseAuth +import FirebaseCoreExtension +import HeartbeatLoggingTestUtils -private class FakeRequest : NSObject, AuthRPCRequest { - let kFakeRequestURL = "https://www.google.com/" - func requestURL() -> URL { - return try! XCTUnwrap(URL(string:kFakeRequestURL)) - } - - func unencodedHTTPRequestBody() throws -> Any { - if let encodingError { - throw encodingError - } - return requestBody - } - - func requestConfiguration() -> FirebaseAuth.AuthRequestConfiguration { - return AuthRequestConfiguration( - APIKey: "kTestAPIKey", - appID: "kTestFirebaseAppID" - ) - } - - func containsPostBody() -> Bool { - return true - } - - var response: FirebaseAuth.AuthRPCResponse - - let encodingError:NSError? - let requestBody: [String: AnyHashable] - - init(withEncodingError error: NSError) { - encodingError = error - requestBody = [:] - response = FakeResponse() - } - - init(withDecodingError error: NSError) { - encodingError = nil - requestBody = [:] - response = FakeResponse(withDecodingError: error) - } - - init(withRequestBody body: [String: AnyHashable]) { - encodingError = nil - requestBody = body - response = FakeResponse() - } -} - -private class FakeResponse: NSObject, AuthRPCResponse { - let decodingError: NSError? - var receivedDictionary : [String : Any] = [:] - init(withDecodingError error: NSError? = nil) { - decodingError = error - } - func setFields(dictionary: [String : Any]) throws { - if let decodingError { - throw decodingError - } - receivedDictionary = dictionary - } -} +private let kFakeAPIKey = "kTestAPIKey" +private let kFakeAppID = "kTestFirebaseAppID" class AuthBackendRPCImplementationTests: RPCBaseTests { let kFakeErrorDomain = "fakeDomain" let kFakeErrorCode = -1 - /** @fn testRequestEncodingError @brief This test checks the behaviour of @c postWithRequest:response:callback: when the request passed returns an error during it's unencodedHTTPRequestBodyWithError: method. @@ -111,7 +53,8 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.RPCRequestEncodingError.rawValue) - let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + let underlyingUnderlying = try XCTUnwrap(underlyingError + .userInfo[NSUnderlyingErrorKey] as? NSError) XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) @@ -213,7 +156,8 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue) - let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + let underlyingUnderlying = try XCTUnwrap(underlyingError + .userInfo[NSUnderlyingErrorKey] as? NSError) XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) @@ -252,7 +196,8 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedResponse.rawValue) - let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + let underlyingUnderlying = try XCTUnwrap(underlyingError + .userInfo[NSUnderlyingErrorKey] as? NSError) XCTAssertEqual(underlyingUnderlying.domain, NSCocoaErrorDomain) XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]) @@ -296,12 +241,14 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue) - let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + let underlyingUnderlying = try XCTUnwrap(underlyingError + .userInfo[NSUnderlyingErrorKey] as? NSError) XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) XCTAssertNotNil(try XCTUnwrap( - underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]) as? [Int]) + underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] + ) as? [Int]) XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) } @@ -342,7 +289,8 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { XCTAssertNil(underlyingError.userInfo[NSUnderlyingErrorKey]) XCTAssertNotNil(try XCTUnwrap( - underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]) as? [Int]) + underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] + ) as? [Int]) XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) } @@ -377,11 +325,13 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue) - let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + let underlyingUnderlying = try XCTUnwrap(underlyingError + .userInfo[NSUnderlyingErrorKey] as? NSError) XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) - let dictionary = try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) + let dictionary = try XCTUnwrap(underlyingError + .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) XCTAssertEqual(dictionary["message"], kErrorMessageCaptchaRequired) XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) } @@ -407,7 +357,10 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { rpcError = error as? NSError } let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode) - try RPCIssuer?.respond(serverErrorMessage: kErrorMessageCaptchaCheckFailed, error: responseError) + try RPCIssuer?.respond( + serverErrorMessage: kErrorMessageCaptchaCheckFailed, + error: responseError + ) XCTAssert(callbackInvoked) XCTAssertNil(rpcResponse) @@ -448,11 +401,13 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue) - let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + let underlyingUnderlying = try XCTUnwrap(underlyingError + .userInfo[NSUnderlyingErrorKey] as? NSError) XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) - let dictionary = try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) + let dictionary = try XCTUnwrap(underlyingError + .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) XCTAssertEqual(dictionary["message"], kErrorMessageCaptchaRequiredInvalidPassword) XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) } @@ -491,11 +446,13 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue) - let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + let underlyingUnderlying = try XCTUnwrap(underlyingError + .userInfo[NSUnderlyingErrorKey] as? NSError) XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) - let dictionary = try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) + let dictionary = try XCTUnwrap(underlyingError + .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) XCTAssertEqual(dictionary["message"], kUnknownServerErrorMessage) XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) } @@ -531,11 +488,13 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError) XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue) - let underlyingUnderlying = try XCTUnwrap(underlyingError.userInfo[NSUnderlyingErrorKey] as? NSError) + let underlyingUnderlying = try XCTUnwrap(underlyingError + .userInfo[NSUnderlyingErrorKey] as? NSError) XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain) XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode) - let dictionary = try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) + let dictionary = try XCTUnwrap(underlyingError + .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) XCTAssertEqual(dictionary, [:]) XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) } @@ -580,7 +539,8 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { as the value of the underlyingError. */ func testUndecodableSuccessResponse() throws { - let request = FakeRequest(withDecodingError: NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)) + let request = + FakeRequest(withDecodingError: NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)) var callbackInvoked = false var rpcResponse: FakeResponse? var rpcError: NSError? @@ -590,9 +550,6 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { rpcResponse = response as? FakeResponse rpcError = error as? NSError } - // It doesn't matter what we respond with here, as long as it's not an error response. The fake - // response will deterministicly simulate a decoding error regardless of the response value it was - // given. try RPCIssuer?.respond(withJSON: [:]) XCTAssert(callbackInvoked) @@ -605,7 +562,8 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain) XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.RPCResponseDecodingError.rawValue) - let dictionary = try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) + let dictionary = try XCTUnwrap(underlyingError + .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable]) XCTAssertEqual(dictionary, [:]) XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey]) } @@ -629,10 +587,166 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { // It doesn't matter what we respond with here, as long as it's not an error response. The fake // response will deterministicly simulate a decoding error regardless of the response value it was // given. - try RPCIssuer?.respond(withJSON: [kTestKey : kTestValue]) + try RPCIssuer?.respond(withJSON: [kTestKey: kTestValue]) XCTAssert(callbackInvoked) XCTAssertNil(rpcError) - XCTAssertEqual(try XCTUnwrap(rpcResponse?.receivedDictionary[kTestKey] as? String), kTestValue) + XCTAssertEqual(try XCTUnwrap(rpcResponse?.receivedDictionary[kTestKey] as? String), kTestValue) + } + + private class FakeHeartbeatLogger: NSObject, FIRHeartbeatLoggerProtocol { + var onFlushHeartbeatsIntoPayloadHandler: (() -> _ObjC_HeartbeatsPayload)? + + func log() { + // This API should not be used by the below tests because the Auth + // SDK does not log heartbeats in it's networking context. + fatalError("FakeHeartbeatLogger log should not be used in tests.") + } + + func flushHeartbeatsIntoPayload() -> FirebaseCoreInternal._ObjC_HeartbeatsPayload { + guard let handler = onFlushHeartbeatsIntoPayloadHandler else { + fatalError("Missing Handler") + } + return handler() + } + + func heartbeatCodeForToday() -> FIRDailyHeartbeatCode { + // This API should not be used by the below tests because the Auth + // SDK uses only the V2 heartbeat API (`flushHeartbeatsIntoPayload`) for + // getting heartbeats. + return FIRDailyHeartbeatCode.none + } + } + + /** @fn testRequest_IncludesHeartbeatPayload_WhenHeartbeatsNeedSending + @brief This test checks the behavior of @c postWithRequest:response:callback: + to verify that a heartbeats payload is attached as a header to an + outgoing request when there are stored heartbeats that need sending. + */ + func testRequest_IncludesHeartbeatPayload_WhenHeartbeatsNeedSending() throws { + // Given + let fakeHeartbeatLogger = FakeHeartbeatLogger() + let requestConfiguration = AuthRequestConfiguration(APIKey: kFakeAPIKey, + appID: kFakeAppID, + heartbeatLogger: fakeHeartbeatLogger) + + let request = FakeRequest(withRequestBody: [:], requestConfiguration: requestConfiguration) + + // When + let nonEmptyHeartbeatsPayload = HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload + fakeHeartbeatLogger.onFlushHeartbeatsIntoPayloadHandler = { + nonEmptyHeartbeatsPayload + } + rpcImplementation?.post(withRequest: request) { response, error in + // The callback never happens and it's fine since we only need to verify the request. + } + + // Then + let expectedHeader = FIRHeaderValueFromHeartbeatsPayload( + HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload + ) + let completeRequest = try XCTUnwrap(RPCIssuer?.completeRequest) + let headerValue = completeRequest.value(forHTTPHeaderField: "X-Firebase-Client") + XCTAssertEqual(headerValue, expectedHeader) + } + + /** @fn testRequest_DoesNotIncludeAHeartbeatPayload_WhenNoHeartbeatsNeedSending + @brief This test checks the behavior of @c postWithRequest:response:callback: + to verify that a request header does not contain heartbeat data in the + case that there are no stored heartbeats that need sending. + */ + func testRequest_DoesNotIncludeAHeartbeatPayload_WhenNoHeartbeatsNeedSending() throws { + // Given + let fakeHeartbeatLogger = FakeHeartbeatLogger() + let requestConfiguration = AuthRequestConfiguration(APIKey: kFakeAPIKey, + appID: kFakeAppID, + heartbeatLogger: fakeHeartbeatLogger) + + let request = FakeRequest(withRequestBody: [:], requestConfiguration: requestConfiguration) + + // When + let emptyHeartbeatsPayload = HeartbeatLoggingTestUtils.emptyHeartbeatsPayload + fakeHeartbeatLogger.onFlushHeartbeatsIntoPayloadHandler = { + emptyHeartbeatsPayload + } + rpcImplementation?.post(withRequest: request) { response, error in + // The callback never happens and it's fine since we only need to verify the request. + } + + // Then + let completeRequest = try XCTUnwrap(RPCIssuer?.completeRequest) + XCTAssertNil(completeRequest.value(forHTTPHeaderField: "X-Firebase-Client")) + } + + private class FakeRequest: NSObject, AuthRPCRequest { + func requestConfiguration() -> AuthRequestConfiguration { + return configuration + } + + let kFakeRequestURL = "https://www.google.com/" + func requestURL() -> URL { + return try! XCTUnwrap(URL(string: kFakeRequestURL)) + } + + func unencodedHTTPRequestBody() throws -> Any { + if let encodingError { + throw encodingError + } + return requestBody + } + + static func makeRequestConfiguration() -> AuthRequestConfiguration { + return AuthRequestConfiguration( + APIKey: kFakeAPIKey, + appID: kFakeAppID + ) + } + + func containsPostBody() -> Bool { + return true + } + + var response: AuthRPCResponse + private let configuration: AuthRequestConfiguration + + let encodingError: NSError? + let requestBody: [String: AnyHashable] + + init(withEncodingError error: NSError) { + encodingError = error + requestBody = [:] + response = FakeResponse() + configuration = FakeRequest.makeRequestConfiguration() + } + + init(withDecodingError error: NSError) { + encodingError = nil + requestBody = [:] + response = FakeResponse(withDecodingError: error) + configuration = FakeRequest.makeRequestConfiguration() + } + + init(withRequestBody body: [String: AnyHashable], + requestConfiguration: AuthRequestConfiguration = FakeRequest.makeRequestConfiguration()) { + encodingError = nil + requestBody = body + response = FakeResponse() + configuration = requestConfiguration + } + } + + private class FakeResponse: NSObject, AuthRPCResponse { + let decodingError: NSError? + var receivedDictionary: [String: Any] = [:] + init(withDecodingError error: NSError? = nil) { + decodingError = error + } + + func setFields(dictionary: [String: Any]) throws { + if let decodingError { + throw decodingError + } + receivedDictionary = dictionary + } } } diff --git a/FirebaseAuth/Tests/Unit/FIRAuthTests.m b/FirebaseAuth/Tests/Unit/FIRAuthTests.m index 4245e595cf7..c3df801174a 100644 --- a/FirebaseAuth/Tests/Unit/FIRAuthTests.m +++ b/FirebaseAuth/Tests/Unit/FIRAuthTests.m @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +#ifdef TODO_SWIFT #import -#import #import #import @@ -29,7 +28,6 @@ @import FirebaseAuth; #import "FirebaseAuth/Sources/User/FIRUser_Internal.h" #import "FirebaseAuth/Tests/Unit/FIRApp+FIRAuthUnitTests.h" -#import "FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.h" #if TARGET_OS_IOS #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuthUIDelegate.h" @@ -2534,3 +2532,4 @@ - (void)waitForAuthGlobalWorkQueueDrain { } @end +#endif diff --git a/FirebaseAuth/Tests/Unit/FIRUserTests.m b/FirebaseAuth/Tests/Unit/FIRUserTests.m index 9d2557c82c7..b21a692da4a 100644 --- a/FirebaseAuth/Tests/Unit/FIRUserTests.m +++ b/FirebaseAuth/Tests/Unit/FIRUserTests.m @@ -14,10 +14,11 @@ * limitations under the License. */ +#ifdef TODO_SWIFT #import #import #import -#ifdef TODO_SWIFT + // Migrate the mocks! #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRUserMetadata.h" diff --git a/FirebaseAuth/Tests/Unit/FakeBackendRPCIssuer.swift b/FirebaseAuth/Tests/Unit/FakeBackendRPCIssuer.swift index cf5f2bd5437..2401e564783 100644 --- a/FirebaseAuth/Tests/Unit/FakeBackendRPCIssuer.swift +++ b/FirebaseAuth/Tests/Unit/FakeBackendRPCIssuer.swift @@ -103,7 +103,8 @@ class FakeBackendRPCIssuer: NSObject, AuthBackendRPCIssuer { "errors": [["reason": errorMessage]]]], error: error) } - @discardableResult func respond(withJSON json: [String: Any], error: NSError? = nil) throws -> Data { + @discardableResult func respond(withJSON json: [String: Any], + error: NSError? = nil) throws -> Data { let data = try JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions.prettyPrinted) try respond(withData: data, error: error) diff --git a/Package.swift b/Package.swift index 87b14c57646..fad6b8601ae 100644 --- a/Package.swift +++ b/Package.swift @@ -460,6 +460,7 @@ let package = Package( name: "AuthUnit", dependencies: [ "FirebaseAuth", + .product(name: "OCMock", package: "OCMock"), "HeartbeatLoggingTestUtils", ], path: "FirebaseAuth/Tests/Unit",