FIRPhoneAuthProviderTests.m 66 KB

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