UserTests.swift 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971
  1. // Copyright 2021 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 Combine
  16. import XCTest
  17. import FirebaseAuth
  18. class UserTests: XCTestCase {
  19. override class func setUp() {
  20. FirebaseApp.configureForTests()
  21. }
  22. override class func tearDown() {
  23. FirebaseApp.app()?.delete { success in
  24. if success {
  25. print("Shut down app successfully.")
  26. } else {
  27. print("💥 There was a problem when shutting down the app..")
  28. }
  29. }
  30. }
  31. override func setUp() {
  32. do {
  33. try Auth.auth().signOut()
  34. } catch {}
  35. }
  36. fileprivate struct ProviderCredentials {
  37. var providerID: String
  38. var federatedID: String
  39. var displayName: String
  40. var idToken: String?
  41. var accessToken: String
  42. var email: String
  43. var localID: String
  44. var phoneNumber: String? = nil
  45. var userInfo: [String: String]? = [:]
  46. }
  47. fileprivate static let continueURL = "continueURL"
  48. fileprivate static let fakeOOBCode = "testoobcode"
  49. fileprivate static let phoneNumber = "12345658"
  50. fileprivate static let verificationID = "55432"
  51. fileprivate static let verificationCode = "12345678"
  52. fileprivate static let accessTokenTimeToLive: TimeInterval = 60 * 60
  53. fileprivate static let approximateExpirationDate: TimeInterval = 60 * 60
  54. fileprivate static let refreshToken = "REFRESH_TOKEN"
  55. fileprivate static let accessToken = "ACCESS_TOKEN"
  56. fileprivate static let newAccessToken = "NewAccessToken"
  57. fileprivate static let email = "user@company.com"
  58. fileprivate static let password = "!@#$%^"
  59. fileprivate static let passwordHash = "UkVEQUNURUQ="
  60. fileprivate static let userName = "User Doe"
  61. fileprivate static let localID = "localId"
  62. fileprivate static let googleEmail = "user@gmail.com"
  63. fileprivate static let googleProfile: [String: String] = {
  64. [
  65. "iss": "https://accounts.google.com\\",
  66. "email": googleEmail,
  67. "given_name": "User",
  68. "family_name": "Doe",
  69. ]
  70. }()
  71. class MockGetAccountInfoResponse: FIRGetAccountInfoResponse {
  72. fileprivate var providerCredentials: ProviderCredentials!
  73. override var users: [FIRGetAccountInfoResponseUser] {
  74. let response = MockGetAccountInfoResponseUser(dictionary: [:])
  75. response.providerCredentials = providerCredentials
  76. return [response]
  77. }
  78. }
  79. class MockSetAccountInfoResponse: FIRSetAccountInfoResponse {}
  80. class MockSecureAccessResponse: FIRSecureTokenResponse {
  81. override var accessToken: String { return "ACCESS_TOKEN" }
  82. }
  83. class MockVerifyPhoneNumberResponse: FIRVerifyPhoneNumberResponse {
  84. fileprivate var providerCredentials: ProviderCredentials!
  85. override var phoneNumber: String? { providerCredentials.phoneNumber }
  86. }
  87. class MockGetAccountInfoResponseUser: FIRGetAccountInfoResponseUser {
  88. fileprivate var providerCredentials: ProviderCredentials!
  89. override var localID: String { providerCredentials.localID }
  90. override var email: String { providerCredentials.email }
  91. override var displayName: String { providerCredentials.displayName }
  92. override var passwordHash: String? { UserTests.passwordHash }
  93. override var phoneNumber: String? { providerCredentials.phoneNumber }
  94. override var providerUserInfo: [FIRGetAccountInfoResponseProviderUserInfo]? {
  95. guard let userInfo = providerCredentials.userInfo else {
  96. return nil
  97. }
  98. let response = MockGetAccountInfoResponseProviderUserInfo(dictionary: userInfo)
  99. response.providerCredentials = providerCredentials
  100. return [response]
  101. }
  102. }
  103. class MockVerifyAssertionResponse: FIRVerifyAssertionResponse {
  104. fileprivate var providerCredentials: ProviderCredentials!
  105. override var localID: String? { providerCredentials.localID }
  106. override var federatedID: String? { providerCredentials.federatedID }
  107. override var providerID: String? { providerCredentials.providerID }
  108. override var displayName: String? { providerCredentials.displayName }
  109. override var profile: [String: NSObject]? { googleProfile as [String: NSString] }
  110. override var username: String? { userName }
  111. override var idToken: String? { providerCredentials.accessToken }
  112. override var refreshToken: String? { UserTests.refreshToken }
  113. override var approximateExpirationDate: Date? {
  114. Date(timeIntervalSinceNow: accessTokenTimeToLive)
  115. }
  116. }
  117. class MockVerifyPasswordResponse: FIRVerifyPasswordResponse {
  118. fileprivate var providerCredentials: ProviderCredentials!
  119. override var refreshToken: String { UserTests.refreshToken }
  120. override var email: String { providerCredentials.email }
  121. override var idToken: String { providerCredentials.accessToken }
  122. override var approximateExpirationDate: Date {
  123. Date(timeIntervalSinceNow: UserTests.accessTokenTimeToLive)
  124. }
  125. }
  126. class MockGetAccountInfoResponseProviderUserInfo: FIRGetAccountInfoResponseProviderUserInfo {
  127. fileprivate var providerCredentials: ProviderCredentials!
  128. override var providerID: String? { providerCredentials.providerID }
  129. override var displayName: String? { providerCredentials.displayName }
  130. override var federatedID: String? { providerCredentials.federatedID }
  131. override var email: String? { googleEmail }
  132. }
  133. class MockAuthBackend: AuthBackendImplementationMock {
  134. fileprivate var providerCredentials: ProviderCredentials!
  135. var verifyAssertionCallback: Result<MockVerifyAssertionResponse, Error> =
  136. .success(MockVerifyAssertionResponse())
  137. override func verifyAssertion(_ request: FIRVerifyAssertionRequest,
  138. callback: @escaping FIRVerifyAssertionResponseCallback) {
  139. XCTAssertEqual(request.apiKey, Credentials.apiKey)
  140. XCTAssertEqual(request.providerID, providerCredentials.providerID)
  141. XCTAssertEqual(request.providerIDToken, providerCredentials.idToken)
  142. XCTAssertEqual(request.providerAccessToken, providerCredentials.accessToken)
  143. XCTAssertTrue(request.returnSecureToken)
  144. switch verifyAssertionCallback {
  145. case let .success(response):
  146. response.providerCredentials = providerCredentials
  147. callback(response, nil)
  148. case let .failure(error):
  149. callback(nil, error)
  150. }
  151. }
  152. override func verifyPassword(_ request: FIRVerifyPasswordRequest,
  153. callback: @escaping FIRVerifyPasswordResponseCallback) {
  154. let response = MockVerifyPasswordResponse()
  155. response.providerCredentials = providerCredentials
  156. callback(response, nil)
  157. }
  158. override func getAccountInfo(_ request: FIRGetAccountInfoRequest,
  159. callback: @escaping FIRGetAccountInfoResponseCallback) {
  160. XCTAssertEqual(request.apiKey, Credentials.apiKey)
  161. XCTAssertEqual(request.accessToken, providerCredentials.accessToken)
  162. let response = MockGetAccountInfoResponse()
  163. response.providerCredentials = providerCredentials
  164. callback(response, nil)
  165. }
  166. override func setAccountInfo(_ request: FIRSetAccountInfoRequest,
  167. callback: @escaping FIRSetAccountInfoResponseCallback) {
  168. XCTAssertEqual(request.apiKey, Credentials.apiKey)
  169. XCTAssertEqual(request.accessToken, providerCredentials.accessToken)
  170. XCTAssertNotNil(request.deleteProviders)
  171. XCTAssertNil(request.email)
  172. XCTAssertNil(request.localID)
  173. XCTAssertNil(request.displayName)
  174. XCTAssertNil(request.photoURL)
  175. XCTAssertNil(request.password)
  176. XCTAssertNil(request.providers)
  177. XCTAssertNil(request.deleteAttributes)
  178. callback(MockSetAccountInfoResponse(), nil)
  179. }
  180. override func verifyPhoneNumber(_ request: FIRVerifyPhoneNumberRequest,
  181. callback: @escaping FIRVerifyPhoneNumberResponseCallback) {
  182. XCTAssertEqual(request.verificationID, UserTests.verificationID)
  183. XCTAssertEqual(request.verificationCode, UserTests.verificationCode)
  184. XCTAssertEqual(request.operation, FIRAuthOperationType.link)
  185. XCTAssertEqual(request.accessToken, providerCredentials.accessToken)
  186. let response = MockVerifyPhoneNumberResponse()
  187. response.providerCredentials = providerCredentials
  188. callback(response, nil)
  189. }
  190. var actionCodeSettings: ActionCodeSettings?
  191. var getOOBConfirmationCodeError: Error?
  192. override func getOOBConfirmationCode(_ request: FIRGetOOBConfirmationCodeRequest,
  193. callback: @escaping FIRGetOOBConfirmationCodeResponseCallback) {
  194. XCTAssertEqual(request.accessToken, UserTests.accessToken)
  195. XCTAssertEqual(request.continueURL, actionCodeSettings?.url?.absoluteString)
  196. XCTAssertEqual(request.dynamicLinkDomain, actionCodeSettings?.dynamicLinkDomain)
  197. if let error = getOOBConfirmationCodeError {
  198. callback(nil, error)
  199. } else {
  200. callback(MockGetOOBConfirmationCodeResponse(), nil)
  201. }
  202. }
  203. override func secureToken(_ request: FIRSecureTokenRequest,
  204. callback: @escaping FIRSecureTokenResponseCallback) {
  205. callback(MockSecureAccessResponse(), nil)
  206. }
  207. }
  208. class MockGetOOBConfirmationCodeResponse: FIRGetOOBConfirmationCodeResponse {
  209. override var oobCode: String { return UserTests.fakeOOBCode }
  210. }
  211. func testlinkAndRetrieveDataSuccess() {
  212. let facebookCredentials = ProviderCredentials(
  213. providerID: FacebookAuthProviderID,
  214. federatedID: "FACEBOOK_ID",
  215. displayName: "Facebook Doe",
  216. idToken: nil,
  217. accessToken: "FACEBOOK_ACCESS_TOKEN",
  218. email: UserTests.email,
  219. localID: UserTests.localID
  220. )
  221. let authBackend = MockAuthBackend()
  222. authBackend.providerCredentials = facebookCredentials
  223. FIRAuthBackend.setBackendImplementation(authBackend)
  224. var cancellables = Set<AnyCancellable>()
  225. let userSignInExpectation = expectation(description: "User associated from a third-party")
  226. let facebookCredential = FacebookAuthProvider
  227. .credential(withAccessToken: facebookCredentials.accessToken)
  228. // when
  229. Auth.auth()
  230. .signIn(with: facebookCredential)
  231. .flatMap { [weak authBackend] authResult -> Future<AuthDataResult, Error> in
  232. XCTAssertEqual(authResult.additionalUserInfo?.profile,
  233. UserTests.googleProfile as [String: NSString])
  234. XCTAssertEqual(authResult.additionalUserInfo?.username,
  235. UserTests.userName)
  236. XCTAssertEqual(authResult.additionalUserInfo?.providerID,
  237. FacebookAuthProviderID)
  238. let googleCredentials = ProviderCredentials(
  239. providerID: GoogleAuthProviderID,
  240. federatedID: "GOOGLE_ID",
  241. displayName: "Google Doe",
  242. idToken: "GOOGLE_ID_TOKEN",
  243. accessToken: "GOOGLE_ACCESS_TOKEN",
  244. email: UserTests.googleEmail,
  245. localID: UserTests.localID,
  246. phoneNumber: nil,
  247. userInfo: [:]
  248. )
  249. authBackend?.providerCredentials = googleCredentials
  250. let linkGoogleCredential = GoogleAuthProvider.credential(
  251. withIDToken: googleCredentials.idToken!,
  252. accessToken: googleCredentials.accessToken
  253. )
  254. return authResult.user
  255. .link(with: linkGoogleCredential)
  256. }
  257. .sink { completion in
  258. switch completion {
  259. case .finished:
  260. print("Finished")
  261. case let .failure(error):
  262. XCTFail("💥 Something went wrong: \(error)")
  263. }
  264. } receiveValue: { linkAuthResult in
  265. let user = linkAuthResult.user
  266. // Verify that the current user and reauthenticated user are same pointers.
  267. XCTAssertEqual(Auth.auth().currentUser, user)
  268. XCTAssertEqual(linkAuthResult.additionalUserInfo?.profile,
  269. UserTests.googleProfile as [String: NSString])
  270. XCTAssertEqual(linkAuthResult.additionalUserInfo?.username,
  271. UserTests.userName)
  272. XCTAssertEqual(linkAuthResult.additionalUserInfo?.providerID,
  273. GoogleAuthProviderID)
  274. userSignInExpectation.fulfill()
  275. }
  276. .store(in: &cancellables)
  277. // then
  278. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  279. }
  280. func testLinkAndRetrieveDataError() {
  281. let facebookCredentials = ProviderCredentials(
  282. providerID: FacebookAuthProviderID,
  283. federatedID: "FACEBOOK_ID",
  284. displayName: "Facebook Doe",
  285. idToken: nil,
  286. accessToken: "FACEBOOK_ACCESS_TOKEN",
  287. email: UserTests.email,
  288. localID: UserTests.localID
  289. )
  290. let authBackend = MockAuthBackend()
  291. authBackend.providerCredentials = facebookCredentials
  292. FIRAuthBackend.setBackendImplementation(authBackend)
  293. var cancellables = Set<AnyCancellable>()
  294. let userSignInExpectation = expectation(description: "User associated from a third-party")
  295. let facebookCredential = FacebookAuthProvider
  296. .credential(withAccessToken: facebookCredentials.accessToken)
  297. // when
  298. Auth.auth()
  299. .signIn(with: facebookCredential)
  300. .flatMap { [weak authBackend] authResult -> Future<AuthDataResult, Error> in
  301. XCTAssertEqual(authResult.additionalUserInfo?.profile,
  302. UserTests.googleProfile as [String: NSString])
  303. XCTAssertEqual(authResult.additionalUserInfo?.username,
  304. UserTests.userName)
  305. XCTAssertEqual(authResult.additionalUserInfo?.providerID,
  306. FacebookAuthProviderID)
  307. XCTAssertEqual(Auth.auth().currentUser, authResult.user)
  308. let googleCredentials = ProviderCredentials(
  309. providerID: GoogleAuthProviderID,
  310. federatedID: "GOOGLE_ID",
  311. displayName: "Google Doe",
  312. idToken: "GOOGLE_ID_TOKEN",
  313. accessToken: "GOOGLE_ACCESS_TOKEN",
  314. email: UserTests.googleEmail,
  315. localID: UserTests.localID,
  316. phoneNumber: nil,
  317. userInfo: [:]
  318. )
  319. authBackend?.providerCredentials = googleCredentials
  320. authBackend?
  321. .verifyAssertionCallback = .failure(FIRAuthErrorUtils
  322. .accountExistsWithDifferentCredentialError(
  323. withEmail: UserTests.userName,
  324. updatedCredential: nil
  325. ))
  326. let linkGoogleCredential = GoogleAuthProvider.credential(
  327. withIDToken: googleCredentials.idToken!,
  328. accessToken: googleCredentials.accessToken
  329. )
  330. return authResult.user
  331. .link(with: linkGoogleCredential)
  332. }
  333. .sink { completion in
  334. if case let .failure(error as NSError) = completion {
  335. XCTAssertEqual(
  336. error.code,
  337. AuthErrorCode.accountExistsWithDifferentCredential.rawValue
  338. )
  339. userSignInExpectation.fulfill()
  340. }
  341. } receiveValue: { linkAuthResult in
  342. XCTFail("💥 result unexpected")
  343. }
  344. .store(in: &cancellables)
  345. // then
  346. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  347. }
  348. func testLinkAndRetrieveDataProviderAlreadyLinked() {
  349. let facebookCredentials = ProviderCredentials(
  350. providerID: FacebookAuthProviderID,
  351. federatedID: "FACEBOOK_ID",
  352. displayName: "Facebook Doe",
  353. idToken: nil,
  354. accessToken: "FACEBOOK_ACCESS_TOKEN",
  355. email: UserTests.email,
  356. localID: UserTests.localID
  357. )
  358. let authBackend = MockAuthBackend()
  359. authBackend.providerCredentials = facebookCredentials
  360. FIRAuthBackend.setBackendImplementation(authBackend)
  361. var cancellables = Set<AnyCancellable>()
  362. let userSignInExpectation = expectation(description: "User associated from a third-party")
  363. let facebookCredential = FacebookAuthProvider
  364. .credential(withAccessToken: facebookCredentials.accessToken)
  365. // when
  366. Auth.auth()
  367. .signIn(with: facebookCredential)
  368. .flatMap { authResult -> Future<AuthDataResult, Error> in
  369. XCTAssertEqual(authResult.additionalUserInfo?.profile,
  370. UserTests.googleProfile as [String: NSString])
  371. XCTAssertEqual(authResult.additionalUserInfo?.username,
  372. UserTests.userName)
  373. XCTAssertEqual(authResult.additionalUserInfo?.providerID,
  374. FacebookAuthProviderID)
  375. XCTAssertEqual(Auth.auth().currentUser, authResult.user)
  376. authBackend.providerCredentials = facebookCredentials
  377. authBackend
  378. .verifyAssertionCallback = .failure(FIRAuthErrorUtils
  379. .accountExistsWithDifferentCredentialError(
  380. withEmail: UserTests.userName,
  381. updatedCredential: nil
  382. ))
  383. let linkFacebookCredential = FacebookAuthProvider
  384. .credential(withAccessToken: facebookCredentials.accessToken)
  385. return authResult.user
  386. .link(with: linkFacebookCredential)
  387. }
  388. .sink { completion in
  389. if case let .failure(error as NSError) = completion {
  390. XCTAssertEqual(error.code, AuthErrorCode.providerAlreadyLinked.rawValue)
  391. userSignInExpectation.fulfill()
  392. }
  393. } receiveValue: { linkAuthResult in
  394. XCTFail("💥 result unexpected")
  395. }
  396. .store(in: &cancellables)
  397. // then
  398. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  399. }
  400. func testLinkAndRetrieveDataErrorAutoSignOut() {
  401. let facebookCredentials = ProviderCredentials(
  402. providerID: FacebookAuthProviderID,
  403. federatedID: "FACEBOOK_ID",
  404. displayName: "Facebook Doe",
  405. idToken: nil,
  406. accessToken: "FACEBOOK_ACCESS_TOKEN",
  407. email: UserTests.email,
  408. localID: UserTests.localID
  409. )
  410. let authBackend = MockAuthBackend()
  411. authBackend.providerCredentials = facebookCredentials
  412. FIRAuthBackend.setBackendImplementation(authBackend)
  413. var cancellables = Set<AnyCancellable>()
  414. let userSignInExpectation = expectation(description: "User associated from a third-party")
  415. let facebookCredential = FacebookAuthProvider
  416. .credential(withAccessToken: facebookCredentials.accessToken)
  417. // when
  418. Auth.auth()
  419. .signIn(with: facebookCredential)
  420. .flatMap { [weak authBackend] authResult -> Future<AuthDataResult, Error> in
  421. XCTAssertEqual(authResult.additionalUserInfo?.profile,
  422. UserTests.googleProfile as [String: NSString])
  423. XCTAssertEqual(authResult.additionalUserInfo?.username,
  424. UserTests.userName)
  425. XCTAssertEqual(authResult.additionalUserInfo?.providerID,
  426. FacebookAuthProviderID)
  427. XCTAssertEqual(Auth.auth().currentUser, authResult.user)
  428. let googleCredentials = ProviderCredentials(
  429. providerID: GoogleAuthProviderID,
  430. federatedID: "GOOGLE_ID",
  431. displayName: "Google Doe",
  432. idToken: "GOOGLE_ID_TOKEN",
  433. accessToken: "GOOGLE_ACCESS_TOKEN",
  434. email: UserTests.googleEmail,
  435. localID: UserTests.localID,
  436. phoneNumber: nil,
  437. userInfo: [:]
  438. )
  439. authBackend?.providerCredentials = googleCredentials
  440. authBackend?
  441. .verifyAssertionCallback = .failure(FIRAuthErrorUtils
  442. .userDisabledError(withMessage: nil))
  443. let linkGoogleCredential = GoogleAuthProvider.credential(
  444. withIDToken: googleCredentials.idToken!,
  445. accessToken: googleCredentials.accessToken
  446. )
  447. return authResult.user
  448. .link(with: linkGoogleCredential)
  449. }
  450. .sink { completion in
  451. if case let .failure(error as NSError) = completion {
  452. XCTAssertEqual(error.code, AuthErrorCode.userDisabled.rawValue)
  453. userSignInExpectation.fulfill()
  454. }
  455. } receiveValue: { linkAuthResult in
  456. XCTFail("💥 result unexpected")
  457. }
  458. .store(in: &cancellables)
  459. // then
  460. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  461. }
  462. func testReauthenticateSuccess() {
  463. // given
  464. let emailCredentials = ProviderCredentials(
  465. providerID: EmailAuthProviderID,
  466. federatedID: "EMAIL_ID",
  467. displayName: "Google Doe",
  468. idToken: nil,
  469. accessToken: UserTests.accessToken,
  470. email: UserTests.email,
  471. localID: UserTests.localID
  472. )
  473. let authBackend = MockAuthBackend()
  474. authBackend.providerCredentials = emailCredentials
  475. FIRAuthBackend.setBackendImplementation(authBackend)
  476. var cancellables = Set<AnyCancellable>()
  477. let userReauthenticateExpectation = expectation(description: "User reauthenticated")
  478. // when
  479. Auth.auth()
  480. .signIn(withEmail: UserTests.email, password: UserTests.password)
  481. .flatMap { authResult -> Future<AuthDataResult, Error> in
  482. let credential = EmailAuthProvider.credential(
  483. withEmail: UserTests.email,
  484. password: UserTests.password
  485. )
  486. authBackend.providerCredentials.accessToken = UserTests.newAccessToken
  487. return authResult.user
  488. .reauthenticate(with: credential)
  489. }
  490. .sink { completion in
  491. switch completion {
  492. case .finished:
  493. print("Finished")
  494. case let .failure(error):
  495. XCTFail("💥 Something went wrong: \(error)")
  496. }
  497. } receiveValue: { authResult in
  498. let user = authResult.user
  499. XCTAssertEqual(user.displayName, emailCredentials.displayName)
  500. XCTAssertEqual(user.email, emailCredentials.email)
  501. userReauthenticateExpectation.fulfill()
  502. }
  503. .store(in: &cancellables)
  504. // then
  505. wait(for: [userReauthenticateExpectation], timeout: expectationTimeout)
  506. }
  507. func testReauthenticateWithCredentialSuccess() {
  508. // given
  509. let googleCredentials = ProviderCredentials(
  510. providerID: GoogleAuthProviderID,
  511. federatedID: "GOOGLE_ID",
  512. displayName: "Google Doe",
  513. idToken: "GOOGLE_ID_TOKEN",
  514. accessToken: "GOOGLE_ACCESS_TOKEN",
  515. email: UserTests.googleEmail,
  516. localID: UserTests.localID
  517. )
  518. let authBackend = MockAuthBackend()
  519. authBackend.providerCredentials = googleCredentials
  520. FIRAuthBackend.setBackendImplementation(authBackend)
  521. var cancellables = Set<AnyCancellable>()
  522. let userReauthenticateExpectation = expectation(description: "User reauthenticated")
  523. let googleCredential = GoogleAuthProvider.credential(
  524. withIDToken: googleCredentials.idToken!,
  525. accessToken: googleCredentials.accessToken
  526. )
  527. // when
  528. Auth.auth()
  529. .signIn(with: googleCredential)
  530. .flatMap { authResult -> Future<AuthDataResult, Error> in
  531. XCTAssertEqual(authResult.additionalUserInfo?.profile,
  532. UserTests.googleProfile as [String: NSString])
  533. XCTAssertEqual(authResult.additionalUserInfo?.username,
  534. UserTests.userName)
  535. XCTAssertEqual(authResult.additionalUserInfo?.providerID,
  536. GoogleAuthProviderID)
  537. return authResult.user
  538. .reauthenticate(with: googleCredential)
  539. }
  540. .sink { completion in
  541. switch completion {
  542. case .finished:
  543. print("Finished")
  544. case let .failure(error):
  545. XCTFail("💥 Something went wrong: \(error)")
  546. }
  547. } receiveValue: { authResult in
  548. XCTAssertEqual(authResult.additionalUserInfo?.profile,
  549. UserTests.googleProfile as [String: NSString])
  550. XCTAssertEqual(authResult.additionalUserInfo?.username,
  551. UserTests.userName)
  552. XCTAssertEqual(authResult.additionalUserInfo?.providerID,
  553. GoogleAuthProviderID)
  554. userReauthenticateExpectation.fulfill()
  555. }
  556. .store(in: &cancellables)
  557. // then
  558. wait(for: [userReauthenticateExpectation], timeout: expectationTimeout)
  559. }
  560. func testReauthenticateFailure() {
  561. // given
  562. let emailCredentials = ProviderCredentials(
  563. providerID: EmailAuthProviderID,
  564. federatedID: "EMAIL_ID",
  565. displayName: "Google Doe",
  566. idToken: nil,
  567. accessToken: UserTests.accessToken,
  568. email: UserTests.email,
  569. localID: UserTests.localID
  570. )
  571. let authBackend = MockAuthBackend()
  572. authBackend.providerCredentials = emailCredentials
  573. FIRAuthBackend.setBackendImplementation(authBackend)
  574. var cancellables = Set<AnyCancellable>()
  575. let userReauthenticateExpectation = expectation(description: "User reauthenticated")
  576. // when
  577. Auth.auth()
  578. .signIn(withEmail: UserTests.email, password: UserTests.password)
  579. .flatMap { authResult -> Future<AuthDataResult, Error> in
  580. authBackend.providerCredentials.displayName = "New User Doe"
  581. authBackend.providerCredentials.email = "newEmail"
  582. authBackend.providerCredentials.localID = "ANOTHER_LOCAL_ID"
  583. authBackend.providerCredentials.accessToken = "NewAccessToken"
  584. let credential = EmailAuthProvider.credential(
  585. withEmail: UserTests.email,
  586. password: UserTests.password
  587. )
  588. return authResult.user
  589. .reauthenticate(with: credential)
  590. }
  591. .sink { completion in
  592. if case let .failure(error as NSError) = completion {
  593. // Verify user mismatch error.
  594. XCTAssertEqual(error.code, AuthErrorCode.userMismatch.rawValue)
  595. userReauthenticateExpectation.fulfill()
  596. }
  597. } receiveValue: { authResult in
  598. XCTFail("💥 result unexpected")
  599. }
  600. .store(in: &cancellables)
  601. // then
  602. wait(for: [userReauthenticateExpectation], timeout: expectationTimeout)
  603. }
  604. func testReauthenticateUserMismatchFailure() {
  605. // given
  606. let emailCredentials = ProviderCredentials(
  607. providerID: EmailAuthProviderID,
  608. federatedID: "EMAIL_ID",
  609. displayName: "Google Doe",
  610. idToken: nil,
  611. accessToken: UserTests.accessToken,
  612. email: UserTests.email,
  613. localID: UserTests.localID
  614. )
  615. let authBackend = MockAuthBackend()
  616. authBackend.providerCredentials = emailCredentials
  617. FIRAuthBackend.setBackendImplementation(authBackend)
  618. var cancellables = Set<AnyCancellable>()
  619. let userReauthenticateExpectation = expectation(description: "User reauthenticated")
  620. // when
  621. Auth.auth()
  622. .signIn(withEmail: UserTests.email, password: UserTests.password)
  623. .flatMap { authResult -> Future<AuthDataResult, Error> in
  624. let googleCredentials = ProviderCredentials(
  625. providerID: GoogleAuthProviderID,
  626. federatedID: "GOOGLE_ID",
  627. displayName: "Google Doe",
  628. idToken: "GOOGLE_ID_TOKEN",
  629. accessToken: "GOOGLE_ACCESS_TOKEN",
  630. email: UserTests.googleEmail,
  631. localID: UserTests.localID,
  632. phoneNumber: nil,
  633. userInfo: [:]
  634. )
  635. authBackend.providerCredentials = googleCredentials
  636. authBackend
  637. .verifyAssertionCallback = .failure(FIRAuthErrorUtils
  638. .userNotFoundError(withMessage: nil))
  639. let googleCredential = GoogleAuthProvider.credential(
  640. withIDToken: "GOOGLE_ID_TOKEN",
  641. accessToken: "GOOGLE_ACCESS_TOKEN"
  642. )
  643. return authResult.user
  644. .reauthenticate(with: googleCredential)
  645. }
  646. .sink { completion in
  647. if case let .failure(error as NSError) = completion {
  648. // Verify user mismatch error.
  649. XCTAssertEqual(error.code, AuthErrorCode.userMismatch.rawValue)
  650. userReauthenticateExpectation.fulfill()
  651. }
  652. } receiveValue: { authResult in
  653. XCTFail("💥 result unexpected")
  654. }
  655. .store(in: &cancellables)
  656. // then
  657. wait(for: [userReauthenticateExpectation], timeout: expectationTimeout)
  658. }
  659. func testUnlinkPhoneAuthCredentialSuccess() {
  660. // given
  661. let emailCredentials = ProviderCredentials(
  662. providerID: PhoneAuthProviderID,
  663. federatedID: "EMAIL_ID",
  664. displayName: "Google Doe",
  665. idToken: nil,
  666. accessToken: UserTests.accessToken,
  667. email: UserTests.email,
  668. localID: UserTests.localID,
  669. userInfo: nil
  670. )
  671. let authBackend = MockAuthBackend()
  672. authBackend.providerCredentials = emailCredentials
  673. FIRAuthBackend.setBackendImplementation(authBackend)
  674. var cancellables = Set<AnyCancellable>()
  675. let userUnlinkedExpectation = expectation(description: "User Unlinked")
  676. // when
  677. Auth.auth()
  678. .signIn(withEmail: UserTests.email, password: UserTests.password)
  679. .flatMap { authResult -> Future<AuthDataResult, Error> in
  680. authBackend.providerCredentials.phoneNumber = Self.phoneNumber
  681. authBackend.providerCredentials.userInfo = ["providerId": PhoneAuthProviderID]
  682. let credential = PhoneAuthProvider.provider()
  683. .credential(withVerificationID: Self.verificationID,
  684. verificationCode: Self.verificationCode)
  685. return authResult.user
  686. .link(with: credential)
  687. }
  688. .flatMap { authResult -> AnyPublisher<User, Error> in
  689. XCTAssertEqual(
  690. Auth.auth().currentUser?.providerData.first?.providerID,
  691. PhoneAuthProviderID
  692. )
  693. XCTAssertEqual(Auth.auth().currentUser?.phoneNumber, Self.phoneNumber)
  694. return authResult.user
  695. .unlink(fromProvider: PhoneAuthProviderID)
  696. .eraseToAnyPublisher()
  697. }
  698. .sink { completion in
  699. switch completion {
  700. case .finished:
  701. print("Finished")
  702. case let .failure(error):
  703. XCTFail("💥 Something went wrong: \(error)")
  704. }
  705. } receiveValue: { user in
  706. XCTAssertNil(Auth.auth().currentUser?.phoneNumber)
  707. userUnlinkedExpectation.fulfill()
  708. }
  709. .store(in: &cancellables)
  710. // then
  711. wait(for: [userUnlinkedExpectation], timeout: expectationTimeout)
  712. }
  713. func testSendVerificationEmailSuccess() {
  714. // given
  715. let emailCredentials = ProviderCredentials(
  716. providerID: PhoneAuthProviderID,
  717. federatedID: "EMAIL_ID",
  718. displayName: "Google Doe",
  719. idToken: nil,
  720. accessToken: UserTests.accessToken,
  721. email: UserTests.email,
  722. localID: UserTests.localID
  723. )
  724. let authBackend = MockAuthBackend()
  725. authBackend.providerCredentials = emailCredentials
  726. FIRAuthBackend.setBackendImplementation(authBackend)
  727. var cancellables = Set<AnyCancellable>()
  728. let sentVerificationEmailExpectation = expectation(description: "Sent verification email")
  729. // when
  730. Auth.auth()
  731. .signIn(withEmail: UserTests.email, password: UserTests.password)
  732. .flatMap { authResult -> Future<Void, Error> in
  733. XCTAssertNotNil(authResult.user)
  734. return authResult.user
  735. .sendEmailVerification()
  736. }
  737. .sink { completion in
  738. switch completion {
  739. case .finished:
  740. print("Finished")
  741. case let .failure(error):
  742. XCTFail("💥 Something went wrong: \(error)")
  743. }
  744. } receiveValue: { _ in
  745. sentVerificationEmailExpectation.fulfill()
  746. }
  747. .store(in: &cancellables)
  748. // then
  749. wait(for: [sentVerificationEmailExpectation], timeout: expectationTimeout)
  750. }
  751. func testSendVerificationEmailWithActionCodeSettingsSuccess() {
  752. // given
  753. let emailCredentials = ProviderCredentials(
  754. providerID: PhoneAuthProviderID,
  755. federatedID: "EMAIL_ID",
  756. displayName: "Google Doe",
  757. idToken: nil,
  758. accessToken: UserTests.accessToken,
  759. email: UserTests.email,
  760. localID: UserTests.localID
  761. )
  762. let authBackend = MockAuthBackend()
  763. authBackend.providerCredentials = emailCredentials
  764. FIRAuthBackend.setBackendImplementation(authBackend)
  765. var cancellables = Set<AnyCancellable>()
  766. let sentVerificationEmailExpectation = expectation(description: "Sent verification email")
  767. // when
  768. Auth.auth()
  769. .signIn(withEmail: UserTests.email, password: UserTests.password)
  770. .flatMap { authResult -> Future<Void, Error> in
  771. XCTAssertNotNil(authResult.user)
  772. let actionCodeSettings = ActionCodeSettings()
  773. actionCodeSettings.url = URL(string: UserTests.continueURL)
  774. actionCodeSettings.dynamicLinkDomain = "example.page.link"
  775. authBackend.actionCodeSettings = actionCodeSettings
  776. return authResult.user
  777. .sendEmailVerification(with: actionCodeSettings)
  778. }
  779. .sink { completion in
  780. switch completion {
  781. case .finished:
  782. print("Finished")
  783. case let .failure(error):
  784. XCTFail("💥 Something went wrong: \(error)")
  785. }
  786. } receiveValue: { _ in
  787. sentVerificationEmailExpectation.fulfill()
  788. }
  789. .store(in: &cancellables)
  790. // then
  791. wait(for: [sentVerificationEmailExpectation], timeout: expectationTimeout)
  792. }
  793. func testSendVerificationEmailInvalidRecipientEmail() {
  794. // given
  795. let emailCredentials = ProviderCredentials(
  796. providerID: PhoneAuthProviderID,
  797. federatedID: "EMAIL_ID",
  798. displayName: "Google Doe",
  799. idToken: nil,
  800. accessToken: UserTests.accessToken,
  801. email: UserTests.email,
  802. localID: UserTests.localID
  803. )
  804. let authBackend = MockAuthBackend()
  805. authBackend.providerCredentials = emailCredentials
  806. authBackend.getOOBConfirmationCodeError = FIRAuthErrorUtils
  807. .invalidRecipientEmailError(withMessage: nil)
  808. FIRAuthBackend.setBackendImplementation(authBackend)
  809. var cancellables = Set<AnyCancellable>()
  810. let sentVerificationEmailExpectation = expectation(description: "Sent verification email")
  811. // when
  812. Auth.auth()
  813. .signIn(withEmail: UserTests.email, password: UserTests.password)
  814. .flatMap { authResult -> Future<Void, Error> in
  815. XCTAssertNotNil(authResult.user)
  816. return authResult.user
  817. .sendEmailVerification()
  818. }
  819. .sink { completion in
  820. if case let .failure(error as NSError) = completion {
  821. // Verify user mismatch error.
  822. XCTAssertEqual(error.code, AuthErrorCode.invalidRecipientEmail.rawValue)
  823. sentVerificationEmailExpectation.fulfill()
  824. }
  825. } receiveValue: { authResult in
  826. XCTFail("💥 result unexpected")
  827. }
  828. .store(in: &cancellables)
  829. // then
  830. wait(for: [sentVerificationEmailExpectation], timeout: expectationTimeout)
  831. }
  832. }