Files
firebase-ios-sdk/FirebaseAuth/Tests/Unit/AuthTests.swift
T
2024-07-18 15:05:15 -07:00

2457 lines
101 KiB
Swift

// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License")
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
import XCTest
@testable import FirebaseAuth
import FirebaseAuthInterop
import FirebaseCore
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
class AuthTests: RPCBaseTests {
static let kAccessToken = "TEST_ACCESS_TOKEN"
static let kNewAccessToken = "NEW_ACCESS_TOKEN"
static let kFakeAPIKey = "FAKE_API_KEY"
static let kFakeRecaptchaResponse = "RecaptchaResponse"
static let kFakeRecaptchaVersion = "RecaptchaVersion"
var auth: Auth!
static var testNum = 0
var authDispatcherCallback: (() -> Void)?
override func setUp() {
super.setUp()
let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000",
gcmSenderID: "00000000000000000-00000000000-000000000")
options.apiKey = AuthTests.kFakeAPIKey
options.projectID = "myProjectID"
let name = "test-AuthTests\(AuthTests.testNum)"
AuthTests.testNum = AuthTests.testNum + 1
FirebaseApp.configure(name: name, options: options)
#if (os(macOS) && !FIREBASE_AUTH_TESTING_USE_MACOS_KEYCHAIN) || SWIFT_PACKAGE
let keychainStorageProvider = FakeAuthKeychainStorage()
#else
let keychainStorageProvider = AuthKeychainStorageReal()
#endif // (os(macOS) && !FIREBASE_AUTH_TESTING_USE_MACOS_KEYCHAIN) || SWIFT_PACKAGE
auth = Auth(
app: FirebaseApp.app(name: name)!,
keychainStorageProvider: keychainStorageProvider
)
// Set authDispatcherCallback implementation in order to save the token refresh task for later
// execution.
AuthDispatcher.shared.dispatchAfterImplementation = { delay, queue, task in
XCTAssertNotNil(task)
XCTAssertGreaterThan(delay, 0)
XCTAssertEqual(kAuthGlobalWorkQueue, queue)
self.authDispatcherCallback = task
}
// Wait until Auth initialization completes
waitForAuthGlobalWorkQueueDrain()
}
private func waitForAuthGlobalWorkQueueDrain() {
let workerSemaphore = DispatchSemaphore(value: 0)
kAuthGlobalWorkQueue.async {
workerSemaphore.signal()
}
_ = workerSemaphore.wait(timeout: DispatchTime.distantFuture)
}
/** @fn testFetchSignInMethodsForEmailSuccess
@brief Tests the flow of a successful @c fetchSignInMethodsForEmail:completion: call.
*/
func testFetchSignInMethodsForEmailSuccess() throws {
let allSignInMethods = ["emailLink", "facebook.com"]
let expectation = self.expectation(description: #function)
rpcIssuer.respondBlock = {
let request = try XCTUnwrap(self.rpcIssuer.request as? CreateAuthURIRequest)
XCTAssertEqual(request.identifier, self.kEmail)
XCTAssertEqual(request.endpoint, "createAuthUri")
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
try self.rpcIssuer.respond(withJSON: ["signinMethods": allSignInMethods])
}
auth?.fetchSignInMethods(forEmail: kEmail) { signInMethods, error in
// 4. After the response triggers the callback, verify the returned signInMethods.
XCTAssertTrue(Thread.isMainThread)
XCTAssertEqual(signInMethods, allSignInMethods)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testFetchSignInMethodsForEmailFailure
@brief Tests the flow of a failed @c fetchSignInMethodsForEmail:completion: call.
*/
func testFetchSignInMethodsForEmailFailure() throws {
let expectation = self.expectation(description: #function)
rpcIssuer.respondBlock = {
let message = "TOO_MANY_ATTEMPTS_TRY_LATER"
try self.rpcIssuer.respond(serverErrorMessage: message)
}
auth?.fetchSignInMethods(forEmail: kEmail) { signInMethods, error in
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(signInMethods)
let rpcError = (error as? NSError)!
XCTAssertEqual(rpcError.code, AuthErrorCode.tooManyRequests.rawValue)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
#if os(iOS)
/** @fn testPhoneAuthSuccess
@brief Tests the flow of a successful @c signInWithCredential:completion for phone auth.
*/
func testPhoneAuthSuccess() throws {
let kVerificationID = "55432"
let kVerificationCode = "12345678"
let expectation = self.expectation(description: #function)
setFakeGetAccountProvider()
setFakeSecureTokenService()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyPhoneNumberRequest)
XCTAssertEqual(request.verificationCode, kVerificationCode)
XCTAssertEqual(request.verificationID, kVerificationID)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"isNewUser": true,
"refreshToken": self.kRefreshToken])
}
try auth?.signOut()
let credential = PhoneAuthProvider.provider(auth: auth)
.credential(withVerificationID: kVerificationID,
verificationCode: kVerificationCode)
auth?.signIn(with: credential) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
guard let user = authResult?.user,
let additionalUserInfo = authResult?.additionalUserInfo else {
XCTFail("authResult.user or additionalUserInfo is missing")
return
}
XCTAssertEqual(user.refreshToken, self.kRefreshToken)
XCTAssertFalse(user.isAnonymous)
XCTAssertTrue(additionalUserInfo.isNewUser)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
assertUser(auth?.currentUser)
}
/** @fn testPhoneAuthMissingVerificationCode
@brief Tests the flow of an unsuccessful @c signInWithCredential:completion for phone auth due
to an empty verification code
*/
func testPhoneAuthMissingVerificationCode() throws {
let kVerificationID = "55432"
let kVerificationCode = ""
let expectation = self.expectation(description: #function)
setFakeGetAccountProvider()
setFakeSecureTokenService()
try auth?.signOut()
let credential = PhoneAuthProvider.provider(auth: auth)
.credential(withVerificationID: kVerificationID,
verificationCode: kVerificationCode)
auth?.signIn(with: credential) { authResult, error in
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.missingVerificationCode.rawValue)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testPhoneAuthMissingVerificationID
@brief Tests the flow of an unsuccessful @c signInWithCredential:completion for phone auth due
to an empty verification ID.
*/
func testPhoneAuthMissingVerificationID() throws {
let kVerificationID = ""
let kVerificationCode = "123"
let expectation = self.expectation(description: #function)
setFakeGetAccountProvider()
setFakeSecureTokenService()
try auth?.signOut()
let credential = PhoneAuthProvider.provider(auth: auth)
.credential(withVerificationID: kVerificationID,
verificationCode: kVerificationCode)
auth?.signIn(with: credential) { authResult, error in
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.missingVerificationID.rawValue)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
#endif
/** @fn testSignInWithEmailLinkSuccess
@brief Tests the flow of a successful @c signInWithEmail:link:completion: call.
*/
func testSignInWithEmailLinkSuccess() throws {
try signInWithEmailLinkSuccessWithLinkOrDeeplink(link: kFakeEmailSignInLink)
}
/** @fn testSignInWithEmailLinkSuccessDeeplink
@brief Tests the flow of a successful @c signInWithEmail:link: call using a deep link.
*/
func testSignInWithEmailLinkSuccessDeeplink() throws {
try signInWithEmailLinkSuccessWithLinkOrDeeplink(link: kFakeEmailSignInDeeplink)
}
private func signInWithEmailLinkSuccessWithLinkOrDeeplink(link: String) throws {
let fakeCode = "testoobcode"
let expectation = self.expectation(description: #function)
setFakeGetAccountProvider()
setFakeSecureTokenService()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? EmailLinkSignInRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.oobCode, fakeCode)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"email": self.kEmail,
"isNewUser": true,
"refreshToken": self.kRefreshToken])
}
try auth?.signOut()
auth?.signIn(withEmail: kEmail, link: link) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
guard let user = authResult?.user else {
XCTFail("authResult.user is missing")
return
}
XCTAssertEqual(user.refreshToken, self.kRefreshToken)
XCTAssertFalse(user.isAnonymous)
XCTAssertEqual(user.email, self.kEmail)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
assertUser(auth?.currentUser)
}
/** @fn testSignInWithEmailLinkFailure
@brief Tests the flow of a failed @c signInWithEmail:link:completion: call.
*/
func testSignInWithEmailLinkFailure() throws {
let expectation = self.expectation(description: #function)
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Send the response from the fake backend.
try self.rpcIssuer.respond(serverErrorMessage: "INVALID_OOB_CODE")
}
try auth?.signOut()
auth?.signIn(withEmail: kEmail, link: kFakeEmailSignInLink) { authResult, error in
// 3. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.invalidActionCode.rawValue)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
XCTAssertNil(auth?.currentUser)
}
#if os(iOS)
/** @fn testSignInWithEmailPasswordWithRecaptchaSuccess
@brief Tests the flow of a successful @c signInWithEmail:password:completion: call.
*/
func testSignInWithEmailPasswordWithRecaptchaSuccess() throws {
let kRefreshToken = "fakeRefreshToken"
let expectation = self.expectation(description: #function)
setFakeGetAccountProvider()
setFakeSecureTokenService()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyPasswordRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.password, self.kFakePassword)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertTrue(request.returnSecureToken)
request.injectRecaptchaFields(recaptchaResponse: AuthTests.kFakeRecaptchaResponse,
recaptchaVersion: AuthTests.kFakeRecaptchaVersion)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"email": self.kEmail,
"isNewUser": true,
"refreshToken": kRefreshToken])
}
try auth?.signOut()
auth?.signIn(withEmail: kEmail, password: kFakePassword) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
guard let user = authResult?.user else {
XCTFail("authResult.user is missing")
return
}
XCTAssertEqual(user.refreshToken, kRefreshToken)
XCTAssertFalse(user.isAnonymous)
XCTAssertEqual(user.email, self.kEmail)
guard let additionalUserInfo = authResult?.additionalUserInfo else {
XCTFail("authResult.additionalUserInfo is missing")
return
}
XCTAssertFalse(additionalUserInfo.isNewUser)
XCTAssertEqual(additionalUserInfo.providerID, EmailAuthProvider.id)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
assertUser(auth?.currentUser)
}
/** @fn testSignInWithEmailPasswordWithRecaptchaFallbackSuccess
@brief Tests the flow of a successful @c signInWithEmail:password:completion: call.
*/
func testSignInWithEmailPasswordWithRecaptchaFallbackSuccess() throws {
let kRefreshToken = "fakeRefreshToken"
let expectation = self.expectation(description: #function)
setFakeGetAccountProvider()
setFakeSecureTokenService()
let kTestRecaptchaKey = "projects/123/keys/456"
rpcIssuer.recaptchaSiteKey = kTestRecaptchaKey
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyPasswordRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.password, self.kFakePassword)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertTrue(request.returnSecureToken)
request.injectRecaptchaFields(recaptchaResponse: AuthTests.kFakeRecaptchaResponse,
recaptchaVersion: AuthTests.kFakeRecaptchaVersion)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(serverErrorMessage: "MISSING_RECAPTCHA_TOKEN")
}
rpcIssuer.nextRespondBlock = {
// 4. Validate again the created Request instance after the recaptcha retry.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyPasswordRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.password, self.kFakePassword)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertTrue(request.returnSecureToken)
request.injectRecaptchaFields(recaptchaResponse: AuthTests.kFakeRecaptchaResponse,
recaptchaVersion: AuthTests.kFakeRecaptchaVersion)
// 5. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"email": self.kEmail,
"isNewUser": true,
"refreshToken": kRefreshToken])
}
try auth?.signOut()
auth?.signIn(withEmail: kEmail, password: kFakePassword) { authResult, error in
// 6. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(error)
guard let user = authResult?.user else {
XCTFail("authResult.user is missing")
return
}
XCTAssertEqual(user.refreshToken, kRefreshToken)
XCTAssertFalse(user.isAnonymous)
XCTAssertEqual(user.email, self.kEmail)
guard let additionalUserInfo = authResult?.additionalUserInfo else {
XCTFail("authResult.additionalUserInfo is missing")
return
}
XCTAssertFalse(additionalUserInfo.isNewUser)
XCTAssertEqual(additionalUserInfo.providerID, EmailAuthProvider.id)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
assertUser(auth?.currentUser)
}
#endif
/** @fn testSignInAndRetrieveDataWithEmailPasswordSuccess
@brief Tests the flow of a successful @c signInAndRetrieveDataWithEmail:password:completion:
call. Superset of historical testSignInWithEmailPasswordSuccess.
*/
func testSignInAndRetrieveDataWithEmailPasswordSuccess() throws {
let kRefreshToken = "fakeRefreshToken"
let expectation = self.expectation(description: #function)
setFakeGetAccountProvider()
setFakeSecureTokenService()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyPasswordRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.password, self.kFakePassword)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertTrue(request.returnSecureToken)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"email": self.kEmail,
"isNewUser": true,
"refreshToken": kRefreshToken])
}
try auth?.signOut()
auth?.signIn(withEmail: kEmail, password: kFakePassword) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
guard let user = authResult?.user else {
XCTFail("authResult.user is missing")
return
}
XCTAssertEqual(user.refreshToken, kRefreshToken)
XCTAssertFalse(user.isAnonymous)
XCTAssertEqual(user.email, self.kEmail)
guard let additionalUserInfo = authResult?.additionalUserInfo else {
XCTFail("authResult.additionalUserInfo is missing")
return
}
XCTAssertFalse(additionalUserInfo.isNewUser)
XCTAssertEqual(additionalUserInfo.providerID, EmailAuthProvider.id)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
assertUser(auth?.currentUser)
}
/** @fn testSignInWithEmailPasswordFailure
@brief Tests the flow of a failed @c signInWithEmail:password:completion: call.
*/
func testSignInWithEmailPasswordFailure() throws {
let expectation = self.expectation(description: #function)
rpcIssuer.respondBlock = {
// 2. Send the response from the fake backend.
try self.rpcIssuer.respond(serverErrorMessage: "INVALID_PASSWORD")
}
try auth?.signOut()
auth?.signIn(withEmail: kEmail, password: kFakePassword) { authResult, error in
// 3. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.wrongPassword.rawValue)
XCTAssertNotNil((error as? NSError)?.userInfo[NSLocalizedDescriptionKey])
expectation.fulfill()
}
waitForExpectations(timeout: 5)
XCTAssertNil(auth?.currentUser)
}
/** @fn testResetPasswordSuccess
@brief Tests the flow of a successful @c confirmPasswordResetWithCode:newPassword:completion:
call.
*/
func testResetPasswordSuccess() throws {
let expectation = self.expectation(description: #function)
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? ResetPasswordRequest)
XCTAssertEqual(request.oobCode, self.kFakeOobCode)
XCTAssertEqual(request.updatedPassword, self.kFakePassword)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: [:])
}
try auth?.signOut()
auth?
.confirmPasswordReset(withCode: kFakeOobCode, newPassword: kFakePassword) { error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testResetPasswordFailure
@brief Tests the flow of a failed @c confirmPasswordResetWithCode:newPassword:completion:
call.
*/
func testResetPasswordFailure() throws {
let expectation = self.expectation(description: #function)
rpcIssuer.respondBlock = {
// 2. Send the response from the fake backend.
try self.rpcIssuer.respond(serverErrorMessage: "INVALID_OOB_CODE")
}
try auth?.signOut()
auth?
.confirmPasswordReset(withCode: kFakeOobCode, newPassword: kFakePassword) { error in
// 3. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.invalidActionCode.rawValue)
XCTAssertNotNil((error as? NSError)?.userInfo[NSLocalizedDescriptionKey])
expectation.fulfill()
}
waitForExpectations(timeout: 5)
XCTAssertNil(auth?.currentUser)
}
/** @fn testCheckActionCodeSuccess
@brief Tests the flow of a successful @c checkActionCode:completion call.
*/
func testCheckActionCodeSuccess() throws {
let kNewEmail = "newEmail@example.com"
let verifyEmailRequestType = "VERIFY_EMAIL"
let expectation = self.expectation(description: #function)
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? ResetPasswordRequest)
XCTAssertEqual(request.oobCode, self.kFakeOobCode)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["email": self.kEmail,
"requestType": verifyEmailRequestType,
"newEmail": kNewEmail])
}
try auth?.signOut()
auth?.checkActionCode(kFakeOobCode) { info, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(error)
XCTAssertEqual(info?.email, kNewEmail)
XCTAssertEqual(info?.operation, ActionCodeOperation.verifyEmail)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testCheckActionCodeFailure
@brief Tests the flow of a failed @c checkActionCode:completion call.
*/
func testCheckActionCodeFailure() throws {
let expectation = self.expectation(description: #function)
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Send the response from the fake backend.
try self.rpcIssuer.respond(serverErrorMessage: "EXPIRED_OOB_CODE")
}
try auth?.signOut()
auth?.checkActionCode(kFakeOobCode) { info, error in
// 3. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.expiredActionCode.rawValue)
XCTAssertNotNil((error as? NSError)?.userInfo[NSLocalizedDescriptionKey])
expectation.fulfill()
}
waitForExpectations(timeout: 5)
XCTAssertNil(auth?.currentUser)
}
/** @fn testApplyActionCodeSuccess
@brief Tests the flow of a successful @c applyActionCode:completion call.
*/
func testApplyActionCodeSuccess() throws {
let expectation = self.expectation(description: #function)
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? SetAccountInfoRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: [:])
}
try auth?.signOut()
auth?.applyActionCode(kFakeOobCode) { error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testApplyActionCodeFailure
@brief Tests the flow of a failed @c checkActionCode:completion call.
*/
func testApplyActionCodeFailure() throws {
let expectation = self.expectation(description: #function)
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Send the response from the fake backend.
try self.rpcIssuer.respond(serverErrorMessage: "INVALID_OOB_CODE")
}
try auth?.signOut()
auth?.applyActionCode(kFakeOobCode) { error in
// 3. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.invalidActionCode.rawValue)
XCTAssertNotNil((error as? NSError)?.userInfo[NSLocalizedDescriptionKey])
expectation.fulfill()
}
waitForExpectations(timeout: 5)
XCTAssertNil(auth?.currentUser)
}
/** @fn testVerifyPasswordResetCodeSuccess
@brief Tests the flow of a successful @c verifyPasswordResetCode:completion call.
*/
func testVerifyPasswordResetCodeSuccess() throws {
let expectation = self.expectation(description: #function)
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? ResetPasswordRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.oobCode, self.kFakeOobCode)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["email": self.kEmail])
}
try auth?.signOut()
auth?.verifyPasswordResetCode(kFakeOobCode) { email, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertEqual(email, self.kEmail)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testVerifyPasswordResetCodeFailure
@brief Tests the flow of a failed @c verifyPasswordResetCode:completion call.
*/
func testVerifyPasswordResetCodeFailure() throws {
let expectation = self.expectation(description: #function)
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Send the response from the fake backend.
try self.rpcIssuer.respond(serverErrorMessage: "INVALID_OOB_CODE")
}
try auth?.signOut()
auth?.verifyPasswordResetCode(kFakeOobCode) { email, error in
// 3. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(email)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.invalidActionCode.rawValue)
XCTAssertNotNil((error as? NSError)?.userInfo[NSLocalizedDescriptionKey])
expectation.fulfill()
}
waitForExpectations(timeout: 5)
XCTAssertNil(auth?.currentUser)
}
/** @fn testSignInWithEmailLinkCredentialSuccess
@brief Tests the flow of a successfully @c signInWithCredential:completion: call with an
email sign-in link credential using FIREmailAuthProvider.
*/
func testSignInWithEmailLinkCredentialSuccess() throws {
let expectation = self.expectation(description: #function)
let fakeCode = "testoobcode"
setFakeGetAccountProvider()
setFakeSecureTokenService()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? EmailLinkSignInRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.oobCode, fakeCode)
XCTAssertEqual(request.email, self.kEmail)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"isNewUser": true,
"refreshToken": self.kRefreshToken])
}
try auth?.signOut()
let emailCredential = EmailAuthProvider.credential(
withEmail: kEmail,
link: kFakeEmailSignInLink
)
auth?.signIn(with: emailCredential) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
guard let user = authResult?.user else {
XCTFail("authResult.user or additionalUserInfo is missing")
return
}
XCTAssertEqual(user.refreshToken, self.kRefreshToken)
XCTAssertFalse(user.isAnonymous)
XCTAssertEqual(user.email, self.kEmail)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testSignInWithEmailLinkCredentialFailure
@brief Tests the flow of a failed @c signInWithCredential:completion: call with an
email-email sign-in link credential using FIREmailAuthProvider.
*/
func testSignInWithEmailLinkCredentialFailure() throws {
let expectation = self.expectation(description: #function)
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Send the response from the fake backend.
try self.rpcIssuer.respond(serverErrorMessage: "USER_DISABLED")
}
try auth?.signOut()
let emailCredential = EmailAuthProvider.credential(
withEmail: kEmail,
link: kFakeEmailSignInLink
)
auth?.signIn(with: emailCredential) { authResult, error in
// 3. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.userDisabled.rawValue)
XCTAssertNotNil((error as? NSError)?.userInfo[NSLocalizedDescriptionKey])
expectation.fulfill()
}
waitForExpectations(timeout: 5)
XCTAssertNil(auth?.currentUser)
}
/** @fn testSignInWithEmailCredentialSuccess
@brief Tests the flow of a successfully @c signInWithCredential:completion: call with an
email-password credential.
*/
func testSignInWithEmailCredentialSuccess() throws {
let expectation = self.expectation(description: #function)
setFakeGetAccountProvider()
setFakeSecureTokenService()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyPasswordRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.password, self.kFakePassword)
XCTAssertEqual(request.email, self.kEmail)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"isNewUser": true,
"refreshToken": self.kRefreshToken])
}
try auth?.signOut()
let emailCredential = EmailAuthProvider.credential(withEmail: kEmail, password: kFakePassword)
auth?.signIn(with: emailCredential) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
guard let user = authResult?.user else {
XCTFail("authResult.user or additionalUserInfo is missing")
return
}
XCTAssertEqual(user.refreshToken, self.kRefreshToken)
XCTAssertFalse(user.isAnonymous)
XCTAssertEqual(user.email, self.kEmail)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testSignInWithEmailCredentialFailure
@brief Tests the flow of a failed @c signInWithCredential:completion: call with an
email-password credential.
*/
func testSignInWithEmailCredentialFailure() throws {
let expectation = self.expectation(description: #function)
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Send the response from the fake backend.
try self.rpcIssuer.respond(serverErrorMessage: "USER_DISABLED")
}
try auth?.signOut()
let emailCredential = EmailAuthProvider.credential(withEmail: kEmail, password: kFakePassword)
auth?.signIn(with: emailCredential) { authResult, error in
// 3. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.userDisabled.rawValue)
XCTAssertNotNil((error as? NSError)?.userInfo[NSLocalizedDescriptionKey])
expectation.fulfill()
}
waitForExpectations(timeout: 5)
XCTAssertNil(auth?.currentUser)
}
/** @fn testSignInWithEmailCredentialEmptyPassword
@brief Tests the flow of a failed @c signInWithCredential:completion: call with an
email-password credential using an empty password. This error occurs on the client side,
so there is no need to fake an RPC response.
*/
func testSignInWithEmailCredentialEmptyPassword() throws {
let expectation = self.expectation(description: #function)
let emailCredential = EmailAuthProvider.credential(withEmail: kEmail, password: "")
auth?.signIn(with: emailCredential) { authResult, error in
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.wrongPassword.rawValue)
XCTAssertNotNil((error as? NSError)?.userInfo[NSLocalizedDescriptionKey])
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
#if os(iOS)
class FakeProvider: NSObject, FederatedAuthProvider {
@available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
func credential(with UIDelegate: FirebaseAuth.AuthUIDelegate?) async throws ->
FirebaseAuth.AuthCredential {
let credential = OAuthCredential(withProviderID: GoogleAuthProvider.id,
sessionID: kOAuthSessionID,
OAuthResponseURLString: kOAuthRequestURI)
XCTAssertEqual(credential.OAuthResponseURLString, kOAuthRequestURI)
XCTAssertEqual(credential.sessionID, kOAuthSessionID)
return credential
}
}
/** @fn testSignInWithProviderSuccess
@brief Tests a successful @c signInWithProvider:UIDelegate:completion: call with an OAuth
provider configured for Google.
*/
func testSignInWithProviderSuccess() throws {
let expectation = self.expectation(description: #function)
setFakeGoogleGetAccountProvider()
setFakeSecureTokenService()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyAssertionRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.providerID, GoogleAuthProvider.id)
XCTAssertTrue(request.returnSecureToken)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": RPCBaseTests.kFakeAccessToken,
"refreshToken": self.kRefreshToken,
"federatedId": self.kGoogleID,
"providerId": GoogleAuthProvider.id,
"localId": self.kLocalID,
"displayName": self.kDisplayName,
"rawUserInfo": self.kGoogleProfile,
"username": self.kUserName])
}
try auth.signOut()
auth.signIn(with: FakeProvider(), uiDelegate: nil) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
do {
try self.assertUserGoogle(authResult?.user)
} catch {
XCTFail("\(error)")
}
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
try assertUserGoogle(auth.currentUser)
}
/** @fn testSignInWithProviderFailure
@brief Tests a failed @c signInWithProvider:UIDelegate:completion: call with the error code
FIRAuthErrorCodeWebSignInUserInteractionFailure.
*/
func testSignInWithProviderFailure() throws {
let expectation = self.expectation(description: #function)
setFakeGoogleGetAccountProvider()
setFakeSecureTokenService()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyAssertionRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.providerID, GoogleAuthProvider.id)
XCTAssertTrue(request.returnSecureToken)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(serverErrorMessage: "USER_DISABLED")
}
try auth.signOut()
auth.signIn(with: FakeProvider(), uiDelegate: nil) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.userDisabled.rawValue)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testSignInWithGoogleAccountExistsError
@brief Tests the flow of a failed @c signInWithCredential:completion: with a Google credential
where the backend returns a needs @needConfirmation equal to true. An
FIRAuthErrorCodeAccountExistsWithDifferentCredential error should be thrown.
*/
func testSignInWithGoogleAccountExistsError() throws {
let expectation = self.expectation(description: #function)
setFakeGoogleGetAccountProvider()
setFakeSecureTokenService()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyAssertionRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.providerID, GoogleAuthProvider.id)
XCTAssertEqual(request.providerIDToken, self.kGoogleIDToken)
XCTAssertEqual(request.providerAccessToken, self.kGoogleAccessToken)
XCTAssertTrue(request.returnSecureToken)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": RPCBaseTests.kFakeAccessToken,
"refreshToken": self.kRefreshToken,
"federatedId": self.kGoogleID,
"providerId": GoogleAuthProvider.id,
"localId": self.kLocalID,
"displayName": self.kGoogleDisplayName,
"rawUserInfo": self.kGoogleProfile,
"username": self.kUserName,
"needConfirmation": true])
}
try auth.signOut()
let googleCredential = GoogleAuthProvider.credential(withIDToken: kGoogleIDToken,
accessToken: kGoogleAccessToken)
auth.signIn(with: googleCredential) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult)
XCTAssertEqual((error as? NSError)?.code,
AuthErrorCode.accountExistsWithDifferentCredential.rawValue)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testSignInWithOAuthCredentialSuccess
@brief Tests the flow of a successful @c signInWithCredential:completion: call with a generic
OAuth credential (In this case, configured for the Google IDP).
*/
func testSignInWithOAuthCredentialSuccess() throws {
let expectation = self.expectation(description: #function)
setFakeGoogleGetAccountProvider()
setFakeSecureTokenService()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyAssertionRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.providerID, GoogleAuthProvider.id)
XCTAssertEqual(request.requestURI, AuthTests.kOAuthRequestURI)
XCTAssertEqual(request.sessionID, AuthTests.kOAuthSessionID)
XCTAssertTrue(request.returnSecureToken)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": RPCBaseTests.kFakeAccessToken,
"refreshToken": self.kRefreshToken,
"federatedId": self.kGoogleID,
"providerId": GoogleAuthProvider.id,
"localId": self.kLocalID,
"displayName": self.kGoogleDisplayName,
"rawUserInfo": self.kGoogleProfile,
"username": self.kUserName])
}
try auth.signOut()
auth.signIn(with: FakeProvider(), uiDelegate: nil) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
do {
try self.assertUserGoogle(authResult?.user)
} catch {
XCTFail("\(error)")
}
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
try assertUserGoogle(auth.currentUser)
}
#endif
/** @fn testSignInWithCredentialSuccess
@brief Tests the flow of a successful @c signInWithCredential:completion: call
with a Google Sign-In credential.
Note: also a superset of the former testSignInWithGoogleCredentialSuccess
*/
func testSignInWithCredentialSuccess() throws {
let expectation = self.expectation(description: #function)
setFakeGoogleGetAccountProvider()
setFakeSecureTokenService()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyAssertionRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.providerID, GoogleAuthProvider.id)
XCTAssertEqual(request.providerIDToken, self.kGoogleIDToken)
XCTAssertEqual(request.providerAccessToken, self.kGoogleAccessToken)
XCTAssertTrue(request.returnSecureToken)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": RPCBaseTests.kFakeAccessToken,
"refreshToken": self.kRefreshToken,
"federatedId": self.kGoogleID,
"providerId": GoogleAuthProvider.id,
"localId": self.kLocalID,
"displayName": self.kGoogleDisplayName,
"rawUserInfo": self.kGoogleProfile,
"username": self.kGoogleDisplayName])
}
try auth.signOut()
let googleCredential = GoogleAuthProvider.credential(withIDToken: kGoogleIDToken,
accessToken: kGoogleAccessToken)
auth.signIn(with: googleCredential) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
do {
try self.assertUserGoogle(authResult?.user)
guard let additionalUserInfo = authResult?.additionalUserInfo,
let profile = additionalUserInfo.profile as? [String: String] else {
XCTFail("authResult.additionalUserInfo is missing")
return
}
XCTAssertEqual(profile, self.kGoogleProfile)
XCTAssertEqual(additionalUserInfo.username, self.kGoogleDisplayName)
XCTAssertEqual(additionalUserInfo.providerID, GoogleAuthProvider.id)
} catch {
XCTFail("\(error)")
}
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
try assertUserGoogle(auth.currentUser)
}
/** @fn testSignInWithGoogleCredentialFailure
@brief Tests the flow of a failed @c signInWithCredential:completion: call with an
Google Sign-In credential.
*/
func testSignInWithGoogleCredentialFailure() throws {
let expectation = self.expectation(description: #function)
setFakeGoogleGetAccountProvider()
setFakeSecureTokenService()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyAssertionRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.providerID, GoogleAuthProvider.id)
XCTAssertTrue(request.returnSecureToken)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(serverErrorMessage: "EMAIL_EXISTS")
}
try auth.signOut()
let googleCredential = GoogleAuthProvider.credential(withIDToken: kGoogleIDToken,
accessToken: kGoogleAccessToken)
auth.signIn(with: googleCredential) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.emailAlreadyInUse.rawValue)
XCTAssertEqual((error as? NSError)?.userInfo[NSLocalizedDescriptionKey] as? String,
"The email address is already in use by another account.")
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testSignInWithAppleCredentialFullNameInRequest
@brief Tests the flow of a successful @c signInWithCredential:completion: call
with an Apple Sign-In credential with a full name. This test differentiates from
@c testSignInWithCredentialSuccess only in verifying the full name.
*/
func testSignInWithAppleCredentialFullNameInRequest() throws {
let expectation = self.expectation(description: #function)
let kAppleIDToken = "APPLE_ID_TOKEN"
let kFirst = "First"
let kLast = "Last"
var fullName = PersonNameComponents()
fullName.givenName = kFirst
fullName.familyName = kLast
setFakeGoogleGetAccountProvider()
setFakeSecureTokenService()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyAssertionRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.providerID, AuthProviderID.apple.rawValue)
XCTAssertEqual(request.providerIDToken, kAppleIDToken)
XCTAssertEqual(request.fullName, fullName)
XCTAssertTrue(request.returnSecureToken)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": RPCBaseTests.kFakeAccessToken,
"refreshToken": self.kRefreshToken,
"federatedId": self.kGoogleID,
"providerId": AuthProviderID.apple.rawValue,
"localId": self.kLocalID,
"displayName": self.kGoogleDisplayName,
"rawUserInfo": self.kGoogleProfile,
"firstName": kFirst,
"lastName": kLast,
"username": self.kGoogleDisplayName])
}
try auth.signOut()
let appleCredential = OAuthProvider.appleCredential(withIDToken: kAppleIDToken,
rawNonce: nil,
fullName: fullName)
auth.signIn(with: appleCredential) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
do {
try self.assertUserGoogle(authResult?.user)
guard let additionalUserInfo = authResult?.additionalUserInfo,
let profile = additionalUserInfo.profile as? [String: String] else {
XCTFail("authResult.additionalUserInfo is missing")
return
}
XCTAssertEqual(profile, self.kGoogleProfile)
XCTAssertEqual(additionalUserInfo.username, self.kGoogleDisplayName)
XCTAssertEqual(additionalUserInfo.providerID, AuthProviderID.apple.rawValue)
} catch {
XCTFail("\(error)")
}
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
XCTAssertNotNil(auth.currentUser)
}
/** @fn testSignInAnonymouslySuccess
@brief Tests the flow of a successful @c signInAnonymouslyWithCompletion: call.
*/
func testSignInAnonymouslySuccess() throws {
let expectation = self.expectation(description: #function)
setFakeSecureTokenService()
setFakeGetAccountProviderAnonymous()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? SignUpNewUserRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertNil(request.email)
XCTAssertNil(request.password)
XCTAssertTrue(request.returnSecureToken)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"email": self.kEmail,
"isNewUser": true,
"refreshToken": self.kRefreshToken])
}
try auth?.signOut()
auth?.signInAnonymously { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertNil(error)
XCTAssertTrue(Thread.isMainThread)
self.assertUserAnonymous(authResult?.user)
guard let userInfo = authResult?.additionalUserInfo else {
XCTFail("authResult.additionalUserInfo is missing")
return
}
XCTAssertTrue(userInfo.isNewUser)
XCTAssertNil(userInfo.username)
XCTAssertNil(userInfo.profile)
XCTAssertEqual(userInfo.providerID, "")
expectation.fulfill()
}
waitForExpectations(timeout: 5)
try assertUserAnonymous(XCTUnwrap(auth?.currentUser))
}
/** @fn testSignInAnonymouslyFailure
@brief Tests the flow of a failed @c signInAnonymouslyWithCompletion: call.
*/
func testSignInAnonymouslyFailure() throws {
let expectation = self.expectation(description: #function)
rpcIssuer.respondBlock = {
try self.rpcIssuer.respond(serverErrorMessage: "OPERATION_NOT_ALLOWED")
}
try auth?.signOut()
auth?.verifyPasswordResetCode(kFakeOobCode) { email, error in
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(email)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.operationNotAllowed.rawValue)
XCTAssertNotNil((error as? NSError)?.userInfo[NSLocalizedDescriptionKey])
expectation.fulfill()
}
waitForExpectations(timeout: 5)
XCTAssertNil(auth?.currentUser)
}
/** @fn testSignInWithCustomTokenSuccess
@brief Tests the flow of a successful @c signInWithCustomToken:completion: call.
*/
func testSignInWithCustomTokenSuccess() throws {
let expectation = self.expectation(description: #function)
setFakeSecureTokenService()
setFakeGetAccountProvider()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyCustomTokenRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.token, self.kCustomToken)
XCTAssertTrue(request.returnSecureToken)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"email": self.kEmail,
"isNewUser": false,
"refreshToken": self.kRefreshToken])
}
try auth?.signOut()
auth?.signIn(withCustomToken: kCustomToken) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
self.assertUser(authResult?.user)
guard let userInfo = authResult?.additionalUserInfo else {
XCTFail("authResult.additionalUserInfo is missing")
return
}
XCTAssertFalse(userInfo.isNewUser)
XCTAssertNil(userInfo.username)
XCTAssertNil(userInfo.profile)
XCTAssertEqual(userInfo.providerID, "")
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
assertUser(auth?.currentUser)
}
/** @fn testSignInWithCustomTokenFailure
@brief Tests the flow of a failed @c signInWithCustomToken:completion: call.
*/
func testSignInWithCustomTokenFailure() throws {
let expectation = self.expectation(description: #function)
rpcIssuer.respondBlock = {
try self.rpcIssuer.respond(serverErrorMessage: "INVALID_CUSTOM_TOKEN")
}
try auth?.signOut()
auth?.signIn(withCustomToken: kCustomToken) { authResult, error in
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult?.user)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.invalidCustomToken.rawValue)
XCTAssertNotNil((error as? NSError)?.userInfo[NSLocalizedDescriptionKey])
expectation.fulfill()
}
waitForExpectations(timeout: 5)
XCTAssertNil(auth?.currentUser)
}
#if os(iOS)
/** @fn testCreateUserWithEmailPasswordWithRecaptchaVerificationSuccess
@brief Tests the flow of a successful @c createUserWithEmail:password:completion: call.
*/
func testCreateUserWithEmailPasswordWithRecaptchaVerificationSuccess() throws {
let expectation = self.expectation(description: #function)
let kTestRecaptchaKey = "projects/123/keys/456"
rpcIssuer.recaptchaSiteKey = kTestRecaptchaKey
setFakeSecureTokenService()
setFakeGetAccountProvider()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? SignUpNewUserRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.password, self.kFakePassword)
XCTAssertTrue(request.returnSecureToken)
request.injectRecaptchaFields(recaptchaResponse: AuthTests.kFakeRecaptchaResponse,
recaptchaVersion: AuthTests.kFakeRecaptchaVersion)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"email": self.kEmail,
"isNewUser": true,
"refreshToken": self.kRefreshToken])
}
try auth?.signOut()
auth?.createUser(withEmail: kEmail, password: kFakePassword) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
self.assertUser(authResult?.user)
guard let userInfo = authResult?.additionalUserInfo else {
XCTFail("authResult.additionalUserInfo is missing")
return
}
XCTAssertTrue(userInfo.isNewUser)
XCTAssertNil(userInfo.username)
XCTAssertNil(userInfo.profile)
XCTAssertEqual(userInfo.providerID, EmailAuthProvider.id)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
assertUser(auth?.currentUser)
}
/** @fn testCreateUserWithEmailPasswordWithRecaptchaVerificationFallbackSuccess
@brief Tests the flow of a successful @c createUserWithEmail:password:completion: call.
*/
func testCreateUserWithEmailPasswordWithRecaptchaVerificationFallbackSuccess() throws {
let expectation = self.expectation(description: #function)
let kTestRecaptchaKey = "projects/123/keys/456"
rpcIssuer.recaptchaSiteKey = kTestRecaptchaKey
setFakeSecureTokenService()
setFakeGetAccountProvider()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? SignUpNewUserRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.password, self.kFakePassword)
XCTAssertTrue(request.returnSecureToken)
request.injectRecaptchaFields(recaptchaResponse: AuthTests.kFakeRecaptchaResponse,
recaptchaVersion: AuthTests.kFakeRecaptchaVersion)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(serverErrorMessage: "MISSING_RECAPTCHA_TOKEN")
}
rpcIssuer.nextRespondBlock = {
// 4. Validate again the created Request instance after the recaptcha retry.
let request = try XCTUnwrap(self.rpcIssuer.request as? SignUpNewUserRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.password, self.kFakePassword)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertTrue(request.returnSecureToken)
request.injectRecaptchaFields(recaptchaResponse: AuthTests.kFakeRecaptchaResponse,
recaptchaVersion: AuthTests.kFakeRecaptchaVersion)
// 5. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"email": self.kEmail,
"isNewUser": true,
"refreshToken": self.kRefreshToken])
}
try auth?.signOut()
auth?.createUser(withEmail: kEmail, password: kFakePassword) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
self.assertUser(authResult?.user)
guard let userInfo = authResult?.additionalUserInfo else {
XCTFail("authResult.additionalUserInfo is missing")
return
}
XCTAssertTrue(userInfo.isNewUser)
XCTAssertNil(userInfo.username)
XCTAssertNil(userInfo.profile)
XCTAssertEqual(userInfo.providerID, EmailAuthProvider.id)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
assertUser(auth?.currentUser)
}
#endif
/** @fn testCreateUserWithEmailPasswordSuccess
@brief Tests the flow of a successful @c createUserWithEmail:password:completion: call.
*/
func testCreateUserWithEmailPasswordSuccess() throws {
let expectation = self.expectation(description: #function)
setFakeSecureTokenService()
setFakeGetAccountProvider()
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? SignUpNewUserRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.password, self.kFakePassword)
XCTAssertTrue(request.returnSecureToken)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"email": self.kEmail,
"isNewUser": true,
"refreshToken": self.kRefreshToken])
}
try auth?.signOut()
auth?.createUser(withEmail: kEmail, password: kFakePassword) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
self.assertUser(authResult?.user)
guard let userInfo = authResult?.additionalUserInfo else {
XCTFail("authResult.additionalUserInfo is missing")
return
}
XCTAssertTrue(userInfo.isNewUser)
XCTAssertNil(userInfo.username)
XCTAssertNil(userInfo.profile)
XCTAssertEqual(userInfo.providerID, EmailAuthProvider.id)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
assertUser(auth?.currentUser)
}
/** @fn testCreateUserWithEmailPasswordFailure
@brief Tests the flow of a failed @c createUserWithEmail:password:completion: call.
*/
func testCreateUserWithEmailPasswordFailure() throws {
let expectation = self.expectation(description: #function)
let reason = "The password must be 6 characters long or more."
rpcIssuer.respondBlock = {
try self.rpcIssuer.respond(serverErrorMessage: "WEAK_PASSWORD")
}
try auth?.signOut()
auth?.createUser(withEmail: kEmail, password: kFakePassword) { authResult, error in
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult?.user)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.weakPassword.rawValue)
XCTAssertEqual((error as? NSError)?.userInfo[NSLocalizedDescriptionKey] as? String, reason)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
XCTAssertNil(auth?.currentUser)
}
/** @fn testCreateUserEmptyPasswordFailure
@brief Tests the flow of a failed @c createUserWithEmail:password:completion: call due to an
empty password. This error occurs on the client side, so there is no need to fake an RPC
response.
*/
func testCreateUserEmptyPasswordFailure() throws {
let expectation = self.expectation(description: #function)
try auth?.signOut()
auth?.createUser(withEmail: kEmail, password: "") { authResult, error in
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult?.user)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.weakPassword.rawValue)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testCreateUserEmptyEmailFailure
@brief Tests the flow of a failed @c createUserWithEmail:password:completion: call due to an
empty email address. This error occurs on the client side, so there is no need to fake an
RPC response.
*/
func testCreateUserEmptyEmailFailure() throws {
let expectation = self.expectation(description: #function)
try auth?.signOut()
auth?.createUser(withEmail: "", password: kFakePassword) { authResult, error in
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(authResult?.user)
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.missingEmail.rawValue)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
#if os(iOS)
/** @fn testSendPasswordResetEmailWithRecaptchaSuccess
@brief Tests the flow of a successful @c sendPasswordResetWithEmail:completion: call.
*/
func testSendPasswordResetEmailWithRecaptchaSuccess() throws {
let expectation = self.expectation(description: #function)
let kTestRecaptchaKey = "projects/123/keys/456"
rpcIssuer.recaptchaSiteKey = kTestRecaptchaKey
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? GetOOBConfirmationCodeRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
request.injectRecaptchaFields(recaptchaResponse: AuthTests.kFakeRecaptchaResponse,
recaptchaVersion: AuthTests.kFakeRecaptchaVersion)
// 3. Send the response from the fake backend.
_ = try self.rpcIssuer.respond(withJSON: [:])
}
auth?.sendPasswordReset(withEmail: kEmail) { error in
// 4. After the response triggers the callback, verify success.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testSendPasswordResetEmailWithRecaptchaFallbackSuccess
@brief Tests the flow of a successful @c sendPasswordResetWithEmail:completion: call.
*/
func testSendPasswordResetEmailWithRecaptchaFallbackSuccess() throws {
let expectation = self.expectation(description: #function)
let kTestRecaptchaKey = "projects/123/keys/456"
rpcIssuer.recaptchaSiteKey = kTestRecaptchaKey
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? GetOOBConfirmationCodeRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
request.injectRecaptchaFields(recaptchaResponse: AuthTests.kFakeRecaptchaResponse,
recaptchaVersion: AuthTests.kFakeRecaptchaVersion)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(serverErrorMessage: "MISSING_RECAPTCHA_TOKEN")
}
rpcIssuer.nextRespondBlock = {
// 4. Validate again the created Request instance after the recaptcha retry.
let request = try XCTUnwrap(self.rpcIssuer.request as? GetOOBConfirmationCodeRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
request.injectRecaptchaFields(recaptchaResponse: AuthTests.kFakeRecaptchaResponse,
recaptchaVersion: AuthTests.kFakeRecaptchaVersion)
// 5. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"email": self.kEmail,
"isNewUser": true,
"refreshToken": self.kRefreshToken])
}
auth?.sendPasswordReset(withEmail: kEmail) { error in
// 4. After the response triggers the callback, verify success.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
#endif
/** @fn testSendPasswordResetEmailSuccess
@brief Tests the flow of a successful @c sendPasswordReset call.
*/
func testSendPasswordResetEmailSuccess() throws {
let expectation = self.expectation(description: #function)
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? GetOOBConfirmationCodeRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
// 3. Send the response from the fake backend.
_ = try self.rpcIssuer.respond(withJSON: [:])
}
auth?.sendPasswordReset(withEmail: kEmail) { error in
// 4. After the response triggers the callback, verify success.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testSendPasswordResetEmailFailure
@brief Tests the flow of a failed @c sendPasswordReset call.
*/
func testSendPasswordResetEmailFailure() throws {
let expectation = self.expectation(description: #function)
rpcIssuer.respondBlock = {
try self.rpcIssuer.respond(underlyingErrorMessage: "ipRefererBlocked")
}
auth?.sendPasswordReset(withEmail: kEmail) { error in
XCTAssertTrue(Thread.isMainThread)
let rpcError = (error as? NSError)!
XCTAssertEqual(rpcError.code, AuthErrorCode.appNotAuthorized.rawValue)
XCTAssertNotNil(rpcError.userInfo[NSLocalizedDescriptionKey])
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
#if os(iOS)
/** @fn testSendSignInLinkToEmailWithRecaptchaSuccess
@brief Tests the flow of a successful @c sendSignInLinkToEmail:actionCodeSettings: call.
*/
func testSendSignInLinkToEmailWithRecaptchaSuccess() throws {
let expectation = self.expectation(description: #function)
let kTestRecaptchaKey = "projects/123/keys/456"
rpcIssuer.recaptchaSiteKey = kTestRecaptchaKey
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? GetOOBConfirmationCodeRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.continueURL, self.kContinueURL)
XCTAssertTrue(request.handleCodeInApp)
request.injectRecaptchaFields(recaptchaResponse: AuthTests.kFakeRecaptchaResponse,
recaptchaVersion: AuthTests.kFakeRecaptchaVersion)
// 3. Send the response from the fake backend.
_ = try self.rpcIssuer.respond(withJSON: [:])
}
auth?.sendSignInLink(toEmail: kEmail,
actionCodeSettings: fakeActionCodeSettings()) { error in
// 4. After the response triggers the callback, verify success.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testSendSignInLinkToEmailWithRecaptchaFallbackSuccess
@brief Tests the flow of a successful @c sendSignInLinkToEmail:actionCodeSettings: call.
*/
func testSendSignInLinkToEmailWithRecaptchaFallbackSuccess() throws {
let expectation = self.expectation(description: #function)
let kTestRecaptchaKey = "projects/123/keys/456"
rpcIssuer.recaptchaSiteKey = kTestRecaptchaKey
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? GetOOBConfirmationCodeRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.continueURL, self.kContinueURL)
XCTAssertTrue(request.handleCodeInApp)
request.injectRecaptchaFields(recaptchaResponse: AuthTests.kFakeRecaptchaResponse,
recaptchaVersion: AuthTests.kFakeRecaptchaVersion)
// 3. Send the response from the fake backend.
_ = try self.rpcIssuer.respond(withJSON: [:])
}
rpcIssuer.nextRespondBlock = {
// 4. Validate again the created Request instance after the recaptcha retry.
let request = try XCTUnwrap(self.rpcIssuer.request as? GetOOBConfirmationCodeRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
request.injectRecaptchaFields(recaptchaResponse: AuthTests.kFakeRecaptchaResponse,
recaptchaVersion: AuthTests.kFakeRecaptchaVersion)
// 5. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": AuthTests.kAccessToken,
"email": self.kEmail,
"isNewUser": true,
"refreshToken": self.kRefreshToken])
}
auth?.sendSignInLink(toEmail: kEmail,
actionCodeSettings: fakeActionCodeSettings()) { error in
// 4. After the response triggers the callback, verify success.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
#endif
/** @fn testSendSignInLinkToEmailSuccess
@brief Tests the flow of a successful @c sendSignInLinkToEmail call.
*/
func testSendSignInLinkToEmailSuccess() throws {
let expectation = self.expectation(description: #function)
// 1. Setup respond block to test and fake send request.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? GetOOBConfirmationCodeRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.continueURL, self.kContinueURL)
XCTAssertTrue(request.handleCodeInApp)
// 3. Send the response from the fake backend.
_ = try self.rpcIssuer.respond(withJSON: [:])
}
auth?.sendSignInLink(toEmail: kEmail,
actionCodeSettings: fakeActionCodeSettings()) { error in
// 4. After the response triggers the callback, verify success.
XCTAssertTrue(Thread.isMainThread)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testSendSignInLinkToEmailFailure
@brief Tests the flow of a failed @c sendSignInLink call.
*/
func testSendSignInLinkToEmailFailure() throws {
let expectation = self.expectation(description: #function)
rpcIssuer.respondBlock = {
try self.rpcIssuer.respond(underlyingErrorMessage: "ipRefererBlocked")
}
auth?.sendSignInLink(toEmail: kEmail,
actionCodeSettings: fakeActionCodeSettings()) { error in
XCTAssertTrue(Thread.isMainThread)
let rpcError = error as? NSError
XCTAssertEqual(rpcError?.code, AuthErrorCode.appNotAuthorized.rawValue)
XCTAssertNotNil(rpcError?.userInfo[NSLocalizedDescriptionKey])
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testUpdateCurrentUserFailure
@brief Tests the flow of a failed @c updateCurrentUser:completion:
call.
*/
func testUpdateCurrentUserFailure() throws {
try waitForSignInWithAccessToken()
let expectation = self.expectation(description: #function)
let kTestAPIKey2 = "fakeAPIKey2"
let auth = try XCTUnwrap(auth)
let user2 = auth.currentUser
user2?.requestConfiguration = AuthRequestConfiguration(apiKey: kTestAPIKey2,
appID: kTestFirebaseAppID)
rpcIssuer.respondBlock = {
try self.rpcIssuer.respond(underlyingErrorMessage: "keyInvalid")
}
// Clear fake so we can inject error
rpcIssuer.fakeGetAccountProviderJSON = nil
auth.updateCurrentUser(user2) { error in
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.invalidAPIKey.rawValue)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testUpdateCurrentUserFailureNetworkError
@brief Tests the flow of a failed @c updateCurrentUser:completion:
call with a network error.
*/
func testUpdateCurrentUserFailureNetworkError() throws {
try waitForSignInWithAccessToken()
let expectation = self.expectation(description: #function)
let kTestAPIKey2 = "fakeAPIKey2"
let auth = try XCTUnwrap(auth)
let user2 = auth.currentUser
user2?.requestConfiguration = AuthRequestConfiguration(apiKey: kTestAPIKey2,
appID: kTestFirebaseAppID)
rpcIssuer.respondBlock = {
let kFakeErrorDomain = "fakeDomain"
let kFakeErrorCode = -1
let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
try self.rpcIssuer.respond(withData: nil, error: responseError)
}
// Clear fake so we can inject error
rpcIssuer.fakeGetAccountProviderJSON = nil
auth.updateCurrentUser(user2) { error in
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.networkError.rawValue)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testUpdateCurrentUserFailureNullUser
@brief Tests the flow of a failed @c updateCurrentUser:completion:
call with FIRAuthErrorCodeNullUser.
*/
func testUpdateCurrentUserFailureNullUser() throws {
try waitForSignInWithAccessToken()
let expectation = self.expectation(description: #function)
auth.updateCurrentUser(nil) { error in
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.nullUser.rawValue)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testUpdateCurrentUserFailureTenantIDMismatch
@brief Tests the flow of a failed @c updateCurrentUser:completion:
call with FIRAuthErrorCodeTenantIDMismatch.
*/
func testUpdateCurrentUserFailureTenantIDMismatch() throws {
// User without tenant id
try waitForSignInWithAccessToken()
let auth = try XCTUnwrap(auth)
let user1 = auth.currentUser
try auth.signOut()
// User with tenant id "tenant-id"
auth.tenantID = "tenant-id-1"
let kTestAccessToken2 = "fakeAccessToken2"
try waitForSignInWithAccessToken(fakeAccessToken: kTestAccessToken2)
let user2 = auth.currentUser
try auth.signOut()
auth.tenantID = "tenant-id-2"
let expectation = self.expectation(description: #function)
auth.updateCurrentUser(user1) { error in
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.tenantIDMismatch.rawValue)
expectation.fulfill()
}
try auth.signOut()
auth.tenantID = "tenant-id-2"
let expectation2 = self.expectation(description: "tenant-id-test2")
auth.updateCurrentUser(user2) { error in
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.tenantIDMismatch.rawValue)
expectation2.fulfill()
}
try auth.signOut()
auth.tenantID = nil
let expectation3 = self.expectation(description: "tenant-id-test3")
auth.updateCurrentUser(user2) { error in
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.tenantIDMismatch.rawValue)
expectation3.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testUpdateCurrentUserSuccess
@brief Tests the flow of a successful @c updateCurrentUser:completion:
call with a network error.
*/
func testUpdateCurrentUserSuccess() throws {
// Sign in with the first user.
try waitForSignInWithAccessToken()
let auth = try XCTUnwrap(auth)
let user1 = auth.currentUser
let kTestAPIKey = "fakeAPIKey"
user1?.requestConfiguration = AuthRequestConfiguration(apiKey: kTestAPIKey,
appID: kTestFirebaseAppID)
try auth.signOut()
let kTestAccessToken2 = "fakeAccessToken2"
try waitForSignInWithAccessToken(fakeAccessToken: kTestAccessToken2)
let user2 = auth.currentUser
let expectation = self.expectation(description: #function)
// Current user should now be user2.
XCTAssertEqual(auth.currentUser, user2)
auth.updateCurrentUser(user1) { error in
XCTAssertNil(error)
// Current user should now be user1.
XCTAssertEqual(auth.currentUser, user1)
XCTAssertNotEqual(auth.currentUser, user2)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testRevokeTokenSuccess
@brief Tests the flow of a successful @c revokeToken:completion.
*/
func testRevokeTokenSuccess() throws {
try waitForSignInWithAccessToken()
let expectation = self.expectation(description: #function)
let code = "code"
rpcIssuer.respondBlock = {
let request = try XCTUnwrap(self.rpcIssuer.request as? RevokeTokenRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.providerID, AuthProviderID.apple.rawValue)
XCTAssertEqual(request.token, code)
XCTAssertEqual(request.tokenType, .authorizationCode)
// Send the response from the fake backend.
_ = try self.rpcIssuer.respond(withJSON: [:])
}
auth?.revokeToken(withAuthorizationCode: code) { error in
// Verify callback success.
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
/** @fn testRevokeTokenMissingCallback
@brief Tests the flow of @c revokeToken:completion with a nil callback.
*/
func testRevokeTokenMissingCallback() throws {
try waitForSignInWithAccessToken()
let code = "code"
let issuer = rpcIssuer
issuer?.respondBlock = {
let request = try XCTUnwrap(issuer?.request as? RevokeTokenRequest)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertEqual(request.providerID, AuthProviderID.apple.rawValue)
XCTAssertEqual(request.token, code)
XCTAssertEqual(request.tokenType, .authorizationCode)
// Send the response from the fake backend.
_ = try issuer?.respond(withJSON: [:])
}
auth?.revokeToken(withAuthorizationCode: code)
}
/** @fn testSignOut
@brief Tests the @c signOut: method.
*/
func testSignOut() throws {
try waitForSignInWithAccessToken()
// Verify signing out succeeds and clears the current user.
let auth = try XCTUnwrap(auth)
try auth.signOut()
XCTAssertNil(auth.currentUser)
}
/** @fn testIsSignInWithEmailLink
@brief Tests the @c isSignInWithEmailLink: method.
*/
func testIsSignInWithEmailLink() throws {
let auth = try XCTUnwrap(auth)
let kBadSignInEmailLink = "http://www.facebook.com"
XCTAssertTrue(auth.isSignIn(withEmailLink: kFakeEmailSignInLink))
XCTAssertTrue(auth.isSignIn(withEmailLink: kFakeEmailSignInDeeplink))
XCTAssertFalse(auth.isSignIn(withEmailLink: kBadSignInEmailLink))
XCTAssertFalse(auth.isSignIn(withEmailLink: ""))
}
/** @fn testAuthStateChanges
@brief Tests @c addAuthStateDidChangeListener: and @c removeAuthStateDidChangeListener: methods.
*/
func testAuthStateChanges() throws {
// Set up listener.
let auth = try XCTUnwrap(auth)
var shouldHaveUser = false
var expectation: XCTestExpectation?
let listener = { listenerAuth, user in
XCTAssertTrue(Thread.isMainThread)
XCTAssertEqual(auth, listenerAuth)
XCTAssertEqual(user, auth.currentUser)
if shouldHaveUser {
XCTAssertNotNil(user)
} else {
XCTAssertNil(user)
}
// `expectation` being nil means the listener is not expected to be fired at this moment.
XCTAssertNotNil(expectation)
expectation?.fulfill()
}
try auth.signOut()
// Listener should fire immediately when attached.
expectation = self.expectation(description: "initial")
shouldHaveUser = false
let handle = auth.addStateDidChangeListener(listener)
waitForExpectations(timeout: 5)
expectation = nil
// Listener should fire for signing in.
expectation = self
.expectation(description: "sign-in") // waited on in waitForSignInWithAccessToken
shouldHaveUser = true
try waitForSignInWithAccessToken()
// Listener should not fire for signing in again.
expectation = nil
shouldHaveUser = true
try waitForSignInWithAccessToken()
// Listener should fire for signing out.
expectation = self.expectation(description: "sign-out")
shouldHaveUser = false
try auth.signOut()
waitForExpectations(timeout: 5)
// Listener should no longer fire once detached.
expectation = nil
auth.removeStateDidChangeListener(handle)
try waitForSignInWithAccessToken()
}
/** @fn testIDTokenChanges
@brief Tests @c addIDTokenDidChangeListener: and @c removeIDTokenDidChangeListener: methods.
*/
func testIDTokenChanges() throws {
// Set up listener.
let auth = try XCTUnwrap(auth)
var shouldHaveUser = false
var expectation: XCTestExpectation?
var fulfilled = false
let listener = { listenerAuth, user in
XCTAssertTrue(Thread.isMainThread)
XCTAssertEqual(auth, listenerAuth)
XCTAssertEqual(user, auth.currentUser)
if shouldHaveUser {
XCTAssertNotNil(user)
} else {
XCTAssertNil(user)
}
// `expectation` being nil means the listener is not expected to be fired at this moment.
XCTAssertNotNil(expectation)
if !fulfilled {
fulfilled = true
expectation?.fulfill()
}
}
try auth.signOut()
// Listener should fire immediately when attached.
expectation = self.expectation(description: "initial")
shouldHaveUser = false
let handle = auth.addIDTokenDidChangeListener(listener)
waitForExpectations(timeout: 5)
expectation = nil
// Listener should fire for signing in. Expectation is waited on in
// waitForSignInWithAccessToken.
fulfilled = false
expectation = self.expectation(description: "sign-in")
shouldHaveUser = true
try waitForSignInWithAccessToken()
// Listener should not fire for signing in again.
expectation = nil
shouldHaveUser = true
try waitForSignInWithAccessToken()
// Listener should fire for signing in again as the same user with another access token.
fulfilled = false
expectation = self.expectation(description: "sign-in")
shouldHaveUser = true
try waitForSignInWithAccessToken(fakeAccessToken: AuthTests.kNewAccessToken)
// Listener should fire for signing out.
fulfilled = false
expectation = self.expectation(description: "sign-out")
shouldHaveUser = false
try auth.signOut()
waitForExpectations(timeout: 5)
// Listener should no longer fire once detached.
expectation = nil
auth.removeStateDidChangeListener(handle)
try waitForSignInWithAccessToken()
}
/** @fn testUseEmulator
@brief Tests the @c useEmulatorWithHost:port: method.
*/
func testUseEmulator() throws {
auth.useEmulator(withHost: "host", port: 12345)
XCTAssertEqual("host:12345", auth.requestConfiguration.emulatorHostAndPort)
#if os(iOS)
let settings = try XCTUnwrap(auth.settings)
XCTAssertTrue(settings.isAppVerificationDisabledForTesting)
#endif
}
/** @fn testUseEmulatorNeverCalled
@brief Tests that the emulatorHostAndPort stored in @c FIRAuthRequestConfiguration is nil if the
@c useEmulatorWithHost:port: is not called.
*/
func testUseEmulatorNeverCalled() throws {
XCTAssertNil(auth.requestConfiguration.emulatorHostAndPort)
#if os(iOS)
let settings = try XCTUnwrap(auth.settings)
XCTAssertFalse(settings.isAppVerificationDisabledForTesting)
#endif
}
/** @fn testUseEmulatorIPv6Address
@brief Tests the @c useEmulatorWithHost:port: method with an IPv6 host address.
*/
func testUseEmulatorIPv6Address() throws {
auth.useEmulator(withHost: "::1", port: 12345)
XCTAssertEqual("[::1]:12345", auth.requestConfiguration.emulatorHostAndPort)
#if os(iOS)
let settings = try XCTUnwrap(auth.settings)
XCTAssertTrue(settings.isAppVerificationDisabledForTesting)
#endif
}
// MARK: Automatic Token Refresh Tests.
/** @fn testAutomaticTokenRefresh
@brief Tests a successful flow to automatically refresh tokens for a signed in user.
*/
func testAutomaticTokenRefresh() throws {
try auth.signOut()
// Enable auto refresh
enableAutoTokenRefresh()
// Sign in a user.
try waitForSignInWithAccessToken()
setFakeSecureTokenService(fakeAccessToken: AuthTests.kNewAccessToken)
// Verify that the current user's access token is the "old" access token before automatic token
// refresh.
XCTAssertEqual(AuthTests.kAccessToken, auth.currentUser?.rawAccessToken())
// Execute saved token refresh task.
let expectation = self.expectation(description: #function)
kAuthGlobalWorkQueue.async {
XCTAssertNotNil(self.authDispatcherCallback)
self.authDispatcherCallback?()
expectation.fulfill()
}
waitForExpectations(timeout: 5)
waitForAuthGlobalWorkQueueDrain()
// Verify that current user's access token is the "new" access token provided in the mock secure
// token response during automatic token refresh.
RPCBaseTests.waitSleep()
XCTAssertEqual(AuthTests.kNewAccessToken, auth.currentUser?.rawAccessToken())
}
/** @fn testAutomaticTokenRefreshInvalidTokenFailure
@brief Tests an unsuccessful flow to auto refresh tokens with an "invalid token" error.
This error should cause the user to be signed out.
*/
func testAutomaticTokenRefreshInvalidTokenFailure() throws {
try auth.signOut()
// Enable auto refresh
enableAutoTokenRefresh()
// Sign in a user.
try waitForSignInWithAccessToken()
// Set up expectation for secureToken RPC made by a failed attempt to refresh tokens.
rpcIssuer.secureTokenErrorString = "INVALID_ID_TOKEN"
// Verify that the current user's access token is the "old" access token before automatic token
// refresh.
XCTAssertEqual(AuthTests.kAccessToken, auth.currentUser?.rawAccessToken())
// Execute saved token refresh task.
let expectation = self.expectation(description: #function)
kAuthGlobalWorkQueue.async {
XCTAssertNotNil(self.authDispatcherCallback)
self.authDispatcherCallback?()
expectation.fulfill()
}
waitForExpectations(timeout: 5)
waitForAuthGlobalWorkQueueDrain()
// Verify that the user is nil after failed attempt to refresh tokens caused signed out.
RPCBaseTests.waitSleep()
XCTAssertNil(auth.currentUser)
}
/** @fn testAutomaticTokenRefreshRetry
@brief Tests that a retry is attempted for a automatic token refresh task (which is not due to
invalid tokens). The initial attempt to refresh the access token fails, but the second
attempt is successful.
*/
func testAutomaticTokenRefreshRetry() throws {
try auth.signOut()
// Enable auto refresh
enableAutoTokenRefresh()
// Sign in a user.
try waitForSignInWithAccessToken()
// Set up expectation for secureToken RPC made by a failed attempt to refresh tokens.
rpcIssuer.secureTokenNetworkError = NSError(domain: "ERROR", code: -1)
// Execute saved token refresh task.
let expectation = self.expectation(description: #function)
kAuthGlobalWorkQueue.async {
XCTAssertNotNil(self.authDispatcherCallback)
self.authDispatcherCallback?()
self.authDispatcherCallback = nil
expectation.fulfill()
}
waitForExpectations(timeout: 5)
waitForAuthGlobalWorkQueueDrain()
rpcIssuer.secureTokenNetworkError = nil
setFakeSecureTokenService(fakeAccessToken: AuthTests.kNewAccessToken)
// The old access token should still be the current user's access token and not the new access
// token (kNewAccessToken).
XCTAssertEqual(AuthTests.kAccessToken, auth.currentUser?.rawAccessToken())
// Execute saved token refresh task.
let expectation2 = self.expectation(description: "dispatchAfterExpectation")
kAuthGlobalWorkQueue.async {
RPCBaseTests.waitSleep()
XCTAssertNotNil(self.authDispatcherCallback)
self.authDispatcherCallback?()
expectation2.fulfill()
}
waitForExpectations(timeout: 5)
waitForAuthGlobalWorkQueueDrain()
// Time for callback to run.
RPCBaseTests.waitSleep()
// Verify that current user's access token is the "new" access token provided in the mock secure
// token response during automatic token refresh.
XCTAssertEqual(AuthTests.kNewAccessToken, auth.currentUser?.rawAccessToken())
}
#if os(iOS)
/** @fn testAutoRefreshAppForegroundedNotification
@brief Tests that app foreground notification triggers the scheduling of an automatic token
refresh task.
*/
func testAutoRefreshAppForegroundedNotification() throws {
try auth.signOut()
// Enable auto refresh
enableAutoTokenRefresh()
// Sign in a user.
try waitForSignInWithAccessToken()
// Post "UIApplicationDidBecomeActiveNotification" to trigger scheduling token refresh task.
NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil)
setFakeSecureTokenService(fakeAccessToken: AuthTests.kNewAccessToken)
// Verify that the current user's access token is the "old" access token before automatic
// token refresh.
XCTAssertEqual(AuthTests.kAccessToken, auth.currentUser?.rawAccessToken())
// Execute saved token refresh task.
let expectation = self.expectation(description: #function)
kAuthGlobalWorkQueue.async {
XCTAssertNotNil(self.authDispatcherCallback)
self.authDispatcherCallback?()
expectation.fulfill()
}
waitForExpectations(timeout: 5)
waitForAuthGlobalWorkQueueDrain()
// Time for callback to run.
RPCBaseTests.waitSleep()
// Verify that current user's access token is the "new" access token provided in the mock
// secure token response during automatic token refresh.
XCTAssertEqual(AuthTests.kNewAccessToken, auth.currentUser?.rawAccessToken())
}
#endif
// MARK: Application Delegate tests.
#if os(iOS)
func testAppDidRegisterForRemoteNotifications_APNSTokenUpdated() {
class FakeAuthTokenManager: AuthAPNSTokenManager {
override var token: AuthAPNSToken? {
get {
return tokenStore
}
set(setToken) {
tokenStore = setToken
}
}
}
let apnsToken = Data()
auth.tokenManager = FakeAuthTokenManager(withApplication: UIApplication.shared)
auth.application(UIApplication.shared,
didRegisterForRemoteNotificationsWithDeviceToken: apnsToken)
XCTAssertEqual(auth.tokenManager.token?.data, apnsToken)
XCTAssertEqual(auth.tokenManager.token?.type, .unknown)
}
func testAppDidFailToRegisterForRemoteNotifications_TokenManagerCancels() {
class FakeAuthTokenManager: AuthAPNSTokenManager {
var cancelled = false
override func cancel(withError error: Error) {
cancelled = true
}
}
let error = NSError(domain: "AuthTests", code: -1)
let fakeTokenManager = FakeAuthTokenManager(withApplication: UIApplication.shared)
auth.tokenManager = fakeTokenManager
XCTAssertFalse(fakeTokenManager.cancelled)
auth.application(UIApplication.shared,
didFailToRegisterForRemoteNotificationsWithError: error)
XCTAssertTrue(fakeTokenManager.cancelled)
}
func testAppDidReceiveRemoteNotificationWithCompletion_NotificationManagerHandleCanNotification() {
class FakeNotificationManager: AuthNotificationManager {
var canHandled = false
override func canHandle(notification: [AnyHashable: Any]) -> Bool {
canHandled = true
return true
}
}
let notification = ["test": ""]
let fakeKeychain = AuthKeychainServices(
service: "AuthTests",
storage: FakeAuthKeychainStorage()
)
let appCredentialManager = AuthAppCredentialManager(withKeychain: fakeKeychain)
let fakeNotificationManager = FakeNotificationManager(withApplication: UIApplication.shared,
appCredentialManager: appCredentialManager)
auth.notificationManager = fakeNotificationManager
XCTAssertFalse(fakeNotificationManager.canHandled)
auth.application(UIApplication.shared,
didReceiveRemoteNotification: notification) { _ in
}
XCTAssertTrue(fakeNotificationManager.canHandled)
}
func testAppOpenURL_AuthPresenterCanHandleURL() throws {
class FakeURLPresenter: AuthURLPresenter {
var canHandled = false
override func canHandle(url: URL) -> Bool {
canHandled = true
return true
}
}
let url = try XCTUnwrap(URL(string: "https://localhost"))
let fakeURLPresenter = FakeURLPresenter()
auth.authURLPresenter = fakeURLPresenter
XCTAssertFalse(fakeURLPresenter.canHandled)
XCTAssertTrue(auth.application(UIApplication.shared, open: url, options: [:]))
XCTAssertTrue(fakeURLPresenter.canHandled)
}
#endif // os(iOS)
// MARK: Interoperability Tests
func testComponentsRegistered() throws {
// Verify that the components are registered properly. Check the count, because any time a new
// component is added it should be added to the test suite as well.
XCTAssertEqual(AuthComponent.componentsToRegister().count, 1)
// TODO: Can/should we do something like?
// XCTAssert(component.protocol == @protocol(FIRAuthInterop));
}
// MARK: Helper Functions
private func enableAutoTokenRefresh() {
let expectation = self.expectation(description: #function)
auth.getToken(forcingRefresh: false) { token, error in
expectation.fulfill()
}
waitForExpectations(timeout: 5)
}
private func waitForSignInWithAccessToken(fakeAccessToken: String = kAccessToken) throws {
let kRefreshToken = "fakeRefreshToken"
let expectation = self.expectation(description: #function)
setFakeGetAccountProvider()
setFakeSecureTokenService()
// 1. Set up respondBlock to test request and send it to generate a fake response.
rpcIssuer.respondBlock = {
// 2. Validate the created Request instance.
let request = try XCTUnwrap(self.rpcIssuer.request as? VerifyPasswordRequest)
XCTAssertEqual(request.email, self.kEmail)
XCTAssertEqual(request.password, self.kFakePassword)
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
XCTAssertTrue(request.returnSecureToken)
// 3. Send the response from the fake backend.
try self.rpcIssuer.respond(withJSON: ["idToken": fakeAccessToken,
"email": self.kEmail,
"isNewUser": true,
"expiresIn": "3600",
"refreshToken": kRefreshToken])
}
auth?.signIn(withEmail: kEmail, password: kFakePassword) { authResult, error in
// 4. After the response triggers the callback, verify the returned result.
XCTAssertTrue(Thread.isMainThread)
guard let user = authResult?.user else {
XCTFail("authResult.user is missing")
return
}
XCTAssertEqual(user.refreshToken, kRefreshToken)
XCTAssertFalse(user.isAnonymous)
XCTAssertEqual(user.email, self.kEmail)
guard let additionalUserInfo = authResult?.additionalUserInfo else {
XCTFail("authResult.additionalUserInfo is missing")
return
}
XCTAssertFalse(additionalUserInfo.isNewUser)
XCTAssertEqual(additionalUserInfo.providerID, EmailAuthProvider.id)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 5)
assertUser(auth?.currentUser)
}
private func assertUser(_ user: User?) {
guard let user = user else {
XCTFail("authResult.additionalUserInfo is missing")
return
}
XCTAssertEqual(user.uid, kLocalID)
XCTAssertEqual(user.displayName, kDisplayName)
XCTAssertEqual(user.email, kEmail)
XCTAssertFalse(user.isAnonymous)
XCTAssertEqual(user.providerData.count, 1)
}
private func assertUserAnonymous(_ user: User?) {
guard let user = user else {
XCTFail("authResult.additionalUserInfo is missing")
return
}
XCTAssertEqual(user.uid, kLocalID)
XCTAssertNil(user.email)
XCTAssertNil(user.displayName)
XCTAssertTrue(user.isAnonymous)
XCTAssertEqual(user.providerData.count, 0)
}
}