mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-11 22:55:59 +05:45
970 lines
38 KiB
Swift
970 lines
38 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
|
||
// TODO(ncooke3): Remove below import after `FIRAuth.[hm]` has been ported.
|
||
// Note– This will break building with SPM. There is a way to achieve the same
|
||
// thing with SPM mixed targets–– I'm just going to port `FIRAuth` next.
|
||
import FirebaseAuth_Private
|
||
|
||
import FirebaseCore
|
||
|
||
class AuthTests: RPCBaseTests {
|
||
static let kAccessToken = "TEST_ACCESS_TOKEN"
|
||
static let kFakeAPIKey = "FAKE_API_KEY"
|
||
static var auth: Auth?
|
||
|
||
override class func setUp() {
|
||
let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000",
|
||
gcmSenderID: "00000000000000000-00000000000-000000000")
|
||
options.apiKey = kFakeAPIKey
|
||
options.projectID = "myProjectID"
|
||
FirebaseApp.configure(name: "test-AuthTests", options: options)
|
||
#if os(macOS) && !FIREBASE_AUTH_TESTING_USE_MACOS_KEYCHAIN
|
||
let keychainStorageProvider = FakeAuthKeychainServices.self
|
||
#else
|
||
let keychainStorageProvider = AuthKeychainServices.self
|
||
#endif // os(macOS) && !FIREBASE_AUTH_TESTING_USE_MACOS_KEYCHAIN
|
||
auth = Auth(
|
||
app: FirebaseApp.app(name: "test-AuthTests")!,
|
||
keychainStorageProvider: keychainStorageProvider
|
||
)
|
||
}
|
||
|
||
override func setUp() {
|
||
super.setUp()
|
||
// Set FIRAuthDispatcher implementation in order to save the token refresh task for later
|
||
// execution.
|
||
AuthDispatcher.shared.dispatchAfterImplementation = { delay, queue, task in
|
||
XCTAssertNotNil(task)
|
||
XCTAssertGreaterThan(delay, 0)
|
||
// TODO:
|
||
XCTFail("implement this")
|
||
// XCTAssertEqual(FIRAuthGlobalWorkQueue(), queue)
|
||
// XCTAssertEqualObjects(FIRAuthGlobalWorkQueue(), queue);
|
||
// self->_FIRAuthDispatcherCallback = 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)
|
||
|
||
// 1. Create a group to synchronize request creation by the fake rpcIssuer in `fetchSignInMethods`.
|
||
let group = createGroup()
|
||
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. After the fake rpcIssuer leaves the group, validate the created Request instance.
|
||
let request = try XCTUnwrap(rpcIssuer?.request as? CreateAuthURIRequest)
|
||
XCTAssertEqual(request.identifier, kEmail)
|
||
XCTAssertEqual(request.endpoint, "createAuthUri")
|
||
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
|
||
|
||
// 3. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(withJSON: ["signinMethods": allSignInMethods])
|
||
|
||
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)
|
||
let group = createGroup()
|
||
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
let message = "TOO_MANY_ATTEMPTS_TRY_LATER"
|
||
try rpcIssuer?.respond(serverErrorMessage: message)
|
||
|
||
waitForExpectations(timeout: 5)
|
||
}
|
||
|
||
// TODO: Three PhoneAuth tests here.
|
||
|
||
/** @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. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. After the fake rpcIssuer leaves the group, validate the created Request instance.
|
||
let request = try XCTUnwrap(rpcIssuer?.request as? EmailLinkSignInRequest)
|
||
XCTAssertEqual(request.email, kEmail)
|
||
XCTAssertEqual(request.oobCode, fakeCode)
|
||
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
|
||
|
||
// 3. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(withJSON: ["idToken": AuthTests.kAccessToken,
|
||
"email": kEmail,
|
||
"isNewUser": true,
|
||
"refreshToken": kRefreshToken])
|
||
|
||
waitForExpectations(timeout: 10)
|
||
assertUser(AuthTests.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. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(serverErrorMessage: "INVALID_OOB_CODE")
|
||
waitForExpectations(timeout: 10)
|
||
XCTAssertNil(AuthTests.auth?.currentUser)
|
||
}
|
||
|
||
/** @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. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. After the fake rpcIssuer leaves the group, validate the created Request instance.
|
||
let request = try XCTUnwrap(rpcIssuer?.request as? VerifyPasswordRequest)
|
||
XCTAssertEqual(request.email, kEmail)
|
||
XCTAssertEqual(request.password, kFakePassword)
|
||
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
|
||
XCTAssertTrue(request.returnSecureToken)
|
||
|
||
// 3. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(withJSON: ["idToken": AuthTests.kAccessToken,
|
||
"email": kEmail,
|
||
"isNewUser": true,
|
||
"refreshToken": kRefreshToken])
|
||
|
||
waitForExpectations(timeout: 10)
|
||
assertUser(AuthTests.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)
|
||
|
||
// 1. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(serverErrorMessage: "INVALID_PASSWORD")
|
||
waitForExpectations(timeout: 10)
|
||
XCTAssertNil(AuthTests.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. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. After the fake rpcIssuer leaves the group, validate the created Request instance.
|
||
let request = try XCTUnwrap(rpcIssuer?.request as? ResetPasswordRequest)
|
||
XCTAssertEqual(request.oobCode, kFakeOobCode)
|
||
XCTAssertEqual(request.updatedPassword, kFakePassword)
|
||
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
|
||
|
||
// 3. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(withJSON: [:])
|
||
|
||
waitForExpectations(timeout: 10)
|
||
}
|
||
|
||
/** @fn testResetPasswordFailure
|
||
@brief Tests the flow of a failed @c confirmPasswordResetWithCode:newPassword:completion:
|
||
call.
|
||
*/
|
||
func testResetPasswordFailure() throws {
|
||
let expectation = self.expectation(description: #function)
|
||
|
||
// 1. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(serverErrorMessage: "INVALID_OOB_CODE")
|
||
waitForExpectations(timeout: 10)
|
||
XCTAssertNil(AuthTests.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. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. After the fake rpcIssuer leaves the group, validate the created Request instance.
|
||
let request = try XCTUnwrap(rpcIssuer?.request as? ResetPasswordRequest)
|
||
XCTAssertEqual(request.oobCode, kFakeOobCode)
|
||
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
|
||
|
||
// 3. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(withJSON: ["email": kEmail,
|
||
"requestType": verifyEmailRequestType,
|
||
"newEmail": kNewEmail])
|
||
waitForExpectations(timeout: 10)
|
||
}
|
||
|
||
/** @fn testCheckActionCodeFailure
|
||
@brief Tests the flow of a failed @c checkActionCode:completion call.
|
||
*/
|
||
func testCheckActionCodeFailure() throws {
|
||
let expectation = self.expectation(description: #function)
|
||
|
||
// 1. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(serverErrorMessage: "EXPIRED_OOB_CODE")
|
||
waitForExpectations(timeout: 10)
|
||
XCTAssertNil(AuthTests.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. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.auth?.applyActionCode(kFakeOobCode) { error in
|
||
// 4. After the response triggers the callback, verify the returned result.
|
||
XCTAssertTrue(Thread.isMainThread)
|
||
XCTAssertNil(error)
|
||
expectation.fulfill()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. After the fake rpcIssuer leaves the group, validate the created Request instance.
|
||
let request = try XCTUnwrap(rpcIssuer?.request as? SetAccountInfoRequest)
|
||
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
|
||
|
||
// 3. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(withJSON: [:])
|
||
waitForExpectations(timeout: 10)
|
||
}
|
||
|
||
/** @fn testApplyActionCodeFailure
|
||
@brief Tests the flow of a failed @c checkActionCode:completion call.
|
||
*/
|
||
func testApplyActionCodeFailure() throws {
|
||
let expectation = self.expectation(description: #function)
|
||
|
||
// 1. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(serverErrorMessage: "INVALID_OOB_CODE")
|
||
waitForExpectations(timeout: 10)
|
||
XCTAssertNil(AuthTests.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. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. After the fake rpcIssuer leaves the group, validate the created Request instance.
|
||
let request = try XCTUnwrap(rpcIssuer?.request as? ResetPasswordRequest)
|
||
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
|
||
XCTAssertEqual(request.oobCode, kFakeOobCode)
|
||
|
||
// 3. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(withJSON: ["email": kEmail])
|
||
waitForExpectations(timeout: 10)
|
||
}
|
||
|
||
/** @fn testVerifyPasswordResetCodeFailure
|
||
@brief Tests the flow of a failed @c verifyPasswordResetCode:completion call.
|
||
*/
|
||
func testVerifyPasswordResetCodeFailure() throws {
|
||
let expectation = self.expectation(description: #function)
|
||
|
||
// 1. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(serverErrorMessage: "INVALID_OOB_CODE")
|
||
waitForExpectations(timeout: 10)
|
||
XCTAssertNil(AuthTests.auth?.currentUser)
|
||
}
|
||
|
||
// TODO: 12 tests signIn(withCredential ...) not visible in swift
|
||
|
||
/** @fn testSignInAnonymouslySuccess
|
||
@brief Tests the flow of a successful @c signInAnonymouslyWithCompletion: call.
|
||
*/
|
||
func testSignInAnonymouslySuccess() throws {
|
||
let expectation = self.expectation(description: #function)
|
||
setFakeSecureTokenService()
|
||
setFakeGetAccountProviderAnonymous()
|
||
|
||
// 1. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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)
|
||
XCTAssertNil(userInfo.providerID)
|
||
expectation.fulfill()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. After the fake rpcIssuer leaves the group, validate the created Request instance.
|
||
let request = try XCTUnwrap(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 rpcIssuer?.respond(withJSON: ["idToken": AuthTests.kAccessToken,
|
||
"email": kEmail,
|
||
"isNewUser": true,
|
||
"refreshToken": kRefreshToken])
|
||
waitForExpectations(timeout: 10)
|
||
assertUserAnonymous(try XCTUnwrap(AuthTests.auth?.currentUser))
|
||
}
|
||
|
||
/** @fn testSignInAnonymouslyFailure
|
||
@brief Tests the flow of a failed @c signInAnonymouslyWithCompletion: call.
|
||
*/
|
||
func testSignInAnonymouslyFailure() throws {
|
||
let expectation = self.expectation(description: #function)
|
||
|
||
// 1. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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.operationNotAllowed.rawValue)
|
||
XCTAssertNotNil((error as? NSError)?.userInfo[NSLocalizedDescriptionKey])
|
||
expectation.fulfill()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(serverErrorMessage: "OPERATION_NOT_ALLOWED")
|
||
waitForExpectations(timeout: 10)
|
||
XCTAssertNil(AuthTests.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. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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)
|
||
XCTAssertNil(userInfo.providerID)
|
||
XCTAssertNil(error)
|
||
expectation.fulfill()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. After the fake rpcIssuer leaves the group, validate the created Request instance.
|
||
let request = try XCTUnwrap(rpcIssuer?.request as? VerifyCustomTokenRequest)
|
||
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
|
||
XCTAssertEqual(request.token, kCustomToken)
|
||
XCTAssertTrue(request.returnSecureToken)
|
||
|
||
// 3. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(withJSON: ["idToken": AuthTests.kAccessToken,
|
||
"email": kEmail,
|
||
"isNewUser": false,
|
||
"refreshToken": kRefreshToken])
|
||
waitForExpectations(timeout: 10)
|
||
assertUser(AuthTests.auth?.currentUser)
|
||
}
|
||
|
||
/** @fn testSignInWithCustomTokenFailure
|
||
@brief Tests the flow of a failed @c signInWithCustomToken:completion: call.
|
||
*/
|
||
func testSignInWithCustomTokenFailure() throws {
|
||
let expectation = self.expectation(description: #function)
|
||
|
||
// 1. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.auth?.signIn(withCustomToken: kCustomToken) { authResult, error in
|
||
// 3. After the response triggers the callback, verify the returned result.
|
||
XCTAssertTrue(Thread.isMainThread)
|
||
XCTAssertNil(authResult?.user)
|
||
XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.invalidCustomToken.rawValue)
|
||
XCTAssertNotNil((error as? NSError)?.userInfo[NSLocalizedDescriptionKey])
|
||
expectation.fulfill()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(serverErrorMessage: "INVALID_CUSTOM_TOKEN")
|
||
waitForExpectations(timeout: 10)
|
||
XCTAssertNil(AuthTests.auth?.currentUser)
|
||
}
|
||
|
||
/** @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. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. After the fake rpcIssuer leaves the group, validate the created Request instance.
|
||
let request = try XCTUnwrap(rpcIssuer?.request as? SignUpNewUserRequest)
|
||
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
|
||
XCTAssertEqual(request.email, kEmail)
|
||
XCTAssertEqual(request.password, kFakePassword)
|
||
XCTAssertTrue(request.returnSecureToken)
|
||
|
||
// 3. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(withJSON: ["idToken": AuthTests.kAccessToken,
|
||
"email": kEmail,
|
||
"isNewUser": true,
|
||
"refreshToken": kRefreshToken])
|
||
waitForExpectations(timeout: 10)
|
||
assertUser(AuthTests.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."
|
||
|
||
// 1. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.auth?.createUser(withEmail: kEmail, password: kFakePassword) { authResult, error in
|
||
// 3. After the response triggers the callback, verify the returned result.
|
||
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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(serverErrorMessage: "WEAK_PASSWORD")
|
||
waitForExpectations(timeout: 10)
|
||
XCTAssertNil(AuthTests.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 AuthTests.auth?.signOut()
|
||
AuthTests.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: 10)
|
||
}
|
||
|
||
/** @fn testCreateUserEmptyEmailFailure
|
||
@brief Tests the flow of a failed @c createUserWithEmail:password:completion: call due to an
|
||
empty email adress. 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 AuthTests.auth?.signOut()
|
||
AuthTests.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: 10)
|
||
}
|
||
|
||
/** @fn testSendPasswordResetEmailSuccess
|
||
@brief Tests the flow of a successful @c sendPasswordReset call.
|
||
*/
|
||
func testSendPasswordResetEmailSuccess() throws {
|
||
let expectation = self.expectation(description: #function)
|
||
|
||
// 1. Create a group to synchronize request creation by the fake rpcIssuer in `fetchSignInMethods`.
|
||
let group = createGroup()
|
||
|
||
AuthTests.auth?.sendPasswordReset(withEmail: kEmail) { error in
|
||
// 4. After the response triggers the callback, verify success.
|
||
XCTAssertTrue(Thread.isMainThread)
|
||
XCTAssertNil(error)
|
||
expectation.fulfill()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. After the fake rpcIssuer leaves the group, validate the created Request instance.
|
||
let request = try XCTUnwrap(rpcIssuer?.request as? GetOOBConfirmationCodeRequest)
|
||
XCTAssertEqual(request.email, kEmail)
|
||
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
|
||
|
||
// 3. Send the response from the fake backend.
|
||
_ = try rpcIssuer?.respond(withJSON: [:])
|
||
|
||
waitForExpectations(timeout: 5)
|
||
}
|
||
|
||
/** @fn testSendPasswordResetEmailFailure
|
||
@brief Tests the flow of a failed @c sendPasswordReset call.
|
||
*/
|
||
func testSendPasswordResetEmailFailure() throws {
|
||
let expectation = self.expectation(description: #function)
|
||
let group = createGroup()
|
||
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
try rpcIssuer?.respond(underlyingErrorMessage: "ipRefererBlocked")
|
||
|
||
waitForExpectations(timeout: 5)
|
||
}
|
||
|
||
// /** @fn testSendSignInLinkToEmailSuccess
|
||
// @brief Tests the flow of a successful @c sendSignInLinkToEmail call.
|
||
// */
|
||
func testSendSignInLinkToEmailSuccess() throws {
|
||
let expectation = self.expectation(description: #function)
|
||
|
||
// 1. Create a group to synchronize request creation by the fake rpcIssuer in `fetchSignInMethods`.
|
||
let group = createGroup()
|
||
|
||
AuthTests.auth?.sendSignInLink(toEmail: kEmail,
|
||
actionCodeSettings: fakeActionCodeSettings()) { error in
|
||
// 4. After the response triggers the callback, verify success.
|
||
XCTAssertTrue(Thread.isMainThread)
|
||
XCTAssertNil(error)
|
||
expectation.fulfill()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. After the fake rpcIssuer leaves the group, validate the created Request instance.
|
||
let request = try XCTUnwrap(rpcIssuer?.request as? GetOOBConfirmationCodeRequest)
|
||
XCTAssertEqual(request.email, kEmail)
|
||
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
|
||
XCTAssertEqual(request.continueURL, kContinueURL)
|
||
XCTAssertTrue(request.handleCodeInApp)
|
||
|
||
// 3. Send the response from the fake backend.
|
||
_ = try rpcIssuer?.respond(withJSON: [:])
|
||
|
||
waitForExpectations(timeout: 5)
|
||
}
|
||
|
||
/** @fn testSendSignInLinkToEmailFailure
|
||
@brief Tests the flow of a failed @c sendSignInLink call.
|
||
*/
|
||
func testSendSignInLinkToEmailFailure() throws {
|
||
let expectation = self.expectation(description: #function)
|
||
let group = createGroup()
|
||
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
try rpcIssuer?.respond(underlyingErrorMessage: "ipRefererBlocked")
|
||
waitForExpectations(timeout: 5)
|
||
}
|
||
|
||
/** @fn testUpdateCurrentUserFailure
|
||
@brief Tests the flow of a failed @c updateCurrentUser:completion:
|
||
call.
|
||
*/
|
||
func testUpdateCurrentUserFailure() throws {
|
||
let kTestAccessToken = "fakeAccessToken"
|
||
// try waitForSignInWithAccessToken(fakeAccessToken: kTestAccessToken)
|
||
let kTestAPIKey2 = "fakeAPIKey2"
|
||
// TODO: next line deadlocks
|
||
let user2 = AuthTests.auth?.currentUser
|
||
|
||
// TODO: requestConfiguration not visible in FIRUser yet.
|
||
// user2?.requestConfiguration = AuthRequestConfiguration(APIKey: kTestAPIKey2, appID: kTestFirebaseAppID)
|
||
}
|
||
|
||
// - (void)testUpdateCurrentUserFailure {
|
||
// NSString *kTestAccessToken = @"fakeAccessToken";
|
||
// NSString *kTestAPIKey = @"fakeAPIKey";
|
||
// [self waitForSignInWithAccessToken:kTestAccessToken APIKey:kTestAPIKey completion:nil];
|
||
// NSString *kTestAPIKey2 = @"fakeAPIKey2";
|
||
// FIRUser *user2 = [FIRAuth auth].currentUser;
|
||
// user2.requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:kTestAPIKey2
|
||
// appID:kFirebaseAppID];
|
||
// OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
|
||
// .andDispatchError2([FIRAuthErrorUtils invalidAPIKeyError]);
|
||
// XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
|
||
// [[FIRAuth auth] updateCurrentUser:user2
|
||
// completion:^(NSError *_Nullable error) {
|
||
// XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidAPIKey);
|
||
// [expectation fulfill];
|
||
// }];
|
||
// [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
|
||
// OCMVerifyAll(_mockBackend);
|
||
// }
|
||
|
||
// MARK: Helper Functions
|
||
|
||
private func waitForSignInWithAccessToken(fakeAccessToken: String = kAccessToken) throws {
|
||
let kRefreshToken = "fakeRefreshToken"
|
||
let expectation = self.expectation(description: #function)
|
||
setFakeGetAccountProvider()
|
||
setFakeSecureTokenService()
|
||
|
||
// 1. Create a group to synchronize request creation by the fake rpcIssuer.
|
||
let group = createGroup()
|
||
|
||
try AuthTests.auth?.signOut()
|
||
AuthTests.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()
|
||
}
|
||
group.wait()
|
||
|
||
// 2. After the fake rpcIssuer leaves the group, validate the created Request instance.
|
||
let request = try XCTUnwrap(rpcIssuer?.request as? VerifyPasswordRequest)
|
||
XCTAssertEqual(request.email, kEmail)
|
||
XCTAssertEqual(request.password, kFakePassword)
|
||
XCTAssertEqual(request.apiKey, AuthTests.kFakeAPIKey)
|
||
XCTAssertTrue(request.returnSecureToken)
|
||
|
||
// 3. Send the response from the fake backend.
|
||
try rpcIssuer?.respond(withJSON: ["idToken": fakeAccessToken,
|
||
"email": kEmail,
|
||
"isNewUser": true,
|
||
"refreshToken": kRefreshToken])
|
||
|
||
waitForExpectations(timeout: 10)
|
||
assertUser(AuthTests.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)
|
||
}
|
||
}
|