AuthStateDidChangePublisherTests.swift 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. // Copyright 2020 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 FirebaseCore
  16. import FirebaseAuth
  17. import FirebaseCombineSwift
  18. import Combine
  19. import XCTest
  20. class AuthStateDidChangePublisherTests: XCTestCase {
  21. static let apiKey = Credentials.apiKey
  22. static let accessTokenTimeToLive: TimeInterval = 60 * 60
  23. static let refreshToken = "REFRESH_TOKEN"
  24. static let accessToken = "ACCESS_TOKEN"
  25. static let email = "johnnyappleseed@apple.com"
  26. static let password = "secret"
  27. static let localID = "LOCAL_ID"
  28. static let displayName = "Johnny Appleseed"
  29. static let passwordHash = "UkVEQUNURUQ="
  30. class MockSignUpNewUserResponse: FIRSignUpNewUserResponse {
  31. override var idToken: String { return AuthStateDidChangePublisherTests.accessToken }
  32. override var refreshToken: String { return AuthStateDidChangePublisherTests.refreshToken }
  33. override var approximateExpirationDate: Date {
  34. Date(timeIntervalSinceNow: AuthStateDidChangePublisherTests.accessTokenTimeToLive)
  35. }
  36. }
  37. class MockGetAccountInfoResponseUser: FIRGetAccountInfoResponseUser {
  38. override var localID: String { return AuthStateDidChangePublisherTests.localID }
  39. override var email: String { return AuthStateDidChangePublisherTests.email }
  40. override var displayName: String { return AuthStateDidChangePublisherTests.displayName }
  41. }
  42. class MockGetAccountInfoResponse: FIRGetAccountInfoResponse {
  43. override var users: [FIRGetAccountInfoResponseUser] {
  44. return [MockGetAccountInfoResponseUser(dictionary: [:])]
  45. }
  46. }
  47. class MockVerifyPasswordResponse: FIRVerifyPasswordResponse {
  48. override var localID: String { return AuthStateDidChangePublisherTests.localID }
  49. override var email: String { return AuthStateDidChangePublisherTests.email }
  50. override var displayName: String { return AuthStateDidChangePublisherTests.displayName }
  51. override var idToken: String { return AuthStateDidChangePublisherTests.accessToken }
  52. override var approximateExpirationDate: Date {
  53. Date(timeIntervalSinceNow: AuthStateDidChangePublisherTests.accessTokenTimeToLive)
  54. }
  55. override var refreshToken: String { return AuthStateDidChangePublisherTests.refreshToken }
  56. }
  57. class MockAuthBackend: AuthBackendImplementationMock {
  58. override func signUpNewUser(_ request: FIRSignUpNewUserRequest,
  59. callback: @escaping FIRSignupNewUserCallback) {
  60. XCTAssertEqual(request.apiKey, AuthStateDidChangePublisherTests.apiKey)
  61. XCTAssertNil(request.email)
  62. XCTAssertNil(request.password)
  63. XCTAssertTrue(request.returnSecureToken)
  64. let response = MockSignUpNewUserResponse()
  65. callback(response, nil)
  66. }
  67. override func getAccountInfo(_ request: FIRGetAccountInfoRequest,
  68. callback: @escaping FIRGetAccountInfoResponseCallback) {
  69. XCTAssertEqual(request.apiKey, AuthStateDidChangePublisherTests.apiKey)
  70. XCTAssertEqual(request.accessToken, AuthStateDidChangePublisherTests.accessToken)
  71. let response = MockGetAccountInfoResponse()
  72. callback(response, nil)
  73. }
  74. override func verifyPassword(_ request: FIRVerifyPasswordRequest,
  75. callback: @escaping FIRVerifyPasswordResponseCallback) {
  76. let response = MockVerifyPasswordResponse()
  77. callback(response, nil)
  78. }
  79. }
  80. override class func setUp() {
  81. FirebaseApp.configureForTests()
  82. }
  83. override class func tearDown() {
  84. FirebaseApp.app()?.delete { success in
  85. if success {
  86. print("Shut down app successfully.")
  87. } else {
  88. print("💥 There was a problem when shutting down the app..")
  89. }
  90. }
  91. }
  92. override func setUp() {
  93. do {
  94. try Auth.auth().signOut()
  95. } catch {}
  96. }
  97. func testPublisherEmitsOnMainThread() {
  98. // given
  99. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  100. let expect = expectation(description: "Publisher emits on main thread")
  101. let cancellable = Auth.auth().authStateDidChangePublisher()
  102. .sink { user in
  103. XCTAssertTrue(Thread.isMainThread)
  104. expect.fulfill()
  105. }
  106. Auth.auth().signInAnonymously()
  107. wait(for: [expect], timeout: expectationTimeout)
  108. cancellable.cancel()
  109. }
  110. func testSubscribersCanReceiveOnNonMainThread() {
  111. // given
  112. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  113. let expect = expectation(description: "Subscribers can receive events on non-main threads")
  114. let cancellable = Auth.auth().authStateDidChangePublisher()
  115. .receive(on: DispatchQueue.global())
  116. .sink { user in
  117. XCTAssertFalse(Thread.isMainThread)
  118. expect.fulfill()
  119. }
  120. Auth.auth().signInAnonymously()
  121. wait(for: [expect], timeout: expectationTimeout)
  122. cancellable.cancel()
  123. }
  124. func testPublisherEmitsWhenAttached() {
  125. // given
  126. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  127. let subscriptionActivatedExpectation =
  128. expectation(description: "Publisher emits value as soon as it is subscribed")
  129. let cancellable = Auth.auth().authStateDidChangePublisher()
  130. .sink { user in
  131. XCTAssertNil(user)
  132. subscriptionActivatedExpectation.fulfill()
  133. }
  134. wait(for: [subscriptionActivatedExpectation], timeout: expectationTimeout)
  135. cancellable.cancel()
  136. }
  137. func testPublisherEmitsWhenUserIsSignedIn() {
  138. // given
  139. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  140. let signedInExpectation =
  141. expectation(description: "Publisher emits value when user is signed in")
  142. let cancellable = Auth.auth().authStateDidChangePublisher()
  143. .sink { user in
  144. print("Running on the main thread? \(Thread.isMainThread)")
  145. if let user = user, user.isAnonymous {
  146. signedInExpectation.fulfill()
  147. }
  148. }
  149. Auth.auth().signInAnonymously()
  150. wait(for: [signedInExpectation], timeout: expectationTimeout)
  151. cancellable.cancel()
  152. }
  153. // Listener should not fire for signing in again.
  154. func testPublisherDoesNotEmitWhenUserSignsInAgain() {
  155. // given
  156. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  157. var expect = expectation(description: "Publisher emits value when user is signed in")
  158. let cancellable = Auth.auth().authStateDidChangePublisher()
  159. .sink { user in
  160. if let user = user, user.isAnonymous {
  161. expect.fulfill()
  162. }
  163. }
  164. // Sign in, expect the publisher to emit
  165. Auth.auth().signInAnonymously()
  166. wait(for: [expect], timeout: expectationTimeout)
  167. // Sign in again, expect the publisher NOT to emit
  168. expect = expectation(description: "Publisher does not emit when user sign in again")
  169. expect.isInverted = true
  170. Auth.auth().signInAnonymously()
  171. wait(for: [expect], timeout: expectationTimeout)
  172. cancellable.cancel()
  173. }
  174. // Listener should fire for signing out.
  175. func testPublisherEmitsWhenUserSignsOut() {
  176. // given
  177. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  178. var expect = expectation(description: "Publisher emits value when user is signed in")
  179. var shouldUserBeNil = false
  180. let cancellable = Auth.auth().authStateDidChangePublisher()
  181. .sink { user in
  182. if shouldUserBeNil {
  183. if user == nil {
  184. expect.fulfill()
  185. }
  186. } else {
  187. if let user = user, user.isAnonymous {
  188. expect.fulfill()
  189. }
  190. }
  191. }
  192. // sign in first
  193. Auth.auth().signInAnonymously()
  194. wait(for: [expect], timeout: expectationTimeout)
  195. // now sign out
  196. expect = expectation(description: "Publisher emits value when user signs out")
  197. shouldUserBeNil = true
  198. do {
  199. try Auth.auth().signOut()
  200. } catch {}
  201. wait(for: [expect], timeout: expectationTimeout)
  202. cancellable.cancel()
  203. }
  204. // Listener should no longer fire once detached.
  205. func testPublisherNoLongerEmitsWhenDetached() {
  206. // given
  207. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  208. var expect = expectation(description: "Publisher emits value when user is signed in")
  209. var shouldUserBeNil = false
  210. let cancellable = Auth.auth().authStateDidChangePublisher()
  211. .sink { user in
  212. if shouldUserBeNil {
  213. if user == nil {
  214. expect.fulfill()
  215. }
  216. } else {
  217. if let user = user, user.isAnonymous {
  218. expect.fulfill()
  219. }
  220. }
  221. }
  222. // sign in first
  223. Auth.auth().signInAnonymously()
  224. wait(for: [expect], timeout: expectationTimeout)
  225. // detach the publisher
  226. expect = expectation(description: "Publisher no longer emits once detached")
  227. expect.isInverted = true
  228. cancellable.cancel()
  229. shouldUserBeNil = true
  230. do {
  231. try Auth.auth().signOut()
  232. } catch {}
  233. wait(for: [expect], timeout: expectationTimeout)
  234. }
  235. }