FIRPhoneAuthProviderTests.m 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  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 <GoogleToolboxForMac/GTMNSDictionary+URLArguments.h>
  17. #import <OCMock/OCMock.h>
  18. #import <XCTest/XCTest.h>
  19. #import "FirebaseCommunity/FIRAuth.h"
  20. #import "FIRPhoneAuthProvider.h"
  21. #import "FIRApp.h"
  22. #import "FIRAuth_Internal.h"
  23. #import "FIRAuthAPNSToken.h"
  24. #import "FIRAuthAPNSTokenManager.h"
  25. #import "FIRAuthAppCredential.h"
  26. #import "FIRAuthAppCredentialManager.h"
  27. #import "FIRAuthBackend.h"
  28. #import "FIRAuthCredential_Internal.h"
  29. #import "FIRAuthErrorUtils.h"
  30. #import "FIRAuthGlobalWorkQueue.h"
  31. #import "FIRAuthNotificationManager.h"
  32. #import "FIRAuthRequestConfiguration.h"
  33. #import "FIRAuthUIDelegate.h"
  34. #import "FIRAuthURLPresenter.h"
  35. #import "FIRGetProjectConfigRequest.h"
  36. #import "FIRGetProjectConfigResponse.h"
  37. #import "FIRSendVerificationCodeRequest.h"
  38. #import "FIRSendVerificationCodeResponse.h"
  39. #import "FIROptions.h"
  40. #import "FIRVerifyClientRequest.h"
  41. #import "FIRVerifyClientResponse.h"
  42. #import "OCMStubRecorder+FIRAuthUnitTests.h"
  43. #import "Phone/FIRPhoneAuthCredential_Internal.h"
  44. @import SafariServices;
  45. NS_ASSUME_NONNULL_BEGIN
  46. /** @var kTestPhoneNumber
  47. @brief A testing phone number.
  48. */
  49. static NSString *const kTestPhoneNumber = @"55555555";
  50. /** @var kTestInvalidPhoneNumber
  51. @brief An invalid testing phone number.
  52. */
  53. static NSString *const kTestInvalidPhoneNumber = @"555+!*55555";
  54. /** @var kTestVerificationID
  55. @brief A testing verfication ID.
  56. */
  57. static NSString *const kTestVerificationID = @"verificationID";
  58. /** @var kTestReceipt
  59. @brief A fake receipt for testing.
  60. */
  61. static NSString *const kTestReceipt = @"receipt";
  62. /** @var kTestSecret
  63. @brief A fake secret for testing.
  64. */
  65. static NSString *const kTestSecret = @"secret";
  66. /** @var kTestOldReceipt
  67. @brief A fake old receipt for testing.
  68. */
  69. static NSString *const kTestOldReceipt = @"old_receipt";
  70. /** @var kTestOldSecret
  71. @brief A fake old secret for testing.
  72. */
  73. static NSString *const kTestOldSecret = @"old_secret";
  74. /** @var kTestVerificationCode
  75. @brief A fake verfication code.
  76. */
  77. static NSString *const kTestVerificationCode = @"verificationCode";
  78. /** @var kFakeClientID
  79. @brief A fake client ID.
  80. */
  81. static NSString *const kFakeClientID = @"123456.apps.googleusercontent.com";
  82. /** @var kFakeReverseClientID
  83. @brief The dot-reversed version of the fake client ID.
  84. */
  85. static NSString *const kFakeReverseClientID = @"com.googleusercontent.apps.123456";
  86. /** @var kFakeBundleID
  87. @brief A fake bundle ID.
  88. */
  89. static NSString *const kFakeBundleID = @"com.firebaseapp.example";
  90. /** @var kFakeAPIKey
  91. @brief A fake API key.
  92. */
  93. static NSString *const kFakeAPIKey = @"asdfghjkl";
  94. /** @var kFakeAuthorizedDomain
  95. @brief A fake authorized domain for the app.
  96. */
  97. static NSString *const kFakeAuthorizedDomain = @"test.firebaseapp.com";
  98. /** @var kFakeReCAPTCHAToken
  99. @brief A fake reCAPTCHA token.
  100. */
  101. static NSString *const kFakeReCAPTCHAToken = @"fakeReCAPTCHAToken";
  102. /** @var kFakeRedirectURLStringWithReCAPTCHAToken
  103. @brief The format for a fake redirect URL string that contains the fake reCAPTCHA token above.
  104. */
  105. static NSString *const kFakeRedirectURLStringWithReCAPTCHAToken = @"com.googleusercontent.apps.1"
  106. "23456://firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal"
  107. "lback%3FauthType%3DverifyApp%26recaptchaToken%3DfakeReCAPTCHAToken";
  108. /** @var kFakeRedirectURLStringInvalidClientID
  109. @brief The format for a fake redirect URL string with invalid client error.
  110. */
  111. static NSString *const kFakeRedirectURLStringInvalidClientID = @"com.googleusercontent.apps.1"
  112. "23456://firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal"
  113. "lback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Finvalid-oauth-client-id%2522%252"
  114. "C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%2520either%252"
  115. "0invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%26"
  116. "authType%3DverifyApp";
  117. /** @var kFakeRedirectURLStringUnknownError
  118. @brief The format for a fake redirect URL string with unknown error response.
  119. */
  120. static NSString *const kFakeRedirectURLStringUnknownError = @"com.googleusercontent.apps.1"
  121. "23456://firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal"
  122. "lback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Funknown-error-id%2522%252"
  123. "C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%2520either%252"
  124. "0invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%26"
  125. "authType%3DverifyApp";
  126. /** @var kTestTimeout
  127. @brief A fake timeout value for waiting for push notification.
  128. */
  129. static const NSTimeInterval kTestTimeout = 5;
  130. /** @var kExpectationTimeout
  131. @brief The maximum time waiting for expectations to fulfill.
  132. */
  133. static const NSTimeInterval kExpectationTimeout = 1;
  134. /** @class FIRPhoneAuthProviderTests
  135. @brief Tests for @c FIRPhoneAuthProvider
  136. */
  137. @interface FIRPhoneAuthProviderTests : XCTestCase
  138. @end
  139. @implementation FIRPhoneAuthProviderTests {
  140. /** @var _mockBackend
  141. @brief The mock @c FIRAuthBackendImplementation .
  142. */
  143. id _mockBackend;
  144. /** @var _provider
  145. @brief The @c FIRPhoneAuthProvider instance under test.
  146. */
  147. FIRPhoneAuthProvider *_provider;
  148. /** @var _mockAuth
  149. @brief The mock @c FIRAuth instance associated with @c _provider .
  150. */
  151. id _mockAuth;
  152. /** @var _mockApp
  153. @brief The mock @c FIRApp instance associated with @c _mockAuth .
  154. */
  155. id _mockApp;
  156. /** @var _mockAPNSTokenManager
  157. @brief The mock @c FIRAuthAPNSTokenManager instance associated with @c _mockAuth .
  158. */
  159. id _mockAPNSTokenManager;
  160. /** @var _mockAppCredentialManager
  161. @brief The mock @c FIRAuthAppCredentialManager instance associated with @c _mockAuth .
  162. */
  163. id _mockAppCredentialManager;
  164. /** @var _mockNotificationManager
  165. @brief The mock @c FIRAuthNotificationManager instance associated with @c _mockAuth .
  166. */
  167. id _mockNotificationManager;
  168. /** @var _mockURLPresenter
  169. @brief The mock @c FIRAuthURLPresenter instance associated with @c _mockAuth .
  170. */
  171. id _mockURLPresenter;
  172. }
  173. - (void)setUp {
  174. [super setUp];
  175. _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation));
  176. [FIRAuthBackend setBackendImplementation:_mockBackend];
  177. _mockAuth = OCMClassMock([FIRAuth class]);
  178. _mockApp = OCMClassMock([FIRApp class]);
  179. OCMStub([_mockAuth app]).andReturn(_mockApp);
  180. id mockOptions = OCMClassMock([FIROptions class]);
  181. OCMStub([(FIRApp *)_mockApp options]).andReturn(mockOptions);
  182. OCMStub([mockOptions clientID]).andReturn(kFakeClientID);
  183. _mockAPNSTokenManager = OCMClassMock([FIRAuthAPNSTokenManager class]);
  184. OCMStub([_mockAuth tokenManager]).andReturn(_mockAPNSTokenManager);
  185. _mockAppCredentialManager = OCMClassMock([FIRAuthAppCredentialManager class]);
  186. OCMStub([_mockAuth appCredentialManager]).andReturn(_mockAppCredentialManager);
  187. _mockNotificationManager = OCMClassMock([FIRAuthNotificationManager class]);
  188. OCMStub([_mockAuth notificationManager]).andReturn(_mockNotificationManager);
  189. _mockURLPresenter = OCMClassMock([FIRAuthURLPresenter class]);
  190. OCMStub([_mockAuth authURLPresenter]).andReturn(_mockURLPresenter);
  191. id mockRequestConfiguration = OCMClassMock([FIRAuthRequestConfiguration class]);
  192. OCMStub([_mockAuth requestConfiguration]).andReturn(mockRequestConfiguration);
  193. OCMStub([mockRequestConfiguration APIKey]).andReturn(kFakeAPIKey);
  194. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  195. }
  196. - (void)tearDown {
  197. [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
  198. [super tearDown];
  199. }
  200. // We're still testing deprecated `verifyPhoneNumber:completion:` extensively.
  201. #pragma clang diagnostic push
  202. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  203. /** @fn testCredentialWithVerificationID
  204. @brief Tests the @c credentialWithToken method to make sure that it returns a valid
  205. FIRAuthCredential instance.
  206. */
  207. - (void)testCredentialWithVerificationID {
  208. FIRPhoneAuthCredential *credential =
  209. [_provider credentialWithVerificationID:kTestVerificationID
  210. verificationCode:kTestVerificationCode];
  211. XCTAssertEqualObjects(credential.verificationID, kTestVerificationID);
  212. XCTAssertEqualObjects(credential.verificationCode, kTestVerificationCode);
  213. XCTAssertNil(credential.temporaryProof);
  214. XCTAssertNil(credential.phoneNumber);
  215. }
  216. /** @fn testVerifyEmptyPhoneNumber
  217. @brief Tests a failed invocation @c verifyPhoneNumber:completion: because an empty phone
  218. number was provided.
  219. */
  220. - (void)testVerifyEmptyPhoneNumber {
  221. // Empty phone number is checked on the client side so no backend RPC is mocked.
  222. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  223. [_provider verifyPhoneNumber:@""
  224. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  225. XCTAssertNotNil(error);
  226. XCTAssertEqual(error.code, FIRAuthErrorCodeMissingPhoneNumber);
  227. [expectation fulfill];
  228. }];
  229. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  230. }
  231. /** @fn testVerifyInvalidPhoneNumber
  232. @brief Tests a failed invocation @c verifyPhoneNumber:completion: because an invalid phone
  233. number was provided.
  234. */
  235. - (void)testVerifyInvalidPhoneNumber {
  236. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  237. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  238. OCMStub([_mockAppCredentialManager credential])
  239. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  240. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  241. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  242. FIRSendVerificationCodeResponseCallback callback) {
  243. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  244. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  245. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  246. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  247. callback(nil, [FIRAuthErrorUtils invalidPhoneNumberErrorWithMessage:nil]);
  248. });
  249. });
  250. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  251. [_provider verifyPhoneNumber:kTestPhoneNumber
  252. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  253. XCTAssertTrue([NSThread isMainThread]);
  254. XCTAssertNil(verificationID);
  255. XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidPhoneNumber);
  256. [expectation fulfill];
  257. }];
  258. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  259. OCMVerifyAll(_mockBackend);
  260. OCMVerifyAll(_mockNotificationManager);
  261. OCMVerifyAll(_mockAppCredentialManager);
  262. }
  263. /** @fn testVerifyPhoneNumber
  264. @brief Tests a successful invocation of @c verifyPhoneNumber:completion:.
  265. */
  266. - (void)testVerifyPhoneNumber {
  267. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  268. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  269. OCMStub([_mockAppCredentialManager credential])
  270. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  271. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  272. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  273. FIRSendVerificationCodeResponseCallback callback) {
  274. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  275. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  276. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  277. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  278. id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
  279. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  280. callback(mockSendVerificationCodeResponse, nil);
  281. });
  282. });
  283. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  284. [_provider verifyPhoneNumber:kTestPhoneNumber
  285. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  286. XCTAssertTrue([NSThread isMainThread]);
  287. XCTAssertNil(error);
  288. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  289. [expectation fulfill];
  290. }];
  291. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  292. OCMVerifyAll(_mockBackend);
  293. OCMVerifyAll(_mockNotificationManager);
  294. OCMVerifyAll(_mockAppCredentialManager);
  295. }
  296. /** @fn testVerifyPhoneNumberUIDelegate
  297. @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion:.
  298. */
  299. - (void)testVerifyPhoneNumberUIDelegate {
  300. id mockBundle = OCMClassMock([NSBundle class]);
  301. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  302. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  303. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  304. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  305. // Simulate missing app token error.
  306. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  307. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  308. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  309. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  310. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  311. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  312. code:FIRAuthErrorCodeMissingAppToken
  313. userInfo:nil];
  314. callback(nil, error);
  315. });
  316. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  317. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  318. FIRGetProjectConfigResponseCallback callback) {
  319. XCTAssertNotNil(request);
  320. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  321. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  322. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  323. andReturn(@[ kFakeAuthorizedDomain]);
  324. callback(mockGetProjectConfigResponse, nil);
  325. });
  326. });
  327. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  328. // Expect view controller presentation by UIDelegate.
  329. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  330. UIDelegate:mockUIDelegate
  331. callbackMatcher:OCMOCK_ANY
  332. completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
  333. __unsafe_unretained id unretainedArgument;
  334. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  335. // `presentURL` is at index 2.
  336. [invocation getArgument:&unretainedArgument atIndex:2];
  337. NSURL *presentURL = unretainedArgument;
  338. XCTAssertEqualObjects(presentURL.scheme, @"https");
  339. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  340. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  341. NSDictionary *params = [NSDictionary gtm_dictionaryWithHttpArgumentsString:presentURL.query];
  342. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  343. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  344. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  345. XCTAssertEqualObjects(params[@"authType"], @"verifyApp");
  346. XCTAssertNotNil(params[@"v"]);
  347. // `callbackMatcher` is at index 4
  348. [invocation getArgument:&unretainedArgument atIndex:4];
  349. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  350. NSMutableString *redirectURL =
  351. [NSMutableString stringWithString:kFakeRedirectURLStringWithReCAPTCHAToken];
  352. // Verify that the URL is rejected by the callback matcher without the event ID.
  353. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  354. [redirectURL appendString:@"%26eventId%3D"];
  355. [redirectURL appendString:params[@"eventId"]];
  356. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  357. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  358. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  359. NSURLComponents *components = [originalComponents copy];
  360. components.query = @"https";
  361. XCTAssertFalse(callbackMatcher([components URL]));
  362. components = [originalComponents copy];
  363. components.host = @"badhost";
  364. XCTAssertFalse(callbackMatcher([components URL]));
  365. components = [originalComponents copy];
  366. components.path = @"badpath";
  367. XCTAssertFalse(callbackMatcher([components URL]));
  368. components = [originalComponents copy];
  369. components.query = @"badquery";
  370. XCTAssertFalse(callbackMatcher([components URL]));
  371. // `completion` is at index 5
  372. [invocation getArgument:&unretainedArgument atIndex:5];
  373. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  374. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  375. completion([NSURL URLWithString:kFakeRedirectURLStringWithReCAPTCHAToken], nil);
  376. });
  377. });
  378. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  379. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  380. FIRSendVerificationCodeResponseCallback callback) {
  381. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  382. XCTAssertNil(request.appCredential);
  383. XCTAssertEqualObjects(request.reCAPTCHAToken, kFakeReCAPTCHAToken);
  384. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  385. id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
  386. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  387. callback(mockSendVerificationCodeResponse, nil);
  388. });
  389. });
  390. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  391. [_provider verifyPhoneNumber:kTestPhoneNumber
  392. UIDelegate:mockUIDelegate
  393. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  394. XCTAssertTrue([NSThread isMainThread]);
  395. XCTAssertNil(error);
  396. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  397. [expectation fulfill];
  398. }];
  399. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  400. OCMVerifyAll(_mockBackend);
  401. OCMVerifyAll(_mockNotificationManager);
  402. }
  403. /** @fn testVerifyPhoneNumberUIDelegateInvalidClientID
  404. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  405. invalid client ID.
  406. */
  407. - (void)testVerifyPhoneNumberUIDelegateInvalidClientID {
  408. id mockBundle = OCMClassMock([NSBundle class]);
  409. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  410. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  411. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  412. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  413. // Simulate missing app token error.
  414. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  415. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  416. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  417. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  418. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  419. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  420. code:FIRAuthErrorCodeMissingAppToken
  421. userInfo:nil];
  422. callback(nil, error);
  423. });
  424. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  425. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  426. FIRGetProjectConfigResponseCallback callback) {
  427. XCTAssertNotNil(request);
  428. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  429. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  430. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  431. andReturn(@[ kFakeAuthorizedDomain]);
  432. callback(mockGetProjectConfigResponse, nil);
  433. });
  434. });
  435. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  436. // Expect view controller presentation by UIDelegate.
  437. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  438. UIDelegate:mockUIDelegate
  439. callbackMatcher:OCMOCK_ANY
  440. completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
  441. __unsafe_unretained id unretainedArgument;
  442. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  443. // `completion` is at index 5
  444. [invocation getArgument:&unretainedArgument atIndex:5];
  445. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  446. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  447. completion([NSURL URLWithString:kFakeRedirectURLStringInvalidClientID], nil);
  448. });
  449. });
  450. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  451. [_provider verifyPhoneNumber:kTestPhoneNumber
  452. UIDelegate:mockUIDelegate
  453. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  454. XCTAssertTrue([NSThread isMainThread]);
  455. XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidClientID);
  456. XCTAssertNil(verificationID);
  457. [expectation fulfill];
  458. }];
  459. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  460. OCMVerifyAll(_mockBackend);
  461. OCMVerifyAll(_mockNotificationManager);
  462. }
  463. /** @fn testVerifyPhoneNumberUIDelegateUnexpectedError
  464. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  465. invalid client ID.
  466. */
  467. - (void)testVerifyPhoneNumberUIDelegateUnexpectedError {
  468. id mockBundle = OCMClassMock([NSBundle class]);
  469. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  470. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  471. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  472. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  473. // Simulate missing app token error.
  474. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  475. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  476. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  477. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  478. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  479. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  480. code:FIRAuthErrorCodeMissingAppToken
  481. userInfo:nil];
  482. callback(nil, error);
  483. });
  484. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  485. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  486. FIRGetProjectConfigResponseCallback callback) {
  487. XCTAssertNotNil(request);
  488. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  489. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  490. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  491. andReturn(@[ kFakeAuthorizedDomain]);
  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]).andDo(^(NSInvocation *invocation) {
  501. __unsafe_unretained id unretainedArgument;
  502. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  503. // `completion` is at index 5
  504. [invocation getArgument:&unretainedArgument atIndex:5];
  505. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  506. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  507. completion([NSURL URLWithString:kFakeRedirectURLStringUnknownError], nil);
  508. });
  509. });
  510. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  511. [_provider verifyPhoneNumber:kTestPhoneNumber
  512. UIDelegate:mockUIDelegate
  513. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  514. XCTAssertTrue([NSThread isMainThread]);
  515. XCTAssertEqual(error.code, FIRAuthErrorCodeAppVerificationUserInteractionFailure);
  516. XCTAssertNil(verificationID);
  517. [expectation fulfill];
  518. }];
  519. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  520. OCMVerifyAll(_mockBackend);
  521. OCMVerifyAll(_mockNotificationManager);
  522. }
  523. /** @fn testVerifyPhoneNumberUIDelegateRaiseException
  524. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  525. exception.
  526. */
  527. - (void)testVerifyPhoneNumberUIDelegateRaiseException {
  528. id mockBundle = OCMClassMock([NSBundle class]);
  529. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  530. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  531. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ @"badscheme" ] } ]);
  532. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  533. XCTAssertThrows([_provider verifyPhoneNumber:kTestPhoneNumber
  534. UIDelegate:mockUIDelegate
  535. completion:^(NSString *_Nullable verificationID,
  536. NSError *_Nullable error) {
  537. XCTFail(@"Shouldn't call completion here.");
  538. }]);
  539. }
  540. /** @fn testNotForwardingNotification
  541. @brief Tests returning an error for the app failing to forward notification.
  542. */
  543. - (void)testNotForwardingNotification {
  544. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  545. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(NO); });
  546. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  547. [_provider verifyPhoneNumber:kTestPhoneNumber
  548. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  549. XCTAssertTrue([NSThread isMainThread]);
  550. XCTAssertNil(verificationID);
  551. XCTAssertEqual(error.code, FIRAuthErrorCodeNotificationNotForwarded);
  552. [expectation fulfill];
  553. }];
  554. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  555. OCMVerifyAll(_mockNotificationManager);
  556. }
  557. /** @fn testMissingAPNSToken
  558. @brief Tests returning an error for the app failing to provide an APNS device token.
  559. */
  560. - (void)testMissingAPNSToken {
  561. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  562. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  563. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  564. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  565. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(nil, nil); });
  566. // Expect verify client request to the backend wth empty token.
  567. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  568. .andCallBlock2(^(FIRVerifyClientRequest *request,
  569. FIRVerifyClientResponseCallback callback) {
  570. XCTAssertNil(request.appToken);
  571. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  572. // The backend is supposed to return an error.
  573. callback(nil, [NSError errorWithDomain:FIRAuthErrorDomain
  574. code:FIRAuthErrorCodeMissingAppToken
  575. userInfo:nil]);
  576. });
  577. });
  578. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  579. [_provider verifyPhoneNumber:kTestPhoneNumber
  580. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  581. XCTAssertTrue([NSThread isMainThread]);
  582. XCTAssertNil(verificationID);
  583. XCTAssertEqualObjects(error.domain, FIRAuthErrorDomain);
  584. XCTAssertEqual(error.code, FIRAuthErrorCodeMissingAppToken);
  585. [expectation fulfill];
  586. }];
  587. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  588. OCMVerifyAll(_mockNotificationManager);
  589. OCMVerifyAll(_mockAppCredentialManager);
  590. OCMVerifyAll(_mockAPNSTokenManager);
  591. }
  592. /** @fn testVerifyClient
  593. @brief Tests verifying client before sending verification code.
  594. */
  595. - (void)testVerifyClient {
  596. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  597. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  598. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  599. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  600. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  601. type:FIRAuthAPNSTokenTypeProd];
  602. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  603. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token, nil); });
  604. // Expect verify client request to the backend.
  605. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  606. .andCallBlock2(^(FIRVerifyClientRequest *request,
  607. FIRVerifyClientResponseCallback callback) {
  608. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  609. XCTAssertFalse(request.isSandbox);
  610. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  611. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  612. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  613. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  614. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  615. callback(mockVerifyClientResponse, nil);
  616. });
  617. });
  618. // Mock receiving of push notification.
  619. OCMExpect([[_mockAppCredentialManager ignoringNonObjectArgs]
  620. didStartVerificationWithReceipt:OCMOCK_ANY timeout:0 callback:OCMOCK_ANY])
  621. .andCallIdDoubleIdBlock(^(NSString *receipt,
  622. NSTimeInterval timeout,
  623. FIRAuthAppCredentialCallback callback) {
  624. XCTAssertEqualObjects(receipt, kTestReceipt);
  625. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get passed
  626. // into the block either, so we can't verify it here.
  627. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  628. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  629. });
  630. });
  631. // Expect send verification code request to the backend.
  632. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  633. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  634. FIRSendVerificationCodeResponseCallback callback) {
  635. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  636. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  637. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  638. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  639. id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
  640. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  641. callback(mockSendVerificationCodeResponse, nil);
  642. });
  643. });
  644. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  645. [_provider verifyPhoneNumber:kTestPhoneNumber
  646. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  647. XCTAssertTrue([NSThread isMainThread]);
  648. XCTAssertNil(error);
  649. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  650. [expectation fulfill];
  651. }];
  652. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  653. OCMVerifyAll(_mockBackend);
  654. OCMVerifyAll(_mockNotificationManager);
  655. OCMVerifyAll(_mockAppCredentialManager);
  656. OCMVerifyAll(_mockAPNSTokenManager);
  657. }
  658. /** @fn testSendVerificationCodeFailedRetry
  659. @brief Tests failed retry after failing to send verification code.
  660. */
  661. - (void)testSendVerificationCodeFailedRetry {
  662. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  663. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  664. // Expect twice due to null check consumes one expectation.
  665. OCMExpect([_mockAppCredentialManager credential])
  666. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  667. secret:kTestOldSecret]);
  668. OCMExpect([_mockAppCredentialManager credential])
  669. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  670. secret:kTestOldSecret]);
  671. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  672. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  673. type:FIRAuthAPNSTokenTypeProd];
  674. // Expect first sendVerificationCode request to the backend, with request containing old app
  675. // credential.
  676. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  677. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  678. FIRSendVerificationCodeResponseCallback callback) {
  679. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  680. XCTAssertEqualObjects(request.appCredential.receipt, kTestOldReceipt);
  681. XCTAssertEqualObjects(request.appCredential.secret, kTestOldSecret);
  682. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  683. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  684. });
  685. });
  686. // Expect send verification code request to the backend, with request containing new app
  687. // credential data.
  688. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  689. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  690. FIRSendVerificationCodeResponseCallback callback) {
  691. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  692. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  693. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  694. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  695. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  696. });
  697. });
  698. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  699. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token, nil); });
  700. // Expect verify client request to the backend.
  701. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  702. .andCallBlock2(^(FIRVerifyClientRequest *request,
  703. FIRVerifyClientResponseCallback callback) {
  704. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  705. XCTAssertFalse(request.isSandbox);
  706. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  707. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  708. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  709. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  710. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  711. callback(mockVerifyClientResponse, nil);
  712. });
  713. });
  714. // Mock receiving of push notification.
  715. OCMStub([[_mockAppCredentialManager ignoringNonObjectArgs]
  716. didStartVerificationWithReceipt:OCMOCK_ANY timeout:0 callback:OCMOCK_ANY])
  717. .andCallIdDoubleIdBlock(^(NSString *receipt,
  718. NSTimeInterval timeout,
  719. FIRAuthAppCredentialCallback callback) {
  720. XCTAssertEqualObjects(receipt, kTestReceipt);
  721. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get passed
  722. // into the block either, so we can't verify it here.
  723. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  724. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  725. });
  726. });
  727. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  728. [_provider verifyPhoneNumber:kTestPhoneNumber
  729. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  730. XCTAssertTrue([NSThread isMainThread]);
  731. XCTAssertNil(verificationID);
  732. XCTAssertEqual(error.code, FIRAuthErrorCodeInternalError);
  733. [expectation fulfill];
  734. }];
  735. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  736. OCMVerifyAll(_mockBackend);
  737. OCMVerifyAll(_mockNotificationManager);
  738. OCMVerifyAll(_mockAppCredentialManager);
  739. OCMVerifyAll(_mockAPNSTokenManager);
  740. }
  741. /** @fn testSendVerificationCodeSuccessFulRetry
  742. @brief Tests successful retry after failing to send verification code.
  743. */
  744. - (void)testSendVerificationCodeSuccessFulRetry {
  745. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  746. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  747. // Expect twice due to null check consumes one expectation.
  748. OCMExpect([_mockAppCredentialManager credential])
  749. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  750. secret:kTestOldSecret]);
  751. OCMExpect([_mockAppCredentialManager credential])
  752. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  753. secret:kTestOldSecret]);
  754. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  755. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  756. type:FIRAuthAPNSTokenTypeProd];
  757. // Expect first sendVerificationCode request to the backend, with request containing old app
  758. // credential.
  759. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  760. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  761. FIRSendVerificationCodeResponseCallback callback) {
  762. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  763. XCTAssertEqualObjects(request.appCredential.receipt, kTestOldReceipt);
  764. XCTAssertEqualObjects(request.appCredential.secret, kTestOldSecret);
  765. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  766. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  767. });
  768. });
  769. // Expect send verification code request to the backend, with request containing new app
  770. // credential data.
  771. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  772. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  773. FIRSendVerificationCodeResponseCallback callback) {
  774. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  775. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  776. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  777. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  778. id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
  779. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  780. callback(mockSendVerificationCodeResponse, nil);
  781. });
  782. });
  783. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  784. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token, nil); });
  785. // Expect verify client request to the backend.
  786. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  787. .andCallBlock2(^(FIRVerifyClientRequest *request,
  788. FIRVerifyClientResponseCallback callback) {
  789. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  790. XCTAssertFalse(request.isSandbox);
  791. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  792. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  793. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  794. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  795. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  796. callback(mockVerifyClientResponse, nil);
  797. });
  798. });
  799. // Mock receiving of push notification.
  800. OCMStub([[_mockAppCredentialManager ignoringNonObjectArgs]
  801. didStartVerificationWithReceipt:OCMOCK_ANY timeout:0 callback:OCMOCK_ANY])
  802. .andCallIdDoubleIdBlock(^(NSString *receipt,
  803. NSTimeInterval timeout,
  804. FIRAuthAppCredentialCallback callback) {
  805. XCTAssertEqualObjects(receipt, kTestReceipt);
  806. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get passed
  807. // into the block either, so we can't verify it here.
  808. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  809. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  810. });
  811. });
  812. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  813. [_provider verifyPhoneNumber:kTestPhoneNumber
  814. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  815. XCTAssertNil(error);
  816. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  817. [expectation fulfill];
  818. }];
  819. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  820. OCMVerifyAll(_mockBackend);
  821. OCMVerifyAll(_mockNotificationManager);
  822. OCMVerifyAll(_mockAppCredentialManager);
  823. OCMVerifyAll(_mockAPNSTokenManager);
  824. }
  825. #pragma clang diagnostic pop // ignored "-Wdeprecated-declarations"
  826. @end
  827. NS_ASSUME_NONNULL_END