FIRPhoneAuthProviderTests.m 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299
  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. callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:[NSError new]]);
  405. });
  406. });
  407. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  408. [_provider verifyPhoneNumber:kTestPhoneNumber
  409. UIDelegate:nil
  410. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  411. XCTAssertTrue([NSThread isMainThread]);
  412. XCTAssertNil(verificationID);
  413. XCTAssertEqual(error.code, FIRAuthErrorCodeNetworkError);
  414. [expectation fulfill];
  415. }];
  416. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  417. OCMVerifyAll(_mockBackend);
  418. OCMVerifyAll(_mockNotificationManager);
  419. OCMVerifyAll(_mockAppCredentialManager);
  420. }
  421. /** @fn testVerifyPhoneNumberUIDelegate
  422. @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion:.
  423. */
  424. - (void)testVerifyPhoneNumberUIDelegate {
  425. id mockBundle = OCMClassMock([NSBundle class]);
  426. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  427. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  428. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  429. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  430. // Simulate missing app token error.
  431. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  432. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  433. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  434. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  435. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  436. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  437. code:FIRAuthErrorCodeMissingAppToken
  438. userInfo:nil];
  439. callback(nil, error);
  440. });
  441. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  442. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  443. FIRGetProjectConfigResponseCallback callback) {
  444. XCTAssertNotNil(request);
  445. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  446. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  447. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  448. andReturn(@[ kFakeAuthorizedDomain]);
  449. callback(mockGetProjectConfigResponse, nil);
  450. });
  451. });
  452. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  453. // Expect view controller presentation by UIDelegate.
  454. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  455. UIDelegate:mockUIDelegate
  456. callbackMatcher:OCMOCK_ANY
  457. completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
  458. __unsafe_unretained id unretainedArgument;
  459. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  460. // `presentURL` is at index 2.
  461. [invocation getArgument:&unretainedArgument atIndex:2];
  462. NSURL *presentURL = unretainedArgument;
  463. XCTAssertEqualObjects(presentURL.scheme, @"https");
  464. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  465. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  466. NSURLComponents *actualURLComponents = [NSURLComponents componentsWithURL:presentURL
  467. resolvingAgainstBaseURL:NO];
  468. NSArray<NSURLQueryItem *> *queryItems = [actualURLComponents queryItems];
  469. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"ibi" from:queryItems],
  470. kFakeBundleID);
  471. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"clientId" from:queryItems],
  472. kFakeClientID);
  473. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"apiKey" from:queryItems],
  474. kFakeAPIKey);
  475. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"authType" from:queryItems],
  476. @"verifyApp");
  477. XCTAssertNotNil([FIRAuthWebUtils queryItemValue:@"v" from:queryItems]);
  478. // `callbackMatcher` is at index 4
  479. [invocation getArgument:&unretainedArgument atIndex:4];
  480. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  481. NSMutableString *redirectURL =
  482. [NSMutableString stringWithString:kFakeRedirectURLStringWithReCAPTCHAToken];
  483. // Verify that the URL is rejected by the callback matcher without the event ID.
  484. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  485. [redirectURL appendString:@"%26eventId%3D"];
  486. [redirectURL appendString:[FIRAuthWebUtils queryItemValue:@"eventId"
  487. from:queryItems]];
  488. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  489. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  490. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  491. NSURLComponents *components = [originalComponents copy];
  492. components.query = @"https";
  493. XCTAssertFalse(callbackMatcher([components URL]));
  494. components = [originalComponents copy];
  495. components.host = @"badhost";
  496. XCTAssertFalse(callbackMatcher([components URL]));
  497. components = [originalComponents copy];
  498. components.path = @"badpath";
  499. XCTAssertFalse(callbackMatcher([components URL]));
  500. components = [originalComponents copy];
  501. components.query = @"badquery";
  502. XCTAssertFalse(callbackMatcher([components URL]));
  503. // `completion` is at index 5
  504. [invocation getArgument:&unretainedArgument atIndex:5];
  505. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  506. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  507. completion([NSURL URLWithString:kFakeRedirectURLStringWithReCAPTCHAToken], nil);
  508. });
  509. });
  510. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  511. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  512. FIRSendVerificationCodeResponseCallback callback) {
  513. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  514. XCTAssertNil(request.appCredential);
  515. XCTAssertEqualObjects(request.reCAPTCHAToken, kFakeReCAPTCHAToken);
  516. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  517. id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
  518. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  519. callback(mockSendVerificationCodeResponse, nil);
  520. });
  521. });
  522. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  523. [_provider verifyPhoneNumber:kTestPhoneNumber
  524. UIDelegate:mockUIDelegate
  525. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  526. XCTAssertTrue([NSThread isMainThread]);
  527. XCTAssertNil(error);
  528. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  529. [expectation fulfill];
  530. }];
  531. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  532. OCMVerifyAll(_mockBackend);
  533. OCMVerifyAll(_mockNotificationManager);
  534. }
  535. /** @fn testVerifyPhoneNumberUIDelegateInvalidClientID
  536. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  537. invalid client ID error.
  538. */
  539. - (void)testVerifyPhoneNumberUIDelegateInvalidClientID {
  540. id mockBundle = OCMClassMock([NSBundle class]);
  541. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  542. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  543. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  544. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  545. // Simulate missing app token error.
  546. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  547. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  548. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  549. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  550. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  551. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  552. code:FIRAuthErrorCodeMissingAppToken
  553. userInfo:nil];
  554. callback(nil, error);
  555. });
  556. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  557. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  558. FIRGetProjectConfigResponseCallback callback) {
  559. XCTAssertNotNil(request);
  560. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  561. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  562. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  563. andReturn(@[ kFakeAuthorizedDomain]);
  564. callback(mockGetProjectConfigResponse, nil);
  565. });
  566. });
  567. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  568. // Expect view controller presentation by UIDelegate.
  569. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  570. UIDelegate:mockUIDelegate
  571. callbackMatcher:OCMOCK_ANY
  572. completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
  573. __unsafe_unretained id unretainedArgument;
  574. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  575. // `completion` is at index 5
  576. [invocation getArgument:&unretainedArgument atIndex:5];
  577. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  578. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  579. completion([NSURL URLWithString:kFakeRedirectURLStringInvalidClientID], nil);
  580. });
  581. });
  582. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  583. [_provider verifyPhoneNumber:kTestPhoneNumber
  584. UIDelegate:mockUIDelegate
  585. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  586. XCTAssertTrue([NSThread isMainThread]);
  587. XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidClientID);
  588. XCTAssertNil(verificationID);
  589. [expectation fulfill];
  590. }];
  591. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  592. OCMVerifyAll(_mockBackend);
  593. OCMVerifyAll(_mockNotificationManager);
  594. }
  595. /** @fn testVerifyPhoneNumberUIDelegateWebNetworkRequestFailed
  596. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in a web
  597. network request failed error.
  598. */
  599. - (void)testVerifyPhoneNumberUIDelegateNetworkRequestFailed {
  600. id mockBundle = OCMClassMock([NSBundle class]);
  601. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  602. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  603. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  604. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  605. // Simulate missing app token error.
  606. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  607. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  608. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  609. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  610. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  611. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  612. code:FIRAuthErrorCodeMissingAppToken
  613. userInfo:nil];
  614. callback(nil, error);
  615. });
  616. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  617. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  618. FIRGetProjectConfigResponseCallback callback) {
  619. XCTAssertNotNil(request);
  620. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  621. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  622. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  623. andReturn(@[ kFakeAuthorizedDomain]);
  624. callback(mockGetProjectConfigResponse, nil);
  625. });
  626. });
  627. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  628. // Expect view controller presentation by UIDelegate.
  629. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  630. UIDelegate:mockUIDelegate
  631. callbackMatcher:OCMOCK_ANY
  632. completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
  633. __unsafe_unretained id unretainedArgument;
  634. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  635. // `completion` is at index 5
  636. [invocation getArgument:&unretainedArgument atIndex:5];
  637. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  638. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  639. completion([NSURL URLWithString:kFakeRedirectURLStringWebNetworkRequestFailed], nil);
  640. });
  641. });
  642. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  643. [_provider verifyPhoneNumber:kTestPhoneNumber
  644. UIDelegate:mockUIDelegate
  645. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  646. XCTAssertTrue([NSThread isMainThread]);
  647. XCTAssertEqual(error.code, FIRAuthErrorCodeWebNetworkRequestFailed);
  648. XCTAssertNil(verificationID);
  649. [expectation fulfill];
  650. }];
  651. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  652. OCMVerifyAll(_mockBackend);
  653. OCMVerifyAll(_mockNotificationManager);
  654. }
  655. /** @fn testVerifyPhoneNumberUIDelegateWebInternalError
  656. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in a web
  657. internal error.
  658. */
  659. - (void)testVerifyPhoneNumberUIDelegateWebInternalError {
  660. id mockBundle = OCMClassMock([NSBundle class]);
  661. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  662. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  663. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  664. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  665. // Simulate missing app token error.
  666. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  667. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  668. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  669. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  670. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  671. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  672. code:FIRAuthErrorCodeMissingAppToken
  673. userInfo:nil];
  674. callback(nil, error);
  675. });
  676. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  677. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  678. FIRGetProjectConfigResponseCallback callback) {
  679. XCTAssertNotNil(request);
  680. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  681. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  682. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  683. andReturn(@[ kFakeAuthorizedDomain]);
  684. callback(mockGetProjectConfigResponse, nil);
  685. });
  686. });
  687. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  688. // Expect view controller presentation by UIDelegate.
  689. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  690. UIDelegate:mockUIDelegate
  691. callbackMatcher:OCMOCK_ANY
  692. completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
  693. __unsafe_unretained id unretainedArgument;
  694. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  695. // `completion` is at index 5
  696. [invocation getArgument:&unretainedArgument atIndex:5];
  697. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  698. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  699. completion([NSURL URLWithString:kFakeRedirectURLStringWebInternalError], nil);
  700. });
  701. });
  702. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  703. [_provider verifyPhoneNumber:kTestPhoneNumber
  704. UIDelegate:mockUIDelegate
  705. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  706. XCTAssertTrue([NSThread isMainThread]);
  707. XCTAssertEqual(error.code, FIRAuthErrorCodeWebInternalError);
  708. XCTAssertNil(verificationID);
  709. [expectation fulfill];
  710. }];
  711. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  712. OCMVerifyAll(_mockBackend);
  713. OCMVerifyAll(_mockNotificationManager);
  714. }
  715. /** @fn testVerifyPhoneNumberUIDelegateUnexpectedError
  716. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  717. invalid client ID.
  718. */
  719. - (void)testVerifyPhoneNumberUIDelegateUnexpectedError {
  720. id mockBundle = OCMClassMock([NSBundle class]);
  721. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  722. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  723. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  724. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  725. // Simulate missing app token error.
  726. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  727. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  728. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  729. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  730. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  731. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  732. code:FIRAuthErrorCodeMissingAppToken
  733. userInfo:nil];
  734. callback(nil, error);
  735. });
  736. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  737. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  738. FIRGetProjectConfigResponseCallback callback) {
  739. XCTAssertNotNil(request);
  740. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  741. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  742. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  743. andReturn(@[ kFakeAuthorizedDomain]);
  744. callback(mockGetProjectConfigResponse, nil);
  745. });
  746. });
  747. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  748. // Expect view controller presentation by UIDelegate.
  749. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  750. UIDelegate:mockUIDelegate
  751. callbackMatcher:OCMOCK_ANY
  752. completion:OCMOCK_ANY]).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:kFakeRedirectURLStringUnknownError], 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, FIRAuthErrorCodeAppVerificationUserInteractionFailure);
  768. XCTAssertNil(verificationID);
  769. [expectation fulfill];
  770. }];
  771. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  772. OCMVerifyAll(_mockBackend);
  773. OCMVerifyAll(_mockNotificationManager);
  774. }
  775. /** @fn testVerifyPhoneNumberUIDelegateUnstructuredError
  776. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  777. error being surfaced with a default NSLocalizedFailureReasonErrorKey due to an unexpected
  778. structure of the error response.
  779. */
  780. - (void)testVerifyPhoneNumberUIDelegateUnstructuredError {
  781. id mockBundle = OCMClassMock([NSBundle class]);
  782. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  783. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  784. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  785. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  786. // Simulate missing app token error.
  787. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  788. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  789. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  790. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  791. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  792. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  793. code:FIRAuthErrorCodeMissingAppToken
  794. userInfo:nil];
  795. callback(nil, error);
  796. });
  797. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  798. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  799. FIRGetProjectConfigResponseCallback callback) {
  800. XCTAssertNotNil(request);
  801. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  802. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  803. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  804. andReturn(@[ kFakeAuthorizedDomain]);
  805. callback(mockGetProjectConfigResponse, nil);
  806. });
  807. });
  808. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  809. // Expect view controller presentation by UIDelegate.
  810. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  811. UIDelegate:mockUIDelegate
  812. callbackMatcher:OCMOCK_ANY
  813. completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
  814. __unsafe_unretained id unretainedArgument;
  815. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  816. // `completion` is at index 5
  817. [invocation getArgument:&unretainedArgument atIndex:5];
  818. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  819. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  820. completion([NSURL URLWithString:kFakeRedirectURLStringUnstructuredError], nil);
  821. });
  822. });
  823. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  824. [_provider verifyPhoneNumber:kTestPhoneNumber
  825. UIDelegate:mockUIDelegate
  826. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  827. XCTAssertTrue([NSThread isMainThread]);
  828. XCTAssertEqual(error.code, FIRAuthErrorCodeAppVerificationUserInteractionFailure);
  829. XCTAssertNil(verificationID);
  830. [expectation fulfill];
  831. }];
  832. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  833. OCMVerifyAll(_mockBackend);
  834. OCMVerifyAll(_mockNotificationManager);
  835. }
  836. /** @fn testVerifyPhoneNumberUIDelegateRaiseException
  837. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  838. exception.
  839. */
  840. - (void)testVerifyPhoneNumberUIDelegateRaiseException {
  841. id mockBundle = OCMClassMock([NSBundle class]);
  842. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  843. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  844. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ @"badscheme" ] } ]);
  845. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  846. XCTAssertThrows([_provider verifyPhoneNumber:kTestPhoneNumber
  847. UIDelegate:mockUIDelegate
  848. completion:^(NSString *_Nullable verificationID,
  849. NSError *_Nullable error) {
  850. XCTFail(@"Shouldn't call completion here.");
  851. }]);
  852. }
  853. /** @fn testNotForwardingNotification
  854. @brief Tests returning an error for the app failing to forward notification.
  855. */
  856. - (void)testNotForwardingNotification {
  857. id mockBundle = OCMClassMock([NSBundle class]);
  858. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  859. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  860. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  861. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  862. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  863. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(NO); });
  864. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  865. [_provider verifyPhoneNumber:kTestPhoneNumber
  866. UIDelegate:nil
  867. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  868. XCTAssertTrue([NSThread isMainThread]);
  869. XCTAssertNil(verificationID);
  870. XCTAssertEqual(error.code, FIRAuthErrorCodeNotificationNotForwarded);
  871. [expectation fulfill];
  872. }];
  873. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  874. OCMVerifyAll(_mockNotificationManager);
  875. }
  876. /** @fn testMissingAPNSToken
  877. @brief Tests returning an error for the app failing to provide an APNS device token.
  878. */
  879. - (void)testMissingAPNSToken {
  880. id mockBundle = OCMClassMock([NSBundle class]);
  881. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  882. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  883. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  884. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  885. // Simulate missing app token error.
  886. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  887. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  888. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  889. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  890. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  891. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  892. code:FIRAuthErrorCodeMissingAppToken
  893. userInfo:nil];
  894. callback(nil, error);
  895. });
  896. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  897. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  898. FIRGetProjectConfigResponseCallback callback) {
  899. XCTAssertNotNil(request);
  900. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  901. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  902. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  903. andReturn(@[ kFakeAuthorizedDomain]);
  904. callback(mockGetProjectConfigResponse, nil);
  905. });
  906. });
  907. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  908. // Expect view controller presentation by UIDelegate.
  909. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  910. UIDelegate:mockUIDelegate
  911. callbackMatcher:OCMOCK_ANY
  912. completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
  913. __unsafe_unretained id unretainedArgument;
  914. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  915. // `completion` is at index 5
  916. [invocation getArgument:&unretainedArgument atIndex:5];
  917. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  918. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  919. completion(nil, [NSError errorWithDomain:FIRAuthErrorDomain
  920. code:FIRAuthErrorCodeMissingAppToken
  921. userInfo:nil]);
  922. });
  923. });
  924. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  925. [_provider verifyPhoneNumber:kTestPhoneNumber
  926. UIDelegate:mockUIDelegate
  927. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  928. XCTAssertTrue([NSThread isMainThread]);
  929. XCTAssertEqual(error.code, FIRAuthErrorCodeMissingAppToken);
  930. XCTAssertNil(verificationID);
  931. [expectation fulfill];
  932. }];
  933. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  934. OCMVerifyAll(_mockBackend);
  935. OCMVerifyAll(_mockNotificationManager);
  936. }
  937. /** @fn testVerifyClient
  938. @brief Tests verifying client before sending verification code.
  939. */
  940. - (void)testVerifyClient {
  941. id mockBundle = OCMClassMock([NSBundle class]);
  942. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  943. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  944. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  945. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  946. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  947. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  948. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  949. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  950. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  951. type:FIRAuthAPNSTokenTypeProd];
  952. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  953. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token, nil); });
  954. // Expect verify client request to the backend.
  955. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  956. .andCallBlock2(^(FIRVerifyClientRequest *request,
  957. FIRVerifyClientResponseCallback callback) {
  958. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  959. XCTAssertFalse(request.isSandbox);
  960. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  961. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  962. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  963. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  964. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  965. callback(mockVerifyClientResponse, nil);
  966. });
  967. });
  968. // Mock receiving of push notification.
  969. OCMExpect([[_mockAppCredentialManager ignoringNonObjectArgs]
  970. didStartVerificationWithReceipt:OCMOCK_ANY timeout:0 callback:OCMOCK_ANY])
  971. .andCallIdDoubleIdBlock(^(NSString *receipt,
  972. NSTimeInterval timeout,
  973. FIRAuthAppCredentialCallback callback) {
  974. XCTAssertEqualObjects(receipt, kTestReceipt);
  975. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get passed
  976. // into the block either, so we can't verify it here.
  977. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  978. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  979. });
  980. });
  981. // Expect send verification code request to the backend.
  982. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  983. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  984. FIRSendVerificationCodeResponseCallback callback) {
  985. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  986. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  987. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  988. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  989. id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
  990. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  991. callback(mockSendVerificationCodeResponse, nil);
  992. });
  993. });
  994. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  995. [_provider verifyPhoneNumber:kTestPhoneNumber
  996. UIDelegate:nil
  997. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  998. XCTAssertTrue([NSThread isMainThread]);
  999. XCTAssertNil(error);
  1000. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  1001. [expectation fulfill];
  1002. }];
  1003. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1004. OCMVerifyAll(_mockBackend);
  1005. OCMVerifyAll(_mockNotificationManager);
  1006. OCMVerifyAll(_mockAppCredentialManager);
  1007. OCMVerifyAll(_mockAPNSTokenManager);
  1008. }
  1009. /** @fn testSendVerificationCodeFailedRetry
  1010. @brief Tests failed retry after failing to send verification code.
  1011. */
  1012. - (void)testSendVerificationCodeFailedRetry {
  1013. id mockBundle = OCMClassMock([NSBundle class]);
  1014. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1015. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  1016. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  1017. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1018. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  1019. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  1020. // Expect twice due to null check consumes one expectation.
  1021. OCMExpect([_mockAppCredentialManager credential])
  1022. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  1023. secret:kTestOldSecret]);
  1024. OCMExpect([_mockAppCredentialManager credential])
  1025. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  1026. secret:kTestOldSecret]);
  1027. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  1028. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  1029. type:FIRAuthAPNSTokenTypeProd];
  1030. // Expect first sendVerificationCode request to the backend, with request containing old app
  1031. // credential.
  1032. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1033. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1034. FIRSendVerificationCodeResponseCallback callback) {
  1035. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1036. XCTAssertEqualObjects(request.appCredential.receipt, kTestOldReceipt);
  1037. XCTAssertEqualObjects(request.appCredential.secret, kTestOldSecret);
  1038. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1039. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  1040. });
  1041. });
  1042. // Expect send verification code request to the backend, with request containing new app
  1043. // credential data.
  1044. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1045. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1046. FIRSendVerificationCodeResponseCallback callback) {
  1047. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1048. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  1049. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  1050. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1051. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  1052. });
  1053. });
  1054. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  1055. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token, nil); });
  1056. // Expect verify client request to the backend.
  1057. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  1058. .andCallBlock2(^(FIRVerifyClientRequest *request,
  1059. FIRVerifyClientResponseCallback callback) {
  1060. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  1061. XCTAssertFalse(request.isSandbox);
  1062. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1063. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  1064. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  1065. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  1066. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  1067. callback(mockVerifyClientResponse, nil);
  1068. });
  1069. });
  1070. // Mock receiving of push notification.
  1071. OCMStub([[_mockAppCredentialManager ignoringNonObjectArgs]
  1072. didStartVerificationWithReceipt:OCMOCK_ANY timeout:0 callback:OCMOCK_ANY])
  1073. .andCallIdDoubleIdBlock(^(NSString *receipt,
  1074. NSTimeInterval timeout,
  1075. FIRAuthAppCredentialCallback callback) {
  1076. XCTAssertEqualObjects(receipt, kTestReceipt);
  1077. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get passed
  1078. // into the block either, so we can't verify it here.
  1079. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1080. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  1081. });
  1082. });
  1083. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1084. [_provider verifyPhoneNumber:kTestPhoneNumber
  1085. UIDelegate:nil
  1086. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1087. XCTAssertTrue([NSThread isMainThread]);
  1088. XCTAssertNil(verificationID);
  1089. XCTAssertEqual(error.code, FIRAuthErrorCodeInternalError);
  1090. [expectation fulfill];
  1091. }];
  1092. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1093. OCMVerifyAll(_mockBackend);
  1094. OCMVerifyAll(_mockNotificationManager);
  1095. OCMVerifyAll(_mockAppCredentialManager);
  1096. OCMVerifyAll(_mockAPNSTokenManager);
  1097. }
  1098. /** @fn testSendVerificationCodeSuccessFulRetry
  1099. @brief Tests successful retry after failing to send verification code.
  1100. */
  1101. - (void)testSendVerificationCodeSuccessFulRetry {
  1102. id mockBundle = OCMClassMock([NSBundle class]);
  1103. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1104. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  1105. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  1106. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1107. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  1108. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  1109. // Expect twice due to null check consumes one expectation.
  1110. OCMExpect([_mockAppCredentialManager credential])
  1111. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  1112. secret:kTestOldSecret]);
  1113. OCMExpect([_mockAppCredentialManager credential])
  1114. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  1115. secret:kTestOldSecret]);
  1116. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  1117. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  1118. type:FIRAuthAPNSTokenTypeProd];
  1119. // Expect first sendVerificationCode request to the backend, with request containing old app
  1120. // credential.
  1121. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1122. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1123. FIRSendVerificationCodeResponseCallback callback) {
  1124. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1125. XCTAssertEqualObjects(request.appCredential.receipt, kTestOldReceipt);
  1126. XCTAssertEqualObjects(request.appCredential.secret, kTestOldSecret);
  1127. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1128. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  1129. });
  1130. });
  1131. // Expect send verification code request to the backend, with request containing new app
  1132. // credential data.
  1133. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1134. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1135. FIRSendVerificationCodeResponseCallback callback) {
  1136. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1137. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  1138. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  1139. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1140. id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
  1141. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  1142. callback(mockSendVerificationCodeResponse, nil);
  1143. });
  1144. });
  1145. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  1146. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token, nil); });
  1147. // Expect verify client request to the backend.
  1148. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  1149. .andCallBlock2(^(FIRVerifyClientRequest *request,
  1150. 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 ignoringNonObjectArgs]
  1163. didStartVerificationWithReceipt:OCMOCK_ANY timeout:0 callback:OCMOCK_ANY])
  1164. .andCallIdDoubleIdBlock(^(NSString *receipt,
  1165. NSTimeInterval timeout,
  1166. FIRAuthAppCredentialCallback callback) {
  1167. XCTAssertEqualObjects(receipt, kTestReceipt);
  1168. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get passed
  1169. // into the block either, so we can't verify it here.
  1170. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1171. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  1172. });
  1173. });
  1174. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1175. [_provider verifyPhoneNumber:kTestPhoneNumber
  1176. UIDelegate:nil
  1177. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1178. XCTAssertNil(error);
  1179. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  1180. [expectation fulfill];
  1181. }];
  1182. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1183. OCMVerifyAll(_mockBackend);
  1184. OCMVerifyAll(_mockNotificationManager);
  1185. OCMVerifyAll(_mockAppCredentialManager);
  1186. OCMVerifyAll(_mockAPNSTokenManager);
  1187. }
  1188. #pragma clang diagnostic pop // ignored "-Wdeprecated-declarations"
  1189. @end
  1190. NS_ASSUME_NONNULL_END