UserTests.swift 34 KB

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