FIRPhoneAuthProviderTests.m 62 KB

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