FIRPhoneAuthProviderTests.m 66 KB

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