UserTests.swift 34 KB

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