FIRPhoneAuthProviderTests.m 72 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <TargetConditionals.h>
  17. #if TARGET_OS_IOS
  18. #import <OCMock/OCMock.h>
  19. #import <SafariServices/SafariServices.h>
  20. #import <XCTest/XCTest.h>
  21. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuth.h"
  22. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuthSettings.h"
  23. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuthUIDelegate.h"
  24. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRPhoneAuthProvider.h"
  25. #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  26. #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h"
  27. #import "FirebaseAuth/Sources/Auth/FIRAuth_Internal.h"
  28. #import "FirebaseAuth/Sources/AuthProvider/FIRAuthCredential_Internal.h"
  29. #import "FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h"
  30. #import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h"
  31. #import "FirebaseAuth/Sources/Backend/FIRAuthRequestConfiguration.h"
  32. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetProjectConfigRequest.h"
  33. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetProjectConfigResponse.h"
  34. #import "FirebaseAuth/Sources/Backend/RPC/FIRSendVerificationCodeRequest.h"
  35. #import "FirebaseAuth/Sources/Backend/RPC/FIRSendVerificationCodeResponse.h"
  36. #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyClientRequest.h"
  37. #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyClientResponse.h"
  38. #import "FirebaseAuth/Sources/SystemService/FIRAuthAPNSToken.h"
  39. #import "FirebaseAuth/Sources/SystemService/FIRAuthAPNSTokenManager.h"
  40. #import "FirebaseAuth/Sources/SystemService/FIRAuthAppCredential.h"
  41. #import "FirebaseAuth/Sources/SystemService/FIRAuthAppCredentialManager.h"
  42. #import "FirebaseAuth/Sources/SystemService/FIRAuthNotificationManager.h"
  43. #import "FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h"
  44. #import "FirebaseAuth/Sources/Utilities/FIRAuthURLPresenter.h"
  45. #import "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"
  46. #import "FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.h"
  47. NS_ASSUME_NONNULL_BEGIN
  48. /** @var kTestPhoneNumber
  49. @brief A testing phone number.
  50. */
  51. static NSString *const kTestPhoneNumber = @"55555555";
  52. /** @var kTestInvalidPhoneNumber
  53. @brief An invalid testing phone number.
  54. */
  55. static NSString *const kTestInvalidPhoneNumber = @"555+!*55555";
  56. /** @var kTestVerificationID
  57. @brief A testing verfication ID.
  58. */
  59. static NSString *const kTestVerificationID = @"verificationID";
  60. /** @var kTestReceipt
  61. @brief A fake receipt for testing.
  62. */
  63. static NSString *const kTestReceipt = @"receipt";
  64. /** @var kTestSecret
  65. @brief A fake secret for testing.
  66. */
  67. static NSString *const kTestSecret = @"secret";
  68. /** @var kTestOldReceipt
  69. @brief A fake old receipt for testing.
  70. */
  71. static NSString *const kTestOldReceipt = @"old_receipt";
  72. /** @var kTestOldSecret
  73. @brief A fake old secret for testing.
  74. */
  75. static NSString *const kTestOldSecret = @"old_secret";
  76. /** @var kTestVerificationCode
  77. @brief A fake verfication code.
  78. */
  79. static NSString *const kTestVerificationCode = @"verificationCode";
  80. /** @var kFakeClientID
  81. @brief A fake client ID.
  82. */
  83. static NSString *const kFakeClientID = @"123456.apps.googleusercontent.com";
  84. /** @var kFakeReverseClientID
  85. @brief The dot-reversed version of the fake client ID.
  86. */
  87. static NSString *const kFakeReverseClientID = @"com.googleusercontent.apps.123456";
  88. /** @var kFakeFirebaseAppID
  89. @brief A fake Firebase app ID.
  90. */
  91. static NSString *const kFakeFirebaseAppID = @"1:123456789:ios:123abc456def";
  92. /** @var kFakeEncodedFirebaseAppID
  93. @brief A fake encoded Firebase app ID to be used as a custom URL scheme.
  94. */
  95. static NSString *const kFakeEncodedFirebaseAppID = @"app-1-123456789-ios-123abc456def";
  96. /** @var kFakeBundleID
  97. @brief A fake bundle ID.
  98. */
  99. static NSString *const kFakeBundleID = @"com.firebaseapp.example";
  100. /** @var kFakeAPIKey
  101. @brief A fake API key.
  102. */
  103. static NSString *const kFakeAPIKey = @"asdfghjkl";
  104. /** @var kFakeAuthorizedDomain
  105. @brief A fake authorized domain for the app.
  106. */
  107. static NSString *const kFakeAuthorizedDomain = @"test.firebaseapp.com";
  108. /** @var kFakeReCAPTCHAToken
  109. @brief A fake reCAPTCHA token.
  110. */
  111. static NSString *const kFakeReCAPTCHAToken = @"fakeReCAPTCHAToken";
  112. /** @var kFakeRedirectURLStringWithReCAPTCHAToken
  113. @brief The format for a fake redirect URL string (minus the scheme) that contains the fake
  114. reCAPTCHA token above.
  115. */
  116. static NSString *const kFakeRedirectURLStringWithReCAPTCHAToken =
  117. @"://firebaseauth/"
  118. @"link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcallback%3FauthType%"
  119. @"3DverifyApp%26recaptchaToken%3DfakeReCAPTCHAToken";
  120. /** @var kFakeRedirectURLStringInvalidClientID
  121. @brief The format for a fake redirect URL string with an invalid client error.
  122. */
  123. static NSString *const kFakeRedirectURLStringInvalidClientID =
  124. @"com.googleusercontent.apps.1"
  125. "23456://firebaseauth/"
  126. "link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal"
  127. "lback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Finvalid-oauth-client-id%2522%"
  128. "252"
  129. "C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%2520either%"
  130. "252"
  131. "0invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%"
  132. "26"
  133. "authType%3DverifyApp";
  134. /** @var kFakeRedirectURLStringWebNetworkRequestFailed
  135. @brief The format for a fake redirect URL string with a web network request failed error.
  136. */
  137. static NSString *const kFakeRedirectURLStringWebNetworkRequestFailed =
  138. @"com.googleusercontent.apps"
  139. ".123456://firebaseauth/"
  140. "link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fc"
  141. "allback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Fnetwork-request-failed%2522%"
  142. "25"
  143. "2C%2522message%2522%253A%2522The%2520network%2520request%2520failed%2520.%2522%257D%"
  144. "26authType"
  145. "%3DverifyApp";
  146. /** @var kFakeRedirectURLStringWebInternalError
  147. @brief The format for a fake redirect URL string with an internal web error.
  148. */
  149. static NSString *const kFakeRedirectURLStringWebInternalError =
  150. @"com.googleusercontent.apps.1"
  151. "23456://firebaseauth/"
  152. "link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal"
  153. "lback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Finternal-error%2522%252C%"
  154. "2522mes"
  155. "sage%2522%253A%2522Internal%2520error%2520.%2522%257D%26authType%3DverifyApp";
  156. /** @var kFakeRedirectURLStringUnknownError
  157. @brief The format for a fake redirect URL string with unknown error response.
  158. */
  159. static NSString *const kFakeRedirectURLStringUnknownError =
  160. @"com.googleusercontent.apps.1"
  161. "23456://firebaseauth/"
  162. "link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal"
  163. "lback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Funknown-error-id%2522%252"
  164. "C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%2520either%"
  165. "252"
  166. "0invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%"
  167. "26"
  168. "authType%3DverifyApp";
  169. /** @var kFakeRedirectURLStringUnstructuredError
  170. @brief The format for a fake redirect URL string with unstructured error response.
  171. */
  172. static NSString *const kFakeRedirectURLStringUnstructuredError =
  173. @"com.googleusercontent.apps.1"
  174. "23456://firebaseauth/"
  175. "link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal"
  176. "lback%3FfirebaseError%3D%257B%2522unstructuredcode%2522%253A%2522auth%252Funknown-error-id%"
  177. "2522%252"
  178. "C%2522unstructuredmessage%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%"
  179. "2520either%252"
  180. "0invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%"
  181. "26"
  182. "authType%3DverifyApp";
  183. /** @var kTestTimeout
  184. @brief A fake timeout value for waiting for push notification.
  185. */
  186. static const NSTimeInterval kTestTimeout = 5;
  187. /** @var kExpectationTimeout
  188. @brief The maximum time waiting for expectations to fulfill.
  189. */
  190. static const NSTimeInterval kExpectationTimeout = 2;
  191. /** @class FIRPhoneAuthProviderTests
  192. @brief Tests for @c FIRPhoneAuthProvider
  193. */
  194. @interface FIRPhoneAuthProviderTests : XCTestCase
  195. @end
  196. @implementation FIRPhoneAuthProviderTests {
  197. /** @var _mockBackend
  198. @brief The mock @c FIRAuthBackendImplementation .
  199. */
  200. id _mockBackend;
  201. /** @var _provider
  202. @brief The @c FIRPhoneAuthProvider instance under test.
  203. */
  204. FIRPhoneAuthProvider *_provider;
  205. /** @var _mockAuth
  206. @brief The mock @c FIRAuth instance associated with @c _provider .
  207. */
  208. id _mockAuth;
  209. /** @var _mockApp
  210. @brief The mock @c FIRApp instance associated with @c _mockAuth .
  211. */
  212. id _mockApp;
  213. /** @var _mockOptions
  214. @brief The mock @c FIROptions instance associated with @c _mockApp.
  215. */
  216. id _mockOptions;
  217. /** @var _mockAPNSTokenManager
  218. @brief The mock @c FIRAuthAPNSTokenManager instance associated with @c _mockAuth .
  219. */
  220. id _mockAPNSTokenManager;
  221. /** @var _mockAppCredentialManager
  222. @brief The mock @c FIRAuthAppCredentialManager instance associated with @c _mockAuth .
  223. */
  224. id _mockAppCredentialManager;
  225. /** @var _mockNotificationManager
  226. @brief The mock @c FIRAuthNotificationManager instance associated with @c _mockAuth .
  227. */
  228. id _mockNotificationManager;
  229. /** @var _mockURLPresenter
  230. @brief The mock @c FIRAuthURLPresenter instance associated with @c _mockAuth .
  231. */
  232. id _mockURLPresenter;
  233. }
  234. - (void)setUp {
  235. [super setUp];
  236. _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation));
  237. [FIRAuthBackend setBackendImplementation:_mockBackend];
  238. _mockAuth = OCMClassMock([FIRAuth class]);
  239. _mockApp = OCMClassMock([FIRApp class]);
  240. OCMStub([_mockAuth app]).andReturn(_mockApp);
  241. _mockOptions = OCMClassMock([FIROptions class]);
  242. OCMStub([(FIRApp *)_mockApp options]).andReturn(_mockOptions);
  243. OCMStub([_mockOptions googleAppID]).andReturn(kFakeFirebaseAppID);
  244. _mockAPNSTokenManager = OCMClassMock([FIRAuthAPNSTokenManager class]);
  245. OCMStub([_mockAuth tokenManager]).andReturn(_mockAPNSTokenManager);
  246. _mockAppCredentialManager = OCMClassMock([FIRAuthAppCredentialManager class]);
  247. OCMStub([_mockAuth appCredentialManager]).andReturn(_mockAppCredentialManager);
  248. _mockNotificationManager = OCMClassMock([FIRAuthNotificationManager class]);
  249. OCMStub([_mockAuth notificationManager]).andReturn(_mockNotificationManager);
  250. _mockURLPresenter = OCMClassMock([FIRAuthURLPresenter class]);
  251. OCMStub([_mockAuth authURLPresenter]).andReturn(_mockURLPresenter);
  252. id mockRequestConfiguration = OCMClassMock([FIRAuthRequestConfiguration class]);
  253. OCMStub([_mockAuth requestConfiguration]).andReturn(mockRequestConfiguration);
  254. OCMStub([mockRequestConfiguration APIKey]).andReturn(kFakeAPIKey);
  255. }
  256. - (void)tearDown {
  257. [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
  258. [super tearDown];
  259. }
  260. // We're still testing deprecated `verifyPhoneNumber:completion:` extensively.
  261. #pragma clang diagnostic push
  262. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  263. /** @fn testCredentialWithVerificationID
  264. @brief Tests the @c credentialWithToken method to make sure that it returns a valid
  265. FIRAuthCredential instance.
  266. */
  267. - (void)testCredentialWithVerificationID {
  268. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  269. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  270. FIRPhoneAuthCredential *credential =
  271. [_provider credentialWithVerificationID:kTestVerificationID
  272. verificationCode:kTestVerificationCode];
  273. XCTAssertEqualObjects(credential.verificationID, kTestVerificationID);
  274. XCTAssertEqualObjects(credential.verificationCode, kTestVerificationCode);
  275. XCTAssertNil(credential.temporaryProof);
  276. XCTAssertNil(credential.phoneNumber);
  277. }
  278. /** @fn testVerifyEmptyPhoneNumber
  279. @brief Tests a failed invocation @c verifyPhoneNumber:completion: because an empty phone
  280. number was provided.
  281. */
  282. - (void)testVerifyEmptyPhoneNumber {
  283. [self mockBundleWithURLScheme:kFakeReverseClientID];
  284. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  285. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  286. // Empty phone number is checked on the client side so no backend RPC is mocked.
  287. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  288. [_provider verifyPhoneNumber:@""
  289. UIDelegate:nil
  290. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  291. XCTAssertNotNil(error);
  292. XCTAssertEqual(error.code, FIRAuthErrorCodeMissingPhoneNumber);
  293. [expectation fulfill];
  294. }];
  295. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  296. }
  297. /** @fn testVerifyInvalidPhoneNumber
  298. @brief Tests a failed invocation @c verifyPhoneNumber:completion: because an invalid phone
  299. number was provided.
  300. */
  301. - (void)testVerifyInvalidPhoneNumber {
  302. [self mockBundleWithURLScheme:kFakeReverseClientID];
  303. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  304. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  305. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  306. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  307. callback(YES);
  308. });
  309. OCMStub([_mockAppCredentialManager credential])
  310. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  311. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  312. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  313. FIRSendVerificationCodeResponseCallback callback) {
  314. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  315. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  316. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  317. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  318. callback(nil, [FIRAuthErrorUtils invalidPhoneNumberErrorWithMessage:nil]);
  319. });
  320. });
  321. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  322. [_provider verifyPhoneNumber:kTestPhoneNumber
  323. UIDelegate:nil
  324. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  325. XCTAssertTrue([NSThread isMainThread]);
  326. XCTAssertNil(verificationID);
  327. XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidPhoneNumber);
  328. [expectation fulfill];
  329. }];
  330. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  331. OCMVerifyAll(_mockBackend);
  332. OCMVerifyAll(_mockNotificationManager);
  333. OCMVerifyAll(_mockAppCredentialManager);
  334. }
  335. /** @fn testVerifyPhoneNumber
  336. @brief Tests a successful invocation of @c verifyPhoneNumber:completion:.
  337. */
  338. - (void)testVerifyPhoneNumber {
  339. [self mockBundleWithURLScheme:kFakeReverseClientID];
  340. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  341. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  342. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  343. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  344. callback(YES);
  345. });
  346. OCMStub([_mockAppCredentialManager credential])
  347. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  348. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  349. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  350. FIRSendVerificationCodeResponseCallback callback) {
  351. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  352. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  353. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  354. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  355. id mockSendVerificationCodeResponse =
  356. OCMClassMock([FIRSendVerificationCodeResponse class]);
  357. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  358. callback(mockSendVerificationCodeResponse, nil);
  359. });
  360. });
  361. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  362. [_provider verifyPhoneNumber:kTestPhoneNumber
  363. UIDelegate:nil
  364. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  365. XCTAssertTrue([NSThread isMainThread]);
  366. XCTAssertNil(error);
  367. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  368. [expectation fulfill];
  369. }];
  370. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  371. OCMVerifyAll(_mockBackend);
  372. OCMVerifyAll(_mockNotificationManager);
  373. OCMVerifyAll(_mockAppCredentialManager);
  374. }
  375. /** @fn testVerifyPhoneNumberInTestMode
  376. @brief Tests a successful invocation of @c verifyPhoneNumber:completion: when app verification
  377. is disabled.
  378. */
  379. - (void)testVerifyPhoneNumberInTestMode {
  380. [self mockBundleWithURLScheme:kFakeReverseClientID];
  381. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  382. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  383. // Disable app verification.
  384. FIRAuthSettings *settings = [[FIRAuthSettings alloc] init];
  385. settings.appVerificationDisabledForTesting = YES;
  386. OCMStub([_mockAuth settings]).andReturn(settings);
  387. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  388. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  389. callback(YES);
  390. });
  391. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  392. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  393. FIRSendVerificationCodeResponseCallback callback) {
  394. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  395. // Assert that the app credential is nil when in test mode.
  396. XCTAssertNil(request.appCredential);
  397. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  398. id mockSendVerificationCodeResponse =
  399. OCMClassMock([FIRSendVerificationCodeResponse class]);
  400. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  401. callback(mockSendVerificationCodeResponse, nil);
  402. });
  403. });
  404. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  405. [_provider verifyPhoneNumber:kTestPhoneNumber
  406. UIDelegate:nil
  407. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  408. XCTAssertTrue([NSThread isMainThread]);
  409. XCTAssertNil(error);
  410. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  411. [expectation fulfill];
  412. }];
  413. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  414. OCMVerifyAll(_mockBackend);
  415. OCMVerifyAll(_mockNotificationManager);
  416. OCMVerifyAll(_mockAppCredentialManager);
  417. }
  418. /** @fn testVerifyPhoneNumberInTestModeFailure
  419. @brief Tests a failed invocation of @c verifyPhoneNumber:completion: when app verification
  420. is disabled.
  421. */
  422. - (void)testVerifyPhoneNumberInTestModeFailure {
  423. [self mockBundleWithURLScheme:kFakeReverseClientID];
  424. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  425. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  426. // Disable app verification.
  427. FIRAuthSettings *settings = [[FIRAuthSettings alloc] init];
  428. settings.appVerificationDisabledForTesting = YES;
  429. OCMStub([_mockAuth settings]).andReturn(settings);
  430. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  431. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  432. callback(YES);
  433. });
  434. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  435. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  436. FIRSendVerificationCodeResponseCallback callback) {
  437. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  438. // Assert that the app credential is nil when in test mode.
  439. XCTAssertNil(request.appCredential);
  440. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  441. NSError *underlying = [NSError errorWithDomain:@"Test Error" code:1 userInfo:nil];
  442. callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:underlying]);
  443. });
  444. });
  445. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  446. [_provider verifyPhoneNumber:kTestPhoneNumber
  447. UIDelegate:nil
  448. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  449. XCTAssertTrue([NSThread isMainThread]);
  450. XCTAssertNil(verificationID);
  451. XCTAssertEqual(error.code, FIRAuthErrorCodeNetworkError);
  452. [expectation fulfill];
  453. }];
  454. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  455. OCMVerifyAll(_mockBackend);
  456. OCMVerifyAll(_mockNotificationManager);
  457. OCMVerifyAll(_mockAppCredentialManager);
  458. }
  459. /** @fn testVerifyPhoneNumberUIDelegateFirebaseAppIdFlow
  460. @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion:.
  461. */
  462. - (void)testVerifyPhoneNumberUIDelegateFirebaseAppIdFlow {
  463. [self mockBundleWithURLScheme:kFakeEncodedFirebaseAppID];
  464. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  465. // Simulate missing app token error.
  466. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  467. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  468. callback(YES);
  469. });
  470. [self mockMissingAPNSToken];
  471. // Fall back to the reCAPTCHA flow.
  472. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  473. [self verifyReCAPTCHAVerificationFlowWithUIDelegate:mockUIDelegate
  474. clientID:nil
  475. firebaseAppID:kFakeFirebaseAppID];
  476. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  477. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  478. FIRSendVerificationCodeResponseCallback callback) {
  479. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  480. XCTAssertNil(request.appCredential);
  481. XCTAssertEqualObjects(request.reCAPTCHAToken, kFakeReCAPTCHAToken);
  482. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  483. id mockSendVerificationCodeResponse =
  484. OCMClassMock([FIRSendVerificationCodeResponse class]);
  485. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  486. callback(mockSendVerificationCodeResponse, nil);
  487. });
  488. });
  489. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  490. [_provider verifyPhoneNumber:kTestPhoneNumber
  491. UIDelegate:mockUIDelegate
  492. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  493. XCTAssertTrue([NSThread isMainThread]);
  494. XCTAssertNil(error);
  495. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  496. [expectation fulfill];
  497. }];
  498. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  499. OCMVerifyAll(_mockBackend);
  500. OCMVerifyAll(_mockNotificationManager);
  501. }
  502. /** @fn testVerifyPhoneNumberUIDelegateFirebaseAppIdWhileClientIdPresentFlow
  503. @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion: when the
  504. client ID is present in the plist file, but the encoded app ID is the registered custom URL
  505. scheme.
  506. */
  507. - (void)testVerifyPhoneNumberUIDelegateFirebaseAppIdWhileClientIdPresentFlow {
  508. [self mockBundleWithURLScheme:kFakeEncodedFirebaseAppID];
  509. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  510. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  511. // Simulate missing app token error.
  512. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  513. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  514. callback(YES);
  515. });
  516. [self mockMissingAPNSToken];
  517. // Fall back to the reCAPTCHA flow.
  518. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  519. [self verifyReCAPTCHAVerificationFlowWithUIDelegate:mockUIDelegate
  520. clientID:nil
  521. firebaseAppID:kFakeFirebaseAppID];
  522. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  523. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  524. FIRSendVerificationCodeResponseCallback callback) {
  525. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  526. XCTAssertNil(request.appCredential);
  527. XCTAssertEqualObjects(request.reCAPTCHAToken, kFakeReCAPTCHAToken);
  528. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  529. id mockSendVerificationCodeResponse =
  530. OCMClassMock([FIRSendVerificationCodeResponse class]);
  531. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  532. callback(mockSendVerificationCodeResponse, nil);
  533. });
  534. });
  535. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  536. [_provider verifyPhoneNumber:kTestPhoneNumber
  537. UIDelegate:mockUIDelegate
  538. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  539. XCTAssertTrue([NSThread isMainThread]);
  540. XCTAssertNil(error);
  541. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  542. [expectation fulfill];
  543. }];
  544. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  545. OCMVerifyAll(_mockBackend);
  546. OCMVerifyAll(_mockNotificationManager);
  547. }
  548. /** @fn testVerifyPhoneNumberUIDelegateClientIdFlow
  549. @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion:.
  550. */
  551. - (void)testVerifyPhoneNumberUIDelegateClientIdFlow {
  552. [self mockBundleWithURLScheme:kFakeReverseClientID];
  553. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  554. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  555. // Simulate missing app token error.
  556. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  557. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  558. callback(YES);
  559. });
  560. [self mockMissingAPNSToken];
  561. // Fall back to the reCAPTCHA flow.
  562. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  563. [self verifyReCAPTCHAVerificationFlowWithUIDelegate:mockUIDelegate
  564. clientID:kFakeClientID
  565. firebaseAppID:nil];
  566. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  567. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  568. FIRSendVerificationCodeResponseCallback callback) {
  569. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  570. XCTAssertNil(request.appCredential);
  571. XCTAssertEqualObjects(request.reCAPTCHAToken, kFakeReCAPTCHAToken);
  572. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  573. id mockSendVerificationCodeResponse =
  574. OCMClassMock([FIRSendVerificationCodeResponse class]);
  575. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  576. callback(mockSendVerificationCodeResponse, nil);
  577. });
  578. });
  579. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  580. [_provider verifyPhoneNumber:kTestPhoneNumber
  581. UIDelegate:mockUIDelegate
  582. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  583. XCTAssertTrue([NSThread isMainThread]);
  584. XCTAssertNil(error);
  585. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  586. [expectation fulfill];
  587. }];
  588. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  589. OCMVerifyAll(_mockBackend);
  590. OCMVerifyAll(_mockNotificationManager);
  591. }
  592. /** @fn testVerifyPhoneNumberUIDelegateInvalidClientID
  593. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  594. invalid client ID error.
  595. */
  596. - (void)testVerifyPhoneNumberUIDelegateInvalidClientID {
  597. [self mockBundleWithURLScheme:kFakeReverseClientID];
  598. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  599. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  600. // Simulate missing app token error.
  601. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  602. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  603. callback(YES);
  604. });
  605. [self mockMissingAPNSToken];
  606. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  607. .andCallBlock2(
  608. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  609. XCTAssertNotNil(request);
  610. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  611. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  612. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  613. kFakeAuthorizedDomain
  614. ]);
  615. callback(mockGetProjectConfigResponse, nil);
  616. });
  617. });
  618. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  619. // Expect view controller presentation by UIDelegate.
  620. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  621. UIDelegate:mockUIDelegate
  622. callbackMatcher:OCMOCK_ANY
  623. completion:OCMOCK_ANY])
  624. .andDo(^(NSInvocation *invocation) {
  625. __unsafe_unretained id unretainedArgument;
  626. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  627. // `completion` is at index 5
  628. [invocation getArgument:&unretainedArgument atIndex:5];
  629. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  630. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  631. completion([NSURL URLWithString:kFakeRedirectURLStringInvalidClientID], nil);
  632. });
  633. });
  634. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  635. [_provider verifyPhoneNumber:kTestPhoneNumber
  636. UIDelegate:mockUIDelegate
  637. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  638. XCTAssertTrue([NSThread isMainThread]);
  639. XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidClientID);
  640. XCTAssertNil(verificationID);
  641. [expectation fulfill];
  642. }];
  643. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  644. OCMVerifyAll(_mockBackend);
  645. OCMVerifyAll(_mockNotificationManager);
  646. }
  647. /** @fn testVerifyPhoneNumberUIDelegateWebNetworkRequestFailed
  648. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in a web
  649. network request failed error.
  650. */
  651. - (void)testVerifyPhoneNumberUIDelegateNetworkRequestFailed {
  652. [self mockBundleWithURLScheme:kFakeReverseClientID];
  653. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  654. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  655. // Simulate missing app token error.
  656. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  657. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  658. callback(YES);
  659. });
  660. [self mockMissingAPNSToken];
  661. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  662. .andCallBlock2(
  663. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  664. XCTAssertNotNil(request);
  665. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  666. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  667. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  668. kFakeAuthorizedDomain
  669. ]);
  670. callback(mockGetProjectConfigResponse, nil);
  671. });
  672. });
  673. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  674. // Expect view controller presentation by UIDelegate.
  675. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  676. UIDelegate:mockUIDelegate
  677. callbackMatcher:OCMOCK_ANY
  678. completion:OCMOCK_ANY])
  679. .andDo(^(NSInvocation *invocation) {
  680. __unsafe_unretained id unretainedArgument;
  681. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  682. // `completion` is at index 5
  683. [invocation getArgument:&unretainedArgument atIndex:5];
  684. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  685. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  686. completion([NSURL URLWithString:kFakeRedirectURLStringWebNetworkRequestFailed], nil);
  687. });
  688. });
  689. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  690. [_provider verifyPhoneNumber:kTestPhoneNumber
  691. UIDelegate:mockUIDelegate
  692. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  693. XCTAssertTrue([NSThread isMainThread]);
  694. XCTAssertEqual(error.code, FIRAuthErrorCodeWebNetworkRequestFailed);
  695. XCTAssertNil(verificationID);
  696. [expectation fulfill];
  697. }];
  698. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  699. OCMVerifyAll(_mockBackend);
  700. OCMVerifyAll(_mockNotificationManager);
  701. }
  702. /** @fn testVerifyPhoneNumberUIDelegateWebInternalError
  703. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in a web
  704. internal error.
  705. */
  706. - (void)testVerifyPhoneNumberUIDelegateWebInternalError {
  707. [self mockBundleWithURLScheme:kFakeReverseClientID];
  708. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  709. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  710. // Simulate missing app token error.
  711. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  712. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  713. callback(YES);
  714. });
  715. [self mockMissingAPNSToken];
  716. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  717. .andCallBlock2(
  718. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  719. XCTAssertNotNil(request);
  720. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  721. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  722. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  723. kFakeAuthorizedDomain
  724. ]);
  725. callback(mockGetProjectConfigResponse, nil);
  726. });
  727. });
  728. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  729. // Expect view controller presentation by UIDelegate.
  730. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  731. UIDelegate:mockUIDelegate
  732. callbackMatcher:OCMOCK_ANY
  733. completion:OCMOCK_ANY])
  734. .andDo(^(NSInvocation *invocation) {
  735. __unsafe_unretained id unretainedArgument;
  736. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  737. // `completion` is at index 5
  738. [invocation getArgument:&unretainedArgument atIndex:5];
  739. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  740. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  741. completion([NSURL URLWithString:kFakeRedirectURLStringWebInternalError], nil);
  742. });
  743. });
  744. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  745. [_provider verifyPhoneNumber:kTestPhoneNumber
  746. UIDelegate:mockUIDelegate
  747. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  748. XCTAssertTrue([NSThread isMainThread]);
  749. XCTAssertEqual(error.code, FIRAuthErrorCodeWebInternalError);
  750. XCTAssertNil(verificationID);
  751. [expectation fulfill];
  752. }];
  753. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  754. OCMVerifyAll(_mockBackend);
  755. OCMVerifyAll(_mockNotificationManager);
  756. }
  757. /** @fn testVerifyPhoneNumberUIDelegateUnexpectedError
  758. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  759. invalid client ID.
  760. */
  761. - (void)testVerifyPhoneNumberUIDelegateUnexpectedError {
  762. [self mockBundleWithURLScheme:kFakeReverseClientID];
  763. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  764. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  765. // Simulate missing app token error.
  766. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  767. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  768. callback(YES);
  769. });
  770. [self mockMissingAPNSToken];
  771. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  772. .andCallBlock2(
  773. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  774. XCTAssertNotNil(request);
  775. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  776. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  777. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  778. kFakeAuthorizedDomain
  779. ]);
  780. callback(mockGetProjectConfigResponse, nil);
  781. });
  782. });
  783. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  784. // Expect view controller presentation by UIDelegate.
  785. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  786. UIDelegate:mockUIDelegate
  787. callbackMatcher:OCMOCK_ANY
  788. completion:OCMOCK_ANY])
  789. .andDo(^(NSInvocation *invocation) {
  790. __unsafe_unretained id unretainedArgument;
  791. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  792. // `completion` is at index 5
  793. [invocation getArgument:&unretainedArgument atIndex:5];
  794. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  795. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  796. completion([NSURL URLWithString:kFakeRedirectURLStringUnknownError], nil);
  797. });
  798. });
  799. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  800. [_provider
  801. verifyPhoneNumber:kTestPhoneNumber
  802. UIDelegate:mockUIDelegate
  803. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  804. XCTAssertTrue([NSThread isMainThread]);
  805. XCTAssertEqual(error.code, FIRAuthErrorCodeAppVerificationUserInteractionFailure);
  806. XCTAssertNil(verificationID);
  807. [expectation fulfill];
  808. }];
  809. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  810. OCMVerifyAll(_mockBackend);
  811. OCMVerifyAll(_mockNotificationManager);
  812. }
  813. /** @fn testVerifyPhoneNumberUIDelegateUnstructuredError
  814. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  815. error being surfaced with a default NSLocalizedFailureReasonErrorKey due to an unexpected
  816. structure of the error response.
  817. */
  818. - (void)testVerifyPhoneNumberUIDelegateUnstructuredError {
  819. [self mockBundleWithURLScheme:kFakeReverseClientID];
  820. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  821. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  822. // Simulate missing app token error.
  823. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  824. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  825. callback(YES);
  826. });
  827. [self mockMissingAPNSToken];
  828. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  829. .andCallBlock2(
  830. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  831. XCTAssertNotNil(request);
  832. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  833. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  834. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  835. kFakeAuthorizedDomain
  836. ]);
  837. callback(mockGetProjectConfigResponse, nil);
  838. });
  839. });
  840. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  841. // Expect view controller presentation by UIDelegate.
  842. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  843. UIDelegate:mockUIDelegate
  844. callbackMatcher:OCMOCK_ANY
  845. completion:OCMOCK_ANY])
  846. .andDo(^(NSInvocation *invocation) {
  847. __unsafe_unretained id unretainedArgument;
  848. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  849. // `completion` is at index 5
  850. [invocation getArgument:&unretainedArgument atIndex:5];
  851. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  852. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  853. completion([NSURL URLWithString:kFakeRedirectURLStringUnstructuredError], nil);
  854. });
  855. });
  856. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  857. [_provider
  858. verifyPhoneNumber:kTestPhoneNumber
  859. UIDelegate:mockUIDelegate
  860. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  861. XCTAssertTrue([NSThread isMainThread]);
  862. XCTAssertEqual(error.code, FIRAuthErrorCodeAppVerificationUserInteractionFailure);
  863. XCTAssertNil(verificationID);
  864. [expectation fulfill];
  865. }];
  866. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  867. OCMVerifyAll(_mockBackend);
  868. OCMVerifyAll(_mockNotificationManager);
  869. }
  870. /** @fn testVerifyPhoneNumberUIDelegateRaiseException
  871. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  872. exception.
  873. */
  874. - (void)testVerifyPhoneNumberUIDelegateRaiseException {
  875. [self mockBundleWithURLScheme:@"badscheme"];
  876. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  877. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  878. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  879. XCTAssertThrows([_provider
  880. verifyPhoneNumber:kTestPhoneNumber
  881. UIDelegate:mockUIDelegate
  882. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  883. XCTFail(@"Shouldn't call completion here.");
  884. }]);
  885. }
  886. /** @fn testNotForwardingNotification
  887. @brief Tests returning an error for the app failing to forward notification.
  888. */
  889. - (void)testNotForwardingNotification {
  890. [self mockBundleWithURLScheme:kFakeReverseClientID];
  891. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  892. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  893. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  894. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  895. callback(NO);
  896. });
  897. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  898. [_provider verifyPhoneNumber:kTestPhoneNumber
  899. UIDelegate:nil
  900. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  901. XCTAssertTrue([NSThread isMainThread]);
  902. XCTAssertNil(verificationID);
  903. XCTAssertEqual(error.code, FIRAuthErrorCodeNotificationNotForwarded);
  904. [expectation fulfill];
  905. }];
  906. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  907. OCMVerifyAll(_mockNotificationManager);
  908. }
  909. /** @fn testMissingAPNSToken
  910. @brief Tests returning an error for the app failing to provide an APNS device token.
  911. */
  912. - (void)testMissingAPNSToken {
  913. [self mockBundleWithURLScheme:kFakeReverseClientID];
  914. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  915. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  916. // Simulate missing app token error.
  917. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  918. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  919. callback(YES);
  920. });
  921. [self mockMissingAPNSToken];
  922. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  923. .andCallBlock2(
  924. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  925. XCTAssertNotNil(request);
  926. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  927. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  928. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  929. kFakeAuthorizedDomain
  930. ]);
  931. callback(mockGetProjectConfigResponse, nil);
  932. });
  933. });
  934. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  935. // Expect view controller presentation by UIDelegate.
  936. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  937. UIDelegate:mockUIDelegate
  938. callbackMatcher:OCMOCK_ANY
  939. completion:OCMOCK_ANY])
  940. .andDo(^(NSInvocation *invocation) {
  941. __unsafe_unretained id unretainedArgument;
  942. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  943. // `completion` is at index 5
  944. [invocation getArgument:&unretainedArgument atIndex:5];
  945. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  946. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  947. completion(nil, [NSError errorWithDomain:FIRAuthErrorDomain
  948. code:FIRAuthErrorCodeMissingAppToken
  949. userInfo:nil]);
  950. });
  951. });
  952. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  953. [_provider verifyPhoneNumber:kTestPhoneNumber
  954. UIDelegate:mockUIDelegate
  955. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  956. XCTAssertTrue([NSThread isMainThread]);
  957. XCTAssertEqual(error.code, FIRAuthErrorCodeMissingAppToken);
  958. XCTAssertNil(verificationID);
  959. [expectation fulfill];
  960. }];
  961. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  962. OCMVerifyAll(_mockBackend);
  963. OCMVerifyAll(_mockNotificationManager);
  964. }
  965. /** @fn testVerifyPhoneNumberUIDelegateiOSSecretMissingFlow
  966. @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion: that falls
  967. back to the reCAPTCHA flow when the push notification is not received before the timeout.
  968. */
  969. - (void)testVerifyPhoneNumberUIDelegateiOSSecretMissingFlow {
  970. [self mockBundleWithURLScheme:kFakeEncodedFirebaseAppID];
  971. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  972. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  973. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  974. callback(YES);
  975. });
  976. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  977. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  978. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  979. type:FIRAuthAPNSTokenTypeProd];
  980. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  981. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  982. callback(token, nil);
  983. });
  984. // Expect verify client request to the backend.
  985. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  986. .andCallBlock2(^(FIRVerifyClientRequest *request, FIRVerifyClientResponseCallback callback) {
  987. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  988. XCTAssertFalse(request.isSandbox);
  989. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  990. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  991. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  992. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  993. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  994. callback(mockVerifyClientResponse, nil);
  995. });
  996. });
  997. // Mock failing to receive the push notification before the timeout.
  998. OCMExpect([_mockAppCredentialManager didStartVerificationWithReceipt:OCMOCK_ANY
  999. timeout:0
  1000. callback:OCMOCK_ANY])
  1001. .ignoringNonObjectArgs()
  1002. .andCallIdDoubleIdBlock(
  1003. ^(NSString *receipt, NSTimeInterval timeout, FIRAuthAppCredentialCallback callback) {
  1004. XCTAssertEqualObjects(receipt, kTestReceipt);
  1005. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get
  1006. // passed into the block either, so we can't verify it here.
  1007. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1008. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:nil]);
  1009. });
  1010. });
  1011. // Fall back to the reCAPTCHA flow.
  1012. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  1013. [self verifyReCAPTCHAVerificationFlowWithUIDelegate:mockUIDelegate
  1014. clientID:nil
  1015. firebaseAppID:kFakeFirebaseAppID];
  1016. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1017. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1018. FIRSendVerificationCodeResponseCallback callback) {
  1019. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1020. XCTAssertNil(request.appCredential);
  1021. XCTAssertEqualObjects(request.reCAPTCHAToken, kFakeReCAPTCHAToken);
  1022. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1023. id mockSendVerificationCodeResponse =
  1024. OCMClassMock([FIRSendVerificationCodeResponse class]);
  1025. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  1026. callback(mockSendVerificationCodeResponse, nil);
  1027. });
  1028. });
  1029. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1030. [_provider verifyPhoneNumber:kTestPhoneNumber
  1031. UIDelegate:mockUIDelegate
  1032. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1033. XCTAssertTrue([NSThread isMainThread]);
  1034. XCTAssertNil(error);
  1035. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  1036. [expectation fulfill];
  1037. }];
  1038. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1039. OCMVerifyAll(_mockBackend);
  1040. OCMVerifyAll(_mockNotificationManager);
  1041. }
  1042. /** @fn testVerifyClient
  1043. @brief Tests verifying client before sending verification code.
  1044. */
  1045. - (void)testVerifyClient {
  1046. [self mockBundleWithURLScheme:kFakeReverseClientID];
  1047. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1048. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  1049. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  1050. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  1051. callback(YES);
  1052. });
  1053. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  1054. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  1055. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  1056. type:FIRAuthAPNSTokenTypeProd];
  1057. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  1058. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  1059. callback(token, nil);
  1060. });
  1061. // Expect verify client request to the backend.
  1062. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  1063. .andCallBlock2(^(FIRVerifyClientRequest *request, FIRVerifyClientResponseCallback callback) {
  1064. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  1065. XCTAssertFalse(request.isSandbox);
  1066. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1067. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  1068. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  1069. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  1070. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  1071. callback(mockVerifyClientResponse, nil);
  1072. });
  1073. });
  1074. // Mock receiving of push notification.
  1075. OCMExpect([_mockAppCredentialManager didStartVerificationWithReceipt:OCMOCK_ANY
  1076. timeout:0
  1077. callback:OCMOCK_ANY])
  1078. .ignoringNonObjectArgs()
  1079. .andCallIdDoubleIdBlock(^(NSString *receipt, NSTimeInterval timeout,
  1080. FIRAuthAppCredentialCallback callback) {
  1081. XCTAssertEqualObjects(receipt, kTestReceipt);
  1082. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get
  1083. // passed into the block either, so we can't verify it here.
  1084. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1085. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  1086. });
  1087. });
  1088. // Expect send verification code request to the backend.
  1089. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1090. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1091. FIRSendVerificationCodeResponseCallback callback) {
  1092. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1093. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  1094. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  1095. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1096. id mockSendVerificationCodeResponse =
  1097. OCMClassMock([FIRSendVerificationCodeResponse class]);
  1098. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  1099. callback(mockSendVerificationCodeResponse, nil);
  1100. });
  1101. });
  1102. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1103. [_provider verifyPhoneNumber:kTestPhoneNumber
  1104. UIDelegate:nil
  1105. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1106. XCTAssertTrue([NSThread isMainThread]);
  1107. XCTAssertNil(error);
  1108. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  1109. [expectation fulfill];
  1110. }];
  1111. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1112. OCMVerifyAll(_mockBackend);
  1113. OCMVerifyAll(_mockNotificationManager);
  1114. OCMVerifyAll(_mockAppCredentialManager);
  1115. OCMVerifyAll(_mockAPNSTokenManager);
  1116. }
  1117. /** @fn testSendVerificationCodeFailedRetry
  1118. @brief Tests failed retry after failing to send verification code.
  1119. */
  1120. - (void)testSendVerificationCodeFailedRetry {
  1121. [self mockBundleWithURLScheme:kFakeReverseClientID];
  1122. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1123. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  1124. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  1125. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  1126. callback(YES);
  1127. });
  1128. // Expect twice due to null check consumes one expectation.
  1129. OCMExpect([_mockAppCredentialManager credential])
  1130. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  1131. secret:kTestOldSecret]);
  1132. OCMExpect([_mockAppCredentialManager credential])
  1133. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  1134. secret:kTestOldSecret]);
  1135. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  1136. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  1137. type:FIRAuthAPNSTokenTypeProd];
  1138. // Expect first sendVerificationCode request to the backend, with request containing old app
  1139. // credential.
  1140. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1141. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1142. FIRSendVerificationCodeResponseCallback callback) {
  1143. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1144. XCTAssertEqualObjects(request.appCredential.receipt, kTestOldReceipt);
  1145. XCTAssertEqualObjects(request.appCredential.secret, kTestOldSecret);
  1146. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1147. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  1148. });
  1149. });
  1150. // Expect send verification code request to the backend, with request containing new app
  1151. // credential data.
  1152. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1153. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1154. FIRSendVerificationCodeResponseCallback callback) {
  1155. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1156. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  1157. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  1158. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1159. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  1160. });
  1161. });
  1162. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  1163. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  1164. callback(token, nil);
  1165. });
  1166. // Expect verify client request to the backend.
  1167. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  1168. .andCallBlock2(^(FIRVerifyClientRequest *request, FIRVerifyClientResponseCallback callback) {
  1169. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  1170. XCTAssertFalse(request.isSandbox);
  1171. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1172. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  1173. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  1174. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  1175. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  1176. callback(mockVerifyClientResponse, nil);
  1177. });
  1178. });
  1179. // Mock receiving of push notification.
  1180. OCMStub([_mockAppCredentialManager didStartVerificationWithReceipt:OCMOCK_ANY
  1181. timeout:0
  1182. callback:OCMOCK_ANY])
  1183. .ignoringNonObjectArgs()
  1184. .andCallIdDoubleIdBlock(^(NSString *receipt, NSTimeInterval timeout,
  1185. FIRAuthAppCredentialCallback callback) {
  1186. XCTAssertEqualObjects(receipt, kTestReceipt);
  1187. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get
  1188. // passed into the block either, so we can't verify it here.
  1189. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1190. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  1191. });
  1192. });
  1193. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1194. [_provider verifyPhoneNumber:kTestPhoneNumber
  1195. UIDelegate:nil
  1196. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1197. XCTAssertTrue([NSThread isMainThread]);
  1198. XCTAssertNil(verificationID);
  1199. XCTAssertEqual(error.code, FIRAuthErrorCodeInternalError);
  1200. [expectation fulfill];
  1201. }];
  1202. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1203. OCMVerifyAll(_mockBackend);
  1204. OCMVerifyAll(_mockNotificationManager);
  1205. OCMVerifyAll(_mockAppCredentialManager);
  1206. OCMVerifyAll(_mockAPNSTokenManager);
  1207. }
  1208. /** @fn testSendVerificationCodeSuccessFulRetry
  1209. @brief Tests successful retry after failing to send verification code.
  1210. */
  1211. - (void)testSendVerificationCodeSuccessfulRetry {
  1212. [self mockBundleWithURLScheme:kFakeReverseClientID];
  1213. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1214. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  1215. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  1216. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  1217. callback(YES);
  1218. });
  1219. // Expect twice due to null check consumes one expectation.
  1220. OCMExpect([_mockAppCredentialManager credential])
  1221. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  1222. secret:kTestOldSecret]);
  1223. OCMExpect([_mockAppCredentialManager credential])
  1224. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  1225. secret:kTestOldSecret]);
  1226. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  1227. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  1228. type:FIRAuthAPNSTokenTypeProd];
  1229. // Expect first sendVerificationCode request to the backend, with request containing old app
  1230. // credential.
  1231. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1232. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1233. FIRSendVerificationCodeResponseCallback callback) {
  1234. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1235. XCTAssertEqualObjects(request.appCredential.receipt, kTestOldReceipt);
  1236. XCTAssertEqualObjects(request.appCredential.secret, kTestOldSecret);
  1237. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1238. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  1239. });
  1240. });
  1241. // Expect send verification code request to the backend, with request containing new app
  1242. // credential data.
  1243. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1244. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1245. FIRSendVerificationCodeResponseCallback callback) {
  1246. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1247. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  1248. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  1249. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1250. id mockSendVerificationCodeResponse =
  1251. OCMClassMock([FIRSendVerificationCodeResponse class]);
  1252. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  1253. callback(mockSendVerificationCodeResponse, nil);
  1254. });
  1255. });
  1256. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  1257. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  1258. callback(token, nil);
  1259. });
  1260. // Expect verify client request to the backend.
  1261. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  1262. .andCallBlock2(^(FIRVerifyClientRequest *request, FIRVerifyClientResponseCallback callback) {
  1263. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  1264. XCTAssertFalse(request.isSandbox);
  1265. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1266. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  1267. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  1268. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  1269. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  1270. callback(mockVerifyClientResponse, nil);
  1271. });
  1272. });
  1273. // Mock receiving of push notification.
  1274. OCMStub([_mockAppCredentialManager didStartVerificationWithReceipt:OCMOCK_ANY
  1275. timeout:0
  1276. callback:OCMOCK_ANY])
  1277. .ignoringNonObjectArgs()
  1278. .andCallIdDoubleIdBlock(^(NSString *receipt, NSTimeInterval timeout,
  1279. FIRAuthAppCredentialCallback callback) {
  1280. XCTAssertEqualObjects(receipt, kTestReceipt);
  1281. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get
  1282. // passed into the block either, so we can't verify it here.
  1283. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1284. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  1285. });
  1286. });
  1287. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1288. [_provider verifyPhoneNumber:kTestPhoneNumber
  1289. UIDelegate:nil
  1290. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1291. XCTAssertNil(error);
  1292. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  1293. [expectation fulfill];
  1294. }];
  1295. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1296. OCMVerifyAll(_mockBackend);
  1297. OCMVerifyAll(_mockNotificationManager);
  1298. OCMVerifyAll(_mockAppCredentialManager);
  1299. OCMVerifyAll(_mockAPNSTokenManager);
  1300. }
  1301. - (void)mockBundleWithURLScheme:(NSString *)URLScheme {
  1302. id mockBundle = OCMClassMock([NSBundle class]);
  1303. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1304. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1305. @{@"CFBundleURLSchemes" : @[ URLScheme ]}
  1306. ]);
  1307. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1308. }
  1309. - (void)mockMissingAPNSToken {
  1310. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  1311. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  1312. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  1313. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  1314. code:FIRAuthErrorCodeMissingAppToken
  1315. userInfo:nil];
  1316. callback(nil, error);
  1317. });
  1318. }
  1319. - (void)verifyReCAPTCHAVerificationFlowWithUIDelegate:(id<FIRAuthUIDelegate>)UIDelegate
  1320. clientID:(nullable NSString *)clientID
  1321. firebaseAppID:(nullable NSString *)firebaseAppID {
  1322. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  1323. .andCallBlock2(
  1324. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  1325. XCTAssertNotNil(request);
  1326. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1327. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  1328. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  1329. kFakeAuthorizedDomain
  1330. ]);
  1331. callback(mockGetProjectConfigResponse, nil);
  1332. });
  1333. });
  1334. // Expect view controller presentation by UIDelegate.
  1335. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  1336. UIDelegate:UIDelegate
  1337. callbackMatcher:OCMOCK_ANY
  1338. completion:OCMOCK_ANY])
  1339. .andDo(^(NSInvocation *invocation) {
  1340. __unsafe_unretained id unretainedArgument;
  1341. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  1342. // `presentURL` is at index 2.
  1343. [invocation getArgument:&unretainedArgument atIndex:2];
  1344. NSURL *presentURL = unretainedArgument;
  1345. XCTAssertEqualObjects(presentURL.scheme, @"https");
  1346. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  1347. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  1348. NSURLComponents *actualURLComponents = [NSURLComponents componentsWithURL:presentURL
  1349. resolvingAgainstBaseURL:NO];
  1350. NSArray<NSURLQueryItem *> *queryItems = [actualURLComponents queryItems];
  1351. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"ibi" from:queryItems],
  1352. kFakeBundleID);
  1353. if (clientID) {
  1354. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"clientId" from:queryItems],
  1355. clientID);
  1356. } else if (firebaseAppID) {
  1357. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"appId" from:queryItems],
  1358. firebaseAppID);
  1359. }
  1360. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"apiKey" from:queryItems],
  1361. kFakeAPIKey);
  1362. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"authType" from:queryItems],
  1363. @"verifyApp");
  1364. XCTAssertNotNil([FIRAuthWebUtils queryItemValue:@"v" from:queryItems]);
  1365. // `callbackMatcher` is at index 4
  1366. [invocation getArgument:&unretainedArgument atIndex:4];
  1367. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  1368. NSMutableString *redirectURL;
  1369. if (clientID) {
  1370. redirectURL = [NSMutableString
  1371. stringWithString:[kFakeReverseClientID stringByAppendingString:
  1372. kFakeRedirectURLStringWithReCAPTCHAToken]];
  1373. } else if (firebaseAppID) {
  1374. redirectURL = [NSMutableString
  1375. stringWithString:
  1376. [kFakeEncodedFirebaseAppID
  1377. stringByAppendingString:kFakeRedirectURLStringWithReCAPTCHAToken]];
  1378. }
  1379. // Verify that the URL is rejected by the callback matcher without the event ID.
  1380. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  1381. [redirectURL appendString:@"%26eventId%3D"];
  1382. [redirectURL appendString:[FIRAuthWebUtils queryItemValue:@"eventId" from:queryItems]];
  1383. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  1384. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  1385. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  1386. NSURLComponents *components = [originalComponents copy];
  1387. components.query = @"https";
  1388. XCTAssertFalse(callbackMatcher([components URL]));
  1389. components = [originalComponents copy];
  1390. components.host = @"badhost";
  1391. XCTAssertFalse(callbackMatcher([components URL]));
  1392. components = [originalComponents copy];
  1393. components.path = @"badpath";
  1394. XCTAssertFalse(callbackMatcher([components URL]));
  1395. components = [originalComponents copy];
  1396. components.query = @"badquery";
  1397. XCTAssertFalse(callbackMatcher([components URL]));
  1398. // `completion` is at index 5
  1399. [invocation getArgument:&unretainedArgument atIndex:5];
  1400. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  1401. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1402. completion([NSURL URLWithString:[kFakeEncodedFirebaseAppID
  1403. stringByAppendingString:
  1404. kFakeRedirectURLStringWithReCAPTCHAToken]],
  1405. nil);
  1406. });
  1407. });
  1408. }
  1409. #pragma clang diagnostic pop // ignored "-Wdeprecated-declarations"
  1410. @end
  1411. NS_ASSUME_NONNULL_END
  1412. #endif