OAuthProviderTests.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. // Copyright 2023 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License")
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. import XCTest
  16. @testable import FirebaseAuth
  17. import FirebaseCore
  18. #if os(iOS)
  19. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  20. class OAuthProviderTests: RPCBaseTests {
  21. static let kFakeAuthorizedDomain = "test.firebaseapp.com"
  22. static let kFakeAuthorizedWebDomain = "test.web.app"
  23. private let kFakeAccessToken = "fakeAccessToken"
  24. private let kFakeIDToken = "fakeIDToken"
  25. private let kFakeProviderID = "fakeProviderID"
  26. static let kFakeAPIKey = "asdfghjkl"
  27. static let kFakeEmulatorHost = "emulatorhost"
  28. static let kFakeEmulatorPort = 12345
  29. static let kFakeClientID = "123456.apps.googleusercontent.com"
  30. static let kFakeOAuthResponseURL = "fakeOAuthResponseURL"
  31. static let kFakeFirebaseAppID = "1:123456789:ios:123abc456def"
  32. static let kFakeEncodedFirebaseAppID = "app-1-123456789-ios-123abc456def"
  33. static let kFakeTenantID = "tenantID"
  34. static let kFakeReverseClientID = "com.googleusercontent.apps.123456"
  35. // Switches for testing different OAuth test flows
  36. // TODO: Consider using an enum for these instead.
  37. static var testTenantID = false
  38. static var testCancel = false
  39. static var testErrorString = false
  40. static var testInternalError = false
  41. static var testInvalidClientID = false
  42. static var testUnknownError = false
  43. static var testAppID = false
  44. static var testEmulator = false
  45. static var testAppCheck = false
  46. static var auth: Auth?
  47. /** @fn testObtainingOAuthCredentialNoIDToken
  48. @brief Tests the correct creation of an OAuthCredential without an IDToken.
  49. */
  50. func testObtainingOAuthCredentialNoIDToken() throws {
  51. initApp(#function)
  52. let credential = OAuthProvider.credential(providerID: .apple,
  53. accessToken: kFakeAccessToken)
  54. XCTAssertEqual(credential.accessToken, kFakeAccessToken)
  55. XCTAssertEqual(credential.provider, AuthProviderID.apple.rawValue)
  56. XCTAssertNil(credential.idToken)
  57. }
  58. /** @fn testObtainingOAuthCredentialWithFullName
  59. @brief Tests the correct creation of an OAuthCredential with a fullName.
  60. */
  61. func testObtainingOAuthCredentialWithFullName() throws {
  62. let kFakeGivenName = "Paul"
  63. let kFakeFamilyName = "B"
  64. var fullName = PersonNameComponents()
  65. fullName.givenName = kFakeGivenName
  66. fullName.familyName = kFakeFamilyName
  67. initApp(#function)
  68. let credential = OAuthProvider.appleCredential(withIDToken: kFakeIDToken, rawNonce: nil,
  69. fullName: fullName)
  70. XCTAssertEqual(credential.fullName, fullName)
  71. XCTAssertEqual(credential.provider, "apple.com")
  72. XCTAssertEqual(credential.idToken, kFakeIDToken)
  73. XCTAssertNil(credential.accessToken)
  74. }
  75. /** @fn testObtainingOAuthCredentialWithIDToken
  76. @brief Tests the correct creation of an OAuthCredential with an IDToken
  77. */
  78. func testObtainingOAuthCredentialWithIDToken() throws {
  79. initApp(#function)
  80. let credential = OAuthProvider.credential(providerID: .email,
  81. idToken: kFakeIDToken,
  82. accessToken: kFakeAccessToken)
  83. XCTAssertEqual(credential.accessToken, kFakeAccessToken)
  84. XCTAssertEqual(credential.provider, AuthProviderID.email.rawValue)
  85. XCTAssertEqual(credential.idToken, kFakeIDToken)
  86. }
  87. /** @fn testObtainingOAuthProvider
  88. @brief Tests the correct creation of an FIROAuthProvider instance.
  89. */
  90. func testObtainingOAuthProvider() throws {
  91. initApp(#function)
  92. let provider = OAuthProvider(providerID: kFakeProviderID, auth: OAuthProviderTests.auth!)
  93. XCTAssertEqual(provider.providerID, kFakeProviderID)
  94. }
  95. /** @fn testGetCredentialWithUIDelegateWithClientID
  96. @brief Tests a successful invocation of @c getCredentialWithUIDelegate
  97. */
  98. func testGetCredentialWithUIDelegateWithClientID() throws {
  99. initApp(#function)
  100. try testOAuthFlow(description: #function)
  101. }
  102. /** @fn testGetCredentialWithUIDelegateWithTenantID
  103. @brief Tests a successful invocation of @c getCredentialWithUIDelegate:completion:
  104. */
  105. func testGetCredentialWithUIDelegateWithTenantID() throws {
  106. initApp(#function)
  107. // Update tenantID on workqueue to enable _protectedDataDidBecomeAvailableObserver to finish
  108. // init.
  109. kAuthGlobalWorkQueue.sync {
  110. OAuthProviderTests.auth?.tenantID = OAuthProviderTests.kFakeTenantID
  111. }
  112. OAuthProviderTests.testTenantID = true
  113. try testOAuthFlow(description: #function)
  114. OAuthProviderTests.auth?.tenantID = nil
  115. OAuthProviderTests.testTenantID = false
  116. }
  117. /** @fn testGetCredentialWithUIDelegateUserCancellationWithClientID
  118. @brief Tests an unsuccessful invocation of @c testGetCredentialWithUIDelegateUserCancellationWithClientID due to user
  119. cancelation.
  120. */
  121. func testGetCredentialWithUIDelegateUserCancellationWithClientID() throws {
  122. initApp(#function)
  123. OAuthProviderTests.testCancel = true
  124. try testOAuthFlow(description: #function)
  125. OAuthProviderTests.testCancel = false
  126. }
  127. /** @fn testGetCredentialWithUIDelegateNetworkRequestFailedWithClientID
  128. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegate due to a
  129. failed network request within the web context.
  130. */
  131. func testGetCredentialWithUIDelegateNetworkRequestFailedWithClientID() throws {
  132. initApp(#function)
  133. OAuthProviderTests.testErrorString = true
  134. try testOAuthFlow(description: #function)
  135. OAuthProviderTests.testErrorString = false
  136. }
  137. /** @fn testGetCredentialWithUIDelegateInternalErrorWithClientID
  138. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegate due to an
  139. internal error within the web context.
  140. */
  141. func testGetCredentialWithUIDelegateInternalErrorWithClientID() throws {
  142. initApp(#function)
  143. OAuthProviderTests.testInternalError = true
  144. try testOAuthFlow(description: #function)
  145. OAuthProviderTests.testInternalError = false
  146. }
  147. /** @fn testGetCredentialWithUIDelegateInvalidClientID
  148. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegate due to an
  149. use of an invalid client ID.
  150. */
  151. func testGetCredentialWithUIDelegateInvalidClientID() throws {
  152. initApp(#function)
  153. OAuthProviderTests.testInvalidClientID = true
  154. try testOAuthFlow(description: #function)
  155. OAuthProviderTests.testInvalidClientID = false
  156. }
  157. /** @fn testGetCredentialWithUIDelegateUnknownErrorWithClientID
  158. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegate due to an
  159. unknown error.
  160. */
  161. func testGetCredentialWithUIDelegateUnknownErrorWithClientID() throws {
  162. initApp(#function)
  163. OAuthProviderTests.testUnknownError = true
  164. try testOAuthFlow(description: #function)
  165. OAuthProviderTests.testUnknownError = false
  166. }
  167. /** @fn testGetCredentialWithUIDelegateWithFirebaseAppID
  168. @brief Tests a successful invocation of @c getCredentialWithUIDelegate
  169. */
  170. func testGetCredentialWithUIDelegateWithFirebaseAppID() throws {
  171. initApp(#function, useAppID: true, omitClientID: true,
  172. scheme: OAuthProviderTests.kFakeEncodedFirebaseAppID)
  173. OAuthProviderTests.testAppID = true
  174. try testOAuthFlow(description: #function)
  175. OAuthProviderTests.testAppID = false
  176. }
  177. /** @fn testGetCredentialWithUIDelegateWithFirebaseAppIDWhileClientIdPresent
  178. @brief Tests a successful invocation of @c getCredentialWithUIDelegate when the
  179. client ID is present in the plist file, but the encoded app ID is the registered custom URL
  180. scheme.
  181. */
  182. func testGetCredentialWithUIDelegateWithFirebaseAppIDWhileClientIdPresent() throws {
  183. initApp(#function, useAppID: true, scheme: OAuthProviderTests.kFakeEncodedFirebaseAppID)
  184. OAuthProviderTests.testAppID = true
  185. try testOAuthFlow(description: #function)
  186. OAuthProviderTests.testAppID = false
  187. }
  188. /** @fn testGetCredentialWithUIDelegateUseEmulator
  189. @brief Tests a successful invocation of @c getCredentialWithUIDelegate when using the emulator.
  190. */
  191. func testGetCredentialWithUIDelegateUseEmulator() throws {
  192. initApp(#function, useAppID: true)
  193. OAuthProviderTests.auth?.requestConfiguration.emulatorHostAndPort =
  194. "\(OAuthProviderTests.kFakeEmulatorHost):\(OAuthProviderTests.kFakeEmulatorPort)"
  195. OAuthProviderTests.testEmulator = true
  196. try testOAuthFlow(description: #function)
  197. OAuthProviderTests.testEmulator = false
  198. }
  199. /** @fn testGetCredentialWithUIDelegateWithAppCheckToken
  200. @brief Tests a successful invocation of @c getCredentialWithUIDelegate
  201. */
  202. func testGetCredentialWithUIDelegateWithAppCheckToken() throws {
  203. let fakeAppCheck = FakeAppCheck()
  204. initApp(#function, useAppID: true)
  205. OAuthProviderTests.auth?.requestConfiguration.appCheck = fakeAppCheck
  206. OAuthProviderTests.testAppCheck = true
  207. try testOAuthFlow(description: #function)
  208. OAuthProviderTests.testAppCheck = false
  209. }
  210. /** @fn testOAuthCredentialCoding
  211. @brief Tests successful archiving and unarchiving of @c GoogleAuthCredential.
  212. */
  213. func testOAuthCredentialCoding() throws {
  214. let kAccessToken = "accessToken"
  215. let kIDToken = "idToken"
  216. let kRawNonce = "nonce"
  217. let kSecret = "sEcret"
  218. let kFullName = PersonNameComponents()
  219. let kPendingToken = "pendingToken"
  220. let credential = OAuthCredential(withProviderID: "dummyProvider",
  221. idToken: kIDToken,
  222. rawNonce: kRawNonce,
  223. accessToken: kAccessToken,
  224. secret: kSecret,
  225. fullName: kFullName,
  226. pendingToken: kPendingToken)
  227. XCTAssertTrue(OAuthCredential.supportsSecureCoding)
  228. let data = try NSKeyedArchiver.archivedData(
  229. withRootObject: credential,
  230. requiringSecureCoding: true
  231. )
  232. let unarchivedCredential = try XCTUnwrap(NSKeyedUnarchiver.unarchivedObject(
  233. ofClasses: [OAuthCredential.self, NSPersonNameComponents.self], from: data
  234. ) as? OAuthCredential)
  235. XCTAssertEqual(unarchivedCredential.idToken, kIDToken)
  236. XCTAssertEqual(unarchivedCredential.rawNonce, kRawNonce)
  237. XCTAssertEqual(unarchivedCredential.accessToken, kAccessToken)
  238. XCTAssertEqual(unarchivedCredential.secret, kSecret)
  239. XCTAssertEqual(unarchivedCredential.fullName, kFullName)
  240. XCTAssertEqual(unarchivedCredential.pendingToken, kPendingToken)
  241. XCTAssertEqual(unarchivedCredential.provider, OAuthProvider.id)
  242. }
  243. private func initApp(_ functionName: String, useAppID: Bool = false, omitClientID: Bool = false,
  244. scheme: String = OAuthProviderTests.kFakeReverseClientID) {
  245. let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000",
  246. gcmSenderID: "00000000000000000-00000000000-000000000")
  247. options.apiKey = OAuthProviderTests.kFakeAPIKey
  248. options.projectID = "myProjectID"
  249. if useAppID {
  250. options.googleAppID = OAuthProviderTests.kFakeFirebaseAppID
  251. }
  252. if !omitClientID {
  253. options.clientID = OAuthProviderTests.kFakeClientID
  254. }
  255. let strippedName = functionName.replacingOccurrences(of: "(", with: "")
  256. .replacingOccurrences(of: ")", with: "")
  257. FirebaseApp.configure(name: strippedName, options: options)
  258. OAuthProviderTests.auth = Auth.auth(app: FirebaseApp.app(name: strippedName)!)
  259. OAuthProviderTests.auth = Auth(
  260. app: FirebaseApp.app(name: strippedName)!,
  261. backend: authBackend
  262. )
  263. OAuthProviderTests.auth?.mainBundleUrlTypes =
  264. [["CFBundleURLSchemes": [scheme]]]
  265. }
  266. private func testOAuthFlow(description: String,
  267. with fakeAppCheck: FakeAppCheck? = nil) throws {
  268. let expectation = self.expectation(description: description)
  269. let provider = OAuthProvider(providerID: kFakeProviderID, auth: OAuthProviderTests.auth!)
  270. // Use fake authURLPresenter so we can test the parameters that get sent to it.
  271. OAuthProviderTests.auth?.authURLPresenter = FakePresenter()
  272. // 1. Setup fakes and parameters for getCredential.
  273. if !OAuthProviderTests.testEmulator {
  274. let projectConfigExpectation = self.expectation(description: "projectConfiguration")
  275. rpcIssuer.projectConfigRequester = { request in
  276. // 3. Validate the created Request instance.
  277. XCTAssertEqual(request.apiKey, OAuthProviderTests.kFakeAPIKey)
  278. XCTAssertEqual(request.endpoint, "getProjectConfig")
  279. // 4. Fulfill the expectation.
  280. projectConfigExpectation.fulfill()
  281. do {
  282. // 5. Send the response from the fake backend.
  283. return try self.rpcIssuer.respond(withJSON: ["authorizedDomains": [
  284. OAuthProviderTests.kFakeAuthorizedWebDomain,
  285. OAuthProviderTests.kFakeAuthorizedDomain]])
  286. } catch {
  287. XCTFail("Failure sending response: \(error)")
  288. return (nil, nil)
  289. }
  290. }
  291. }
  292. class FakePresenter: NSObject, AuthWebViewControllerDelegate {
  293. func webViewController(_ webViewController: AuthWebViewController,
  294. canHandle URL: URL) -> Bool {
  295. XCTFail("Do not call")
  296. return false
  297. }
  298. func webViewControllerDidCancel(_ webViewController: AuthWebViewController) {
  299. XCTFail("Do not call")
  300. }
  301. func webViewController(_ webViewController: AuthWebViewController,
  302. didFailWithError error: Error) {
  303. XCTFail("Do not call")
  304. }
  305. func present(_ presentURL: URL, uiDelegate UIDelegate: AuthUIDelegate?,
  306. callbackMatcher: @escaping (URL?) -> Bool,
  307. completion: @escaping (URL?, Error?) -> Void) {
  308. // 6. Verify flow triggers present in the FakePresenter class with the right parameters.
  309. if OAuthProviderTests.testEmulator {
  310. XCTAssertEqual(presentURL.scheme, "http")
  311. XCTAssertEqual(presentURL.host, OAuthProviderTests.kFakeEmulatorHost)
  312. XCTAssertEqual(presentURL.port, OAuthProviderTests.kFakeEmulatorPort)
  313. XCTAssertEqual(presentURL.path, "/emulator/auth/handler")
  314. } else {
  315. XCTAssertEqual(presentURL.scheme, "https")
  316. XCTAssertEqual(presentURL.host, OAuthProviderTests.kFakeAuthorizedDomain)
  317. XCTAssertEqual(presentURL.path, "/__/auth/handler")
  318. }
  319. let params = AuthWebUtils.dictionary(withHttpArgumentsString: presentURL.query)
  320. XCTAssertEqual(params["ibi"], Bundle.main.bundleIdentifier)
  321. if OAuthProviderTests.testAppID {
  322. XCTAssertEqual(params["appId"], OAuthProviderTests.kFakeFirebaseAppID)
  323. } else {
  324. XCTAssertEqual(params["clientId"], OAuthProviderTests.kFakeClientID)
  325. }
  326. XCTAssertEqual(params["apiKey"], OAuthProviderTests.kFakeAPIKey)
  327. XCTAssertEqual(params["authType"], "signInWithRedirect")
  328. XCTAssertNotNil(params["v"])
  329. if OAuthProviderTests.testTenantID {
  330. XCTAssertEqual(params["tid"], OAuthProviderTests.kFakeTenantID)
  331. } else {
  332. XCTAssertNil(params["tid"])
  333. }
  334. let appCheckToken = presentURL.fragment
  335. let verifyAppCheckToken = OAuthProviderTests.testAppCheck ? "fac=fakeAppCheckToken" : nil
  336. XCTAssertEqual(appCheckToken, verifyAppCheckToken)
  337. // 7. Test callbackMatcher
  338. let kFakeRedirectStart = OAuthProviderTests
  339. .testAppID ? "app-1-123456789-ios-123abc456def" :
  340. OAuthProviderTests.kFakeReverseClientID
  341. let kFakeRedirectURLBase = kFakeRedirectStart + "://firebaseauth/" +
  342. "link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcallback%3F"
  343. var kFakeRedirectURLRest = "authType%3DsignInWithRedirect%26link%3D"
  344. if OAuthProviderTests.testInternalError {
  345. kFakeRedirectURLRest = "firebaseError%3D%257B%2522code%2522%253" +
  346. "A%2522auth%252Finternal-error%2522%252C%2522message%2522%253A%2522Internal%2520" +
  347. "error%2520.%2522%257D%26authType%3DsignInWithRedirect"
  348. } else if OAuthProviderTests.testInvalidClientID {
  349. kFakeRedirectURLRest = "firebaseError%3D%257B%2522code%2522%253A%2522auth" +
  350. "%252Finvalid-oauth-client-id%2522%252C%2522message%2522%253A%2522The%2520OAuth%2520client%" +
  351. "2520ID%2520provided%2520is%2520either%2520invalid%2520or%2520does%2520not%2520match%2520the%" +
  352. "2520specified%2520API%2520key.%2522%257D%26authType%3DsignInWithRedirect"
  353. } else if OAuthProviderTests.testErrorString {
  354. kFakeRedirectURLRest = "firebaseError%3D%257B%2522code%2" +
  355. "522%253A%2522auth%252Fnetwork-request-failed%2522%252C%2522message%2522%253A%2522The%" +
  356. "2520network%2520request%2520failed%2520.%2522%257D%26authType%3DsignInWithRedirect"
  357. } else if OAuthProviderTests.testUnknownError {
  358. kFakeRedirectURLRest = "firebaseError%3D%257B%2522code%2522%253A%2522auth%2" +
  359. "52Funknown-error-id%2522%252C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%" +
  360. "2520provided%2520is%2520either%2520invalid%2520or%2520does%2520not%2520match%2520the%2520" +
  361. "specified%2520API%2520key.%2522%257D%26authType%3DsignInWithRedirect"
  362. }
  363. var redirectURL = "\(kFakeRedirectURLBase)\(kFakeRedirectURLRest)"
  364. // Add fake OAuthResponse to callback.
  365. if !OAuthProviderTests.testErrorString, !OAuthProviderTests.testInternalError,
  366. !OAuthProviderTests.testInvalidClientID, !OAuthProviderTests.testUnknownError {
  367. redirectURL += OAuthProviderTests.kFakeOAuthResponseURL
  368. }
  369. // Verify that the URL is rejected by the callback matcher without the event ID.
  370. XCTAssertFalse(callbackMatcher(URL(string: "\(redirectURL)")))
  371. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  372. let redirectWithEventID =
  373. "\(redirectURL)%26eventId%3D\(params["eventId"] ?? "missingEventID")"
  374. let originalComponents = URLComponents(string: redirectWithEventID)!
  375. XCTAssertTrue(callbackMatcher(originalComponents.url))
  376. var components = originalComponents
  377. components.query = "https"
  378. XCTAssertFalse(callbackMatcher(components.url))
  379. components = originalComponents
  380. components.host = "badhost"
  381. XCTAssertFalse(callbackMatcher(components.url))
  382. components = originalComponents
  383. components.path = "badpath"
  384. XCTAssertFalse(callbackMatcher(components.url))
  385. components = originalComponents
  386. components.query = "badquery"
  387. XCTAssertFalse(callbackMatcher(components.url))
  388. // 8. Do the callback to the original call.
  389. kAuthGlobalWorkQueue.async {
  390. if OAuthProviderTests.testCancel {
  391. completion(nil, AuthErrorUtils.webContextCancelledError(message: nil))
  392. } else {
  393. completion(originalComponents.url, nil)
  394. }
  395. }
  396. }
  397. }
  398. // 2. Request the credential.
  399. provider.getCredentialWith(nil) { credential, error in
  400. // 9. After the response triggers the callback, verify the values in the callback credential
  401. XCTAssertTrue(Thread.isMainThread)
  402. if OAuthProviderTests.testCancel {
  403. XCTAssertNil(credential)
  404. XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.webContextCancelled.rawValue)
  405. } else if OAuthProviderTests.testErrorString {
  406. XCTAssertNil(credential)
  407. XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.webNetworkRequestFailed.rawValue)
  408. } else if OAuthProviderTests.testInternalError {
  409. XCTAssertNil(credential)
  410. XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.webInternalError.rawValue)
  411. } else if OAuthProviderTests.testInvalidClientID {
  412. XCTAssertNil(credential)
  413. XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.invalidClientID.rawValue)
  414. } else if OAuthProviderTests.testUnknownError {
  415. XCTAssertNil(credential)
  416. XCTAssertEqual(
  417. (error as? NSError)?.code,
  418. AuthErrorCode.webSignInUserInteractionFailure.rawValue
  419. )
  420. } else {
  421. XCTAssertNil(error)
  422. let oAuthCredential = credential as? OAuthCredential
  423. XCTAssertEqual(
  424. oAuthCredential?.OAuthResponseURLString,
  425. OAuthProviderTests.kFakeOAuthResponseURL
  426. )
  427. }
  428. expectation.fulfill()
  429. }
  430. waitForExpectations(timeout: 5)
  431. }
  432. }
  433. #endif