// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import Foundation import Combine import XCTest import FirebaseAuth class UserTests: XCTestCase { override class func setUp() { FirebaseApp.configureForTests() } override class func tearDown() { FirebaseApp.app()?.delete { success in if success { print("Shut down app successfully.") } else { print("💥 There was a problem when shutting down the app..") } } } override func setUp() { do { try Auth.auth().signOut() } catch {} } fileprivate struct ProviderCredentials { var providerID: String var federatedID: String var displayName: String var idToken: String? var accessToken: String var email: String var localID: String var phoneNumber: String? = nil var userInfo: [String: String]? = [:] } fileprivate static let continueURL = "continueURL" fileprivate static let fakeOOBCode = "testoobcode" fileprivate static let phoneNumber = "12345658" fileprivate static let verificationID = "55432" fileprivate static let verificationCode = "12345678" fileprivate static let accessTokenTimeToLive: TimeInterval = 60 * 60 fileprivate static let approximateExpirationDate: TimeInterval = 60 * 60 fileprivate static let refreshToken = "REFRESH_TOKEN" fileprivate static let accessToken = "ACCESS_TOKEN" fileprivate static let newAccessToken = "NewAccessToken" fileprivate static let email = "user@company.com" fileprivate static let password = "!@#$%^" fileprivate static let passwordHash = "UkVEQUNURUQ=" fileprivate static let userName = "User Doe" fileprivate static let localID = "localId" fileprivate static let googleEmail = "user@gmail.com" fileprivate static let googleProfile: [String: String] = { [ "iss": "https://accounts.google.com\\", "email": googleEmail, "given_name": "User", "family_name": "Doe", ] }() class MockGetAccountInfoResponse: FIRGetAccountInfoResponse { fileprivate var providerCredentials: ProviderCredentials! override var users: [FIRGetAccountInfoResponseUser] { let response = MockGetAccountInfoResponseUser(dictionary: [:]) response.providerCredentials = providerCredentials return [response] } } class MockSetAccountInfoResponse: FIRSetAccountInfoResponse {} class MockSecureAccessResponse: FIRSecureTokenResponse { override var accessToken: String { return "ACCESS_TOKEN" } } class MockVerifyPhoneNumberResponse: FIRVerifyPhoneNumberResponse { fileprivate var providerCredentials: ProviderCredentials! override var phoneNumber: String? { providerCredentials.phoneNumber } } class MockGetAccountInfoResponseUser: FIRGetAccountInfoResponseUser { fileprivate var providerCredentials: ProviderCredentials! override var localID: String { providerCredentials.localID } override var email: String { providerCredentials.email } override var displayName: String { providerCredentials.displayName } override var passwordHash: String? { UserTests.passwordHash } override var phoneNumber: String? { providerCredentials.phoneNumber } override var providerUserInfo: [FIRGetAccountInfoResponseProviderUserInfo]? { guard let userInfo = providerCredentials.userInfo else { return nil } let response = MockGetAccountInfoResponseProviderUserInfo(dictionary: userInfo) response.providerCredentials = providerCredentials return [response] } } class MockVerifyAssertionResponse: FIRVerifyAssertionResponse { fileprivate var providerCredentials: ProviderCredentials! override var localID: String? { providerCredentials.localID } override var federatedID: String? { providerCredentials.federatedID } override var providerID: String? { providerCredentials.providerID } override var displayName: String? { providerCredentials.displayName } override var profile: [String: NSObject]? { googleProfile as [String: NSString] } override var username: String? { userName } override var idToken: String? { providerCredentials.accessToken } override var refreshToken: String? { UserTests.refreshToken } override var approximateExpirationDate: Date? { Date(timeIntervalSinceNow: accessTokenTimeToLive) } } class MockVerifyPasswordResponse: FIRVerifyPasswordResponse { fileprivate var providerCredentials: ProviderCredentials! override var refreshToken: String { UserTests.refreshToken } override var email: String { providerCredentials.email } override var idToken: String { providerCredentials.accessToken } override var approximateExpirationDate: Date { Date(timeIntervalSinceNow: UserTests.accessTokenTimeToLive) } } class MockGetAccountInfoResponseProviderUserInfo: FIRGetAccountInfoResponseProviderUserInfo { fileprivate var providerCredentials: ProviderCredentials! override var providerID: String? { providerCredentials.providerID } override var displayName: String? { providerCredentials.displayName } override var federatedID: String? { providerCredentials.federatedID } override var email: String? { googleEmail } } class MockAuthBackend: AuthBackendImplementationMock { fileprivate var providerCredentials: ProviderCredentials! var verifyAssertionCallback: Result = .success(MockVerifyAssertionResponse()) override func verifyAssertion(_ request: FIRVerifyAssertionRequest, callback: @escaping FIRVerifyAssertionResponseCallback) { XCTAssertEqual(request.apiKey, Credentials.apiKey) XCTAssertEqual(request.providerID, providerCredentials.providerID) XCTAssertEqual(request.providerIDToken, providerCredentials.idToken) XCTAssertEqual(request.providerAccessToken, providerCredentials.accessToken) XCTAssertTrue(request.returnSecureToken) switch verifyAssertionCallback { case let .success(response): response.providerCredentials = providerCredentials callback(response, nil) case let .failure(error): callback(nil, error) } } override func verifyPassword(_ request: FIRVerifyPasswordRequest, callback: @escaping FIRVerifyPasswordResponseCallback) { let response = MockVerifyPasswordResponse() response.providerCredentials = providerCredentials callback(response, nil) } override func getAccountInfo(_ request: FIRGetAccountInfoRequest, callback: @escaping FIRGetAccountInfoResponseCallback) { XCTAssertEqual(request.apiKey, Credentials.apiKey) XCTAssertEqual(request.accessToken, providerCredentials.accessToken) let response = MockGetAccountInfoResponse() response.providerCredentials = providerCredentials callback(response, nil) } override func setAccountInfo(_ request: FIRSetAccountInfoRequest, callback: @escaping FIRSetAccountInfoResponseCallback) { XCTAssertEqual(request.apiKey, Credentials.apiKey) XCTAssertEqual(request.accessToken, providerCredentials.accessToken) XCTAssertNotNil(request.deleteProviders) XCTAssertNil(request.email) XCTAssertNil(request.localID) XCTAssertNil(request.displayName) XCTAssertNil(request.photoURL) XCTAssertNil(request.password) XCTAssertNil(request.providers) XCTAssertNil(request.deleteAttributes) callback(MockSetAccountInfoResponse(), nil) } override func verifyPhoneNumber(_ request: FIRVerifyPhoneNumberRequest, callback: @escaping FIRVerifyPhoneNumberResponseCallback) { XCTAssertEqual(request.verificationID, UserTests.verificationID) XCTAssertEqual(request.verificationCode, UserTests.verificationCode) XCTAssertEqual(request.operation, FIRAuthOperationType.link) XCTAssertEqual(request.accessToken, providerCredentials.accessToken) let response = MockVerifyPhoneNumberResponse() response.providerCredentials = providerCredentials callback(response, nil) } var actionCodeSettings: ActionCodeSettings? var getOOBConfirmationCodeError: Error? override func getOOBConfirmationCode(_ request: FIRGetOOBConfirmationCodeRequest, callback: @escaping FIRGetOOBConfirmationCodeResponseCallback) { XCTAssertEqual(request.accessToken, UserTests.accessToken) XCTAssertEqual(request.continueURL, actionCodeSettings?.url?.absoluteString) XCTAssertEqual(request.dynamicLinkDomain, actionCodeSettings?.dynamicLinkDomain) if let error = getOOBConfirmationCodeError { callback(nil, error) } else { callback(MockGetOOBConfirmationCodeResponse(), nil) } } override func secureToken(_ request: FIRSecureTokenRequest, callback: @escaping FIRSecureTokenResponseCallback) { callback(MockSecureAccessResponse(), nil) } } class MockGetOOBConfirmationCodeResponse: FIRGetOOBConfirmationCodeResponse { override var oobCode: String { return UserTests.fakeOOBCode } } func testlinkAndRetrieveDataSuccess() { let facebookCredentials = ProviderCredentials( providerID: FacebookAuthProviderID, federatedID: "FACEBOOK_ID", displayName: "Facebook Doe", idToken: nil, accessToken: "FACEBOOK_ACCESS_TOKEN", email: UserTests.email, localID: UserTests.localID ) let authBackend = MockAuthBackend() authBackend.providerCredentials = facebookCredentials FIRAuthBackend.setBackendImplementation(authBackend) var cancellables = Set() let userSignInExpectation = expectation(description: "User associated from a third-party") let facebookCredential = FacebookAuthProvider .credential(withAccessToken: facebookCredentials.accessToken) // when Auth.auth() .signIn(with: facebookCredential) .flatMap { [weak authBackend] authResult -> Future in XCTAssertEqual(authResult.additionalUserInfo?.profile, UserTests.googleProfile as [String: NSString]) XCTAssertEqual(authResult.additionalUserInfo?.username, UserTests.userName) XCTAssertEqual(authResult.additionalUserInfo?.providerID, FacebookAuthProviderID) let googleCredentials = ProviderCredentials( providerID: GoogleAuthProviderID, federatedID: "GOOGLE_ID", displayName: "Google Doe", idToken: "GOOGLE_ID_TOKEN", accessToken: "GOOGLE_ACCESS_TOKEN", email: UserTests.googleEmail, localID: UserTests.localID, phoneNumber: nil, userInfo: [:] ) authBackend?.providerCredentials = googleCredentials let linkGoogleCredential = GoogleAuthProvider.credential( withIDToken: googleCredentials.idToken!, accessToken: googleCredentials.accessToken ) return authResult.user .link(with: linkGoogleCredential) } .sink { completion in switch completion { case .finished: print("Finished") case let .failure(error): XCTFail("💥 Something went wrong: \(error)") } } receiveValue: { linkAuthResult in let user = linkAuthResult.user // Verify that the current user and reauthenticated user are same pointers. XCTAssertEqual(Auth.auth().currentUser, user) XCTAssertEqual(linkAuthResult.additionalUserInfo?.profile, UserTests.googleProfile as [String: NSString]) XCTAssertEqual(linkAuthResult.additionalUserInfo?.username, UserTests.userName) XCTAssertEqual(linkAuthResult.additionalUserInfo?.providerID, GoogleAuthProviderID) userSignInExpectation.fulfill() } .store(in: &cancellables) // then wait(for: [userSignInExpectation], timeout: expectationTimeout) } func testLinkAndRetrieveDataError() { let facebookCredentials = ProviderCredentials( providerID: FacebookAuthProviderID, federatedID: "FACEBOOK_ID", displayName: "Facebook Doe", idToken: nil, accessToken: "FACEBOOK_ACCESS_TOKEN", email: UserTests.email, localID: UserTests.localID ) let authBackend = MockAuthBackend() authBackend.providerCredentials = facebookCredentials FIRAuthBackend.setBackendImplementation(authBackend) var cancellables = Set() let userSignInExpectation = expectation(description: "User associated from a third-party") let facebookCredential = FacebookAuthProvider .credential(withAccessToken: facebookCredentials.accessToken) // when Auth.auth() .signIn(with: facebookCredential) .flatMap { [weak authBackend] authResult -> Future in XCTAssertEqual(authResult.additionalUserInfo?.profile, UserTests.googleProfile as [String: NSString]) XCTAssertEqual(authResult.additionalUserInfo?.username, UserTests.userName) XCTAssertEqual(authResult.additionalUserInfo?.providerID, FacebookAuthProviderID) XCTAssertEqual(Auth.auth().currentUser, authResult.user) let googleCredentials = ProviderCredentials( providerID: GoogleAuthProviderID, federatedID: "GOOGLE_ID", displayName: "Google Doe", idToken: "GOOGLE_ID_TOKEN", accessToken: "GOOGLE_ACCESS_TOKEN", email: UserTests.googleEmail, localID: UserTests.localID, phoneNumber: nil, userInfo: [:] ) authBackend?.providerCredentials = googleCredentials authBackend? .verifyAssertionCallback = .failure(FIRAuthErrorUtils .accountExistsWithDifferentCredentialError( withEmail: UserTests.userName, updatedCredential: nil )) let linkGoogleCredential = GoogleAuthProvider.credential( withIDToken: googleCredentials.idToken!, accessToken: googleCredentials.accessToken ) return authResult.user .link(with: linkGoogleCredential) } .sink { completion in if case let .failure(error as NSError) = completion { XCTAssertEqual( error.code, AuthErrorCode.accountExistsWithDifferentCredential.rawValue ) userSignInExpectation.fulfill() } } receiveValue: { linkAuthResult in XCTFail("💥 result unexpected") } .store(in: &cancellables) // then wait(for: [userSignInExpectation], timeout: expectationTimeout) } func testLinkAndRetrieveDataProviderAlreadyLinked() { let facebookCredentials = ProviderCredentials( providerID: FacebookAuthProviderID, federatedID: "FACEBOOK_ID", displayName: "Facebook Doe", idToken: nil, accessToken: "FACEBOOK_ACCESS_TOKEN", email: UserTests.email, localID: UserTests.localID ) let authBackend = MockAuthBackend() authBackend.providerCredentials = facebookCredentials FIRAuthBackend.setBackendImplementation(authBackend) var cancellables = Set() let userSignInExpectation = expectation(description: "User associated from a third-party") let facebookCredential = FacebookAuthProvider .credential(withAccessToken: facebookCredentials.accessToken) // when Auth.auth() .signIn(with: facebookCredential) .flatMap { authResult -> Future in XCTAssertEqual(authResult.additionalUserInfo?.profile, UserTests.googleProfile as [String: NSString]) XCTAssertEqual(authResult.additionalUserInfo?.username, UserTests.userName) XCTAssertEqual(authResult.additionalUserInfo?.providerID, FacebookAuthProviderID) XCTAssertEqual(Auth.auth().currentUser, authResult.user) authBackend.providerCredentials = facebookCredentials authBackend .verifyAssertionCallback = .failure(FIRAuthErrorUtils .accountExistsWithDifferentCredentialError( withEmail: UserTests.userName, updatedCredential: nil )) let linkFacebookCredential = FacebookAuthProvider .credential(withAccessToken: facebookCredentials.accessToken) return authResult.user .link(with: linkFacebookCredential) } .sink { completion in if case let .failure(error as NSError) = completion { XCTAssertEqual(error.code, AuthErrorCode.providerAlreadyLinked.rawValue) userSignInExpectation.fulfill() } } receiveValue: { linkAuthResult in XCTFail("💥 result unexpected") } .store(in: &cancellables) // then wait(for: [userSignInExpectation], timeout: expectationTimeout) } func testLinkAndRetrieveDataErrorAutoSignOut() { let facebookCredentials = ProviderCredentials( providerID: FacebookAuthProviderID, federatedID: "FACEBOOK_ID", displayName: "Facebook Doe", idToken: nil, accessToken: "FACEBOOK_ACCESS_TOKEN", email: UserTests.email, localID: UserTests.localID ) let authBackend = MockAuthBackend() authBackend.providerCredentials = facebookCredentials FIRAuthBackend.setBackendImplementation(authBackend) var cancellables = Set() let userSignInExpectation = expectation(description: "User associated from a third-party") let facebookCredential = FacebookAuthProvider .credential(withAccessToken: facebookCredentials.accessToken) // when Auth.auth() .signIn(with: facebookCredential) .flatMap { [weak authBackend] authResult -> Future in XCTAssertEqual(authResult.additionalUserInfo?.profile, UserTests.googleProfile as [String: NSString]) XCTAssertEqual(authResult.additionalUserInfo?.username, UserTests.userName) XCTAssertEqual(authResult.additionalUserInfo?.providerID, FacebookAuthProviderID) XCTAssertEqual(Auth.auth().currentUser, authResult.user) let googleCredentials = ProviderCredentials( providerID: GoogleAuthProviderID, federatedID: "GOOGLE_ID", displayName: "Google Doe", idToken: "GOOGLE_ID_TOKEN", accessToken: "GOOGLE_ACCESS_TOKEN", email: UserTests.googleEmail, localID: UserTests.localID, phoneNumber: nil, userInfo: [:] ) authBackend?.providerCredentials = googleCredentials authBackend? .verifyAssertionCallback = .failure(FIRAuthErrorUtils .userDisabledError(withMessage: nil)) let linkGoogleCredential = GoogleAuthProvider.credential( withIDToken: googleCredentials.idToken!, accessToken: googleCredentials.accessToken ) return authResult.user .link(with: linkGoogleCredential) } .sink { completion in if case let .failure(error as NSError) = completion { XCTAssertEqual(error.code, AuthErrorCode.userDisabled.rawValue) userSignInExpectation.fulfill() } } receiveValue: { linkAuthResult in XCTFail("💥 result unexpected") } .store(in: &cancellables) // then wait(for: [userSignInExpectation], timeout: expectationTimeout) } func testReauthenticateSuccess() { // given let emailCredentials = ProviderCredentials( providerID: EmailAuthProviderID, federatedID: "EMAIL_ID", displayName: "Google Doe", idToken: nil, accessToken: UserTests.accessToken, email: UserTests.email, localID: UserTests.localID ) let authBackend = MockAuthBackend() authBackend.providerCredentials = emailCredentials FIRAuthBackend.setBackendImplementation(authBackend) var cancellables = Set() let userReauthenticateExpectation = expectation(description: "User reauthenticated") // when Auth.auth() .signIn(withEmail: UserTests.email, password: UserTests.password) .flatMap { authResult -> Future in let credential = EmailAuthProvider.credential( withEmail: UserTests.email, password: UserTests.password ) authBackend.providerCredentials.accessToken = UserTests.newAccessToken return authResult.user .reauthenticate(with: credential) } .sink { completion in switch completion { case .finished: print("Finished") case let .failure(error): XCTFail("💥 Something went wrong: \(error)") } } receiveValue: { authResult in let user = authResult.user XCTAssertEqual(user.displayName, emailCredentials.displayName) XCTAssertEqual(user.email, emailCredentials.email) userReauthenticateExpectation.fulfill() } .store(in: &cancellables) // then wait(for: [userReauthenticateExpectation], timeout: expectationTimeout) } func testReauthenticateWithCredentialSuccess() { // given let googleCredentials = ProviderCredentials( providerID: GoogleAuthProviderID, federatedID: "GOOGLE_ID", displayName: "Google Doe", idToken: "GOOGLE_ID_TOKEN", accessToken: "GOOGLE_ACCESS_TOKEN", email: UserTests.googleEmail, localID: UserTests.localID ) let authBackend = MockAuthBackend() authBackend.providerCredentials = googleCredentials FIRAuthBackend.setBackendImplementation(authBackend) var cancellables = Set() let userReauthenticateExpectation = expectation(description: "User reauthenticated") let googleCredential = GoogleAuthProvider.credential( withIDToken: googleCredentials.idToken!, accessToken: googleCredentials.accessToken ) // when Auth.auth() .signIn(with: googleCredential) .flatMap { authResult -> Future in XCTAssertEqual(authResult.additionalUserInfo?.profile, UserTests.googleProfile as [String: NSString]) XCTAssertEqual(authResult.additionalUserInfo?.username, UserTests.userName) XCTAssertEqual(authResult.additionalUserInfo?.providerID, GoogleAuthProviderID) return authResult.user .reauthenticate(with: googleCredential) } .sink { completion in switch completion { case .finished: print("Finished") case let .failure(error): XCTFail("💥 Something went wrong: \(error)") } } receiveValue: { authResult in XCTAssertEqual(authResult.additionalUserInfo?.profile, UserTests.googleProfile as [String: NSString]) XCTAssertEqual(authResult.additionalUserInfo?.username, UserTests.userName) XCTAssertEqual(authResult.additionalUserInfo?.providerID, GoogleAuthProviderID) userReauthenticateExpectation.fulfill() } .store(in: &cancellables) // then wait(for: [userReauthenticateExpectation], timeout: expectationTimeout) } func testReauthenticateFailure() { // given let emailCredentials = ProviderCredentials( providerID: EmailAuthProviderID, federatedID: "EMAIL_ID", displayName: "Google Doe", idToken: nil, accessToken: UserTests.accessToken, email: UserTests.email, localID: UserTests.localID ) let authBackend = MockAuthBackend() authBackend.providerCredentials = emailCredentials FIRAuthBackend.setBackendImplementation(authBackend) var cancellables = Set() let userReauthenticateExpectation = expectation(description: "User reauthenticated") // when Auth.auth() .signIn(withEmail: UserTests.email, password: UserTests.password) .flatMap { authResult -> Future in authBackend.providerCredentials.displayName = "New User Doe" authBackend.providerCredentials.email = "newEmail" authBackend.providerCredentials.localID = "ANOTHER_LOCAL_ID" authBackend.providerCredentials.accessToken = "NewAccessToken" let credential = EmailAuthProvider.credential( withEmail: UserTests.email, password: UserTests.password ) return authResult.user .reauthenticate(with: credential) } .sink { completion in if case let .failure(error as NSError) = completion { // Verify user mismatch error. XCTAssertEqual(error.code, AuthErrorCode.userMismatch.rawValue) userReauthenticateExpectation.fulfill() } } receiveValue: { authResult in XCTFail("💥 result unexpected") } .store(in: &cancellables) // then wait(for: [userReauthenticateExpectation], timeout: expectationTimeout) } func testReauthenticateUserMismatchFailure() { // given let emailCredentials = ProviderCredentials( providerID: EmailAuthProviderID, federatedID: "EMAIL_ID", displayName: "Google Doe", idToken: nil, accessToken: UserTests.accessToken, email: UserTests.email, localID: UserTests.localID ) let authBackend = MockAuthBackend() authBackend.providerCredentials = emailCredentials FIRAuthBackend.setBackendImplementation(authBackend) var cancellables = Set() let userReauthenticateExpectation = expectation(description: "User reauthenticated") // when Auth.auth() .signIn(withEmail: UserTests.email, password: UserTests.password) .flatMap { authResult -> Future in let googleCredentials = ProviderCredentials( providerID: GoogleAuthProviderID, federatedID: "GOOGLE_ID", displayName: "Google Doe", idToken: "GOOGLE_ID_TOKEN", accessToken: "GOOGLE_ACCESS_TOKEN", email: UserTests.googleEmail, localID: UserTests.localID, phoneNumber: nil, userInfo: [:] ) authBackend.providerCredentials = googleCredentials authBackend .verifyAssertionCallback = .failure(FIRAuthErrorUtils .userNotFoundError(withMessage: nil)) let googleCredential = GoogleAuthProvider.credential( withIDToken: "GOOGLE_ID_TOKEN", accessToken: "GOOGLE_ACCESS_TOKEN" ) return authResult.user .reauthenticate(with: googleCredential) } .sink { completion in if case let .failure(error as NSError) = completion { // Verify user mismatch error. XCTAssertEqual(error.code, AuthErrorCode.userMismatch.rawValue) userReauthenticateExpectation.fulfill() } } receiveValue: { authResult in XCTFail("💥 result unexpected") } .store(in: &cancellables) // then wait(for: [userReauthenticateExpectation], timeout: expectationTimeout) } func testUnlinkPhoneAuthCredentialSuccess() { // given let emailCredentials = ProviderCredentials( providerID: PhoneAuthProviderID, federatedID: "EMAIL_ID", displayName: "Google Doe", idToken: nil, accessToken: UserTests.accessToken, email: UserTests.email, localID: UserTests.localID, userInfo: nil ) let authBackend = MockAuthBackend() authBackend.providerCredentials = emailCredentials FIRAuthBackend.setBackendImplementation(authBackend) var cancellables = Set() let userUnlinkedExpectation = expectation(description: "User Unlinked") // when Auth.auth() .signIn(withEmail: UserTests.email, password: UserTests.password) .flatMap { authResult -> Future in authBackend.providerCredentials.phoneNumber = Self.phoneNumber authBackend.providerCredentials.userInfo = ["providerId": PhoneAuthProviderID] let credential = PhoneAuthProvider.provider() .credential(withVerificationID: Self.verificationID, verificationCode: Self.verificationCode) return authResult.user .link(with: credential) } .flatMap { authResult -> AnyPublisher in XCTAssertEqual( Auth.auth().currentUser?.providerData.first?.providerID, PhoneAuthProviderID ) XCTAssertEqual(Auth.auth().currentUser?.phoneNumber, Self.phoneNumber) return authResult.user .unlink(fromProvider: PhoneAuthProviderID) .eraseToAnyPublisher() } .sink { completion in switch completion { case .finished: print("Finished") case let .failure(error): XCTFail("💥 Something went wrong: \(error)") } } receiveValue: { user in XCTAssertNil(Auth.auth().currentUser?.phoneNumber) userUnlinkedExpectation.fulfill() } .store(in: &cancellables) // then wait(for: [userUnlinkedExpectation], timeout: expectationTimeout) } func testSendVerificationEmailSuccess() { // given let emailCredentials = ProviderCredentials( providerID: PhoneAuthProviderID, federatedID: "EMAIL_ID", displayName: "Google Doe", idToken: nil, accessToken: UserTests.accessToken, email: UserTests.email, localID: UserTests.localID ) let authBackend = MockAuthBackend() authBackend.providerCredentials = emailCredentials FIRAuthBackend.setBackendImplementation(authBackend) var cancellables = Set() let sentVerificationEmailExpectation = expectation(description: "Sent verification email") // when Auth.auth() .signIn(withEmail: UserTests.email, password: UserTests.password) .flatMap { authResult -> Future in XCTAssertNotNil(authResult.user) return authResult.user .sendEmailVerification() } .sink { completion in switch completion { case .finished: print("Finished") case let .failure(error): XCTFail("💥 Something went wrong: \(error)") } } receiveValue: { _ in sentVerificationEmailExpectation.fulfill() } .store(in: &cancellables) // then wait(for: [sentVerificationEmailExpectation], timeout: expectationTimeout) } func testSendVerificationEmailWithActionCodeSettingsSuccess() { // given let emailCredentials = ProviderCredentials( providerID: PhoneAuthProviderID, federatedID: "EMAIL_ID", displayName: "Google Doe", idToken: nil, accessToken: UserTests.accessToken, email: UserTests.email, localID: UserTests.localID ) let authBackend = MockAuthBackend() authBackend.providerCredentials = emailCredentials FIRAuthBackend.setBackendImplementation(authBackend) var cancellables = Set() let sentVerificationEmailExpectation = expectation(description: "Sent verification email") // when Auth.auth() .signIn(withEmail: UserTests.email, password: UserTests.password) .flatMap { authResult -> Future in XCTAssertNotNil(authResult.user) let actionCodeSettings = ActionCodeSettings() actionCodeSettings.url = URL(string: UserTests.continueURL) actionCodeSettings.dynamicLinkDomain = "example.page.link" authBackend.actionCodeSettings = actionCodeSettings return authResult.user .sendEmailVerification(with: actionCodeSettings) } .sink { completion in switch completion { case .finished: print("Finished") case let .failure(error): XCTFail("💥 Something went wrong: \(error)") } } receiveValue: { _ in sentVerificationEmailExpectation.fulfill() } .store(in: &cancellables) // then wait(for: [sentVerificationEmailExpectation], timeout: expectationTimeout) } func testSendVerificationEmailInvalidRecipientEmail() { // given let emailCredentials = ProviderCredentials( providerID: PhoneAuthProviderID, federatedID: "EMAIL_ID", displayName: "Google Doe", idToken: nil, accessToken: UserTests.accessToken, email: UserTests.email, localID: UserTests.localID ) let authBackend = MockAuthBackend() authBackend.providerCredentials = emailCredentials authBackend.getOOBConfirmationCodeError = FIRAuthErrorUtils .invalidRecipientEmailError(withMessage: nil) FIRAuthBackend.setBackendImplementation(authBackend) var cancellables = Set() let sentVerificationEmailExpectation = expectation(description: "Sent verification email") // when Auth.auth() .signIn(withEmail: UserTests.email, password: UserTests.password) .flatMap { authResult -> Future in XCTAssertNotNil(authResult.user) return authResult.user .sendEmailVerification() } .sink { completion in if case let .failure(error as NSError) = completion { // Verify user mismatch error. XCTAssertEqual(error.code, AuthErrorCode.invalidRecipientEmail.rawValue) sentVerificationEmailExpectation.fulfill() } } receiveValue: { authResult in XCTFail("💥 result unexpected") } .store(in: &cancellables) // then wait(for: [sentVerificationEmailExpectation], timeout: expectationTimeout) } }