aboutsummaryrefslogtreecommitdiffhomepage
path: root/Example/Auth/Tests/FIRAuthTests.m
diff options
context:
space:
mode:
Diffstat (limited to 'Example/Auth/Tests/FIRAuthTests.m')
-rw-r--r--Example/Auth/Tests/FIRAuthTests.m1743
1 files changed, 1743 insertions, 0 deletions
diff --git a/Example/Auth/Tests/FIRAuthTests.m b/Example/Auth/Tests/FIRAuthTests.m
new file mode 100644
index 0000000..3a6f717
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthTests.m
@@ -0,0 +1,1743 @@
+/*
+ * 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 <XCTest/XCTest.h>
+
+#import "FIRAppInternal.h"
+#import "EmailPassword/FIREmailAuthProvider.h"
+#import "Google/FIRGoogleAuthProvider.h"
+#import "Phone/FIRPhoneAuthCredential.h"
+#import "Phone/FIRPhoneAuthProvider.h"
+#import "FIRAdditionalUserInfo.h"
+#import "FIRAuth_Internal.h"
+#import "FIRAuthErrorUtils.h"
+#import "FIRAuthDispatcher.h"
+#import "FIRAuthGlobalWorkQueue.h"
+#import "FIRUser_Internal.h"
+#import "FIRAuthBackend.h"
+#import "FIRCreateAuthURIRequest.h"
+#import "FIRCreateAuthURIResponse.h"
+#import "FIRGetAccountInfoRequest.h"
+#import "FIRGetAccountInfoResponse.h"
+#import "FIRGetOOBConfirmationCodeRequest.h"
+#import "FIRGetOOBConfirmationCodeResponse.h"
+#import "FIRSecureTokenRequest.h"
+#import "FIRSecureTokenResponse.h"
+#import "FIRResetPasswordRequest.h"
+#import "FIRResetPasswordResponse.h"
+#import "FIRSetAccountInfoRequest.h"
+#import "FIRSetAccountInfoResponse.h"
+#import "FIRSignUpNewUserRequest.h"
+#import "FIRSignUpNewUserResponse.h"
+#import "FIRVerifyCustomTokenRequest.h"
+#import "FIRVerifyCustomTokenResponse.h"
+#import "FIRVerifyAssertionRequest.h"
+#import "FIRVerifyAssertionResponse.h"
+#import "FIRVerifyPasswordRequest.h"
+#import "FIRVerifyPasswordResponse.h"
+#import "FIRVerifyPhoneNumberRequest.h"
+#import "FIRVerifyPhoneNumberResponse.h"
+#import "FIRApp+FIRAuthUnitTests.h"
+#import "OCMStubRecorder+FIRAuthUnitTests.h"
+#import <OCMock/OCMock.h>
+
+/** @var kFirebaseAppName1
+ @brief A fake Firebase app name.
+ */
+static NSString *const kFirebaseAppName1 = @"FIREBASE_APP_NAME_1";
+
+/** @var kFirebaseAppName2
+ @brief Another fake Firebase app name.
+ */
+static NSString *const kFirebaseAppName2 = @"FIREBASE_APP_NAME_2";
+
+/** @var kAPIKey
+ @brief The fake API key.
+ */
+static NSString *const kAPIKey = @"FAKE_API_KEY";
+
+/** @var kAccessToken
+ @brief The fake access token.
+ */
+static NSString *const kAccessToken = @"ACCESS_TOKEN";
+
+/** @var kNewAccessToken
+ @brief Another fake access token used to simulate token refreshed via automatic token refresh.
+ */
+NSString *kNewAccessToken = @"NewAccessToken";
+
+/** @var kAccessTokenValidInterval
+ @brief The time to live for the fake access token.
+ */
+static const NSTimeInterval kAccessTokenTimeToLive = 60 * 60;
+
+/** @var kTestTokenExpirationTimeInterval
+ @brief The fake time interval that it takes a token to expire.
+ */
+static const NSTimeInterval kTestTokenExpirationTimeInterval = 55 * 60;
+
+/** @var kRefreshToken
+ @brief The fake refresh token.
+ */
+static NSString *const kRefreshToken = @"REFRESH_TOKEN";
+
+/** @var kEmail
+ @brief The fake user email.
+ */
+static NSString *const kEmail = @"user@company.com";
+
+/** @var kPassword
+ @brief The fake user password.
+ */
+static NSString *const kPassword = @"!@#$%^";
+
+/** @var kPasswordHash
+ @brief The fake user password hash.
+ */
+static NSString *const kPasswordHash = @"UkVEQUNURUQ=";
+
+/** @var kLocalID
+ @brief The fake local user ID.
+ */
+static NSString *const kLocalID = @"LOCAL_ID";
+
+/** @var kDisplayName
+ @brief The fake user display name.
+ */
+static NSString *const kDisplayName = @"User Doe";
+
+/** @var kGoogleUD
+ @brief The fake user ID under Google Sign-In.
+ */
+static NSString *const kGoogleID = @"GOOGLE_ID";
+
+/** @var kGoogleEmail
+ @brief The fake user email under Google Sign-In.
+ */
+static NSString *const kGoogleEmail = @"user@gmail.com";
+
+/** @var kGoogleDisplayName
+ @brief The fake user display name under Google Sign-In.
+ */
+static NSString *const kGoogleDisplayName = @"Google Doe";
+
+/** @var kGoogleAccessToken
+ @brief The fake access token from Google Sign-In.
+ */
+static NSString *const kGoogleAccessToken = @"GOOGLE_ACCESS_TOKEN";
+
+/** @var kGoogleIDToken
+ @brief The fake ID token from Google Sign-In.
+ */
+static NSString *const kGoogleIDToken = @"GOOGLE_ID_TOKEN";
+
+/** @var kCustomToken
+ @brief The fake custom token to sign in.
+ */
+static NSString *const kCustomToken = @"CUSTOM_TOKEN";
+
+/** @var kVerificationCode
+ @brief Fake verification code used for testing.
+ */
+static NSString *const kVerificationCode = @"12345678";
+
+/** @var kVerificationID
+ @brief Fake verification ID for testing.
+ */
+static NSString *const kVerificationID = @"55432";
+
+/** @var kExpectationTimeout
+ @brief The maximum time waiting for expectations to fulfill.
+ */
+static const NSTimeInterval kExpectationTimeout = 1;
+
+/** @var kWaitInterval
+ @brief The time waiting for background tasks to finish before continue when necessary.
+ */
+static const NSTimeInterval kWaitInterval = .5;
+
+/** @class FIRAuthTests
+ @brief Tests for @c FIRAuth.
+ */
+@interface FIRAuthTests : XCTestCase
+@end
+@implementation FIRAuthTests {
+
+ /** @var _mockBackend
+ @brief The mock @c FIRAuthBackendImplementation .
+ */
+ id _mockBackend;
+
+ /** @var _FIRAuthDispatcherCallback
+ @brief Used to save a task from FIRAuthDispatcher to be executed later.
+ */
+ __block void (^_Nonnull _FIRAuthDispatcherCallback)(void);
+}
+
+/** @fn googleProfile
+ @brief The fake user profile under additional user data in @c FIRVerifyAssertionResponse.
+ */
++ (NSDictionary *)googleProfile {
+ static NSDictionary *kGoogleProfile = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ kGoogleProfile = @{
+ @"iss": @"https://accounts.google.com\\",
+ @"email": kGoogleEmail,
+ @"given_name": @"User",
+ @"family_name": @"Doe"
+ };
+ });
+ return kGoogleProfile;
+}
+
+- (void)setUp {
+ [super setUp];
+ _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation));
+ [FIRAuthBackend setBackendImplementation:_mockBackend];
+ [FIRApp resetAppForAuthUnitTests];
+
+ // Set FIRAuthDispatcher implementation in order to save the token refresh task for later
+ // execution.
+ [[FIRAuthDispatcher sharedInstance]
+ setDispatchAfterImplementation:^(NSTimeInterval delay,
+ dispatch_queue_t _Nonnull queue,
+ void (^task)(void)) {
+ XCTAssertNotNil(task);
+ XCTAssert(delay > 0);
+ XCTAssertEqualObjects(FIRAuthGlobalWorkQueue(), queue);
+ _FIRAuthDispatcherCallback = task;
+ }];
+}
+
+- (void)tearDown {
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [[FIRAuthDispatcher sharedInstance] setDispatchAfterImplementation:nil];
+ [super tearDown];
+}
+
+#pragma mark - Life Cycle Tests
+
+/** @fn testSingleton
+ @brief Verifies the @c auth method behaves like a singleton.
+ */
+- (void)testSingleton {
+ FIRAuth *auth1 = [FIRAuth auth];
+ XCTAssertNotNil(auth1);
+ FIRAuth *auth2 = [FIRAuth auth];
+ XCTAssertEqual(auth1, auth2);
+}
+
+/** @fn testDefaultAuth
+ @brief Verifies the @c auth method associates with the default Firebase app.
+ */
+- (void)testDefaultAuth {
+ FIRAuth *auth1 = [FIRAuth auth];
+ FIRAuth *auth2 = [FIRAuth authWithApp:[FIRApp defaultApp]];
+ XCTAssertEqual(auth1, auth2);
+ XCTAssertEqual(auth1.app, [FIRApp defaultApp]);
+}
+
+/** @fn testNilAppException
+ @brief Verifies the @c auth method raises an exception if the default FIRApp is not configured.
+ */
+- (void)testNilAppException {
+ [FIRApp resetApps];
+ XCTAssertThrows([FIRAuth auth]);
+}
+
+/** @fn testAppAPIkey
+ @brief Verifies the API key is correctly copied from @c FIRApp to @c FIRAuth .
+ */
+- (void)testAppAPIkey {
+ FIRAuth *auth = [FIRAuth auth];
+ XCTAssertEqualObjects(auth.APIKey, kAPIKey);
+}
+
+/** @fn testAppAssociation
+ @brief Verifies each @c FIRApp instance associates with a @c FIRAuth .
+ */
+- (void)testAppAssociation {
+ FIRApp *app1 = [self app1];
+ FIRAuth *auth1 = [FIRAuth authWithApp:app1];
+ XCTAssertNotNil(auth1);
+ XCTAssertEqual(auth1.app, app1);
+
+ FIRApp *app2 = [self app2];
+ FIRAuth *auth2 = [FIRAuth authWithApp:app2];
+ XCTAssertNotNil(auth2);
+ XCTAssertEqual(auth2.app, app2);
+
+ XCTAssertNotEqual(auth1, auth2);
+}
+
+/** @fn testLifeCycle
+ @brief Verifies the life cycle of @c FIRAuth is the same as its associated @c FIRApp .
+ */
+- (void)testLifeCycle {
+ __weak FIRApp *app;
+ __weak FIRAuth *auth;
+ @autoreleasepool {
+ FIRApp *app1 = [self app1];
+ app = app1;
+ auth = [FIRAuth authWithApp:app1];
+ // Verify that neither the app nor the auth is released yet, i.e., the app owns the auth
+ // because nothing else retains the auth.
+ XCTAssertNotNil(app);
+ XCTAssertNotNil(auth);
+ }
+ [self waitForTimeIntervel:kWaitInterval];
+ // Verify that both the app and the auth are released upon exit of the autorelease pool,
+ // i.e., the app is the sole owner of the auth.
+ XCTAssertNil(app);
+ XCTAssertNil(auth);
+}
+
+/** @fn testGetUID
+ @brief Verifies that FIRApp's getUIDImplementation is correctly set by FIRAuth.
+ */
+- (void)testGetUID {
+ FIRApp *app = [FIRApp defaultApp];
+ XCTAssertNotNil(app.getUIDImplementation);
+ [[FIRAuth auth] signOut:NULL];
+ XCTAssertNil(app.getUIDImplementation());
+ [self waitForSignIn];
+ XCTAssertEqualObjects(app.getUIDImplementation(), kLocalID);
+}
+
+#pragma mark - Server API Tests
+
+/** @fn testFetchProvidersForEmailSuccess
+ @brief Tests the flow of a successful @c fetchProvidersForEmail:completion: call.
+ */
+- (void)testFetchProvidersForEmailSuccess {
+ NSArray<NSString *> *allProviders =
+ @[ FIRGoogleAuthProviderID, FIREmailAuthProviderID ];
+ OCMExpect([_mockBackend createAuthURI:[OCMArg any]
+ callback:[OCMArg any]])
+ .andCallBlock2(^(FIRCreateAuthURIRequest *_Nullable request,
+ FIRCreateAuthURIResponseCallback callback) {
+ XCTAssertEqualObjects(request.identifier, kEmail);
+ XCTAssertNotNil(request.endpoint);
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockCreateAuthURIResponse = OCMClassMock([FIRCreateAuthURIResponse class]);
+ OCMStub([mockCreateAuthURIResponse allProviders]).andReturn(allProviders);
+ callback(mockCreateAuthURIResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] fetchProvidersForEmail:kEmail
+ completion:^(NSArray<NSString *> *_Nullable providers,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqualObjects(providers, allProviders);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testFetchProvidersForEmailSuccessDeprecatedProviderID
+ @brief Tests the flow of a successful @c fetchProvidersForEmail:completion: call using the
+ deprecated FIREmailPasswordAuthProviderID.
+ */
+- (void)testFetchProvidersForEmailSuccessDeprecatedProviderID {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ NSArray<NSString *> *allProviders =
+ @[ FIRGoogleAuthProviderID, FIREmailPasswordAuthProviderID ];
+#pragma clang diagnostic pop
+ OCMExpect([_mockBackend createAuthURI:[OCMArg any]
+ callback:[OCMArg any]])
+ .andCallBlock2(^(FIRCreateAuthURIRequest *_Nullable request,
+ FIRCreateAuthURIResponseCallback callback) {
+ XCTAssertEqualObjects(request.identifier, kEmail);
+ XCTAssertNotNil(request.endpoint);
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockCreateAuthURIResponse = OCMClassMock([FIRCreateAuthURIResponse class]);
+ OCMStub([mockCreateAuthURIResponse allProviders]).andReturn(allProviders);
+ callback(mockCreateAuthURIResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] fetchProvidersForEmail:kEmail
+ completion:^(NSArray<NSString *> *_Nullable providers,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqualObjects(providers, allProviders);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testFetchProvidersForEmailFailure
+ @brief Tests the flow of a failed @c fetchProvidersForEmail:completion: call.
+ */
+- (void)testFetchProvidersForEmailFailure {
+ OCMExpect([_mockBackend createAuthURI:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils tooManyRequestsErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] fetchProvidersForEmail:kEmail
+ completion:^(NSArray<NSString *> *_Nullable providers,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(providers);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeTooManyRequests);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testPhoneAuthSuccess
+ @brief Tests the flow of a successful @c signInWithCredential:completion for phone auth.
+ */
+- (void)testPhoneAuthSuccess {
+ OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPhoneNumberRequest *_Nullable request,
+ FIRVerifyPhoneNumberResponseCallback callback) {
+ XCTAssertEqualObjects(request.verificationCode, kVerificationCode);
+ XCTAssertEqualObjects(request.verificationID, kVerificationID);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVerifyPhoneResponse = OCMClassMock([FIRVerifyPhoneNumberResponse class]);
+ [self stubTokensWithMockResponse:mockVerifyPhoneResponse];
+ callback(mockVerifyPhoneResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
+ verificationCode:kVerificationCode];
+
+ [[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUser:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testPhoneAuthMissingVerificationCode
+ @brief Tests the flow of an unsuccessful @c signInWithCredential:completion for phone auth due
+ to an empty verification code
+ */
+- (void)testPhoneAuthMissingVerificationCode {
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
+ verificationCode:@""];
+
+ [[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeMissingVerificationCode);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+/** @fn testPhoneAuthMissingVerificationID
+ @brief Tests the flow of an unsuccessful @c signInWithCredential:completion for phone auth due
+ to an empty verification ID.
+ */
+- (void)testPhoneAuthMissingVerificationID {
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:@""
+ verificationCode:kVerificationCode];
+
+ [[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeMissingVerificationID);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+/** @fn testSignInWithEmailPasswordSuccess
+ @brief Tests the flow of a successful @c signInWithEmail:password:completion: call.
+ */
+- (void)testSignInWithEmailPasswordSuccess {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
+ FIRVerifyPasswordResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.email, kEmail);
+ XCTAssertEqualObjects(request.password, kPassword);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVerifyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
+ [self stubTokensWithMockResponse:mockVerifyPasswordResponse];
+ callback(mockVerifyPasswordResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInWithEmail:kEmail password:kPassword completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUser:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithEmailPasswordFailure
+ @brief Tests the flow of a failed @c signInWithEmail:password:completion: call.
+ */
+- (void)testSignInWithEmailPasswordFailure {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils wrongPasswordErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInWithEmail:kEmail password:kPassword completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeWrongPassword);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testResetPasswordSuccess
+ @brief Tests the flow of a successful @c confirmPasswordResetWithCode:newPassword:completion:
+ call.
+ */
+- (void)testResetPasswordSuccess {
+ NSString *fakeEmail = @"fakeEmail";
+ NSString *fakeCode = @"fakeCode";
+ NSString *fakeNewPassword = @"fakeNewPassword";
+ OCMExpect([_mockBackend resetPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRResetPasswordRequest *_Nullable request,
+ FIRResetPasswordCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.oobCode, fakeCode);
+ XCTAssertEqualObjects(request.updatedPassword, fakeNewPassword);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockResetPasswordResponse = OCMClassMock([FIRResetPasswordResponse class]);
+ OCMStub([mockResetPasswordResponse email]).andReturn(fakeEmail);
+ callback(mockResetPasswordResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] confirmPasswordResetWithCode:fakeCode
+ newPassword:fakeNewPassword
+ completion:^(NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testResetPasswordFailure
+ @brief Tests the flow of a failed @c confirmPasswordResetWithCode:newPassword:completion:
+ call.
+ */
+- (void)testResetPasswordFailure {
+ NSString *fakeCode = @"fakeCode";
+ NSString *fakeNewPassword = @"fakeNewPassword";
+ OCMExpect([_mockBackend resetPassword:[OCMArg any] callback:[OCMArg any]])
+ ._andDispatchError2([FIRAuthErrorUtils invalidActionCodeErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] confirmPasswordResetWithCode:fakeCode
+ newPassword:fakeNewPassword
+ completion:^(NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidActionCode);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testCheckActionCodeSuccess
+ @brief Tests the flow of a successful @c checkActionCode:completion call.
+ */
+- (void)testCheckActionCodeSuccess {
+ NSString *verifyEmailRequestType = @"VERIFY_EMAIL";
+ NSString *fakeEmail = @"fakeEmail";
+ NSString *fakeNewEmail = @"fakeNewEmail";
+ NSString *fakeCode = @"fakeCode";
+ OCMExpect([_mockBackend resetPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRResetPasswordRequest *_Nullable request,
+ FIRResetPasswordCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.oobCode, fakeCode);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockResetPasswordResponse = OCMClassMock([FIRResetPasswordResponse class]);
+ OCMStub([mockResetPasswordResponse email]).andReturn(fakeEmail);
+ OCMStub([mockResetPasswordResponse verifiedEmail]).andReturn(fakeNewEmail);
+ OCMStubRecorder *stub =
+ OCMStub([(FIRResetPasswordResponse *) mockResetPasswordResponse requestType]);
+ stub.andReturn(verifyEmailRequestType);
+ callback(mockResetPasswordResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] checkActionCode:fakeCode completion:^(FIRActionCodeInfo *_Nullable info,
+ NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ XCTAssertEqual(info.operation, FIRActionCodeOperationVerifyEmail);
+ XCTAssert([fakeNewEmail isEqualToString:[info dataForKey:FIRActionCodeEmailKey]]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testCheckActionCodeFailure
+ @brief Tests the flow of a failed @c checkActionCode:completion call.
+ */
+- (void)testCheckActionCodeFailure {
+ NSString *fakeCode = @"fakeCode";
+ OCMExpect([_mockBackend resetPassword:[OCMArg any] callback:[OCMArg any]])
+ ._andDispatchError2([FIRAuthErrorUtils expiredActionCodeErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] checkActionCode:fakeCode completion:^(FIRActionCodeInfo *_Nullable info,
+ NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNotNil(error);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeExpiredActionCode);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testApplyActionCodeSuccess
+ @brief Tests the flow of a successful @c applyActionCode:completion call.
+ */
+- (void)testApplyActionCodeSuccess {
+ NSString *fakeCode = @"fakeCode";
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
+ FIRSetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.OOBCode, fakeCode);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
+ callback(mockSetAccountInfoResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] applyActionCode:fakeCode completion:^(NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testApplyActionCodeFailure
+ @brief Tests the flow of a failed @c checkActionCode:completion call.
+ */
+- (void)testApplyActionCodeFailure {
+ NSString *fakeCode = @"fakeCode";
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ ._andDispatchError2([FIRAuthErrorUtils invalidActionCodeErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] applyActionCode:fakeCode completion:^(NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNotNil(error);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidActionCode);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testVerifyPasswordResetCodeSuccess
+ @brief Tests the flow of a successful @c verifyPasswordResetCode:completion call.
+ */
+- (void)testVerifyPasswordResetCodeSuccess {
+ NSString *passwordResetRequestType = @"PASSWORD_RESET";
+ NSString *fakeEmail = @"fakeEmail";
+ NSString *fakeCode = @"fakeCode";
+ OCMExpect([_mockBackend resetPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRResetPasswordRequest *_Nullable request,
+ FIRResetPasswordCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.oobCode, fakeCode);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockResetPasswordResponse = OCMClassMock([FIRResetPasswordResponse class]);
+ OCMStub([mockResetPasswordResponse email]).andReturn(fakeEmail);
+ OCMStubRecorder *stub =
+ OCMStub([(FIRResetPasswordResponse *) mockResetPasswordResponse requestType]);
+ stub.andReturn(passwordResetRequestType);
+ callback(mockResetPasswordResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] verifyPasswordResetCode:fakeCode completion:^(NSString *_Nullable email,
+ NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ XCTAssertEqual(email, fakeEmail);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testVerifyPasswordResetCodeFailure
+ @brief Tests the flow of a failed @c verifyPasswordResetCode:completion call.
+ */
+- (void)testVeridyPasswordResetCodeFailure {
+ NSString *fakeCode = @"fakeCode";
+ OCMExpect([_mockBackend resetPassword:[OCMArg any] callback:[OCMArg any]])
+ ._andDispatchError2([FIRAuthErrorUtils invalidActionCodeErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] verifyPasswordResetCode:fakeCode completion:^(NSString *_Nullable email,
+ NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNotNil(error);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidActionCode);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithEmailCredentialSuccess
+ @brief Tests the flow of a successfully @c signInWithCredential:completion: call with an
+ email-password credential.
+ */
+- (void)testSignInWithEmailCredentialSuccess {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
+ FIRVerifyPasswordResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.email, kEmail);
+ XCTAssertEqualObjects(request.password, kPassword);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
+ [self stubTokensWithMockResponse:mockVeriyPasswordResponse];
+ callback(mockVeriyPasswordResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *emailCredential =
+ [FIREmailAuthProvider credentialWithEmail:kEmail password:kPassword];
+ [[FIRAuth auth] signInWithCredential:emailCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUser:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithEmailCredentialSuccess
+ @brief Tests the flow of a successfully @c signInWithCredential:completion: call with an
+ email-password credential using the deprecated FIREmailPasswordAuthProvider.
+ */
+- (void)testSignInWithEmailCredentialSuccessWithDepricatedProvider {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
+ FIRVerifyPasswordResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.email, kEmail);
+ XCTAssertEqualObjects(request.password, kPassword);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
+ [self stubTokensWithMockResponse:mockVeriyPasswordResponse];
+ callback(mockVeriyPasswordResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ FIRAuthCredential *emailCredential =
+ [FIREmailPasswordAuthProvider credentialWithEmail:kEmail password:kPassword];
+#pragma clang diagnostic pop
+ [[FIRAuth auth] signInWithCredential:emailCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUser:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithEmailCredentialFailure
+ @brief Tests the flow of a failed @c signInWithCredential:completion: call with an
+ email-password credential.
+ */
+- (void)testSignInWithEmailCredentialFailure {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils userDisabledErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *emailCredential =
+ [FIREmailAuthProvider credentialWithEmail:kEmail password:kPassword];
+ [[FIRAuth auth] signInWithCredential:emailCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithEmailCredentialEmptyPassword
+ @brief Tests the flow of a failed @c signInWithCredential:completion: call with an
+ email-password credential using an empty password. This error occurs on the client side,
+ so there is no need to fake an RPC response.
+ */
+- (void)testSignInWithEmailCredentialEmptyPassword {
+ NSString *emptyString = @"";
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *emailCredential =
+ [FIREmailAuthProvider credentialWithEmail:kEmail password:emptyString];
+ [[FIRAuth auth] signInWithCredential:emailCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeWrongPassword);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+/** @fn testSignInWithGoogleAccountExistsError
+ @brief Tests the flow of a failed @c signInWithCredential:completion: with a Google credential
+ where the backend returns a needs @needConfirmation equal to true. An
+ FIRAuthErrorCodeAccountExistsWithDifferentCredential error should be thrown.
+ */
+- (void)testSignInWithGoogleAccountExistsError {
+ OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
+ FIRVerifyAssertionResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.providerID, FIRGoogleAuthProviderID);
+ XCTAssertEqualObjects(request.providerIDToken, kGoogleIDToken);
+ XCTAssertEqualObjects(request.providerAccessToken, kGoogleAccessToken);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
+ OCMStub([mockVeriyAssertionResponse needConfirmation]).andReturn(YES);
+ OCMStub([mockVeriyAssertionResponse email]).andReturn(kEmail);
+ [self stubTokensWithMockResponse:mockVeriyAssertionResponse];
+ callback(mockVeriyAssertionResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *googleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [[FIRAuth auth] signInWithCredential:googleCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeAccountExistsWithDifferentCredential);
+ XCTAssertEqualObjects(error.userInfo[FIRAuthErrorUserInfoEmailKey], kEmail);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithGoogleCredentialSuccess
+ @brief Tests the flow of a successful @c signInWithCredential:completion: call with an
+ Google Sign-In credential.
+ */
+- (void)testSignInWithGoogleCredentialSuccess {
+ OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
+ FIRVerifyAssertionResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.providerID, FIRGoogleAuthProviderID);
+ XCTAssertEqualObjects(request.providerIDToken, kGoogleIDToken);
+ XCTAssertEqualObjects(request.providerAccessToken, kGoogleAccessToken);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
+ OCMStub([mockVeriyAssertionResponse federatedID]).andReturn(kGoogleID);
+ OCMStub([mockVeriyAssertionResponse providerID]).andReturn(FIRGoogleAuthProviderID);
+ OCMStub([mockVeriyAssertionResponse localID]).andReturn(kLocalID);
+ OCMStub([mockVeriyAssertionResponse displayName]).andReturn(kGoogleDisplayName);
+ [self stubTokensWithMockResponse:mockVeriyAssertionResponse];
+ callback(mockVeriyAssertionResponse, nil);
+ });
+ });
+ [self expectGetAccountInfoGoogle];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *googleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [[FIRAuth auth] signInWithCredential:googleCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserGoogle:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUserGoogle:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInAndRetrieveDataWithCredentialSuccess
+ @brief Tests the flow of a successful @c signInAndRetrieveDataWithCredential:completion: call
+ with an Google Sign-In credential.
+ */
+- (void)testSignInAndRetrieveDataWithCredentialSuccess {
+ OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
+ FIRVerifyAssertionResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.providerID, FIRGoogleAuthProviderID);
+ XCTAssertEqualObjects(request.providerIDToken, kGoogleIDToken);
+ XCTAssertEqualObjects(request.providerAccessToken, kGoogleAccessToken);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
+ OCMStub([mockVeriyAssertionResponse federatedID]).andReturn(kGoogleID);
+ OCMStub([mockVeriyAssertionResponse providerID]).andReturn(FIRGoogleAuthProviderID);
+ OCMStub([mockVeriyAssertionResponse localID]).andReturn(kLocalID);
+ OCMStub([mockVeriyAssertionResponse displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockVeriyAssertionResponse profile]).andReturn([[self class] googleProfile]);
+ OCMStub([mockVeriyAssertionResponse username]).andReturn(kDisplayName);
+ [self stubTokensWithMockResponse:mockVeriyAssertionResponse];
+ callback(mockVeriyAssertionResponse, nil);
+ });
+ });
+ [self expectGetAccountInfoGoogle];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *googleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:googleCredential
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserGoogle:authResult.user];
+ XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.username, kDisplayName);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRGoogleAuthProviderID);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUserGoogle:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithGoogleCredentialFailure
+ @brief Tests the flow of a failed @c signInWithCredential:completion: call with an
+ Google Sign-In credential.
+ */
+- (void)testSignInWithGoogleCredentialFailure {
+ OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils emailAlreadyInUseErrorWithEmail:kGoogleEmail]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *googleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [[FIRAuth auth] signInWithCredential:googleCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeEmailAlreadyInUse);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInAnonymouslySuccess
+ @brief Tests the flow of a successful @c signInAnonymously:completion: call.
+ */
+- (void)testSignInAnonymouslySuccess {
+ OCMExpect([_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSignUpNewUserRequest *_Nullable request,
+ FIRSignupNewUserCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertNil(request.email);
+ XCTAssertNil(request.password);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSignUpNewUserResponse = OCMClassMock([FIRSignUpNewUserResponse class]);
+ [self stubTokensWithMockResponse:mockSignUpNewUserResponse];
+ callback(mockSignUpNewUserResponse, nil);
+ });
+ });
+ [self expectGetAccountInfoAnonymous];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInAnonymouslyWithCompletion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserAnonymous:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUserAnonymous:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInAnonymouslyFailure
+ @brief Tests the flow of a failed @c signInAnonymously:completion: call.
+ */
+- (void)testSignInAnonymouslyFailure {
+ OCMExpect([_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils operationNotAllowedErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInAnonymouslyWithCompletion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeOperationNotAllowed);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithCustomTokenSuccess
+ @brief Tests the flow of a successful @c signInWithCustomToken:completion: call.
+ */
+- (void)testSignInWithCustomTokenSuccess {
+ OCMExpect([_mockBackend verifyCustomToken:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyCustomTokenRequest *_Nullable request,
+ FIRVerifyCustomTokenResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.token, kCustomToken);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyCustomTokenResponse = OCMClassMock([FIRVerifyCustomTokenResponse class]);
+ [self stubTokensWithMockResponse:mockVeriyCustomTokenResponse];
+ callback(mockVeriyCustomTokenResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInWithCustomToken:kCustomToken completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUser:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithCustomTokenFailure
+ @brief Tests the flow of a failed @c signInWithCustomToken:completion: call.
+ */
+- (void)testSignInWithCustomTokenFailure {
+ OCMExpect([_mockBackend verifyCustomToken:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils invalidCustomTokenErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInWithCustomToken:kCustomToken completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidCustomToken);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testCreateUserWithEmailPasswordSuccess
+ @brief Tests the flow of a successful @c createUserWithEmail:password:completion: call.
+ */
+- (void)testCreateUserWithEmailPasswordSuccess {
+ OCMExpect([_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSignUpNewUserRequest *_Nullable request,
+ FIRSignupNewUserCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.email, kEmail);
+ XCTAssertEqualObjects(request.password, kPassword);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSignUpNewUserResponse = OCMClassMock([FIRSignUpNewUserResponse class]);
+ [self stubTokensWithMockResponse:mockSignUpNewUserResponse];
+ callback(mockSignUpNewUserResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] createUserWithEmail:kEmail
+ password:kPassword
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUser:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testCreateUserWithEmailPasswordFailure
+ @brief Tests the flow of a failed @c createUserWithEmail:password:completion: call.
+ */
+- (void)testCreateUserWithEmailPasswordFailure {
+ NSString *reason = @"Password shouldn't be a common word.";
+ OCMExpect([_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils weakPasswordErrorWithServerResponseReason:reason]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] createUserWithEmail:kEmail
+ password:kPassword
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeWeakPassword);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ XCTAssertEqualObjects(error.userInfo[NSLocalizedFailureReasonErrorKey], reason);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testCreateUserEmptyPasswordFailure
+ @brief Tests the flow of a failed @c createUserWithEmail:password:completion: call due to an
+ empty password. This error occurs on the client side, so there is no need to fake an RPC
+ response.
+ */
+- (void)testCreateUserEmptyPasswordFailure {
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] createUserWithEmail:kEmail
+ password:@""
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeWeakPassword);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+/** @fn testSendPasswordResetEmailSuccess
+ @brief Tests the flow of a successful @c sendPasswordResetWithEmail:completion: call.
+ */
+- (void)testSendPasswordResetEmailSuccess {
+ OCMExpect([_mockBackend getOOBConfirmationCode:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetOOBConfirmationCodeRequest *_Nullable request,
+ FIRGetOOBConfirmationCodeResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.email, kEmail);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback([[FIRGetOOBConfirmationCodeResponse alloc] init], nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] sendPasswordResetWithEmail:kEmail completion:^(NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSendPasswordResetEmailFailure
+ @brief Tests the flow of a failed @c sendPasswordResetWithEmail:completion: call.
+ */
+- (void)testSendPasswordResetEmailFailure {
+ OCMExpect([_mockBackend getOOBConfirmationCode:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils appNotAuthorizedError]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] sendPasswordResetWithEmail:kEmail completion:^(NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeAppNotAuthorized);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignOut
+ @brief Tests the @c signOut: method.
+ */
+- (void)testSignOut {
+ [self waitForSignIn];
+ // Verify signing out succeeds and clears the current user.
+ NSError *error;
+ XCTAssertTrue([[FIRAuth auth] signOut:&error]);
+ XCTAssertNil([FIRAuth auth].currentUser);
+}
+
+/** @fn testAuthStateChanges
+ @brief Tests @c addAuthStateDidChangeListener: and @c removeAuthStateDidChangeListener: methods.
+ */
+- (void)testAuthStateChanges {
+ // Set up listener.
+ __block XCTestExpectation *expectation;
+ __block BOOL shouldHaveUser;
+ FIRAuthStateDidChangeListenerBlock listener = ^(FIRAuth *auth, FIRUser *_Nullable user) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(auth, [FIRAuth auth]);
+ XCTAssertEqual(user, [FIRAuth auth].currentUser);
+ if (shouldHaveUser) {
+ XCTAssertNotNil(user);
+ } else {
+ XCTAssertNil(user);
+ }
+ // `expectation` being nil means the listener is not expected to be fired at this moment.
+ XCTAssertNotNil(expectation);
+ [expectation fulfill];
+ };
+ [[FIRAuth auth] signOut:NULL];
+ [self waitForTimeIntervel:kWaitInterval]; // Wait until dust settled from previous tests.
+
+ // Listener should fire immediately when attached.
+ expectation = [self expectationWithDescription:@"initial"];
+ shouldHaveUser = NO;
+ FIRAuthStateDidChangeListenerHandle handle =
+ [[FIRAuth auth] addAuthStateDidChangeListener:listener];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // Listener should fire for signing in.
+ expectation = [self expectationWithDescription:@"sign-in"];
+ shouldHaveUser = YES;
+ [self waitForSignIn];
+
+ // Listener should not fire for signing in again.
+ shouldHaveUser = YES;
+ [self waitForSignIn];
+ [self waitForTimeIntervel:kWaitInterval]; // make sure listener is not called
+
+ // Listener should fire for signing out.
+ expectation = [self expectationWithDescription:@"sign-out"];
+ shouldHaveUser = NO;
+ [[FIRAuth auth] signOut:NULL];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // Listener should no longer fire once detached.
+ expectation = nil;
+ [[FIRAuth auth] removeAuthStateDidChangeListener:handle];
+ [self waitForSignIn];
+ [self waitForTimeIntervel:kWaitInterval]; // make sure listener is no longer called
+}
+
+/** @fn testIDTokenChanges
+ @brief Tests @c addIDTokenDidChangeListener: and @c removeIDTokenDidChangeListener: methods.
+ */
+- (void)testIDTokenChanges {
+ // Set up listener.
+ __block XCTestExpectation *expectation;
+ __block BOOL shouldHaveUser;
+ FIRIDTokenDidChangeListenerBlock listener = ^(FIRAuth *auth, FIRUser *_Nullable user) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(auth, [FIRAuth auth]);
+ XCTAssertEqual(user, [FIRAuth auth].currentUser);
+ if (shouldHaveUser) {
+ XCTAssertNotNil(user);
+ } else {
+ XCTAssertNil(user);
+ }
+ // `expectation` being nil means the listener is not expected to be fired at this moment.
+ XCTAssertNotNil(expectation);
+ [expectation fulfill];
+ };
+ [[FIRAuth auth] signOut:NULL];
+ [self waitForTimeIntervel:kWaitInterval]; // Wait until dust settled from previous tests.
+
+ // Listener should fire immediately when attached.
+ expectation = [self expectationWithDescription:@"initial"];
+ shouldHaveUser = NO;
+ FIRIDTokenDidChangeListenerHandle handle =
+ [[FIRAuth auth] addIDTokenDidChangeListener:listener];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // Listener should fire for signing in.
+ expectation = [self expectationWithDescription:@"sign-in"];
+ shouldHaveUser = YES;
+ [self waitForSignIn];
+
+ // Listener should fire for signing in again as the same user.
+ expectation = [self expectationWithDescription:@"sign-in again"];
+ shouldHaveUser = YES;
+ [self waitForSignIn];
+
+ // Listener should fire for signing out.
+ expectation = [self expectationWithDescription:@"sign-out"];
+ shouldHaveUser = NO;
+ [[FIRAuth auth] signOut:NULL];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // Listener should no longer fire once detached.
+ expectation = nil;
+ [[FIRAuth auth] removeIDTokenDidChangeListener:handle];
+ [self waitForSignIn];
+ [self waitForTimeIntervel:kWaitInterval]; // make sure listener is no longer called
+}
+
+#pragma mark - Automatic Token Refresh Tests.
+
+/** @fn testAutomaticTokenRefresh
+ @brief Tests a successful flow to automatically refresh tokens for a signed in user.
+ */
+- (void)testAutomaticTokenRefresh {
+ [[FIRAuth auth] signOut:NULL];
+
+ // Enable auto refresh
+ [self enableAutoTokenRefresh];
+
+ // Sign in a user.
+ [self waitForSignIn];
+
+ // Set up expectation for secureToken RPC made by token refresh task.
+ [self mockSecureTokenResponseWithError:nil];
+
+ // Verify that the current user's access token is the "old" access token before automatic token
+ // refresh.
+ XCTAssertEqualObjects(kAccessToken, [FIRAuth auth].currentUser.rawAccessToken);
+
+ // Execute saved token refresh task.
+ XCTestExpectation *dispatchAfterExpectation =
+ [self expectationWithDescription:@"dispatchAfterExpectation"];
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ XCTAssertNotNil(_FIRAuthDispatcherCallback);
+ _FIRAuthDispatcherCallback();
+ [dispatchAfterExpectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // Verify that current user's access token is the "new" access token provided in the mock secure
+ // token response during automatic token refresh.
+ XCTAssertEqualObjects(kNewAccessToken, [FIRAuth auth].currentUser.rawAccessToken);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testAutomaticTokenRefreshInvalidTokenFailure
+ @brief Tests an unsuccessful flow to auto refresh tokens with an "invalid token" error.
+ This error should cause the user to be signed out.
+ */
+- (void)testAutomaticTokenRefreshInvalidTokenFailure {
+ [[FIRAuth auth] signOut:NULL];
+ // Enable auto refresh
+ [self enableAutoTokenRefresh];
+
+ // Sign in a user.
+ [self waitForSignIn];
+
+ // Set up expectation for secureToken RPC made by a failed attempt to refresh tokens.
+ [self mockSecureTokenResponseWithError:[FIRAuthErrorUtils invalidUserTokenErrorWithMessage:nil]];
+
+ // Verify that current user is still valid.
+ XCTAssertEqualObjects(kAccessToken, [FIRAuth auth].currentUser.rawAccessToken);
+
+ // Execute saved token refresh task.
+ XCTestExpectation *dispatchAfterExpectation =
+ [self expectationWithDescription:@"dispatchAfterExpectation"];
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ XCTAssertNotNil(_FIRAuthDispatcherCallback);
+ _FIRAuthDispatcherCallback();
+ [dispatchAfterExpectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ //Verify that the user is nil after failed attempt to refresh tokens caused signed out.
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testAutomaticTokenRefreshRetry
+ @brief Tests that a retry is attempted for a automatic token refresh task (which is not due to
+ invalid tokens). The initial attempt to refresh the access token fails, but the second
+ attempt is successful.
+ */
+- (void)testAutomaticTokenRefreshRetry {
+ [[FIRAuth auth] signOut:NULL];
+ // Enable auto refresh
+ [self enableAutoTokenRefresh];
+
+ // Sign in a user.
+ [self waitForSignIn];
+
+ // Set up expectation for secureToken RPC made by a failed attempt to refresh tokens.
+ [self mockSecureTokenResponseWithError:[NSError errorWithDomain:@"ERROR" code:-1 userInfo:nil]];
+
+ // Execute saved token refresh task.
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ XCTAssertNotNil(_FIRAuthDispatcherCallback);
+ _FIRAuthDispatcherCallback();
+ _FIRAuthDispatcherCallback = nil;
+ [expectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // The old access token should still be the current user's access token and not the new access
+ // token (kNewAccessToken).
+ XCTAssertEqualObjects(kAccessToken, [FIRAuth auth].currentUser.rawAccessToken);
+
+ // Set up expectation for secureToken RPC made by a successful attempt to refresh tokens.
+ [self mockSecureTokenResponseWithError:nil];
+
+ // Execute saved token refresh task.
+ XCTestExpectation *dispatchAfterExpectation =
+ [self expectationWithDescription:@"dispatchAfterExpectation"];
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ XCTAssertNotNil(_FIRAuthDispatcherCallback);
+ _FIRAuthDispatcherCallback();
+ [dispatchAfterExpectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // Verify that current user's access token is the "new" access token provided in the mock secure
+ // token response during automatic token refresh.
+ XCTAssertEqualObjects([FIRAuth auth].currentUser.rawAccessToken, kNewAccessToken);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testAutomaticTokenRefreshInvalidTokenFailure
+ @brief Tests that app foreground notification triggers the scheduling of an automatic token
+ refresh task.
+ */
+- (void)testAutoRefreshAppForegroundedNotification {
+ [[FIRAuth auth] signOut:NULL];
+ // Enable auto refresh
+ [self enableAutoTokenRefresh];
+
+ // Sign in a user.
+ [self waitForSignIn];
+
+ // Post "UIApplicationDidBecomeActiveNotification" to trigger scheduling token refresh task.
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:UIApplicationDidBecomeActiveNotification object:nil];
+
+ // Verify that current user is still valid with old access token.
+ XCTAssertEqualObjects(kAccessToken, [FIRAuth auth].currentUser.rawAccessToken);
+
+ // Set up expectation for secureToken RPC made by a successful attempt to refresh tokens.
+ [self mockSecureTokenResponseWithError:nil];
+
+ // Execute saved token refresh task.
+ XCTestExpectation *dispatchAfterExpectation =
+ [self expectationWithDescription:@"dispatchAfterExpectation"];
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ XCTAssertNotNil(_FIRAuthDispatcherCallback);
+ _FIRAuthDispatcherCallback();
+ [dispatchAfterExpectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ // Verify that current user is still valid with new access token.
+ XCTAssertEqualObjects(kNewAccessToken, [FIRAuth auth].currentUser.rawAccessToken);
+ OCMVerifyAll(_mockBackend);
+}
+
+#pragma mark - Helpers
+
+/** @fn mockSecureTokenResponseWithError:
+ @brief Set up expectation for secureToken RPC.
+ @param error The error that the mock should return if any.
+ */
+- (void)mockSecureTokenResponseWithError:(nullable NSError *)error {
+ // Set up expectation for secureToken RPC made by a successful attempt to refresh tokens.
+ XCTestExpectation *secureTokenResponseExpectation =
+ [self expectationWithDescription:@"secureTokenResponseExpectation"];
+ OCMExpect([_mockBackend secureToken:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSecureTokenRequest *_Nullable request,
+ FIRSecureTokenResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.refreshToken, kRefreshToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ if (error) {
+ callback(nil, error);
+ [secureTokenResponseExpectation fulfill];
+ return;
+ }
+ id mockSecureTokenResponse = OCMClassMock([FIRSecureTokenResponse class]);
+ OCMStub([mockSecureTokenResponse accessToken]).andReturn(kNewAccessToken);
+ NSDate *futureDate =
+ [[NSDate date] dateByAddingTimeInterval:kTestTokenExpirationTimeInterval];
+ OCMStub([mockSecureTokenResponse approximateExpirationDate]).andReturn(futureDate);
+ callback(mockSecureTokenResponse, nil);
+ [secureTokenResponseExpectation fulfill];
+ });
+ });
+}
+
+/** @fn enableAutoTokenRefresh
+ @brief Enables automatic token refresh by invoking FIRAuth's implementation of FIRApp's
+ |getTokenWithImplementation|.
+ */
+- (void)enableAutoTokenRefresh {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"autoTokenRefreshcallback"];
+ [[FIRAuth auth].app getTokenForcingRefresh:NO withCallback:^(NSString *_Nullable token,
+ NSError *_Nullable error) {
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+/** @fn app1
+ @brief Creates a Firebase app.
+ @return A @c FIRApp with some name.
+ */
+- (FIRApp *)app1 {
+ return [FIRApp appForAuthUnitTestsWithName:kFirebaseAppName1];
+}
+
+/** @fn app2
+ @brief Creates another Firebase app.
+ @return A @c FIRApp with some other name.
+ */
+- (FIRApp *)app2 {
+ return [FIRApp appForAuthUnitTestsWithName:kFirebaseAppName2];
+}
+
+/** @fn stubSecureTokensWithMockResponse
+ @brief Creates stubs on the mock response object with access and refresh tokens
+ @param mockResponse The mock response object.
+ */
+- (void)stubTokensWithMockResponse:(id)mockResponse {
+ OCMStub([mockResponse IDToken]).andReturn(kAccessToken);
+ OCMStub([mockResponse approximateExpirationDate])
+ .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
+ OCMStub([mockResponse refreshToken]).andReturn(kRefreshToken);
+}
+
+/** @fn expectGetAccountInfo
+ @brief Expects a GetAccountInfo request on the mock backend and calls back with fake account
+ data.
+ */
+- (void)expectGetAccountInfo {
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
+ FIRGetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
+ OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockGetAccountInfoResponseUser ]);
+ callback(mockGetAccountInfoResponse, nil);
+ });
+ });
+}
+
+/** @fn assertUser
+ @brief Asserts the given FIRUser matching the fake data returned by @c expectGetAccountInfo.
+ @param user The user object to be verified.
+ */
+- (void)assertUser:(FIRUser *)user {
+ XCTAssertNotNil(user);
+ XCTAssertEqualObjects(user.uid, kLocalID);
+ XCTAssertEqualObjects(user.displayName, kDisplayName);
+ XCTAssertEqualObjects(user.email, kEmail);
+ XCTAssertFalse(user.anonymous);
+ XCTAssertEqual(user.providerData.count, 0u);
+}
+
+/** @fn expectGetAccountInfoGoogle
+ @brief Expects a GetAccountInfo request on the mock backend and calls back with fake account
+ data for a Google Sign-In user.
+ */
+- (void)expectGetAccountInfoGoogle {
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
+ FIRGetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockGoogleUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
+ OCMStub([mockGoogleUserInfo providerID]).andReturn(FIRGoogleAuthProviderID);
+ OCMStub([mockGoogleUserInfo displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGoogleUserInfo federatedID]).andReturn(kGoogleID);
+ OCMStub([mockGoogleUserInfo email]).andReturn(kGoogleEmail);
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser providerUserInfo])
+ .andReturn((@[ mockGoogleUserInfo ]));
+ id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
+ OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockGetAccountInfoResponseUser ]);
+ callback(mockGetAccountInfoResponse, nil);
+ });
+ });
+}
+
+/** @fn assertUserGoogle
+ @brief Asserts the given FIRUser matching the fake data returned by
+ @c expectGetAccountInfoGoogle.
+ @param user The user object to be verified.
+ */
+- (void)assertUserGoogle:(FIRUser *)user {
+ XCTAssertNotNil(user);
+ XCTAssertEqualObjects(user.uid, kLocalID);
+ XCTAssertEqualObjects(user.displayName, kDisplayName);
+ XCTAssertEqual(user.providerData.count, 1u);
+ id<FIRUserInfo> googleUserInfo = user.providerData[0];
+ XCTAssertEqualObjects(googleUserInfo.providerID, FIRGoogleAuthProviderID);
+ XCTAssertEqualObjects(googleUserInfo.uid, kGoogleID);
+ XCTAssertEqualObjects(googleUserInfo.displayName, kGoogleDisplayName);
+ XCTAssertEqualObjects(googleUserInfo.email, kGoogleEmail);
+}
+
+/** @fn expectGetAccountInfoAnonymous
+ @brief Expects a GetAccountInfo request on the mock backend and calls back with fake anonymous
+ account data.
+ */
+- (void)expectGetAccountInfoAnonymous {
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
+ FIRGetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
+ OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockGetAccountInfoResponseUser ]);
+ callback(mockGetAccountInfoResponse, nil);
+ });
+ });
+}
+
+/** @fn assertUserAnonymous
+ @brief Asserts the given FIRUser matching the fake data returned by
+ @c expectGetAccountInfoAnonymous.
+ @param user The user object to be verified.
+ */
+- (void)assertUserAnonymous:(FIRUser *)user {
+ XCTAssertNotNil(user);
+ XCTAssertEqualObjects(user.uid, kLocalID);
+ XCTAssertNil(user.displayName);
+ XCTAssertTrue(user.anonymous);
+ XCTAssertEqual(user.providerData.count, 0u);
+}
+
+/** @fn waitForSignIn
+ @brief Signs in a user to prepare for tests.
+ @remarks This method also waits for all other pending @c XCTestExpectation instances.
+ */
+- (void)waitForSignIn {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
+ FIRVerifyPasswordResponseCallback callback) {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
+ [self stubTokensWithMockResponse:mockVeriyPasswordResponse];
+ callback(mockVeriyPasswordResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signInWithEmail:kEmail password:kPassword completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+ XCTAssertNotNil([FIRAuth auth].currentUser);
+}
+
+/** @fn waitForTimeInterval:
+ @brief Wait for a particular time interval.
+ @remarks This method also waits for all other pending @c XCTestExpectation instances.
+ */
+- (void)waitForTimeIntervel:(NSTimeInterval)timeInterval {
+ static dispatch_queue_t queue;
+ static dispatch_once_t onceToken;
+ XCTestExpectation *expectation = [self expectationWithDescription:@"waitForTimeIntervel:"];
+ dispatch_once(&onceToken, ^{
+ queue = dispatch_queue_create("com.google.FIRAuthUnitTests.waitForTimeIntervel", NULL);
+ });
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeInterval * NSEC_PER_SEC), queue, ^() {
+ [expectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:timeInterval + kExpectationTimeout handler:nil];
+}
+
+@end