/* * 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 #import #import "FIRAuth_Internal.h" #import "FIRAuthErrorUtils.h" #import "FIRAuthBackend.h" #import "FIRAuthGlobalWorkQueue.h" #import "FIRAuthOperationType.h" #import "FIRAuthTokenResult.h" #import "FIREmailLinkSignInResponse.m" #import "FIRSecureTokenService.h" #import "FIRSecureTokenRequest.h" #import "FIRSecureTokenResponse.h" #import "FIRSignUpNewUserResponse.h" #import "FIRGetAccountInfoRequest.h" #import "FIRGetAccountInfoResponse.h" #import "FIRSetAccountInfoRequest.h" #import "FIRSetAccountInfoResponse.h" #import "FIRUser_Internal.h" #import "FIRUserInfo.h" #import "FIRUserMetadata.h" #import "FIRVerifyAssertionResponse.h" #import "FIRVerifyAssertionRequest.h" #import "FIRVerifyPasswordRequest.h" #import "FIRVerifyPasswordResponse.h" #import "FIRVerifyPhoneNumberRequest.h" #import "FIRVerifyPhoneNumberResponse.h" #import "FIRApp+FIRAuthUnitTests.h" #import "OCMStubRecorder+FIRAuthUnitTests.h" #import #if TARGET_OS_IOS #import "Phone/FIRPhoneAuthCredential_Internal.h" #import "FIRPhoneAuthProvider.h" #endif NS_ASSUME_NONNULL_BEGIN /** @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 = @"eyJhbGciOimnuzI1NiIsImtpZCI6ImY1YjE4Mjc2YTQ4NjYxZDBhODBiYzh" "jM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vZmItc2EtdXBncm" "FkZWQiLCJhdWQiOiJ0ZXN0X2F1ZCIsImF1dGhfdGltZSI6MTUyMjM2MDU0OSwidXNlcl9pZCI6InRlc3RfdXNlcl9pZCIs" "InN1YiI6InRlc3Rfc3ViIiwiaWF0IjoxNTIyMzYwNTU3LCJleHAiOjE1MjIzNjQxNTcsImVtYWlsIjoiYXVuaXRlc3R1c2" "VyQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6" "WyJhdW5pdGVzdHVzZXJAZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0=.WFQqSrpVnxx7m" "UrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpOvqKC" "3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH7jjN" "hHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqVkPbW" "lddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA"; /** @var kAccessTokenWithBase64URLCharacters @brief The fake access where the AUD value is "??????????>>>>>>>>>>" and the email value is ">>>>>>>>????????@gmail.com". */ static NSString *const kAccessTokenWithBase64URLCharacter = @"ey?hbGciOimnuzI1NiIsImtpZCI6ImY1YjE4M" "jc2YTQ4NjYxZDBhODBiYzhjM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2ds" "ZS5jb20vZmItc2EtdXBncmFkZWQiLCJhdWQiOiI_Pz8_Pz8_Pz8_Pj4-Pj4-Pj4-PiIsImF1dGhfdGltZSI6MTUyMjM2MD" "U0OSwidXNlcl9pZCI6InRlc3RfdXNlcl9pZCIsInN1YiI6InRlc3Rfc3ViIiwiaWF0IjoxNTIyMzYwNTU3LCJleHAiOjE1" "MjIzNjQxNTcsImVtYWlsIjoiPj4-Pj4-Pj4_Pz8_Pz8_P0BnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm" "ZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsiYXVuaXRlc3R1c2VyQGdtYWlsLmNvbSJdfSwic2lnbl9pbl9w" "cm92aWRlciI6IlBhc3N3b3JkIn19.WFQqSrpVnxx7mUrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-" "4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpOvqKC3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt" "1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH7jjNhHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7" "jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqVkPbWlddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA"; /** @var kbase64URLEncodedEmail @brief The fake email address with a value containing non-valid base64 encoded characters. @remarks This is used to ensure that the token parser is able to handle base64 URL enconded strings. */ static NSString *const kbase64URLEncodedEmail = @">>>>>>>>????????@gmail.com"; /** @var kbase64URLEncodedAUD @brief The fake AUD with a value containing non-valid base64 encoded characters. @remarks This is used to ensure that the token parser is able to handle base64 URL enconded strings. */ static NSString *const kbase64URLEncodedAUD = @"??????????>>>>>>>>>>"; /** @var kAccessTokenLength415 @brief The fake access token with 415 characters in the claims potion of the token. */ static NSString *const kAccessTokenLength415 = @"eyJhbGciOimnuzI1NiIsImtpZCI6ImY1YjE4Mjc2YTQ4NjYxZD" "BhODBiYzhjM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vdGVzd" "CIsImF1ZCI6InRlc3RfYXVkIiwiYXV0aF90aW1lIjoxNTIyMzYwNTQ5LCJ1c2VyX2lkIjoidGVzdF91c2VyX2lkIiwic3V" "iIjoidGVzdF9zdWIiLCJpYXQiOjE1MjIzNjA1NTcsImV4cCI6MTUyMjM2NDE1NywiZW1haWwiOiJhdW5pdGVzdHVzZXJAZ" "21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbImF" "1bml0ZXN0dXNlckBnbWFpbC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ=.WFQqSrpVnxx7m" "UrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpOvqKC" "3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH7jjN" "hHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqVkPbW" "lddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA"; /** @var kAccessTokenLength416 @brief The fake access token with 416 characters in the claims potion of the token. */ static NSString *const kAccessTokenLength416 = @"eyJhbGciOimnuzI1NiIsImtpZCI6ImY1YjE4Mjc2YTQ4NjYxZD" "BhODBiYzhjM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vdGVzd" "DIiLCJhdWQiOiJ0ZXN0X2F1ZCIsImF1dGhfdGltZSI6MTUyMjM2MDU0OSwidXNlcl9pZCI6InRlc3RfdXNlcl9pZCIsInN" "1YiI6InRlc3Rfc3ViIiwiaWF0IjoxNTIyMzYwNTU3LCJleHAiOjE1MjIzNjQxNTcsImVtYWlsIjoiYXVuaXRlc3R1c2VyQ" "GdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJ" "hdW5pdGVzdHVzZXJAZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0=.WFQqSrpVnxx7m" "UrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpOvqKC" "3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH7jjN" "hHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqVkPbW" "lddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA"; /** @var kAccessTokenLength4523 @brief The fake access token with 523 characters in the claims potion of the token. */ static NSString *const kAccessTokenLength523 = @"eyJhbGciOimnuzI1NiIsImtpZCI6ImY1YjE4Mjc2YTQ4NjYxZD" "BhODBiYzhjM2U5NDM0OTc0ZDFmMWRiNTEifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vdGVzd" "DQiLCJhdWQiOiJ0ZXN0X2F1ZCIsImF1dGhfdGltZSI6MTUyMjM2MDU0OSwidXNlcl9pZCI6InRlc3RfdXNlcl9pZF81NDM" "yIiwic3ViIjoidGVzdF9zdWIiLCJpYXQiOjE1MjIzNjA1NTcsImV4cCI6MTUyMjM2NDE1OSwiZW1haWwiOiJhdW5pdGVzd" "HVzZXI0QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWl" "sIjpbImF1bml0ZXN0dXNlckBnbWFpbC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ=.WFQqSrpVn" "xx7mUrdKZA517Sp4ZBt-l2xQzGKNMVE90JB3vuNa-NyWZC-aTYMvND3-4aS3qRnN2kvk9KJAaF3eI_BKkcbZuq8O7iDVpO" "vqKC3QcW0PnwqSPChL3XqoDF322FcBEgemwwgaEVZMuo7GhJvHw-XtBt1KRXOoGHcr3P6RsvoulUouKQmqt6TP27eZtrgH" "7jjNhHm7gjX_WaRmgTOvYsuDbBBGdE15yIVZ3acI4cFUgwMRhaW-dDV7jTOqZGYJlTsI5oRMehphoVnYnEedJga28r4mqV" "kPbWlddL4dVVm85FYmQcRc0b2CLMnSevBDlwu754ZUZmRgnuvDA"; /** @var kNewAccessToken @brief A new value for the fake access token. */ static NSString *const kNewAccessToken = @"NEW_ACCESS_TOKEN"; /** @var kAccessTokenValidInterval @brief The time to live for the fake access token. */ static const NSTimeInterval kAccessTokenTimeToLive = 60 * 60; /** @var kRefreshToken @brief The fake refresh token. */ static NSString *const kRefreshToken = @"REFRESH_TOKEN"; /** @var kLocalID @brief The fake local user ID. */ static NSString *const kLocalID = @"LOCAL_ID"; /** @var kAnotherLocalID @brief The fake local ID of another user. */ static NSString *const kAnotherLocalID = @"ANOTHER_LOCAL_ID"; /** @var kGoogleIDToken @brief The fake ID token from Google Sign-In. */ static NSString *const kGoogleIDToken = @"GOOGLE_ID_TOKEN"; /** @var kFacebookIDToken @brief The fake ID token from Facebook Sign-In. Facebook provider ID token is always nil. */ static NSString *const kFacebookIDToken = nil; /** @var kGoogleAccessToken @brief The fake access token from Google Sign-In. */ static NSString *const kGoogleAccessToken = @"GOOGLE_ACCESS_TOKEN"; /** @var kFacebookAccessToken @brief The fake access token from Facebook Sign-In. */ static NSString *const kFacebookAccessToken = @"FACEBOOK_ACCESS_TOKEN"; /** @var kEmail @brief The fake user email. */ static NSString *const kEmail = @"user@company.com"; /** @var kPhoneNumber @brief The fake user phone number. */ static NSString *const kPhoneNumber = @"12345658"; /** @var kTemporaryProof @brief The fake temporary proof. */ static NSString *const kTemporaryProof = @"12345658"; /** @var kNewEmail @brief A new value for the fake user email. */ static NSString *const kNewEmail = @"newuser@company.com"; /** @var kUserName @brief The fake user name. */ static NSString *const kUserName = @"User Doe"; /** @var kNewDisplayName @brief A new value for the fake user display name. */ static NSString *const kNewDisplayName = @"New User Doe"; /** @var kPhotoURL @brief The fake user profile image URL string. */ static NSString *const kPhotoURL = @"https://host.domain/image"; /** @var kNewPhotoURL @brief A new value for the fake user profile image URL string.. */ static NSString *const kNewPhotoURL = @"https://host.domain/new/image"; /** @var kFakePassword @brief The fake user password. */ static NSString *const kFakePassword = @"123456"; /** @var kNewPassword @brief The fake new user password. */ static NSString *const kNewPassword = @"1234567"; /** @var kPasswordHash @brief The fake user password hash. */ static NSString *const kPasswordHash = @"UkVEQUNURUQ="; /** @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 kEmailDisplayName @brief The fake user display name for email password user. */ static NSString *const kEmailDisplayName = @"Email Doe"; /** @var kFacebookDisplayName @brief The fake user display name under Facebook Sign-In. */ static NSString *const kFacebookDisplayName = @"Facebook Doe"; /** @var kGooglePhotoURL @brief The fake user profile image URL string under Google Sign-In. */ static NSString *const kGooglePhotoURL = @"https://googleusercontents.com/user/profile"; /** @var kFacebookID @brief The fake user ID under Facebook Login. */ static NSString *const kFacebookID = @"FACEBOOK_ID"; /** @var kFacebookEmail @brief The fake user email under Facebook Login. */ static NSString *const kFacebookEmail = @"user@facebook.com"; /** @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 kUserArchiverKey @brief The key used to archive and unarchive the user object for test NSSecureCoding. */ static NSString *const kUserArchiverKey = @"userArchiverKey"; /** @var kCreationDateInSeconds @brief The fake creation date. */ static NSTimeInterval const kCreationDateTimeIntervalInSeconds = 1505858500; /** @var kLastSignInDateTimeIntervalInSeconds @brief The fake last sign in date date. */ static NSTimeInterval const kLastSignInDateTimeIntervalInSeconds = 1505858583; /** @var kExpectationTimeout @brief The maximum time waiting for expectations to fulfill. */ static const NSTimeInterval kExpectationTimeout = 2; /** @extention FIRSecureTokenService @brief Extends the FIRSecureTokenService class to expose one private method for testing only. */ @interface FIRSecureTokenService () /** @fn hasValidAccessToken @brief private method exposed so it can be mocked to prevent the fake expiration date from affecting the the unit tests. */ - (BOOL)hasValidAccessToken; @end /** @class FIRUserTests @brief Tests for @c FIRUser . */ @interface FIRUserTests : XCTestCase @end @implementation FIRUserTests { /** @var _mockBackend @brief The mock @c FIRAuthBackendImplementation . */ id _mockBackend; } /** @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 = @{ @"email": kGoogleEmail, @"given_name": @"User", @"family_name": @"Doe" }; }); return kGoogleProfile; } - (void)setUp { [super setUp]; _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation)); [FIRAuthBackend setBackendImplementation:_mockBackend]; [FIRApp resetAppForAuthUnitTests]; } - (void)tearDown { [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil]; [super tearDown]; } #pragma mark - Tests /** @fn testUserPropertiesAndNSSecureCoding @brief Tests properties of the @c FIRUser instance before and after being serialized/deserialized. */ - (void)testUserPropertiesAndNSSecureCoding { // Mock auth provider user info for email/password for GetAccountInfo. id mockPasswordUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]); OCMStub([mockPasswordUserInfo providerID]).andReturn(FIREmailAuthProviderID); OCMStub([mockPasswordUserInfo federatedID]).andReturn(kEmail); OCMStub([mockPasswordUserInfo email]).andReturn(kEmail); // Mock auth provider user info from Google for GetAccountInfo. id mockGoogleUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]); OCMStub([mockGoogleUserInfo providerID]).andReturn(FIRGoogleAuthProviderID); OCMStub([mockGoogleUserInfo displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGoogleUserInfo photoURL]).andReturn([NSURL URLWithString:kGooglePhotoURL]); OCMStub([mockGoogleUserInfo federatedID]).andReturn(kGoogleID); OCMStub([mockGoogleUserInfo email]).andReturn(kGoogleEmail); // Mock auth provider user info from Facebook for GetAccountInfo. id mockFacebookUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]); OCMStub([mockFacebookUserInfo providerID]).andReturn(FIRFacebookAuthProviderID); OCMStub([mockFacebookUserInfo federatedID]).andReturn(kFacebookID); OCMStub([mockFacebookUserInfo email]).andReturn(kFacebookEmail); #if TARGET_OS_IOS // Mock auth provider user info from Phone auth provider for GetAccountInfo. id mockPhoneUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]); OCMStub([mockPhoneUserInfo providerID]).andReturn(FIRPhoneAuthProviderID); OCMStub([mockPhoneUserInfo phoneNumber]).andReturn(kPhoneNumber); #endif // Mock the root user info object for GetAccountInfo. id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser emailVerified]).andReturn(YES); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser photoURL]).andReturn([NSURL URLWithString:kPhotoURL]); OCMStub([mockGetAccountInfoResponseUser creationDate]). andReturn([NSDate dateWithTimeIntervalSince1970:kCreationDateTimeIntervalInSeconds]); OCMStub([mockGetAccountInfoResponseUser lastLoginDate]). andReturn([NSDate dateWithTimeIntervalSince1970:kLastSignInDateTimeIntervalInSeconds]); NSArray *providerUserInfos = @[ #if TARGET_OS_IOS mockPhoneUserInfo, #endif mockPasswordUserInfo, mockGoogleUserInfo, mockFacebookUserInfo ]; OCMStub([mockGetAccountInfoResponseUser providerUserInfo]).andReturn(providerUserInfos); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { // Verify FIRUserInfo properties on FIRUser itself. XCTAssertEqualObjects(user.providerID, @"Firebase"); XCTAssertEqualObjects(user.uid, kLocalID); XCTAssertEqualObjects(user.displayName, kGoogleDisplayName); XCTAssertEqualObjects(user.photoURL, [NSURL URLWithString:kPhotoURL]); XCTAssertEqualObjects(user.email, kEmail); XCTAssertEqualObjects(user.metadata.creationDate, [NSDate dateWithTimeIntervalSince1970:kCreationDateTimeIntervalInSeconds]); XCTAssertEqualObjects(user.metadata.lastSignInDate, [NSDate dateWithTimeIntervalSince1970:kLastSignInDateTimeIntervalInSeconds]); // Verify FIRUser properties besides providerData contents. XCTAssertFalse(user.anonymous); XCTAssertTrue(user.emailVerified); XCTAssertEqualObjects(user.refreshToken, kRefreshToken); XCTAssertEqual(user.providerData.count, providerUserInfos.count); NSDictionary> *providerMap = [self dictionaryWithUserInfoArray:user.providerData]; // Verify FIRUserInfo properties from email/password. id passwordUserInfo = providerMap[FIREmailAuthProviderID]; XCTAssertEqualObjects(passwordUserInfo.uid, kEmail); XCTAssertNil(passwordUserInfo.displayName); XCTAssertNil(passwordUserInfo.photoURL); XCTAssertEqualObjects(passwordUserInfo.email, kEmail); // Verify FIRUserInfo properties from the Google auth provider. id googleUserInfo = providerMap[FIRGoogleAuthProviderID]; XCTAssertEqualObjects(googleUserInfo.uid, kGoogleID); XCTAssertEqualObjects(googleUserInfo.displayName, kGoogleDisplayName); XCTAssertEqualObjects(googleUserInfo.photoURL, [NSURL URLWithString:kGooglePhotoURL]); XCTAssertEqualObjects(googleUserInfo.email, kGoogleEmail); // Verify FIRUserInfo properties from the Facebook auth provider. id facebookUserInfo = providerMap[FIRFacebookAuthProviderID]; XCTAssertEqualObjects(facebookUserInfo.uid, kFacebookID); XCTAssertNil(facebookUserInfo.displayName); XCTAssertNil(facebookUserInfo.photoURL); XCTAssertEqualObjects(facebookUserInfo.email, kFacebookEmail); #if TARGET_OS_IOS // Verify FIRUserInfo properties from the phone auth provider. id phoneUserInfo = providerMap[FIRPhoneAuthProviderID]; XCTAssertEqualObjects(phoneUserInfo.phoneNumber, kPhoneNumber); #endif // Test NSSecureCoding XCTAssertTrue([FIRUser supportsSecureCoding]); NSMutableData *data = [NSMutableData data]; NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; [archiver encodeObject:user forKey:kUserArchiverKey]; [archiver finishEncoding]; NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; FIRUser *unarchivedUser = [unarchiver decodeObjectForKey:kUserArchiverKey]; // Verify NSSecureCoding for FIRUser XCTAssertEqualObjects(unarchivedUser.providerID, user.providerID); XCTAssertEqualObjects(unarchivedUser.uid, user.uid); XCTAssertEqualObjects(unarchivedUser.email, user.email); XCTAssertEqualObjects(unarchivedUser.photoURL, user.photoURL); XCTAssertEqualObjects(unarchivedUser.displayName, user.displayName); // Verify NSSecureCoding properties besides providerData contents. XCTAssertEqual(unarchivedUser.anonymous, user.anonymous); XCTAssertEqual(unarchivedUser.emailVerified, user.emailVerified); XCTAssertEqualObjects(unarchivedUser.refreshToken, user.refreshToken); XCTAssertEqualObjects(unarchivedUser.metadata.creationDate, user.metadata.creationDate); XCTAssertEqualObjects(unarchivedUser.metadata.lastSignInDate, user.metadata.lastSignInDate); XCTAssertEqual(unarchivedUser.providerData.count, user.providerData.count); NSDictionary> *unarchivedProviderMap = [self dictionaryWithUserInfoArray:unarchivedUser.providerData]; // Verify NSSecureCoding properties from email/password. id unarchivedPasswordUserInfo = unarchivedProviderMap[FIREmailAuthProviderID]; XCTAssertEqualObjects(unarchivedPasswordUserInfo.uid, passwordUserInfo.uid); XCTAssertEqualObjects(unarchivedPasswordUserInfo.displayName, passwordUserInfo.displayName); XCTAssertEqualObjects(unarchivedPasswordUserInfo.photoURL, passwordUserInfo.photoURL); XCTAssertEqualObjects(unarchivedPasswordUserInfo.email, passwordUserInfo.email); // Verify NSSecureCoding properties from the Google auth provider. id unarchivedGoogleUserInfo = unarchivedProviderMap[FIRGoogleAuthProviderID]; XCTAssertEqualObjects(unarchivedGoogleUserInfo.uid, googleUserInfo.uid); XCTAssertEqualObjects(unarchivedGoogleUserInfo.displayName, googleUserInfo.displayName); XCTAssertEqualObjects(unarchivedGoogleUserInfo.photoURL, googleUserInfo.photoURL); XCTAssertEqualObjects(unarchivedGoogleUserInfo.email, googleUserInfo.email); // Verify NSSecureCoding properties from the Facebook auth provider. id unarchivedFacebookUserInfo = unarchivedProviderMap[FIRFacebookAuthProviderID]; XCTAssertEqualObjects(unarchivedFacebookUserInfo.uid, facebookUserInfo.uid); XCTAssertEqualObjects(unarchivedFacebookUserInfo.displayName, facebookUserInfo.displayName); XCTAssertEqualObjects(unarchivedFacebookUserInfo.photoURL, facebookUserInfo.photoURL); XCTAssertEqualObjects(unarchivedFacebookUserInfo.email, facebookUserInfo.email); #if TARGET_OS_IOS // Verify FIRUserInfo properties from the phone auth provider. id unarchivedPhoneUserInfo = unarchivedProviderMap[FIRPhoneAuthProviderID]; XCTAssertEqualObjects(unarchivedPhoneUserInfo.phoneNumber, phoneUserInfo.phoneNumber); #endif [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testUpdateEmailSuccess @brief Tests the flow of a successful @c updateEmail:completion: call. */ - (void)testUpdateEmailSuccess { id (^mockUserInfoWithDisplayName)(NSString *) = ^(NSString *displayName) { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(displayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); return mockGetAccountInfoResponseUser; }; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; id userInfoResponse = mockUserInfoWithDisplayName(kGoogleDisplayName); [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse completion:^(FIRUser *user) { // Pretend that the display name on the server has been changed since last request. [self expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoWithDisplayName(kNewDisplayName)]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request, FIRSetAccountInfoResponseCallback callback) { XCTAssertEqualObjects(request.APIKey, kAPIKey); XCTAssertEqualObjects(request.accessToken, kAccessToken); XCTAssertEqualObjects(request.email, kNewEmail); XCTAssertNil(request.localID); XCTAssertNil(request.displayName); XCTAssertNil(request.photoURL); XCTAssertNil(request.password); XCTAssertNil(request.providers); XCTAssertNil(request.deleteAttributes); XCTAssertNil(request.deleteProviders); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]); OCMStub([mockSetAccountInfoResponse email]).andReturn(kNewEmail); OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName); callback(mockSetAccountInfoResponse, nil); }); }); [user updateEmail:kNewEmail completion:^(NSError *_Nullable error) { XCTAssertNil(error); XCTAssertEqualObjects(user.email, kNewEmail); XCTAssertEqualObjects(user.displayName, kNewDisplayName); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testUpdateEmailWithAuthLinkAccountSuccess @brief Tests a successful @c updateEmail:completion: call updates provider info. */ - (void)testUpdateEmailWithAuthLinkAccountSuccess { id (^mockUserInfoWithDisplayName)(NSString *) = ^(NSString *displayName) { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(displayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); return mockGetAccountInfoResponseUser; }; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; id userInfoResponse = mockUserInfoWithDisplayName(kGoogleDisplayName); [self signInWithEmailLinkWithMockUserInfoResponse:userInfoResponse completion:^(FIRUser *user) { // Pretend that the display name on the server has been changed since last request. [self expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoWithDisplayName(kNewDisplayName)]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request, FIRSetAccountInfoResponseCallback callback) { XCTAssertEqualObjects(request.APIKey, kAPIKey); XCTAssertEqualObjects(request.accessToken, kAccessToken); XCTAssertEqualObjects(request.email, kNewEmail); XCTAssertNil(request.localID); XCTAssertNil(request.displayName); XCTAssertNil(request.photoURL); XCTAssertNil(request.password); XCTAssertNil(request.providers); XCTAssertNil(request.deleteAttributes); XCTAssertNil(request.deleteProviders); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]); OCMStub([mockSetAccountInfoResponse email]).andReturn(kNewEmail); OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName); callback(mockSetAccountInfoResponse, nil); }); }); [user updateEmail:kNewEmail completion:^(NSError *_Nullable error) { XCTAssertNil(error); XCTAssertEqualObjects(user.email, kNewEmail); XCTAssertEqualObjects(user.displayName, kNewDisplayName); XCTAssertFalse(user.isAnonymous); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testUpdateEmailFailure @brief Tests the flow of a failed @c updateEmail:completion: call. */ - (void)testUpdateEmailFailure { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andDispatchError2([FIRAuthErrorUtils invalidEmailErrorWithMessage:nil]); [user updateEmail:kNewEmail completion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidEmail); // Email should not have changed on the client side. XCTAssertEqualObjects(user.email, kEmail); // User is still signed in. XCTAssertEqual([FIRAuth auth].currentUser, user); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testUpdateEmailAutoSignOut @brief Tests the flow of a failed @c updateEmail:completion: call that automatically signs out. */ - (void)testUpdateEmailAutoSignOut { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andDispatchError2([FIRAuthErrorUtils invalidUserTokenErrorWithMessage:nil]); [user updateEmail:kNewEmail completion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidUserToken); // Email should not have changed on the client side. XCTAssertEqualObjects(user.email, kEmail); // User is no longer signed in. XCTAssertNil([FIRAuth auth].currentUser); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } #if TARGET_OS_IOS /** @fn testUpdatePhoneSuccess @brief Tests the flow of a successful @c updatePhoneNumberCredential:completion: call. */ - (void)testUpdatePhoneSuccess { id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); if (phoneNumber.length) { OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber); } return mockGetAccountInfoResponseUser; }; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; id userInfoResponse = mockUserInfoWithPhoneNumber(nil); [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse completion:^(FIRUser *user) { [self expectVerifyPhoneNumberRequestWithPhoneNumber:kPhoneNumber isLinkOperation:NO error:nil]; id userInfoResponseUpdate = mockUserInfoWithPhoneNumber(kPhoneNumber); [self expectGetAccountInfoWithMockUserInfoResponse:userInfoResponseUpdate]; FIRPhoneAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:kVerificationCode]; [user updatePhoneNumberCredential:credential completion:^(NSError * _Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects([FIRAuth auth].currentUser.phoneNumber, kPhoneNumber); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testUpdatePhoneNumberFailure @brief Tests the flow of a failed @c updatePhoneNumberCredential:completion: call. */ - (void)testUpdatePhoneNumberFailure { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]]) .andDispatchError2([FIRAuthErrorUtils invalidPhoneNumberErrorWithMessage:nil]); FIRPhoneAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:kVerificationCode]; [user updatePhoneNumberCredential:credential completion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidPhoneNumber); XCTAssertEqual([FIRAuth auth].currentUser, user); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testUpdatePhoneNumberFailureAutoSignOut @brief Tests the flow of a failed @c updatePhoneNumberCredential:completion: call that automatically signs out. */ - (void)testUpdatePhoneNumberFailureAutoSignOut { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]]) .andDispatchError2([FIRAuthErrorUtils userTokenExpiredErrorWithMessage:nil]); FIRPhoneAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:kVerificationCode]; [user updatePhoneNumberCredential:credential completion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeUserTokenExpired); XCTAssertNil([FIRAuth auth].currentUser); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } #endif /** @fn testUpdatePasswordSuccess @brief Tests the flow of a successful @c updatePassword:completion: call. */ - (void)testUpdatePasswordSuccess { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request, FIRSetAccountInfoResponseCallback callback) { XCTAssertEqualObjects(request.APIKey, kAPIKey); XCTAssertEqualObjects(request.accessToken, kAccessToken); XCTAssertEqualObjects(request.password, kNewPassword); XCTAssertNil(request.localID); XCTAssertNil(request.displayName); XCTAssertNil(request.photoURL); XCTAssertNil(request.email); XCTAssertNil(request.providers); XCTAssertNil(request.deleteAttributes); XCTAssertNil(request.deleteProviders); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]); OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName); callback(mockSetAccountInfoResponse, nil); }); }); [user updatePassword:kNewPassword completion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertFalse(user.isAnonymous); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testUpdatePasswordFailure @brief Tests the flow of a failed @c updatePassword:completion: call. */ - (void)testUpdatePasswordFailure { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andDispatchError2([FIRAuthErrorUtils requiresRecentLoginErrorWithMessage:nil]); [user updatePassword:kNewPassword completion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeRequiresRecentLogin); XCTAssertEqual([FIRAuth auth].currentUser, user); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testUpdateEmptyPasswordFailure @brief Tests the flow of a failed @c updatePassword:completion: call due to an empty password. */ - (void)testUpdateEmptyPasswordFailure { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { [user updatePassword:@"" completion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeWeakPassword); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; } /** @fn testUpdatePasswordFailureAutoSignOut @brief Tests the flow of a failed @c updatePassword:completion: call that automatically signs out. */ - (void)testUpdatePasswordFailureAutoSignOut { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andDispatchError2([FIRAuthErrorUtils userDisabledErrorWithMessage:nil]); [user updatePassword:kNewPassword completion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled); XCTAssertNil([FIRAuth auth].currentUser); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testChangeProfileSuccess @brief Tests a successful user profile change flow. */ - (void)testChangeProfileSuccess { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser photoURL]).andReturn(kPhotoURL); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request, FIRSetAccountInfoResponseCallback callback) { XCTAssertEqualObjects(request.APIKey, kAPIKey); XCTAssertEqualObjects(request.accessToken, kAccessToken); XCTAssertEqualObjects(request.displayName, kNewDisplayName); XCTAssertEqualObjects(request.photoURL, [NSURL URLWithString:kNewPhotoURL]); XCTAssertNil(request.localID); XCTAssertNil(request.email); XCTAssertNil(request.password); XCTAssertNil(request.providers); XCTAssertNil(request.deleteAttributes); XCTAssertNil(request.deleteProviders); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]); OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName); callback(mockSetAccountInfoResponse, nil); }); }); FIRUserProfileChangeRequest *profileChange = [user profileChangeRequest]; profileChange.photoURL = [NSURL URLWithString:kNewPhotoURL]; profileChange.displayName = kNewDisplayName; [profileChange commitChangesWithCompletion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects(user.displayName, kNewDisplayName); XCTAssertEqualObjects(user.photoURL, [NSURL URLWithString:kNewPhotoURL]); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testChangeProfileFailure @brief Tests a failed user profile change flow. */ - (void)testChangeProfileFailure { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andDispatchError2([FIRAuthErrorUtils tooManyRequestsErrorWithMessage:nil]); FIRUserProfileChangeRequest *profileChange = [user profileChangeRequest]; profileChange.displayName = kNewDisplayName; [profileChange commitChangesWithCompletion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeTooManyRequests); XCTAssertEqualObjects(user.displayName, kGoogleDisplayName); XCTAssertEqual([FIRAuth auth].currentUser, user); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testChangeProfileFailureAutoSignOut @brief Tests a failed user profile change flow that automatically signs out. */ - (void)testChangeProfileFailureAutoSignOut { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andDispatchError2([FIRAuthErrorUtils userNotFoundErrorWithMessage:nil]); FIRUserProfileChangeRequest *profileChange = [user profileChangeRequest]; profileChange.displayName = kNewDisplayName; [profileChange commitChangesWithCompletion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeUserNotFound); XCTAssertEqualObjects(user.displayName, kGoogleDisplayName); XCTAssertNil([FIRAuth auth].currentUser); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testReloadSuccess @brief Tests the flow of a successful @c reloadWithCompletion: call. */ - (void)testReloadSuccess { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { id mockGetAccountInfoResponseUserNew = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUserNew localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUserNew email]).andReturn(kNewEmail); OCMStub([mockGetAccountInfoResponseUserNew displayName]).andReturn(kNewDisplayName); OCMStub([mockGetAccountInfoResponseUserNew passwordHash]).andReturn(kPasswordHash); [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUserNew]; [user reloadWithCompletion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects(user.email, kNewEmail); XCTAssertEqualObjects(user.displayName, kNewDisplayName); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testGetIDTokenResultSuccess @brief Tests the flow of a successful @c getIDTokenResultWithCompletion: call. */ - (void)testGetIDTokenResultSuccess { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; id mockSecureTokenService = OCMClassMock([FIRSecureTokenService class]); OCMStub([mockSecureTokenService hasValidAccessToken]).andReturn(YES); [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { [user getIDTokenResultWithCompletion:^(FIRAuthTokenResult *_Nullable tokenResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects(tokenResult.token, kAccessToken); XCTAssertTrue(tokenResult.issuedAtDate && [tokenResult.issuedAtDate isKindOfClass:[NSDate class]]); XCTAssertTrue(tokenResult.authDate && [tokenResult.authDate isKindOfClass:[NSDate class]]); XCTAssertTrue(tokenResult.expirationDate && [tokenResult.expirationDate isKindOfClass:[NSDate class]]); XCTAssertTrue(tokenResult.claims && [tokenResult.claims isKindOfClass:[NSDictionary class]]); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testGetIDTokenResultForcingRefreshFailure @brief Tests the flow successful @c getIDTokenResultForcingRefresh:completion: calls. */ - (void)testGetIDTokenResultForcingRefreshSuccess { [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessToken]; [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessTokenLength415]; [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessTokenLength416]; [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessTokenLength523]; [self getIDTokenResultForcingRefreshSuccessWithIDToken:kAccessTokenWithBase64URLCharacter]; } /** @fn testGetIDTokenResultSuccessWithBase64EncodedURL @brief Tests the flow of a successful @c getIDTokenResultWithCompletion: call using a base64 url encoded string. */ - (void)testGetIDTokenResultSuccessWithBase64EncodedURL { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { OCMExpect([_mockBackend secureToken:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRSecureTokenRequest *_Nullable request, FIRSecureTokenResponseCallback callback) { XCTAssertEqualObjects(request.APIKey, kAPIKey); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockSecureTokenResponse = OCMClassMock([FIRSecureTokenResponse class]); OCMStub([mockSecureTokenResponse accessToken]).andReturn(kAccessTokenWithBase64URLCharacter); callback(mockSecureTokenResponse, nil); }); }); [user getIDTokenResultForcingRefresh:YES completion:^(FIRAuthTokenResult *_Nullable tokenResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects(tokenResult.token, kAccessTokenWithBase64URLCharacter); XCTAssertTrue(tokenResult.issuedAtDate && [tokenResult.issuedAtDate isKindOfClass:[NSDate class]]); XCTAssertTrue(tokenResult.authDate && [tokenResult.authDate isKindOfClass:[NSDate class]]); XCTAssertTrue(tokenResult.expirationDate && [tokenResult.expirationDate isKindOfClass:[NSDate class]]); XCTAssertTrue(tokenResult.claims && [tokenResult.claims isKindOfClass:[NSDictionary class]]); NSDictionary *claims = tokenResult.claims; XCTAssertEqualObjects(claims[@"email"], kbase64URLEncodedEmail); XCTAssertEqualObjects(claims[@"aud"], kbase64URLEncodedAUD); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testGetIDTokenResultForcingRefreshFailure @brief Tests the flow of a failed @c getIDTokenResultForcingRefresh:completion: call. */ - (void)testGetIDTokenResultForcingRefreshFailure { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { OCMExpect([_mockBackend secureToken:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRSecureTokenRequest *_Nullable request, FIRSecureTokenResponseCallback callback) { XCTAssertEqualObjects(request.APIKey, kAPIKey); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { NSError *underlying = [NSError errorWithDomain:@"Test Error" code:1 userInfo:nil]; callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:underlying]); }); }); [user getIDTokenResultForcingRefresh:YES completion:^(FIRAuthTokenResult *_Nullable tokenResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(tokenResult); XCTAssertEqual(error.code, FIRAuthErrorCodeNetworkError); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testReloadFailure @brief Tests the flow of a failed @c reloadWithCompletion: call. */ - (void)testReloadFailure { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andDispatchError2([FIRAuthErrorUtils quotaExceededErrorWithMessage:nil]); [user reloadWithCompletion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeQuotaExceeded); XCTAssertEqual([FIRAuth auth].currentUser, user); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testReloadFailureAutoSignOut @brief Tests the flow of a failed @c reloadWithCompletion: call that automtatically signs out. */ - (void)testReloadFailureAutoSignOut { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andDispatchError2([FIRAuthErrorUtils userTokenExpiredErrorWithMessage:nil]); [user reloadWithCompletion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeUserTokenExpired); XCTAssertNil([FIRAuth auth].currentUser); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testReauthenticateSuccess @brief Tests the flow of a successful @c reauthenticateWithCredential:completion: call. */ - (void)testReauthenticateSuccess { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request, FIRVerifyPasswordResponseCallback callback) { dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]); // New authentication comes back with new access token. OCMStub([mockVeriyPasswordResponse IDToken]).andReturn(kNewAccessToken); OCMStub([mockVeriyPasswordResponse approximateExpirationDate]) .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]); OCMStub([mockVeriyPasswordResponse refreshToken]).andReturn(kRefreshToken); callback(mockVeriyPasswordResponse, nil); }); }); OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request, FIRGetAccountInfoResponseCallback callback) { XCTAssertEqualObjects(request.APIKey, kAPIKey); // Verify that the new access token is being used for subsequent requests. XCTAssertEqualObjects(request.accessToken, kNewAccessToken); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]); OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockGetAccountInfoResponseUser ]); callback(mockGetAccountInfoResponse, nil); }); }); FIRAuthCredential *emailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; [user reauthenticateWithCredential:emailCredential completion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); // Verify that the current user is unchanged. XCTAssertEqual([FIRAuth auth].currentUser, user); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testReauthenticateAndRetrieveDataSuccess @brief Tests the flow of a successful @c reauthenticateAndRetrieveDataWithCredential:completion: call. */ - (void)testReauthenticateAndRetrieveDataSuccess { [self expectVerifyAssertionRequest:FIRGoogleAuthProviderID federatedID:kGoogleID displayName:kGoogleDisplayName profile:[[self class] googleProfile] providerIDToken:kGoogleIDToken providerAccessToken:kGoogleAccessToken]; 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, kUserName); XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRGoogleAuthProviderID); XCTAssertNil(error); [self expectVerifyAssertionRequest:FIRGoogleAuthProviderID federatedID:kGoogleID displayName:kGoogleDisplayName profile:[[self class] googleProfile] providerIDToken:kGoogleIDToken providerAccessToken:kGoogleAccessToken]; FIRAuthCredential *reauthenticateGoogleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [authResult.user reauthenticateAndRetrieveDataWithCredential:reauthenticateGoogleCredential completion:^(FIRAuthDataResult *_Nullable reauthenticateAuthResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); // Verify that the current user is unchanged. XCTAssertEqual([FIRAuth auth].currentUser, authResult.user); // Verify that the current user and reauthenticated user are not same pointers. XCTAssertNotEqualObjects(authResult.user, reauthenticateAuthResult.user); // Verify that anyway the current user and reauthenticated user have same IDs. XCTAssertEqualObjects(authResult.user.uid, reauthenticateAuthResult.user.uid); XCTAssertEqualObjects(authResult.user.displayName, reauthenticateAuthResult.user.displayName); XCTAssertEqualObjects(reauthenticateAuthResult.additionalUserInfo.profile, [[self class] googleProfile]); XCTAssertEqualObjects(reauthenticateAuthResult.additionalUserInfo.username, kUserName); XCTAssertEqualObjects(reauthenticateAuthResult.additionalUserInfo.providerID, FIRGoogleAuthProviderID); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; [self assertUserGoogle:[FIRAuth auth].currentUser]; OCMVerifyAll(_mockBackend); } /** @fn testReauthenticateFailure @brief Tests the flow of a failed @c reauthenticateWithCredential:completion: call. */ - (void)testReauthenticateFailure { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request, FIRVerifyPasswordResponseCallback callback) { dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]); OCMStub([mockVeriyPasswordResponse IDToken]).andReturn(kNewAccessToken); OCMStub([mockVeriyPasswordResponse approximateExpirationDate]) .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]); OCMStub([mockVeriyPasswordResponse refreshToken]).andReturn(kRefreshToken); callback(mockVeriyPasswordResponse, nil); }); }); OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request, FIRGetAccountInfoResponseCallback callback) { dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockGetAccountInfoResponseUserNew = OCMClassMock([FIRGetAccountInfoResponseUser class]); // The newly-signed-in user has a different ID. OCMStub([mockGetAccountInfoResponseUserNew localID]).andReturn(kAnotherLocalID); OCMStub([mockGetAccountInfoResponseUserNew email]).andReturn(kNewEmail); OCMStub([mockGetAccountInfoResponseUserNew displayName]).andReturn(kNewDisplayName); OCMStub([mockGetAccountInfoResponseUserNew passwordHash]).andReturn(kPasswordHash); id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]); OCMStub([mockGetAccountInfoResponse users]) .andReturn(@[ mockGetAccountInfoResponseUserNew ]); callback(mockGetAccountInfoResponse, nil); }); }); FIRAuthCredential *emailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; [user reauthenticateWithCredential:emailCredential completion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); // Verify user mismatch error. XCTAssertEqual(error.code, FIRAuthErrorCodeUserMismatch); // Verify that the current user is unchanged. XCTAssertEqual([FIRAuth auth].currentUser, user); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testReauthenticateUserMismatchFailure @brief Tests the flow of a failed @c reauthenticateWithCredential:completion: call due to trying to reauthenticate a user that does not exist. */ - (void)testReauthenticateUserMismatchFailure { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request, FIRVerifyAssertionResponseCallback callback) { dispatch_async(FIRAuthGlobalWorkQueue(), ^() { callback(nil, [FIRAuthErrorUtils userNotFoundErrorWithMessage:nil]); }); }); FIRAuthCredential *googleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [user reauthenticateWithCredential:googleCredential completion:^(NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); // Verify user mismatch error. XCTAssertEqual(error.code, FIRAuthErrorCodeUserMismatch); // Verify that the current user is unchanged. XCTAssertEqual([FIRAuth auth].currentUser, user); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testlinkAndRetrieveDataSuccess @brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion: call. */ - (void)testlinkAndRetrieveDataSuccess { [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID federatedID:kFacebookID displayName:kFacebookDisplayName profile:[[self class] googleProfile] providerIDToken:kFacebookIDToken providerAccessToken:kFacebookAccessToken]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName); XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID); XCTAssertNil(error); [self expectVerifyAssertionRequest:FIRGoogleAuthProviderID federatedID:kGoogleID displayName:kGoogleDisplayName profile:[[self class] googleProfile] providerIDToken:kGoogleIDToken providerAccessToken:kGoogleAccessToken]; FIRAuthCredential *linkGoogleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [authResult.user linkAndRetrieveDataWithCredential:linkGoogleCredential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); // Verify that the current user is unchanged. XCTAssertEqual([FIRAuth auth].currentUser, authResult.user); // Verify that the current user and reauthenticated user are same pointers. XCTAssertEqualObjects(authResult.user, linkAuthResult.user); // Verify that anyway the current user and linked user have same IDs. XCTAssertEqualObjects(authResult.user.uid, linkAuthResult.user.uid); XCTAssertEqualObjects(authResult.user.displayName, linkAuthResult.user.displayName); XCTAssertEqualObjects(linkAuthResult.additionalUserInfo.profile, [[self class] googleProfile]); XCTAssertEqualObjects(linkAuthResult.additionalUserInfo.username, kUserName); XCTAssertEqualObjects(linkAuthResult.additionalUserInfo.providerID, FIRGoogleAuthProviderID); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; [self assertUserGoogle:[FIRAuth auth].currentUser]; OCMVerifyAll(_mockBackend); } /** @fn testlinkAndRetrieveDataError @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion: call with an error from the backend. */ - (void)testlinkAndRetrieveDataError { [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID federatedID:kFacebookID displayName:kFacebookDisplayName profile:[[self class] googleProfile] providerIDToken:kFacebookIDToken providerAccessToken:kFacebookAccessToken]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName); XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID); XCTAssertNil(error); OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request, FIRVerifyAssertionResponseCallback callback) { dispatch_async(FIRAuthGlobalWorkQueue(), ^() { callback(nil, [FIRAuthErrorUtils accountExistsWithDifferentCredentialErrorWithEmail:kEmail]); }); }); FIRAuthCredential *linkGoogleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [authResult.user linkAndRetrieveDataWithCredential:linkGoogleCredential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeAccountExistsWithDifferentCredential); XCTAssertEqual(error.userInfo[FIRAuthErrorUserInfoEmailKey], kEmail); XCTAssertEqual([FIRAuth auth].currentUser, authResult.user); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testlinkAndRetrieveDataProviderAlreadyLinked @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion: call with FIRAuthErrorCodeProviderAlreadyLinked, which is a client side error. */ - (void)testlinkAndRetrieveDataProviderAlreadyLinked { [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID federatedID:kFacebookID displayName:kFacebookDisplayName profile:[[self class] googleProfile] providerIDToken:kFacebookIDToken providerAccessToken:kFacebookAccessToken]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName); XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID); XCTAssertNil(error); FIRAuthCredential *linkFacebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; [authResult.user linkAndRetrieveDataWithCredential:linkFacebookCredential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked); XCTAssertEqual([FIRAuth auth].currentUser, authResult.user); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testlinkAndRetrieveDataErrorAutoSignOut @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion: call that automatically signs out. */ - (void)testlinkAndRetrieveDataErrorAutoSignOut { [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID federatedID:kFacebookID displayName:kFacebookDisplayName profile:[[self class] googleProfile] providerIDToken:kFacebookIDToken providerAccessToken:kFacebookAccessToken]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName); XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID); XCTAssertNil(error); OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request, FIRVerifyAssertionResponseCallback callback) { dispatch_async(FIRAuthGlobalWorkQueue(), ^() { callback(nil, [FIRAuthErrorUtils userDisabledErrorWithMessage:nil]); }); }); FIRAuthCredential *linkGoogleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [authResult.user linkAndRetrieveDataWithCredential:linkGoogleCredential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled); XCTAssertNil([FIRAuth auth].currentUser); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testLinkingAnonymousAccountsUpdatesIsAnonymous @brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion: invocation for email credential. */ - (void)testLinkingAnonymousAccountsUpdatesIsAnonymous { FIRAuthCredential *linkEmailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail link:@"https://google.com?oobCode=aCode&mode=signIn"]; id (^mockUserInfoWithDisplayName)(NSString *, BOOL) = ^(NSString *displayName, BOOL hasProviders) { NSArray *providers = hasProviders ? @[ @{ @"providerId": FIREmailAuthProviderID, @"email": kEmail } ] : @[]; FIRGetAccountInfoResponseUser *responseUser = [[FIRGetAccountInfoResponseUser alloc] initWithDictionary:@{ @"providerUserInfo": providers, @"localId": kLocalID, @"displayName": displayName, @"email": kEmail }]; return responseUser; }; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; id userInfoResponse = mockUserInfoWithDisplayName(kGoogleDisplayName, NO); [self signInAnonymouslyWithMockGetAccountInfoResponse:userInfoResponse completion:^(FIRUser *user) { // Pretend that the display name and providers on the server have been updated. // Get account info is expected to be invoked twice. id updatedMockUser = mockUserInfoWithDisplayName(kNewDisplayName, YES); [self expectGetAccountInfoWithMockUserInfoResponse:updatedMockUser]; [self expectGetAccountInfoWithMockUserInfoResponse:updatedMockUser]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request, FIRSetAccountInfoResponseCallback callback) { id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]); OCMStub([mockSetAccountInfoResponse email]).andReturn(kNewEmail); OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName); callback(mockSetAccountInfoResponse, nil); }); XCTAssertTrue(user.isAnonymous); [user linkAndRetrieveDataWithCredential:linkEmailCredential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects(user.email, kEmail); XCTAssertFalse(user.isAnonymous); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testlinkEmailAndRetrieveDataSuccess @brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion: invocation for email credential. */ - (void)testlinkEmailAndRetrieveDataSuccess { [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID federatedID:kFacebookID displayName:kFacebookDisplayName profile:[[self class] googleProfile] providerIDToken:kFacebookIDToken providerAccessToken:kFacebookAccessToken]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName); XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID); XCTAssertNil(error); id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kEmailDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); // Get account info is expected to be invoked twice. [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request, FIRSetAccountInfoResponseCallback callback) { XCTAssertEqualObjects(request.APIKey, kAPIKey); XCTAssertEqualObjects(request.accessToken, kAccessToken); XCTAssertEqualObjects(request.password, kFakePassword); XCTAssertNil(request.localID); XCTAssertNil(request.displayName); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]); callback(mockSetAccountInfoResponse, nil); }); }); FIRAuthCredential *linkEmailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects(linkAuthResult.user.email, kEmail); XCTAssertEqualObjects(linkAuthResult.user.displayName, kEmailDisplayName); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testlinkEmailProviderAlreadyLinkedError @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion: invocation for email credential and FIRAuthErrorCodeProviderAlreadyLinked which is a client side error. */ - (void)testlinkEmailProviderAlreadyLinkedError { [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID federatedID:kFacebookID displayName:kFacebookDisplayName profile:[[self class] googleProfile] providerIDToken:kFacebookIDToken providerAccessToken:kFacebookAccessToken]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName); XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID); XCTAssertNil(error); id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kEmailDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); // Get account info is expected to be invoked twice. [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request, FIRSetAccountInfoResponseCallback callback) { XCTAssertEqualObjects(request.APIKey, kAPIKey); XCTAssertEqualObjects(request.accessToken, kAccessToken); XCTAssertEqualObjects(request.password, kFakePassword); XCTAssertNil(request.localID); XCTAssertNil(request.displayName); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]); callback(mockSetAccountInfoResponse, nil); }); }); FIRAuthCredential *linkEmailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertNil(error); XCTAssertEqualObjects(linkAuthResult.user.email, kEmail); XCTAssertEqualObjects(linkAuthResult.user.displayName, kEmailDisplayName); // Try linking same credential a second time to trigger client side error. [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked); [expectation fulfill]; }]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testlinkEmailAndRetrieveDataError @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion: invocation for email credential and an error from the backend. */ - (void)testlinkEmailAndRetrieveDataError { [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID federatedID:kFacebookID displayName:kFacebookDisplayName profile:[[self class] googleProfile] providerIDToken:kFacebookIDToken providerAccessToken:kFacebookAccessToken]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName); XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID); XCTAssertNil(error); 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(), ^() { callback(nil, [FIRAuthErrorUtils tooManyRequestsErrorWithMessage:nil]); }); }); FIRAuthCredential *linkEmailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeTooManyRequests); XCTAssertEqual([FIRAuth auth].currentUser, authResult.user); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testlinkEmailAndRetrieveDataError @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion: invocation that automatically signs out. */ - (void)testlinkEmailAndRetrieveDataErrorAutoSignOut { [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID federatedID:kFacebookID displayName:kFacebookDisplayName profile:[[self class] googleProfile] providerIDToken:kFacebookIDToken providerAccessToken:kFacebookAccessToken]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName); XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID); XCTAssertNil(error); 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(), ^() { callback(nil, [FIRAuthErrorUtils userTokenExpiredErrorWithMessage:nil]); }); }); FIRAuthCredential *linkEmailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeUserTokenExpired); XCTAssertNil([FIRAuth auth].currentUser); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testlinkCredentialSuccess @brief Tests the flow of a successful @c linkWithCredential:completion: call, without additional IDP data. */ - (void)testlinkCredentialSuccess { [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID federatedID:kFacebookID displayName:kFacebookDisplayName profile:[[self class] googleProfile] providerIDToken:kFacebookIDToken providerAccessToken:kFacebookAccessToken]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName); XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID); XCTAssertNil(error); [self expectVerifyAssertionRequest:FIRGoogleAuthProviderID federatedID:kGoogleID displayName:kGoogleDisplayName profile:[[self class] googleProfile] providerIDToken:kGoogleIDToken providerAccessToken:kGoogleAccessToken]; FIRAuthCredential *linkGoogleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [authResult.user linkWithCredential:linkGoogleCredential completion:^(FIRUser *_Nullable user, NSError *_Nullable error) { XCTAssertNil(error); id userInfo = user.providerData.firstObject; XCTAssertEqual(userInfo.providerID, FIRGoogleAuthProviderID); XCTAssertEqual([FIRAuth auth].currentUser, authResult.user); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; [self assertUserGoogle:[FIRAuth auth].currentUser]; OCMVerifyAll(_mockBackend); } /** @fn testlinkCredentialError @brief Tests the flow of an unsuccessful @c linkWithCredential:completion: call, with an error from the backend. */ - (void)testlinkCredentialError { [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID federatedID:kFacebookID displayName:kFacebookDisplayName profile:[[self class] googleProfile] providerIDToken:kFacebookIDToken providerAccessToken:kFacebookAccessToken]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName); XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID); XCTAssertNil(error); OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request, FIRVerifyAssertionResponseCallback callback) { dispatch_async(FIRAuthGlobalWorkQueue(), ^() { callback(nil, [FIRAuthErrorUtils userDisabledErrorWithMessage:nil]); }); }); FIRAuthCredential *linkGoogleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [authResult.user linkWithCredential:linkGoogleCredential completion:^(FIRUser *_Nullable user, NSError *_Nullable error) { XCTAssertNil(user); XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testlinkCredentialProviderAlreadyLinkedError @brief Tests the flow of an unsuccessful @c linkWithCredential:completion: call, with a client side error. */ - (void)testlinkCredentialProviderAlreadyLinkedError { [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID federatedID:kFacebookID displayName:kFacebookDisplayName profile:[[self class] googleProfile] providerIDToken:kFacebookIDToken providerAccessToken:kFacebookAccessToken]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; FIRAuthCredential *facebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUserFacebook:authResult.user]; XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName); XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID); XCTAssertNil(error); FIRAuthCredential *linkFacebookCredential = [FIRFacebookAuthProvider credentialWithAccessToken:kGoogleAccessToken]; [authResult.user linkWithCredential:linkFacebookCredential completion:^(FIRUser *_Nullable user, NSError *_Nullable error) { XCTAssertNil(user); XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } #if TARGET_OS_IOS /** @fn testlinkPhoneAuthCredentialSuccess @brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion: call using a phoneAuthCredential. */ - (void)testlinkPhoneAuthCredentialSuccess { id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); if (phoneNumber.length) { NSDictionary *userInfoDictionary = @{ @"providerId" : FIRPhoneAuthProviderID }; FIRGetAccountInfoResponseProviderUserInfo *userInfo = [[FIRGetAccountInfoResponseProviderUserInfo alloc] initWithDictionary:userInfoDictionary]; OCMStub([mockGetAccountInfoResponseUser providerUserInfo]).andReturn(@[ userInfo ]); OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber); } return mockGetAccountInfoResponseUser; }; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; id userInfoResponse = mockUserInfoWithPhoneNumber(nil); [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse completion:^(FIRUser *user) { [self expectVerifyPhoneNumberRequestWithPhoneNumber:kPhoneNumber isLinkOperation:YES error:nil]; id userInfoResponseUpdate = mockUserInfoWithPhoneNumber(kPhoneNumber); [self expectGetAccountInfoWithMockUserInfoResponse:userInfoResponseUpdate]; FIRPhoneAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:kVerificationCode]; [user linkAndRetrieveDataWithCredential:credential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertNil(error); XCTAssertEqualObjects([FIRAuth auth].currentUser.providerData.firstObject.providerID, FIRPhoneAuthProviderID); XCTAssertEqualObjects([FIRAuth auth].currentUser.phoneNumber, kPhoneNumber); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testUnlinkPhoneAuthCredentialSuccess @brief Tests the flow of a successful @c unlinkFromProvider:completion: call using a @c FIRPhoneAuthProvider. */ - (void)testUnlinkPhoneAuthCredentialSuccess { id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); if (phoneNumber.length) { NSDictionary *userInfoDictionary = @{ @"providerId" : FIRPhoneAuthProviderID }; FIRGetAccountInfoResponseProviderUserInfo *userInfo = [[FIRGetAccountInfoResponseProviderUserInfo alloc] initWithDictionary:userInfoDictionary]; OCMStub([mockGetAccountInfoResponseUser providerUserInfo]).andReturn(@[ userInfo ]); OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber); } return mockGetAccountInfoResponseUser; }; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request, FIRSetAccountInfoResponseCallback callback) { XCTAssertEqualObjects(request.APIKey, kAPIKey); XCTAssertEqualObjects(request.accessToken, kAccessToken); XCTAssertNotNil(request.deleteProviders); XCTAssertNil(request.email); XCTAssertNil(request.localID); XCTAssertNil(request.displayName); XCTAssertNil(request.photoURL); XCTAssertNil(request.password); XCTAssertNil(request.providers); XCTAssertNil(request.deleteAttributes); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]); callback(mockSetAccountInfoResponse, nil); }); }); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; id userInfoResponse = mockUserInfoWithPhoneNumber(nil); [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse completion:^(FIRUser *user) { [self expectVerifyPhoneNumberRequestWithPhoneNumber:kPhoneNumber isLinkOperation:YES error:nil]; id userInfoResponseUpdate = mockUserInfoWithPhoneNumber(kPhoneNumber); [self expectGetAccountInfoWithMockUserInfoResponse:userInfoResponseUpdate]; FIRPhoneAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:kVerificationCode]; // Link phone credential. [user linkAndRetrieveDataWithCredential:credential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertNil(error); XCTAssertEqualObjects([FIRAuth auth].currentUser.providerData.firstObject.providerID, FIRPhoneAuthProviderID); XCTAssertEqualObjects([FIRAuth auth].currentUser.phoneNumber, kPhoneNumber); // Immediately unlink the phone auth provider. [user unlinkFromProvider:FIRPhoneAuthProviderID completion:^(FIRUser *_Nullable user, NSError *_Nullable error) { XCTAssertNil(error); XCTAssertNil([FIRAuth auth].currentUser.phoneNumber); [expectation fulfill]; }]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testlinkPhoneAuthCredentialFailure @brief Tests the flow of a failed call to @c linkAndRetrieveDataWithCredential:completion: due to a phone provider already being linked. */ - (void)testlinkPhoneAuthCredentialFailure { id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); if (phoneNumber.length) { OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber); } return mockGetAccountInfoResponseUser; }; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; id userInfoResponse = mockUserInfoWithPhoneNumber(nil); [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse completion:^(FIRUser *user) { NSError *error = [FIRAuthErrorUtils providerAlreadyLinkedError]; [self expectVerifyPhoneNumberRequestWithPhoneNumber:nil isLinkOperation:YES error:error]; FIRPhoneAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:kVerificationCode]; [user linkAndRetrieveDataWithCredential:credential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn testlinkPhoneCredentialAlreadyExistsError @brief Tests the flow of @c linkAndRetrieveDataWithCredential:completion: call using a phoneAuthCredential and a credential already exisits error. In this case we should get a FIRAuthCredential in the error object. */ - (void)testlinkPhoneCredentialAlreadyExistsError { id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); if (phoneNumber.length) { OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber); } return mockGetAccountInfoResponseUser; }; void (^expectVerifyPhoneNumberRequest)(NSString *) = ^(NSString *phoneNumber) { OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRVerifyPhoneNumberRequest *_Nullable request, FIRVerifyPhoneNumberResponseCallback callback) { XCTAssertEqualObjects(request.verificationID, kVerificationID); XCTAssertEqualObjects(request.verificationCode, kVerificationCode); XCTAssertEqualObjects(request.accessToken, kAccessToken); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { FIRPhoneAuthCredential *credential = [[FIRPhoneAuthCredential alloc] initWithTemporaryProof:kTemporaryProof phoneNumber:kPhoneNumber providerID:FIRPhoneAuthProviderID]; callback(nil, [FIRAuthErrorUtils credentialAlreadyInUseErrorWithMessage:nil credential:credential]); }); }); }; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; id userInfoResponse = mockUserInfoWithPhoneNumber(nil); [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse completion:^(FIRUser *user) { expectVerifyPhoneNumberRequest(kPhoneNumber); FIRPhoneAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:kVerificationCode]; [user linkAndRetrieveDataWithCredential:credential completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeCredentialAlreadyInUse); FIRPhoneAuthCredential *credential = error.userInfo[FIRAuthUpdatedCredentialKey]; XCTAssertEqual(credential.temporaryProof, kTemporaryProof); XCTAssertEqual(credential.phoneNumber, kPhoneNumber); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } #endif #pragma mark - Helpers /** @fn getIDTokenResultForcingRefreshSuccess @brief Helper for testing the flow of a successful @c getIDTokenResultForcingRefresh:completion: call. */ - (void)getIDTokenResultForcingRefreshSuccessWithIDToken:(NSString *)idToken { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { OCMExpect([_mockBackend secureToken:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRSecureTokenRequest *_Nullable request, FIRSecureTokenResponseCallback callback) { XCTAssertEqualObjects(request.APIKey, kAPIKey); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockSecureTokenResponse = OCMClassMock([FIRSecureTokenResponse class]); OCMStub([mockSecureTokenResponse accessToken]).andReturn(idToken); callback(mockSecureTokenResponse, nil); }); }); [user getIDTokenResultForcingRefresh:YES completion:^(FIRAuthTokenResult *_Nullable tokenResult, NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects(tokenResult.token, idToken); XCTAssertTrue(tokenResult.issuedAtDate && [tokenResult.issuedAtDate isKindOfClass:[NSDate class]]); XCTAssertTrue(tokenResult.authDate && [tokenResult.authDate isKindOfClass:[NSDate class]]); XCTAssertTrue(tokenResult.expirationDate && [tokenResult.expirationDate isKindOfClass:[NSDate class]]); XCTAssertTrue(tokenResult.claims && [tokenResult.claims isKindOfClass:[NSDictionary class]]); [expectation fulfill]; }]; }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } /** @fn signInAnonymouslyWithMockGetAccountInfoResponse:completion: @brief Signs in with an anonymous account with mocked backend end calls. @param mockUserInfoResponse A mocked FIRGetAccountInfoResponseUser object. @param completion The completion block that takes the newly signed-in user as the only parameter. */ - (void)signInAnonymouslyWithMockGetAccountInfoResponse:(id)mockUserInfoResponse completion:(void (^)(FIRUser *user))completion { OCMExpect([_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRSignUpNewUserRequest *_Nullable request, FIRSignupNewUserCallback callback) { id mockSignUpResponse = OCMClassMock([FIRSignUpNewUserResponse class]); OCMStub([mockSignUpResponse IDToken]).andReturn(kAccessToken); OCMStub([mockSignUpResponse approximateExpirationDate]) .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]); OCMStub([mockSignUpResponse refreshToken]).andReturn(kRefreshToken); callback(mockSignUpResponse, nil); }); [self expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoResponse]; [[FIRAuth auth] signOut:NULL]; [[FIRAuth auth] signInAnonymouslyWithCompletion:^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) { XCTAssertNotNil(result.user); XCTAssertNil(error); completion(result.user); }]; } /** @fn signInWithEmailPasswordWithMockGetAccountInfoResponse:completion: @brief Signs in with an email and password account with mocked backend end calls. @param mockUserInfoResponse A mocked FIRGetAccountInfoResponseUser object. @param completion The completion block that takes the newly signed-in user as the only parameter. */ - (void)signInWithEmailPasswordWithMockUserInfoResponse:(id)mockUserInfoResponse completion:(void (^)(FIRUser *user))completion { OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request, FIRVerifyPasswordResponseCallback callback) { dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]); OCMStub([mockVeriyPasswordResponse IDToken]).andReturn(kAccessToken); OCMStub([mockVeriyPasswordResponse approximateExpirationDate]) .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]); OCMStub([mockVeriyPasswordResponse refreshToken]).andReturn(kRefreshToken); callback(mockVeriyPasswordResponse, nil); }); }); [self expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoResponse]; [[FIRAuth auth] signOut:NULL]; [[FIRAuth auth] signInWithEmail:kEmail password:kFakePassword completion:^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) { XCTAssertNotNil(result.user); XCTAssertNil(error); completion(result.user); }]; } /** @fn signInWithEmailLinkWithMockGetAccountInfoResponse:completion: @brief Signs in with an email link auth account with mocked backend end calls. @param mockUserInfoResponse A mocked FIRGetAccountInfoResponseUser object. @param completion The completion block that takes the newly signed-in user as the only parameter. */ - (void)signInWithEmailLinkWithMockUserInfoResponse:(id)mockUserInfoResponse completion:(void (^)(FIRUser *user))completion { OCMExpect([_mockBackend emailLinkSignin:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIREmailLinkSignInRequest *_Nullable request, FIREmailLinkSigninResponseCallback callback) { id mockVerifyLinkResponse = OCMClassMock([FIREmailLinkSignInResponse class]); OCMStub([mockVerifyLinkResponse IDToken]).andReturn(kAccessToken); OCMStub([mockVerifyLinkResponse approximateExpirationDate]) .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]); OCMStub([mockVerifyLinkResponse refreshToken]).andReturn(kRefreshToken); callback(mockVerifyLinkResponse, nil); }); [self expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoResponse]; [[FIRAuth auth] signOut:NULL]; [[FIRAuth auth] signInWithEmail:kEmail link:@"https://www.google.com?oobCode=aCode&mode=signIn" completion:^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) { XCTAssertNotNil(result.user); XCTAssertNil(error); completion(result.user); }]; } /** @fn expectGetAccountInfoWithMockUserInfoResponse: @brief Expects a GetAccountInfo request on the mock backend and calls back with provided fake account data. @param mockUserInfoResponse A mock @c FIRGetAccountInfoResponseUser object containing user info. */ - (void)expectGetAccountInfoWithMockUserInfoResponse:(id)mockUserInfoResponse { 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 mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]); OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockUserInfoResponse ]); callback(mockGetAccountInfoResponse, nil); }); }); } /** @fn dictionaryWithUserInfoArray: @brief Converts an array of @c FIRUserInfo into a dictionary that indexed by provider IDs. @param userInfoArray An array of @c FIRUserInfo objects. @return A dictionary contains same values as @c userInfoArray does but keyed by their @c providerID . */ - (NSDictionary> *) dictionaryWithUserInfoArray:(NSArray> *)userInfoArray { NSMutableDictionary> *map = [NSMutableDictionary dictionaryWithCapacity:userInfoArray.count]; for (id userInfo in userInfoArray) { XCTAssertNil(map[userInfo.providerID]); map[userInfo.providerID] = userInfo; } return map; } /** @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 assertUserGoogle @brief Asserts the given FIRUser matching the fake data returned by @c expectGetAccountInfo:federatedID:displayName: . @param user The user object to be verified. */ - (void)assertUserGoogle:(FIRUser *)user { XCTAssertNotNil(user); XCTAssertEqualObjects(user.uid, kLocalID); XCTAssertEqualObjects(user.displayName, kGoogleDisplayName); XCTAssertEqual(user.providerData.count, 1u); id googleUserInfo = user.providerData[0]; XCTAssertEqualObjects(googleUserInfo.providerID, FIRGoogleAuthProviderID); XCTAssertEqualObjects(googleUserInfo.uid, kGoogleID); XCTAssertEqualObjects(googleUserInfo.displayName, kGoogleDisplayName); XCTAssertEqualObjects(googleUserInfo.email, kGoogleEmail); } /** @fn assertUserFacebook @brief Asserts the given FIRUser matching the fake data returned by @c expectGetAccountInfo:federatedID:displayName: . @param user The user object to be verified. */ - (void)assertUserFacebook:(FIRUser *)user { XCTAssertNotNil(user); XCTAssertEqualObjects(user.uid, kLocalID); XCTAssertEqualObjects(user.displayName, kFacebookDisplayName); XCTAssertEqual(user.providerData.count, 1u); id googleUserInfo = user.providerData[0]; XCTAssertEqualObjects(googleUserInfo.providerID, FIRFacebookAuthProviderID); XCTAssertEqualObjects(googleUserInfo.uid, kFacebookID); XCTAssertEqualObjects(googleUserInfo.displayName, kFacebookDisplayName); XCTAssertEqualObjects(googleUserInfo.email, kGoogleEmail); } /** @fn expectGetAccountInfo:federatedID:displayName: @brief Expects a GetAccountInfo request on the mock backend and calls back with fake account data for a Google Sign-In user. */ - (void)expectGetAccountInfo:(NSString *)providerId federatedID:(NSString *)federatedID displayName:(NSString *)displayName { 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(providerId); OCMStub([mockGoogleUserInfo displayName]).andReturn(displayName); OCMStub([mockGoogleUserInfo federatedID]).andReturn(federatedID); OCMStub([mockGoogleUserInfo email]).andReturn(kGoogleEmail); id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(displayName); OCMStub([mockGetAccountInfoResponseUser providerUserInfo]) .andReturn((@[ mockGoogleUserInfo ])); id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]); OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockGetAccountInfoResponseUser ]); callback(mockGetAccountInfoResponse, nil); }); }); } /** @fn expectVerifyAssertionRequest:federatedID:displayName:profile:providerAccessToken: @brief Expects a Verify Assertion request on the mock backend and calls back with fake account data. */ - (void)expectVerifyAssertionRequest:(NSString *)providerId federatedID:(NSString *)federatedID displayName:(NSString *)displayName profile:(NSDictionary *)profile providerIDToken:(nullable NSString *)providerIDToken providerAccessToken:(NSString *)providerAccessToken { OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request, FIRVerifyAssertionResponseCallback callback) { XCTAssertEqualObjects(request.APIKey, kAPIKey); XCTAssertEqualObjects(request.providerID, providerId); XCTAssertEqualObjects(request.providerIDToken, providerIDToken); XCTAssertEqualObjects(request.providerAccessToken, providerAccessToken); XCTAssertTrue(request.returnSecureToken); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { id mockVeriyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]); OCMStub([mockVeriyAssertionResponse federatedID]).andReturn(federatedID); OCMStub([mockVeriyAssertionResponse providerID]).andReturn(providerId); OCMStub([mockVeriyAssertionResponse localID]).andReturn(kLocalID); OCMStub([mockVeriyAssertionResponse displayName]).andReturn(displayName); OCMStub([mockVeriyAssertionResponse profile]).andReturn(profile); OCMStub([mockVeriyAssertionResponse username]).andReturn(kUserName); [self stubTokensWithMockResponse:mockVeriyAssertionResponse]; callback(mockVeriyAssertionResponse, nil); }); }); [self expectGetAccountInfo:providerId federatedID:federatedID displayName:displayName]; } #if TARGET_OS_IOS /** @fn expectVerifyPhoneNumberRequestWithPhoneNumber:error: @brief Expects a verify phone numner request on the mock backend and calls back with fake account data or an error. @param phoneNumber Optionally; The phone number to use in the mocked response. @param isLinkOperation Boolean value that indicates whether or not this method is triggered by a link operation. @param error Optionally; The error to return in the mocked response. */ - (void)expectVerifyPhoneNumberRequestWithPhoneNumber:(nullable NSString *)phoneNumber isLinkOperation:(BOOL)isLinkOperation error:(nullable NSError*)error { OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]]) .andCallBlock2(^(FIRVerifyPhoneNumberRequest *_Nullable request, FIRVerifyPhoneNumberResponseCallback callback) { XCTAssertEqualObjects(request.verificationID, kVerificationID); XCTAssertEqualObjects(request.verificationCode, kVerificationCode); if (isLinkOperation) { XCTAssertEqual(request.operation, FIRAuthOperationTypeLink); } else { XCTAssertEqual(request.operation, FIRAuthOperationTypeUpdate); } XCTAssertEqualObjects(request.accessToken, kAccessToken); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { if (error) { callback(nil, error); return; } id mockVerifyPhoneNumberResponse = OCMClassMock([FIRVerifyPhoneNumberResponse class]); OCMStub([mockVerifyPhoneNumberResponse phoneNumber]).andReturn(phoneNumber); callback(mockVerifyPhoneNumberResponse, nil); }); }); } #endif @end NS_ASSUME_NONNULL_END