AuthBackendRPCImplentationTests.swift 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. // Copyright 2023 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. import XCTest
  16. @testable import FirebaseAuth
  17. import FirebaseCoreExtension
  18. import FirebaseCoreInternal
  19. private let kFakeAPIKey = "kTestAPIKey"
  20. private let kFakeAppID = "kTestFirebaseAppID"
  21. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  22. class AuthBackendRPCImplementationTests: RPCBaseTests {
  23. let kFakeErrorDomain = "fakeDomain"
  24. let kFakeErrorCode = -1
  25. /** @fn testRequestEncodingError
  26. @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
  27. request passed returns an error during it's unencodedHTTPRequestBodyWithError: method.
  28. The error returned should be delivered to the caller without any change.
  29. */
  30. func testRequestEncodingError() throws {
  31. let encodingError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
  32. let request = FakeRequest(withEncodingError: encodingError)
  33. var callbackInvoked = false
  34. var rpcResponse: FakeResponse?
  35. var rpcError: NSError?
  36. rpcImplementation?.post(with: request) { response, error in
  37. callbackInvoked = true
  38. rpcResponse = response
  39. rpcError = error as? NSError
  40. }
  41. XCTAssert(callbackInvoked)
  42. XCTAssertNil(rpcResponse)
  43. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  44. XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue)
  45. let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError)
  46. XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
  47. XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.RPCRequestEncodingError.rawValue)
  48. let underlyingUnderlying = try XCTUnwrap(underlyingError
  49. .userInfo[NSUnderlyingErrorKey] as? NSError)
  50. XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain)
  51. XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode)
  52. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey])
  53. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
  54. }
  55. /** @fn testBodyDataSerializationError
  56. @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
  57. request returns an object which isn't serializable by @c NSJSONSerialization.
  58. The error from @c NSJSONSerialization should be returned as the underlyingError for an
  59. @c NSError with the code @c FIRAuthErrorCodeJSONSerializationError.
  60. */
  61. func testBodyDataSerializationError() throws {
  62. let request = FakeRequest(withRequestBody: ["unencodable": self])
  63. var callbackInvoked = false
  64. var rpcResponse: FakeResponse?
  65. var rpcError: NSError?
  66. rpcImplementation?.post(with: request) { response, error in
  67. callbackInvoked = true
  68. rpcResponse = response
  69. rpcError = error as? NSError
  70. }
  71. XCTAssert(callbackInvoked)
  72. XCTAssertNil(rpcResponse)
  73. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  74. XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue)
  75. let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError)
  76. XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.JSONSerializationError.rawValue)
  77. XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
  78. XCTAssertNil(underlyingError.userInfo[NSUnderlyingErrorKey])
  79. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey])
  80. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
  81. }
  82. /** @fn testNetworkError
  83. @brief This test checks to make sure a network error is properly wrapped and forwarded with the
  84. correct code (FIRAuthErrorCodeNetworkError).
  85. */
  86. func testNetworkError() throws {
  87. let request = FakeRequest(withRequestBody: [:])
  88. var callbackInvoked = false
  89. var rpcResponse: FakeResponse?
  90. var rpcError: NSError?
  91. rpcImplementation?.post(with: request) { response, error in
  92. callbackInvoked = true
  93. rpcResponse = response
  94. rpcError = error as? NSError
  95. }
  96. let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
  97. try rpcIssuer?.respond(withData: nil, error: responseError)
  98. XCTAssert(callbackInvoked)
  99. XCTAssertNil(rpcResponse)
  100. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  101. XCTAssertEqual(rpcError?.code, AuthErrorCode.networkError.rawValue)
  102. let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError)
  103. XCTAssertEqual(underlyingError.domain, kFakeErrorDomain)
  104. XCTAssertEqual(underlyingError.code, kFakeErrorCode)
  105. XCTAssertNil(underlyingError.userInfo[NSUnderlyingErrorKey])
  106. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey])
  107. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
  108. }
  109. /** @fn testUnparsableErrorResponse
  110. @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
  111. response isn't deserializable by @c NSJSONSerialization and an error
  112. condition (with an associated error response message) was expected. We are expecting to
  113. receive the original network error wrapped in an @c NSError with the code
  114. @c FIRAuthErrorCodeUnexpectedHTTPResponse.
  115. */
  116. func testUnparsableErrorResponse() throws {
  117. let request = FakeRequest(withRequestBody: [:])
  118. var callbackInvoked = false
  119. var rpcResponse: FakeResponse?
  120. var rpcError: NSError?
  121. rpcImplementation?.post(with: request) { response, error in
  122. callbackInvoked = true
  123. rpcResponse = response
  124. rpcError = error as? NSError
  125. }
  126. let data = "<html><body>An error occurred.</body></html>".data(using: .utf8)
  127. let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
  128. try rpcIssuer?.respond(withData: data, error: responseError)
  129. XCTAssert(callbackInvoked)
  130. XCTAssertNil(rpcResponse)
  131. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  132. XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue)
  133. let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError)
  134. XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
  135. XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue)
  136. let underlyingUnderlying = try XCTUnwrap(underlyingError
  137. .userInfo[NSUnderlyingErrorKey] as? NSError)
  138. XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain)
  139. XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode)
  140. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey])
  141. XCTAssertEqual(data,
  142. try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey] as? Data))
  143. }
  144. /** @fn testUnparsableSuccessResponse
  145. @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
  146. response isn't deserializable by @c NSJSONSerialization and no error
  147. condition was indicated. We are expecting to
  148. receive the @c NSJSONSerialization error wrapped in an @c NSError with the code
  149. @c FIRAuthErrorCodeUnexpectedServerResponse.
  150. */
  151. func testUnparsableSuccessResponse() throws {
  152. let request = FakeRequest(withRequestBody: [:])
  153. var callbackInvoked = false
  154. var rpcResponse: FakeResponse?
  155. var rpcError: NSError?
  156. rpcImplementation?.post(with: request) { response, error in
  157. callbackInvoked = true
  158. rpcResponse = response
  159. rpcError = error as? NSError
  160. }
  161. let data = "<xml>Some non-JSON value.</xml>".data(using: .utf8)
  162. try rpcIssuer?.respond(withData: data, error: nil)
  163. XCTAssert(callbackInvoked)
  164. XCTAssertNil(rpcResponse)
  165. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  166. XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue)
  167. let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError)
  168. XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
  169. XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedResponse.rawValue)
  170. let underlyingUnderlying = try XCTUnwrap(underlyingError
  171. .userInfo[NSUnderlyingErrorKey] as? NSError)
  172. XCTAssertEqual(underlyingUnderlying.domain, NSCocoaErrorDomain)
  173. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey])
  174. XCTAssertEqual(data,
  175. try XCTUnwrap(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey] as? Data))
  176. }
  177. /** @fn testNonDictionaryErrorResponse
  178. @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
  179. response deserialized by @c NSJSONSerialization is not a dictionary, and an error was
  180. expected. We are expecting to receive the original network error wrapped in an @c NSError
  181. with the code @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded response
  182. in the @c NSError.userInfo dictionary associated with the key
  183. @c FIRAuthErrorUserInfoDeserializedResponseKey.
  184. */
  185. func testNonDictionaryErrorResponse() throws {
  186. let request = FakeRequest(withRequestBody: [:])
  187. var callbackInvoked = false
  188. var rpcResponse: FakeResponse?
  189. var rpcError: NSError?
  190. rpcImplementation?.post(with: request) { response, error in
  191. callbackInvoked = true
  192. rpcResponse = response
  193. rpcError = error as? NSError
  194. }
  195. // We are responding with a JSON-encoded string value representing an array - which is
  196. // unexpected. It should normally be a dictionary, and we need to check for this sort
  197. // of thing. Because we can successfully decode this value, however, we do return it
  198. // in the error results. We check for this array later in the test.
  199. let data = "[]".data(using: .utf8)
  200. let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
  201. try rpcIssuer?.respond(withData: data, error: responseError)
  202. XCTAssert(callbackInvoked)
  203. XCTAssertNil(rpcResponse)
  204. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  205. XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue)
  206. let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError)
  207. XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
  208. XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue)
  209. let underlyingUnderlying = try XCTUnwrap(underlyingError
  210. .userInfo[NSUnderlyingErrorKey] as? NSError)
  211. XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain)
  212. XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode)
  213. XCTAssertNotNil(try XCTUnwrap(
  214. underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]
  215. ) as? [Int])
  216. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
  217. }
  218. /** @fn testNonDictionarySuccessResponse
  219. @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
  220. response deserialized by @c NSJSONSerialization is not a dictionary, and no error was
  221. expected. We are expecting to receive an @c NSError with the code
  222. @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded response in the
  223. @c NSError.userInfo dictionary associated with the key
  224. @c FIRAuthErrorUserInfoDecodedResponseKey.
  225. */
  226. func testNonDictionarySuccessResponse() throws {
  227. let request = FakeRequest(withRequestBody: [:])
  228. var callbackInvoked = false
  229. var rpcResponse: FakeResponse?
  230. var rpcError: NSError?
  231. rpcImplementation?.post(with: request) { response, error in
  232. callbackInvoked = true
  233. rpcResponse = response
  234. rpcError = error as? NSError
  235. }
  236. // We are responding with a JSON-encoded string value representing an array - which is
  237. // unexpected. It should normally be a dictionary, and we need to check for this sort
  238. // of thing. Because we can successfully decode this value, however, we do return it
  239. // in the error results. We check for this array later in the test.
  240. let data = "[]".data(using: .utf8)
  241. try rpcIssuer?.respond(withData: data, error: nil)
  242. XCTAssert(callbackInvoked)
  243. XCTAssertNil(rpcResponse)
  244. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  245. XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue)
  246. let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError)
  247. XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
  248. XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedResponse.rawValue)
  249. XCTAssertNil(underlyingError.userInfo[NSUnderlyingErrorKey])
  250. XCTAssertNotNil(try XCTUnwrap(
  251. underlyingError.userInfo[AuthErrorUtils.userInfoDeserializedResponseKey]
  252. ) as? [Int])
  253. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
  254. }
  255. /** @fn testCaptchaRequiredResponse
  256. @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
  257. we get an error message indicating captcha is required. The backend should not be returning
  258. this error to mobile clients. If it does, we should wrap it in an @c NSError with the code
  259. @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded error message in the
  260. @c NSError.userInfo dictionary associated with the key
  261. @c FIRAuthErrorUserInfoDeserializedResponseKey.
  262. */
  263. func testCaptchaRequiredResponse() throws {
  264. let kErrorMessageCaptchaRequired = "CAPTCHA_REQUIRED"
  265. let request = FakeRequest(withRequestBody: [:])
  266. var callbackInvoked = false
  267. var rpcResponse: FakeResponse?
  268. var rpcError: NSError?
  269. rpcImplementation?.post(with: request) { response, error in
  270. callbackInvoked = true
  271. rpcResponse = response
  272. rpcError = error as? NSError
  273. }
  274. let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
  275. try rpcIssuer?.respond(serverErrorMessage: kErrorMessageCaptchaRequired, error: responseError)
  276. XCTAssert(callbackInvoked)
  277. XCTAssertNil(rpcResponse)
  278. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  279. XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue)
  280. let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError)
  281. XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
  282. XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue)
  283. let underlyingUnderlying = try XCTUnwrap(underlyingError
  284. .userInfo[NSUnderlyingErrorKey] as? NSError)
  285. XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain)
  286. XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode)
  287. let dictionary = try XCTUnwrap(underlyingError
  288. .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable])
  289. XCTAssertEqual(dictionary["message"], kErrorMessageCaptchaRequired)
  290. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
  291. }
  292. /** @fn testCaptchaCheckFailedResponse
  293. @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
  294. we get an error message indicating captcha check failed. The backend should not be returning
  295. this error to mobile clients. If it does, we should wrap it in an @c NSError with the code
  296. @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded error message in the
  297. @c NSError.userInfo dictionary associated with the key
  298. @c FIRAuthErrorUserInfoDecodedErrorResponseKey.
  299. */
  300. func testCaptchaCheckFailedResponse() throws {
  301. let kErrorMessageCaptchaCheckFailed = "CAPTCHA_CHECK_FAILED"
  302. let request = FakeRequest(withRequestBody: [:])
  303. var callbackInvoked = false
  304. var rpcResponse: FakeResponse?
  305. var rpcError: NSError?
  306. rpcImplementation?.post(with: request) { response, error in
  307. callbackInvoked = true
  308. rpcResponse = response
  309. rpcError = error as? NSError
  310. }
  311. let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
  312. try rpcIssuer?.respond(
  313. serverErrorMessage: kErrorMessageCaptchaCheckFailed,
  314. error: responseError
  315. )
  316. XCTAssert(callbackInvoked)
  317. XCTAssertNil(rpcResponse)
  318. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  319. XCTAssertEqual(rpcError?.code, AuthErrorCode.captchaCheckFailed.rawValue)
  320. }
  321. /** @fn testCaptchaRequiredInvalidPasswordResponse
  322. @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
  323. we get an error message indicating captcha is required and an invalid password was entered.
  324. The backend should not be returning this error to mobile clients. If it does, we should wrap
  325. it in an @c NSError with the code
  326. @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded error message in the
  327. @c NSError.userInfo dictionary associated with the key
  328. @c FIRAuthErrorUserInfoDeserializedResponseKey.
  329. */
  330. func testCaptchaRequiredInvalidPasswordResponse() throws {
  331. let kErrorMessageCaptchaRequiredInvalidPassword = "CAPTCHA_REQUIRED_INVALID_PASSWORD"
  332. let request = FakeRequest(withRequestBody: [:])
  333. var callbackInvoked = false
  334. var rpcResponse: FakeResponse?
  335. var rpcError: NSError?
  336. rpcImplementation?.post(with: request) { response, error in
  337. callbackInvoked = true
  338. rpcResponse = response
  339. rpcError = error as? NSError
  340. }
  341. let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
  342. try rpcIssuer?.respond(serverErrorMessage: kErrorMessageCaptchaRequiredInvalidPassword,
  343. error: responseError)
  344. XCTAssert(callbackInvoked)
  345. XCTAssertNil(rpcResponse)
  346. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  347. XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue)
  348. let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError)
  349. XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
  350. XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue)
  351. let underlyingUnderlying = try XCTUnwrap(underlyingError
  352. .userInfo[NSUnderlyingErrorKey] as? NSError)
  353. XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain)
  354. XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode)
  355. let dictionary = try XCTUnwrap(underlyingError
  356. .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable])
  357. XCTAssertEqual(dictionary["message"], kErrorMessageCaptchaRequiredInvalidPassword)
  358. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
  359. }
  360. /** @fn testDecodableErrorResponseWithUnknownMessage
  361. @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
  362. response deserialized by @c NSJSONSerialization represents a valid error response (and an
  363. error was indicated) but we didn't receive an error message we know about. We are expecting
  364. to receive the original network error wrapped in an @c NSError with the code
  365. @c FIRAuthInternalErrorCodeUnexpectedErrorResponse with the decoded
  366. error message in the @c NSError.userInfo dictionary associated with the key
  367. @c FIRAuthErrorUserInfoDeserializedResponseKey.
  368. */
  369. func testDecodableErrorResponseWithUnknownMessage() throws {
  370. let kUnknownServerErrorMessage = "UNKNOWN_MESSAGE"
  371. let request = FakeRequest(withRequestBody: [:])
  372. var callbackInvoked = false
  373. var rpcResponse: FakeResponse?
  374. var rpcError: NSError?
  375. rpcImplementation?.post(with: request) { response, error in
  376. callbackInvoked = true
  377. rpcResponse = response
  378. rpcError = error as? NSError
  379. }
  380. // We need to return a valid "error" response here, but we are going to intentionally use a
  381. // bogus error message.
  382. let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
  383. try rpcIssuer?.respond(serverErrorMessage: kUnknownServerErrorMessage, error: responseError)
  384. XCTAssert(callbackInvoked)
  385. XCTAssertNil(rpcResponse)
  386. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  387. XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue)
  388. let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError)
  389. XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
  390. XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue)
  391. let underlyingUnderlying = try XCTUnwrap(underlyingError
  392. .userInfo[NSUnderlyingErrorKey] as? NSError)
  393. XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain)
  394. XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode)
  395. let dictionary = try XCTUnwrap(underlyingError
  396. .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable])
  397. XCTAssertEqual(dictionary["message"], kUnknownServerErrorMessage)
  398. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
  399. }
  400. /** @fn testErrorResponseWithNoErrorMessage
  401. @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
  402. response deserialized by @c NSJSONSerialization is a dictionary, and an error was indicated,
  403. but no error information was present in the decoded response. We are expecting to receive
  404. the original network error wrapped in an @c NSError with the code
  405. @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded
  406. response message in the @c NSError.userInfo dictionary associated with the key
  407. @c FIRAuthErrorUserInfoDeserializedResponseKey.
  408. */
  409. func testErrorResponseWithNoErrorMessage() throws {
  410. let request = FakeRequest(withRequestBody: [:])
  411. var callbackInvoked = false
  412. var rpcResponse: FakeResponse?
  413. var rpcError: NSError?
  414. rpcImplementation?.post(with: request) { response, error in
  415. callbackInvoked = true
  416. rpcResponse = response
  417. rpcError = error as? NSError
  418. }
  419. let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
  420. try rpcIssuer?.respond(withJSON: [:], error: responseError)
  421. XCTAssert(callbackInvoked)
  422. XCTAssertNil(rpcResponse)
  423. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  424. XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue)
  425. let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError)
  426. XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
  427. XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.unexpectedErrorResponse.rawValue)
  428. let underlyingUnderlying = try XCTUnwrap(underlyingError
  429. .userInfo[NSUnderlyingErrorKey] as? NSError)
  430. XCTAssertEqual(underlyingUnderlying.domain, kFakeErrorDomain)
  431. XCTAssertEqual(underlyingUnderlying.code, kFakeErrorCode)
  432. let dictionary = try XCTUnwrap(underlyingError
  433. .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable])
  434. XCTAssertEqual(dictionary, [:])
  435. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
  436. }
  437. /** @fn testClientErrorResponse
  438. @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
  439. response contains a client error specified by an error messsage sent from the backend.
  440. */
  441. func testClientErrorResponse() throws {
  442. let request = FakeRequest(withRequestBody: [:])
  443. var callbackInvoked = false
  444. var rpcResponse: FakeResponse?
  445. var rpcError: NSError?
  446. rpcImplementation?.post(with: request) { response, error in
  447. callbackInvoked = true
  448. rpcResponse = response
  449. rpcError = error as? NSError
  450. }
  451. let responseError = NSError(domain: kFakeErrorDomain, code: kFakeErrorCode)
  452. let kUserDisabledErrorMessage = "USER_DISABLED"
  453. let kServerErrorDetailMarker = " : "
  454. let kFakeUserDisabledCustomErrorMessage = "The user has been disabled."
  455. let customErrorMessage = "\(kUserDisabledErrorMessage)" +
  456. "\(kServerErrorDetailMarker)\(kFakeUserDisabledCustomErrorMessage)"
  457. try rpcIssuer?.respond(serverErrorMessage: customErrorMessage, error: responseError)
  458. XCTAssert(callbackInvoked)
  459. XCTAssertNil(rpcResponse)
  460. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  461. XCTAssertEqual(rpcError?.code, AuthErrorCode.userDisabled.rawValue)
  462. let customMessage = try XCTUnwrap(rpcError?.userInfo[NSLocalizedDescriptionKey] as? String)
  463. XCTAssertEqual(customMessage, kFakeUserDisabledCustomErrorMessage)
  464. }
  465. /** @fn testUndecodableSuccessResponse
  466. @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
  467. response isn't decodable by the response class but no error condition was expected. We are
  468. expecting to receive an @c NSError with the code
  469. @c FIRAuthErrorCodeUnexpectedServerResponse and the error from @c setWithDictionary:error:
  470. as the value of the underlyingError.
  471. */
  472. // TODO: Broken test - the fake backend may treat things differently around the response and
  473. // errors.
  474. func xxx_testUndecodableSuccessResponse() throws {
  475. let request =
  476. FakeRequest(withDecodingError: NSError(domain: kFakeErrorDomain, code: kFakeErrorCode))
  477. var callbackInvoked = false
  478. // var rpcResponse: FakeResponse = FakeResponse(withDecodingError: <#T##NSError?#>)
  479. var rpcResponse: FakeResponse?
  480. var rpcError: NSError?
  481. rpcImplementation?.post(with: request) { response, error in
  482. // rpcImplementation?.post(with: request, response: rpcResponse) { error in
  483. callbackInvoked = true
  484. rpcResponse = response
  485. rpcError = error as? NSError
  486. }
  487. try rpcIssuer?.respond(withJSON: [:])
  488. XCTAssert(callbackInvoked)
  489. // XCTAssertNil(rpcError)
  490. XCTAssertNil(rpcResponse)
  491. XCTAssertEqual(rpcError?.domain, AuthErrors.domain)
  492. XCTAssertEqual(rpcError?.code, AuthErrorCode.internalError.rawValue)
  493. let underlyingError = try XCTUnwrap(rpcError?.userInfo[NSUnderlyingErrorKey] as? NSError)
  494. XCTAssertEqual(underlyingError.domain, AuthErrorUtils.internalErrorDomain)
  495. XCTAssertEqual(underlyingError.code, AuthInternalErrorCode.RPCResponseDecodingError.rawValue)
  496. let dictionary = try XCTUnwrap(underlyingError
  497. .userInfo[AuthErrorUtils.userInfoDeserializedResponseKey] as? [String: AnyHashable])
  498. XCTAssertEqual(dictionary, [:])
  499. XCTAssertNil(underlyingError.userInfo[AuthErrorUtils.userInfoDataKey])
  500. }
  501. /** @fn testSuccessfulResponse
  502. @brief Tests that a decoded dictionary is handed to the response instance.
  503. */
  504. func testSuccessfulResponse() throws {
  505. let request = FakeRequest(withRequestBody: [:])
  506. var callbackInvoked = false
  507. var rpcResponse: FakeResponse?
  508. var rpcError: NSError?
  509. let kTestKey = "TestKey"
  510. let kTestValue = "TestValue"
  511. rpcImplementation?.post(with: request) { response, error in
  512. callbackInvoked = true
  513. rpcResponse = response
  514. rpcError = error as? NSError
  515. }
  516. // It doesn't matter what we respond with here, as long as it's not an error response. The fake
  517. // response will deterministicly simulate a decoding error regardless of the response value it
  518. // was given.
  519. try rpcIssuer?.respond(withJSON: [kTestKey: kTestValue])
  520. XCTAssert(callbackInvoked)
  521. XCTAssertNil(rpcError)
  522. XCTAssertEqual(try XCTUnwrap(rpcResponse?.receivedDictionary[kTestKey] as? String), kTestValue)
  523. }
  524. // TODO: enable heartbeat logger tests for SPM
  525. #if COCOAPODS
  526. private class FakeHeartbeatLogger: NSObject, FIRHeartbeatLoggerProtocol {
  527. var onFlushHeartbeatsIntoPayloadHandler: (() -> _ObjC_HeartbeatsPayload)?
  528. func log() {
  529. // This API should not be used by the below tests because the Auth
  530. // SDK does not log heartbeats in it's networking context.
  531. fatalError("FakeHeartbeatLogger log should not be used in tests.")
  532. }
  533. func flushHeartbeatsIntoPayload() -> FirebaseCoreInternal._ObjC_HeartbeatsPayload {
  534. guard let handler = onFlushHeartbeatsIntoPayloadHandler else {
  535. fatalError("Missing Handler")
  536. }
  537. return handler()
  538. }
  539. func heartbeatCodeForToday() -> FIRDailyHeartbeatCode {
  540. // This API should not be used by the below tests because the Auth
  541. // SDK uses only the V2 heartbeat API (`flushHeartbeatsIntoPayload`) for
  542. // getting heartbeats.
  543. return FIRDailyHeartbeatCode.none
  544. }
  545. }
  546. /** @fn testRequest_IncludesHeartbeatPayload_WhenHeartbeatsNeedSending
  547. @brief This test checks the behavior of @c postWithRequest:response:callback:
  548. to verify that a heartbeats payload is attached as a header to an
  549. outgoing request when there are stored heartbeats that need sending.
  550. */
  551. func testRequest_IncludesHeartbeatPayload_WhenHeartbeatsNeedSending() throws {
  552. // Given
  553. let fakeHeartbeatLogger = FakeHeartbeatLogger()
  554. let requestConfiguration = AuthRequestConfiguration(apiKey: kFakeAPIKey,
  555. appID: kFakeAppID,
  556. heartbeatLogger: fakeHeartbeatLogger)
  557. let request = FakeRequest(withRequestBody: [:], requestConfiguration: requestConfiguration)
  558. // When
  559. let nonEmptyHeartbeatsPayload = HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload
  560. fakeHeartbeatLogger.onFlushHeartbeatsIntoPayloadHandler = {
  561. nonEmptyHeartbeatsPayload
  562. }
  563. rpcImplementation?.post(with: request) { response, error in
  564. // The callback never happens and it's fine since we only need to verify the request.
  565. XCTFail("Should not be a callback")
  566. }
  567. // Then
  568. let expectedHeader = FIRHeaderValueFromHeartbeatsPayload(
  569. HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload
  570. )
  571. let completeRequest = try XCTUnwrap(rpcIssuer?.completeRequest)
  572. let headerValue = completeRequest.value(forHTTPHeaderField: "X-Firebase-Client")
  573. XCTAssertEqual(headerValue, expectedHeader)
  574. }
  575. #endif
  576. /** @fn testRequest_IncludesAppCheckHeader
  577. @brief This test checks the behavior of @c postWithRequest:response:callback:
  578. to verify that a appCheck token is attached as a header to an
  579. outgoing request.
  580. */
  581. func testRequest_IncludesAppCheckHeader() throws {
  582. // Given
  583. let fakeAppCheck = FakeAppCheck()
  584. let requestConfiguration = AuthRequestConfiguration(apiKey: kFakeAPIKey,
  585. appID: kFakeAppID,
  586. appCheck: fakeAppCheck)
  587. let request = FakeRequest(withRequestBody: [:], requestConfiguration: requestConfiguration)
  588. rpcImplementation?.post(with: request) { response, error in
  589. // The callback never happens and it's fine since we only need to verify the request.
  590. XCTFail("Should not be a callback")
  591. }
  592. let completeRequest = try XCTUnwrap(rpcIssuer?.completeRequest)
  593. let headerValue = completeRequest.value(forHTTPHeaderField: "X-Firebase-AppCheck")
  594. XCTAssertEqual(headerValue, fakeAppCheck.fakeAppCheckToken)
  595. }
  596. // TODO: enable for SPM
  597. #if COCOAPODS
  598. /** @fn testRequest_DoesNotIncludeAHeartbeatPayload_WhenNoHeartbeatsNeedSending
  599. @brief This test checks the behavior of @c postWithRequest:response:callback:
  600. to verify that a request header does not contain heartbeat data in the
  601. case that there are no stored heartbeats that need sending.
  602. */
  603. func testRequest_DoesNotIncludeAHeartbeatPayload_WhenNoHeartbeatsNeedSending() throws {
  604. // Given
  605. let fakeHeartbeatLogger = FakeHeartbeatLogger()
  606. let requestConfiguration = AuthRequestConfiguration(apiKey: kFakeAPIKey,
  607. appID: kFakeAppID,
  608. heartbeatLogger: fakeHeartbeatLogger)
  609. let request = FakeRequest(withRequestBody: [:], requestConfiguration: requestConfiguration)
  610. // When
  611. let emptyHeartbeatsPayload = HeartbeatLoggingTestUtils.emptyHeartbeatsPayload
  612. fakeHeartbeatLogger.onFlushHeartbeatsIntoPayloadHandler = {
  613. emptyHeartbeatsPayload
  614. }
  615. rpcImplementation?.post(with: request) { response, error in
  616. // The callback never happens and it's fine since we only need to verify the request.
  617. }
  618. // Then
  619. let completeRequest = try XCTUnwrap(rpcIssuer?.completeRequest)
  620. XCTAssertNil(completeRequest.value(forHTTPHeaderField: "X-Firebase-Client"))
  621. }
  622. #endif
  623. private class FakeRequest: AuthRPCRequest {
  624. typealias Response = FakeResponse
  625. func requestConfiguration() -> AuthRequestConfiguration {
  626. return configuration
  627. }
  628. let kFakeRequestURL = "https://www.google.com/"
  629. func requestURL() -> URL {
  630. return try! XCTUnwrap(URL(string: kFakeRequestURL))
  631. }
  632. func unencodedHTTPRequestBody() throws -> [String: AnyHashable] {
  633. if let encodingError {
  634. throw encodingError
  635. }
  636. return requestBody
  637. }
  638. static func makeRequestConfiguration() -> AuthRequestConfiguration {
  639. return AuthRequestConfiguration(
  640. apiKey: kFakeAPIKey,
  641. appID: kFakeAppID
  642. )
  643. }
  644. func containsPostBody() -> Bool {
  645. return true
  646. }
  647. var response: FakeResponse
  648. private let configuration: AuthRequestConfiguration
  649. let encodingError: NSError?
  650. let requestBody: [String: AnyHashable]
  651. init(withEncodingError error: NSError) {
  652. encodingError = error
  653. requestBody = [:]
  654. response = FakeResponse()
  655. configuration = FakeRequest.makeRequestConfiguration()
  656. }
  657. init(withDecodingError error: NSError) {
  658. encodingError = nil
  659. requestBody = [:]
  660. response = FakeResponse(withDecodingError: error)
  661. configuration = FakeRequest.makeRequestConfiguration()
  662. }
  663. init(withRequestBody body: [String: AnyHashable],
  664. requestConfiguration: AuthRequestConfiguration = FakeRequest.makeRequestConfiguration()) {
  665. encodingError = nil
  666. requestBody = body
  667. response = FakeResponse()
  668. configuration = requestConfiguration
  669. }
  670. }
  671. private class FakeResponse: AuthRPCResponse {
  672. // TODO: Will this work?
  673. required init() {
  674. decodingError = nil
  675. }
  676. let decodingError: NSError?
  677. var receivedDictionary: [String: AnyHashable] = [:]
  678. init(withDecodingError error: NSError? = nil) {
  679. decodingError = error
  680. }
  681. func setFields(dictionary: [String: AnyHashable]) throws {
  682. if let decodingError {
  683. throw decodingError
  684. }
  685. receivedDictionary = dictionary
  686. }
  687. }
  688. }