| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789 |
- // 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 FirebaseCoreExtension
- #if COCOAPODS || SWIFT_PACKAGE
- // Heartbeats are not supported in the internal build system.
- import FirebaseCoreInternal
- #endif
- private let kFakeAPIKey = "kTestAPIKey"
- private let kFakeAppID = "kTestFirebaseAppID"
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- class AuthBackendTests: RPCBaseTests {
- let kFakeErrorDomain = "fakeDomain"
- let kFakeErrorCode = -1
- /** @fn testRequestEncodingError
- @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
- request passed returns an error during it's unencodedHTTPRequestBodyWithError: method.
- The error returned should be delivered to the caller without any change.
- */
- func testRequestEncodingError() async throws {
- let encodingError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
- let request = FakeRequest(withEncodingError: encodingError)
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.internalError.rawValue)
- let underlyingError = try XCTUnwrap(rpcError.userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
- XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.JSONSerializationError.rawValue)
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey])
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
- }
- }
- /** @fn testBodyDataSerializationError
- @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
- request returns an object which isn't serializable by @c NSJSONSerialization.
- The error from @c NSJSONSerialization should be returned as the underlyingError for an
- @c NSError with the code @c FIRAuthErrorCodeJSONSerializationError.
- */
- func testBodyDataSerializationError() async throws {
- let request = FakeRequest(withRequestBody: ["unencodable": self])
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.internalError.rawValue)
- let underlyingError = try XCTUnwrap(rpcError.userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.JSONSerializationError.rawValue)
- XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
- XCTAssertNil(underlyingError.userInfo[NSUnderlyingErrorKey])
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey])
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
- }
- }
- /** @fn testNetworkError
- @brief This test checks to make sure a network error is properly wrapped and forwarded with the
- correct code (FIRAuthErrorCodeNetworkError).
- */
- func testNetworkError() async throws {
- let request = FakeRequest(withRequestBody: [:])
- rpcIssuer.respondBlock = {
- let responseError = NSError(domain: self.kFakeErrorDomain, code: self.kFakeErrorCode)
- return (nil, responseError)
- }
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.networkError.rawValue)
- let underlyingError = try XCTUnwrap(rpcError.userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingError.domain, kFakeErrorDomain)
- XCTAssertEqual(underlyingError.code, kFakeErrorCode)
- XCTAssertNil(underlyingError.userInfo[NSUnderlyingErrorKey])
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey])
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
- }
- }
- /** @fn testUnparsableErrorResponse
- @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
- response isn't deserializable by @c NSJSONSerialization and an error
- condition (with an associated error response message) was expected. We are expecting to
- receive the original network error wrapped in an @c NSError with the code
- @c FIRAuthErrorCodeUnexpectedHTTPResponse.
- */
- func testUnparsableErrorResponse() async throws {
- let data = "<html><body>An error occurred.</body></html>".data(using: .utf8)
- let request = FakeRequest(withRequestBody: [:])
- rpcIssuer.respondBlock = {
- let responseError = NSError(domain: self.kFakeErrorDomain, code: self.kFakeErrorCode)
- return (data, error: responseError)
- }
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.internalError.rawValue)
- let underlyingError = try XCTUnwrap(rpcError.userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
- XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue)
- let underlyingUnderlying = try XCTUnwrap(underlyingError
- .userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain)
- XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode)
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey])
- XCTAssertEqual(data,
- try XCTUnwrap(underlyingError
- .userInfo[AuthErrorUtils.userInfoDataKey] as? Data))
- }
- }
- /** @fn testUnparsableSuccessResponse
- @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
- response isn't deserializable by @c NSJSONSerialization and no error
- condition was indicated. We are expecting to
- receive the @c NSJSONSerialization error wrapped in an @c NSError with the code
- @c FIRAuthErrorCodeUnexpectedServerResponse.
- */
- func testUnparsableSuccessResponse() async throws {
- let data = "<xml>Some non-JSON value.</xml>".data(using: .utf8)
- let request = FakeRequest(withRequestBody: [:])
- rpcIssuer.respondBlock = {
- (data, nil)
- }
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.internalError.rawValue)
- let underlyingError = try XCTUnwrap(rpcError.userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
- XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedResponse.rawValue)
- let underlyingUnderlying = try XCTUnwrap(underlyingError
- .userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingUnderlying.domain, NSCocoaErrorDomain)
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey])
- XCTAssertEqual(data,
- try XCTUnwrap(underlyingError
- .userInfo[AuthErrorUtils.userInfoDataKey] as? Data))
- }
- }
- /** @fn testNonDictionaryErrorResponse
- @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
- response deserialized by @c NSJSONSerialization is not a dictionary, and an error was
- expected. We are expecting to receive the original network error wrapped in an @c NSError
- with the code @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded response
- in the @c NSError.userInfo dictionary associated with the key
- @c FIRAuthErrorUserInfoDeserializedResponseKey.
- */
- func testNonDictionaryErrorResponse() async throws {
- // We are responding with a JSON-encoded string value representing an array - which is
- // unexpected. It should normally be a dictionary, and we need to check for this sort
- // of thing. Because we can successfully decode this value, however, we do return it
- // in the error results. We check for this array later in the test.
- let data = "[]".data(using: .utf8)
- let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
- let request = FakeRequest(withRequestBody: [:])
- rpcIssuer.respondBlock = {
- (data, responseError)
- }
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.internalError.rawValue)
- let underlyingError = try XCTUnwrap(rpcError.userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
- XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue)
- let underlyingUnderlying = try XCTUnwrap(underlyingError
- .userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain)
- XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode)
- XCTAssertNotNil(try XCTUnwrap(
- underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]
- ) as? [Int])
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
- }
- }
- /** @fn testNonDictionarySuccessResponse
- @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
- response deserialized by @c NSJSONSerialization is not a dictionary, and no error was
- expected. We are expecting to receive an @c NSError with the code
- @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded response in the
- @c NSError.userInfo dictionary associated with the key
- `userInfoDeserializedResponseKey`.
- */
- func testNonDictionarySuccessResponse() async throws {
- // We are responding with a JSON-encoded string value representing an array - which is
- // unexpected. It should normally be a dictionary, and we need to check for this sort
- // of thing. Because we can successfully decode this value, however, we do return it
- // in the error results. We check for this array later in the test.
- let data = "[]".data(using: .utf8)
- let request = FakeRequest(withRequestBody: [:])
- rpcIssuer.respondBlock = {
- (data, nil)
- }
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.internalError.rawValue)
- let underlyingError = try XCTUnwrap(rpcError.userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
- XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedResponse.rawValue)
- XCTAssertNil(underlyingError.userInfo[NSUnderlyingErrorKey])
- XCTAssertNotNil(try XCTUnwrap(
- underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]
- ) as? [Int])
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
- }
- }
- /** @fn testCaptchaRequiredResponse
- @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
- we get an error message indicating captcha is required. The backend should not be returning
- this error to mobile clients. If it does, we should wrap it in an @c NSError with the code
- @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded error message in the
- @c NSError.userInfo dictionary associated with the key
- @c FIRAuthErrorUserInfoDeserializedResponseKey.
- */
- func testCaptchaRequiredResponse() async throws {
- let kErrorMessageCaptchaRequired = "CAPTCHA_REQUIRED"
- let request = FakeRequest(withRequestBody: [:])
- rpcIssuer.respondBlock = {
- let responseError = NSError(domain: self.kFakeErrorDomain, code: self.kFakeErrorCode)
- return try self.rpcIssuer.respond(serverErrorMessage: kErrorMessageCaptchaRequired,
- error: responseError)
- }
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.internalError.rawValue)
- let underlyingError = try XCTUnwrap(rpcError.userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
- XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue)
- let underlyingUnderlying = try XCTUnwrap(underlyingError
- .userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain)
- XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode)
- let dictionary = try XCTUnwrap(underlyingError
- .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable])
- XCTAssertEqual(dictionary["message"], kErrorMessageCaptchaRequired)
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
- }
- }
- /** @fn testCaptchaCheckFailedResponse
- @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
- we get an error message indicating captcha check failed. The backend should not be returning
- this error to mobile clients. If it does, we should wrap it in an @c NSError with the code
- @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded error message in the
- @c NSError.userInfo dictionary associated with the key
- @c FIRAuthErrorUserInfoDecodedErrorResponseKey.
- */
- func testCaptchaCheckFailedResponse() async throws {
- let kErrorMessageCaptchaCheckFailed = "CAPTCHA_CHECK_FAILED"
- let request = FakeRequest(withRequestBody: [:])
- rpcIssuer.respondBlock = {
- let responseError = NSError(domain: self.kFakeErrorDomain, code: self.kFakeErrorCode)
- return try self.rpcIssuer.respond(
- serverErrorMessage: kErrorMessageCaptchaCheckFailed,
- error: responseError
- )
- }
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.captchaCheckFailed.rawValue)
- }
- }
- /** @fn testCaptchaRequiredInvalidPasswordResponse
- @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
- we get an error message indicating captcha is required and an invalid password was entered.
- The backend should not be returning this error to mobile clients. If it does, we should wrap
- it in an @c NSError with the code
- @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded error message in the
- @c NSError.userInfo dictionary associated with the key
- @c FIRAuthErrorUserInfoDeserializedResponseKey.
- */
- func testCaptchaRequiredInvalidPasswordResponse() async throws {
- let kErrorMessageCaptchaRequiredInvalidPassword = "CAPTCHA_REQUIRED_INVALID_PASSWORD"
- let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
- let request = FakeRequest(withRequestBody: [:])
- rpcIssuer.respondBlock = {
- try self.rpcIssuer.respond(serverErrorMessage: kErrorMessageCaptchaRequiredInvalidPassword,
- error: responseError)
- }
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.internalError.rawValue)
- let underlyingError = try XCTUnwrap(rpcError.userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
- XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue)
- let underlyingUnderlying = try XCTUnwrap(underlyingError
- .userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain)
- XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode)
- let dictionary = try XCTUnwrap(underlyingError
- .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable])
- XCTAssertEqual(dictionary["message"], kErrorMessageCaptchaRequiredInvalidPassword)
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
- }
- }
- /// Test Blocking Function Error Response flow
- func testBlockingFunctionError() async throws {
- let kErrorMessageBlocking = "BLOCKING_FUNCTION_ERROR_RESPONSE"
- let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
- let request = FakeRequest(withRequestBody: [:])
- rpcIssuer.respondBlock = {
- try self.rpcIssuer.respond(serverErrorMessage: kErrorMessageBlocking, error: responseError)
- }
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.blockingCloudFunctionError.rawValue)
- }
- }
- /// Test Blocking Function Error Response flow - including JSON parsing.
- /// Regression Test for #14052
- func testBlockingFunctionErrorWithJSON() async throws {
- let kErrorMessageBlocking = "BLOCKING_FUNCTION_ERROR_RESPONSE"
- let stringWithJSON = "BLOCKING_FUNCTION_ERROR_RESPONSE : ((HTTP request to" +
- "http://127.0.0.1:9999/project-id/us-central1/beforeUserCreated returned HTTP error 400:" +
- " {\"error\":{\"details\":{\"code\":\"invalid-email\"},\"message\":\"invalid " +
- "email\",\"status\":\"INVALID_ARGUMENT\"}}))"
- let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
- let request = FakeRequest(withRequestBody: [:])
- rpcIssuer.respondBlock = {
- try self.rpcIssuer.respond(
- serverErrorMessage: kErrorMessageBlocking + " : " + stringWithJSON,
- error: responseError
- )
- }
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.blockingCloudFunctionError.rawValue)
- XCTAssertEqual(rpcError.localizedDescription, "invalid email")
- }
- }
- /** @fn testDecodableErrorResponseWithUnknownMessage
- @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
- response deserialized by @c NSJSONSerialization represents a valid error response (and an
- error was indicated) but we didn't receive an error message we know about. We are expecting
- to receive the original network error wrapped in an @c NSError with the code
- @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded
- error message in the @c NSError.userInfo dictionary associated with the key
- @c FIRAuthErrorUserInfoDeserializedResponseKey.
- */
- func testDecodableErrorResponseWithUnknownMessage() async throws {
- // We need to return a valid "error" response here, but we are going to intentionally use a
- // bogus error message.
- let kUnknownServerErrorMessage = "UNKNOWN_MESSAGE"
- let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
- let request = FakeRequest(withRequestBody: [:])
- rpcIssuer.respondBlock = {
- try self.rpcIssuer.respond(serverErrorMessage: kUnknownServerErrorMessage,
- error: responseError)
- }
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.internalError.rawValue)
- let underlyingError = try XCTUnwrap(rpcError.userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
- XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue)
- let underlyingUnderlying = try XCTUnwrap(underlyingError
- .userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain)
- XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode)
- let dictionary = try XCTUnwrap(underlyingError
- .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable])
- XCTAssertEqual(dictionary["message"], kUnknownServerErrorMessage)
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
- }
- }
- /** @fn testErrorResponseWithNoErrorMessage
- @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
- response deserialized by @c NSJSONSerialization is a dictionary, and an error was indicated,
- but no error information was present in the decoded response. We are expecting to receive
- the original network error wrapped in an @c NSError with the code
- @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded
- response message in the @c NSError.userInfo dictionary associated with the key
- @c FIRAuthErrorUserInfoDeserializedResponseKey.
- */
- func testErrorResponseWithNoErrorMessage() async throws {
- let request = FakeRequest(withRequestBody: [:])
- let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
- rpcIssuer.respondBlock = {
- try self.rpcIssuer.respond(withJSON: [:], error: responseError)
- }
- do {
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.internalError.rawValue)
- let underlyingError = try XCTUnwrap(rpcError.userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
- XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue)
- let underlyingUnderlying = try XCTUnwrap(underlyingError
- .userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain)
- XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode)
- let dictionary = try XCTUnwrap(underlyingError
- .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable])
- XCTAssertEqual(dictionary, [:])
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
- }
- }
- /** @fn testClientErrorResponse
- @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
- response contains a client error specified by an error message sent from the backend.
- */
- func testClientErrorResponse() async throws {
- let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
- let kUserDisabledErrorMessage = "USER_DISABLED"
- let kServerErrorDetailMarker = " : "
- let kFakeUserDisabledCustomErrorMessage = "The user has been disabled."
- let customErrorMessage = "\(kUserDisabledErrorMessage)" +
- "\(kServerErrorDetailMarker)\(kFakeUserDisabledCustomErrorMessage)"
- rpcIssuer.respondBlock = {
- try self.rpcIssuer.respond(serverErrorMessage: customErrorMessage, error: responseError)
- }
- do {
- let _ = try await authBackend.call(with: FakeRequest(withRequestBody: [:]))
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.userDisabled.rawValue)
- let customMessage = try XCTUnwrap(rpcError.userInfo[NSLocalizedDescriptionKey] as? String)
- XCTAssertEqual(customMessage, kFakeUserDisabledCustomErrorMessage)
- }
- }
- /** @fn testUndecodableSuccessResponse
- @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
- response isn't decodable by the response class but no error condition was expected. We are
- expecting to receive an @c NSError with the code
- @c FIRAuthErrorCodeUnexpectedServerResponse and the error from @c setWithDictionary:error:
- as the value of the underlyingError.
- */
- func testUndecodableSuccessResponse() async throws {
- rpcIssuer.respondBlock = {
- try self.rpcIssuer.respond(withJSON: [:])
- }
- do {
- let request = FakeDecodingErrorRequest(withRequestBody: [:])
- let _ = try await authBackend.call(with: request)
- XCTFail("Expected to throw")
- } catch {
- let rpcError = error as NSError
- XCTAssertEqual(rpcError.domain, AuthErrors.domain)
- XCTAssertEqual(rpcError.code, AuthErrorCode.internalError.rawValue)
- let underlyingError = try XCTUnwrap(rpcError.userInfo[NSUnderlyingErrorKey] as? NSError)
- XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
- XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.RPCResponseDecodingError.rawValue)
- let dictionary = try XCTUnwrap(underlyingError
- .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable])
- XCTAssertEqual(dictionary, [:])
- XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
- }
- }
- /** @fn testSuccessfulResponse
- @brief Tests that a decoded dictionary is handed to the response instance.
- */
- func testSuccessfulResponse() async throws {
- let kTestKey = "TestKey"
- let kTestValue = "TestValue"
- rpcIssuer.respondBlock = {
- // It doesn't matter what we respond with here, as long as it's not an error response. The
- // fake response will deterministically simulate a decoding error regardless of the response
- // value it was given.
- try self.rpcIssuer.respond(withJSON: [kTestKey: kTestValue])
- }
- let rpcResponse = try await authBackend.call(with: FakeRequest(withRequestBody: [:]))
- XCTAssertEqual(try XCTUnwrap(rpcResponse.receivedValue), kTestValue)
- }
- #if COCOAPODS || SWIFT_PACKAGE
- private class FakeHeartbeatLogger: NSObject, FIRHeartbeatLoggerProtocol {
- func headerValue() -> String? {
- // `asyncHeaderValue` should be used instead.
- fatalError("FakeHeartbeatLogger headerValue should not be used in tests.")
- }
- func asyncHeaderValue() async -> String? {
- let payload = flushHeartbeatsIntoPayload()
- guard !payload.isEmpty else {
- return nil
- }
- return payload.headerValue()
- }
- var onFlushHeartbeatsIntoPayloadHandler: (() -> _ObjC_HeartbeatsPayload)?
- func log() {
- // This API should not be used by the below tests because the Auth
- // SDK does not log heartbeats in its networking context.
- fatalError("FakeHeartbeatLogger log should not be used in tests.")
- }
- func flushHeartbeatsIntoPayload() -> FirebaseCoreInternal._ObjC_HeartbeatsPayload {
- guard let handler = onFlushHeartbeatsIntoPayloadHandler else {
- fatalError("Missing Handler")
- }
- return handler()
- }
- func heartbeatCodeForToday() -> FIRDailyHeartbeatCode {
- // This API should not be used by the below tests because the Auth
- // SDK uses only the V2 heartbeat API (`flushHeartbeatsIntoPayload`) for
- // getting heartbeats.
- return FIRDailyHeartbeatCode.none
- }
- }
- /** @fn testRequest_IncludesHeartbeatPayload_WhenHeartbeatsNeedSending
- @brief This test checks the behavior of @c postWithRequest:response:callback:
- to verify that a heartbeats payload is attached as a header to an
- outgoing request when there are stored heartbeats that need sending.
- */
- func testRequest_IncludesHeartbeatPayload_WhenHeartbeatsNeedSending() async throws {
- // Given
- let fakeHeartbeatLogger = FakeHeartbeatLogger()
- let requestConfiguration = AuthRequestConfiguration(apiKey: kFakeAPIKey,
- appID: kFakeAppID,
- heartbeatLogger: fakeHeartbeatLogger)
- let request = FakeRequest(withRequestBody: [:], requestConfiguration: requestConfiguration)
- // When
- let nonEmptyHeartbeatsPayload = HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload
- fakeHeartbeatLogger.onFlushHeartbeatsIntoPayloadHandler = {
- nonEmptyHeartbeatsPayload
- }
- rpcIssuer.respondBlock = {
- // Force return from async post
- try self.rpcIssuer.respond(withJSON: [:])
- }
- _ = try? await authBackend.call(with: request)
- // Then
- let expectedHeader = HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload.headerValue()
- let completeRequest = await rpcIssuer.completeRequest.value
- let headerValue = completeRequest.value(forHTTPHeaderField: "X-Firebase-Client")
- XCTAssertEqual(headerValue, expectedHeader)
- }
- /** @fn testRequest_IncludesAppCheckHeader
- @brief This test checks the behavior of @c postWithRequest:response:callback:
- to verify that a appCheck token is attached as a header to an
- outgoing request.
- */
- func testRequest_IncludesAppCheckHeader() async throws {
- // Given
- let fakeAppCheck = FakeAppCheck()
- let requestConfiguration = AuthRequestConfiguration(apiKey: kFakeAPIKey,
- appID: kFakeAppID,
- appCheck: fakeAppCheck)
- let request = FakeRequest(withRequestBody: [:], requestConfiguration: requestConfiguration)
- rpcIssuer.respondBlock = {
- // Just force return from async call.
- try self.rpcIssuer.respond(withJSON: [:])
- }
- _ = try? await authBackend.call(with: request)
- let completeRequest = await rpcIssuer.completeRequest.value
- let headerValue = completeRequest.value(forHTTPHeaderField: "X-Firebase-AppCheck")
- XCTAssertEqual(headerValue, fakeAppCheck.fakeAppCheckToken)
- }
- /** @fn testRequest_DoesNotIncludeAHeartbeatPayload_WhenNoHeartbeatsNeedSending
- @brief This test checks the behavior of @c postWithRequest:response:callback:
- to verify that a request header does not contain heartbeat data in the
- case that there are no stored heartbeats that need sending.
- */
- func testRequest_DoesNotIncludeAHeartbeatPayload_WhenNoHeartbeatsNeedSending() async throws {
- // Given
- let fakeHeartbeatLogger = FakeHeartbeatLogger()
- let requestConfiguration = AuthRequestConfiguration(apiKey: kFakeAPIKey,
- appID: kFakeAppID,
- heartbeatLogger: fakeHeartbeatLogger)
- let request = FakeRequest(withRequestBody: [:], requestConfiguration: requestConfiguration)
- // When
- let emptyHeartbeatsPayload = HeartbeatLoggingTestUtils.emptyHeartbeatsPayload
- fakeHeartbeatLogger.onFlushHeartbeatsIntoPayloadHandler = {
- emptyHeartbeatsPayload
- }
- rpcIssuer.respondBlock = {
- // Force return from async post
- try self.rpcIssuer.respond(withJSON: [:])
- }
- _ = try? await authBackend.call(with: request)
- // Then
- let completeRequest = await rpcIssuer.completeRequest.value
- XCTAssertNil(completeRequest.value(forHTTPHeaderField: "X-Firebase-Client"))
- }
- #endif // COCOAPODS || SWIFT_PACKAGE
- private class FakeRequest: AuthRPCRequest {
- typealias Response = FakeResponse
- func requestConfiguration() -> AuthRequestConfiguration {
- return configuration
- }
- let kFakeRequestURL = "https://www.google.com/"
- func requestURL() -> URL {
- return try! XCTUnwrap(URL(string: kFakeRequestURL))
- }
- var unencodedHTTPRequestBody: [String: AnyHashable]? {
- if encodingError == nil {
- return requestBody
- }
- // Else, return an unencodable request body that will cause an error to be thrown.
- struct UnencodableObject: Hashable {
- static func == (lhs: UnencodableObject, rhs: UnencodableObject) -> Bool {
- true
- }
- }
- return ["foo": UnencodableObject()]
- }
- static func makeRequestConfiguration() -> AuthRequestConfiguration {
- return AuthRequestConfiguration(
- apiKey: kFakeAPIKey,
- appID: kFakeAppID
- )
- }
- private let configuration: AuthRequestConfiguration
- let encodingError: NSError?
- let requestBody: [String: AnyHashable]
- init(withEncodingError error: NSError) {
- encodingError = error
- requestBody = [:]
- configuration = FakeRequest.makeRequestConfiguration()
- }
- init(withDecodingError error: NSError) {
- encodingError = nil
- requestBody = [:]
- configuration = FakeRequest.makeRequestConfiguration()
- }
- init(withRequestBody body: [String: AnyHashable],
- requestConfiguration: AuthRequestConfiguration = FakeRequest.makeRequestConfiguration()) {
- encodingError = nil
- requestBody = body
- configuration = requestConfiguration
- }
- }
- private struct FakeResponse: AuthRPCResponse {
- var receivedValue: String?
- init(dictionary: [String: AnyHashable]) throws {
- receivedValue = dictionary["TestKey"] as? String
- }
- }
- private class FakeDecodingErrorRequest: AuthRPCRequest {
- typealias Response = FakeDecodingErrorResponse
- func requestURL() -> URL {
- return fakeRequest.requestURL()
- }
- var unencodedHTTPRequestBody: [String: AnyHashable]? {
- fakeRequest.unencodedHTTPRequestBody
- }
- func requestConfiguration() -> FirebaseAuth.AuthRequestConfiguration {
- return fakeRequest.requestConfiguration()
- }
- let fakeRequest: FakeRequest
- init(withRequestBody body: [String: AnyHashable]) {
- fakeRequest = FakeRequest(withRequestBody: body)
- }
- }
- private struct FakeDecodingErrorResponse: AuthRPCResponse {
- init(dictionary: [String: AnyHashable]) throws {
- throw NSError(domain: "dummy", code: -1)
- }
- }
- }
|