FIRPhoneAuthProviderTests.m 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
  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 an 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 kFakeRedirectURLStringWebNetworkRequestFailed
  118. @brief The format for a fake redirect URL string with a web network request failed error.
  119. */
  120. static NSString *const kFakeRedirectURLStringWebNetworkRequestFailed = @"com.googleusercontent.apps"
  121. ".123456://firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fc"
  122. "allback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Fnetwork-request-failed%2522%25"
  123. "2C%2522message%2522%253A%2522The%2520network%2520request%2520failed%2520.%2522%257D%26authType"
  124. "%3DverifyApp";
  125. /** @var kFakeRedirectURLStringWebInternalError
  126. @brief The format for a fake redirect URL string with an internal web error.
  127. */
  128. static NSString *const kFakeRedirectURLStringWebInternalError = @"com.googleusercontent.apps.1"
  129. "23456://firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal"
  130. "lback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Finternal-error%2522%252C%2522mes"
  131. "sage%2522%253A%2522Internal%2520error%2520.%2522%257D%26authType%3DverifyApp";
  132. /** @var kFakeRedirectURLStringUnknownError
  133. @brief The format for a fake redirect URL string with unknown error response.
  134. */
  135. static NSString *const kFakeRedirectURLStringUnknownError = @"com.googleusercontent.apps.1"
  136. "23456://firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal"
  137. "lback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Funknown-error-id%2522%252"
  138. "C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%2520either%252"
  139. "0invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%26"
  140. "authType%3DverifyApp";
  141. /** @var kTestTimeout
  142. @brief A fake timeout value for waiting for push notification.
  143. */
  144. static const NSTimeInterval kTestTimeout = 5;
  145. /** @var kExpectationTimeout
  146. @brief The maximum time waiting for expectations to fulfill.
  147. */
  148. static const NSTimeInterval kExpectationTimeout = 1;
  149. /** @class FIRPhoneAuthProviderTests
  150. @brief Tests for @c FIRPhoneAuthProvider
  151. */
  152. @interface FIRPhoneAuthProviderTests : XCTestCase
  153. @end
  154. @implementation FIRPhoneAuthProviderTests {
  155. /** @var _mockBackend
  156. @brief The mock @c FIRAuthBackendImplementation .
  157. */
  158. id _mockBackend;
  159. /** @var _provider
  160. @brief The @c FIRPhoneAuthProvider instance under test.
  161. */
  162. FIRPhoneAuthProvider *_provider;
  163. /** @var _mockAuth
  164. @brief The mock @c FIRAuth instance associated with @c _provider .
  165. */
  166. id _mockAuth;
  167. /** @var _mockApp
  168. @brief The mock @c FIRApp instance associated with @c _mockAuth .
  169. */
  170. id _mockApp;
  171. /** @var _mockAPNSTokenManager
  172. @brief The mock @c FIRAuthAPNSTokenManager instance associated with @c _mockAuth .
  173. */
  174. id _mockAPNSTokenManager;
  175. /** @var _mockAppCredentialManager
  176. @brief The mock @c FIRAuthAppCredentialManager instance associated with @c _mockAuth .
  177. */
  178. id _mockAppCredentialManager;
  179. /** @var _mockNotificationManager
  180. @brief The mock @c FIRAuthNotificationManager instance associated with @c _mockAuth .
  181. */
  182. id _mockNotificationManager;
  183. /** @var _mockURLPresenter
  184. @brief The mock @c FIRAuthURLPresenter instance associated with @c _mockAuth .
  185. */
  186. id _mockURLPresenter;
  187. }
  188. - (void)setUp {
  189. [super setUp];
  190. _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation));
  191. [FIRAuthBackend setBackendImplementation:_mockBackend];
  192. _mockAuth = OCMClassMock([FIRAuth class]);
  193. _mockApp = OCMClassMock([FIRApp class]);
  194. OCMStub([_mockAuth app]).andReturn(_mockApp);
  195. id mockOptions = OCMClassMock([FIROptions class]);
  196. OCMStub([(FIRApp *)_mockApp options]).andReturn(mockOptions);
  197. OCMStub([mockOptions clientID]).andReturn(kFakeClientID);
  198. _mockAPNSTokenManager = OCMClassMock([FIRAuthAPNSTokenManager class]);
  199. OCMStub([_mockAuth tokenManager]).andReturn(_mockAPNSTokenManager);
  200. _mockAppCredentialManager = OCMClassMock([FIRAuthAppCredentialManager class]);
  201. OCMStub([_mockAuth appCredentialManager]).andReturn(_mockAppCredentialManager);
  202. _mockNotificationManager = OCMClassMock([FIRAuthNotificationManager class]);
  203. OCMStub([_mockAuth notificationManager]).andReturn(_mockNotificationManager);
  204. _mockURLPresenter = OCMClassMock([FIRAuthURLPresenter class]);
  205. OCMStub([_mockAuth authURLPresenter]).andReturn(_mockURLPresenter);
  206. id mockRequestConfiguration = OCMClassMock([FIRAuthRequestConfiguration class]);
  207. OCMStub([_mockAuth requestConfiguration]).andReturn(mockRequestConfiguration);
  208. OCMStub([mockRequestConfiguration APIKey]).andReturn(kFakeAPIKey);
  209. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  210. }
  211. - (void)tearDown {
  212. [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
  213. [super tearDown];
  214. }
  215. // We're still testing deprecated `verifyPhoneNumber:completion:` extensively.
  216. #pragma clang diagnostic push
  217. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  218. /** @fn testCredentialWithVerificationID
  219. @brief Tests the @c credentialWithToken method to make sure that it returns a valid
  220. FIRAuthCredential instance.
  221. */
  222. - (void)testCredentialWithVerificationID {
  223. FIRPhoneAuthCredential *credential =
  224. [_provider credentialWithVerificationID:kTestVerificationID
  225. verificationCode:kTestVerificationCode];
  226. XCTAssertEqualObjects(credential.verificationID, kTestVerificationID);
  227. XCTAssertEqualObjects(credential.verificationCode, kTestVerificationCode);
  228. XCTAssertNil(credential.temporaryProof);
  229. XCTAssertNil(credential.phoneNumber);
  230. }
  231. /** @fn testVerifyEmptyPhoneNumber
  232. @brief Tests a failed invocation @c verifyPhoneNumber:completion: because an empty phone
  233. number was provided.
  234. */
  235. - (void)testVerifyEmptyPhoneNumber {
  236. // Empty phone number is checked on the client side so no backend RPC is mocked.
  237. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  238. [_provider verifyPhoneNumber:@""
  239. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  240. XCTAssertNotNil(error);
  241. XCTAssertEqual(error.code, FIRAuthErrorCodeMissingPhoneNumber);
  242. [expectation fulfill];
  243. }];
  244. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  245. }
  246. /** @fn testVerifyInvalidPhoneNumber
  247. @brief Tests a failed invocation @c verifyPhoneNumber:completion: because an invalid phone
  248. number was provided.
  249. */
  250. - (void)testVerifyInvalidPhoneNumber {
  251. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  252. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  253. OCMStub([_mockAppCredentialManager credential])
  254. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  255. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  256. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  257. FIRSendVerificationCodeResponseCallback callback) {
  258. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  259. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  260. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  261. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  262. callback(nil, [FIRAuthErrorUtils invalidPhoneNumberErrorWithMessage:nil]);
  263. });
  264. });
  265. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  266. [_provider verifyPhoneNumber:kTestPhoneNumber
  267. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  268. XCTAssertTrue([NSThread isMainThread]);
  269. XCTAssertNil(verificationID);
  270. XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidPhoneNumber);
  271. [expectation fulfill];
  272. }];
  273. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  274. OCMVerifyAll(_mockBackend);
  275. OCMVerifyAll(_mockNotificationManager);
  276. OCMVerifyAll(_mockAppCredentialManager);
  277. }
  278. /** @fn testVerifyPhoneNumber
  279. @brief Tests a successful invocation of @c verifyPhoneNumber:completion:.
  280. */
  281. - (void)testVerifyPhoneNumber {
  282. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  283. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  284. OCMStub([_mockAppCredentialManager credential])
  285. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  286. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  287. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  288. FIRSendVerificationCodeResponseCallback callback) {
  289. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  290. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  291. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  292. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  293. id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
  294. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  295. callback(mockSendVerificationCodeResponse, nil);
  296. });
  297. });
  298. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  299. [_provider verifyPhoneNumber:kTestPhoneNumber
  300. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  301. XCTAssertTrue([NSThread isMainThread]);
  302. XCTAssertNil(error);
  303. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  304. [expectation fulfill];
  305. }];
  306. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  307. OCMVerifyAll(_mockBackend);
  308. OCMVerifyAll(_mockNotificationManager);
  309. OCMVerifyAll(_mockAppCredentialManager);
  310. }
  311. /** @fn testVerifyPhoneNumberUIDelegate
  312. @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion:.
  313. */
  314. - (void)testVerifyPhoneNumberUIDelegate {
  315. id mockBundle = OCMClassMock([NSBundle class]);
  316. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  317. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  318. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  319. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  320. // Simulate missing app token error.
  321. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  322. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  323. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  324. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  325. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  326. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  327. code:FIRAuthErrorCodeMissingAppToken
  328. userInfo:nil];
  329. callback(nil, error);
  330. });
  331. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  332. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  333. FIRGetProjectConfigResponseCallback callback) {
  334. XCTAssertNotNil(request);
  335. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  336. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  337. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  338. andReturn(@[ kFakeAuthorizedDomain]);
  339. callback(mockGetProjectConfigResponse, nil);
  340. });
  341. });
  342. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  343. // Expect view controller presentation by UIDelegate.
  344. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  345. UIDelegate:mockUIDelegate
  346. callbackMatcher:OCMOCK_ANY
  347. completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
  348. __unsafe_unretained id unretainedArgument;
  349. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  350. // `presentURL` is at index 2.
  351. [invocation getArgument:&unretainedArgument atIndex:2];
  352. NSURL *presentURL = unretainedArgument;
  353. XCTAssertEqualObjects(presentURL.scheme, @"https");
  354. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  355. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  356. NSDictionary *params = [NSDictionary gtm_dictionaryWithHttpArgumentsString:presentURL.query];
  357. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  358. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  359. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  360. XCTAssertEqualObjects(params[@"authType"], @"verifyApp");
  361. XCTAssertNotNil(params[@"v"]);
  362. // `callbackMatcher` is at index 4
  363. [invocation getArgument:&unretainedArgument atIndex:4];
  364. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  365. NSMutableString *redirectURL =
  366. [NSMutableString stringWithString:kFakeRedirectURLStringWithReCAPTCHAToken];
  367. // Verify that the URL is rejected by the callback matcher without the event ID.
  368. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  369. [redirectURL appendString:@"%26eventId%3D"];
  370. [redirectURL appendString:params[@"eventId"]];
  371. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  372. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  373. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  374. NSURLComponents *components = [originalComponents copy];
  375. components.query = @"https";
  376. XCTAssertFalse(callbackMatcher([components URL]));
  377. components = [originalComponents copy];
  378. components.host = @"badhost";
  379. XCTAssertFalse(callbackMatcher([components URL]));
  380. components = [originalComponents copy];
  381. components.path = @"badpath";
  382. XCTAssertFalse(callbackMatcher([components URL]));
  383. components = [originalComponents copy];
  384. components.query = @"badquery";
  385. XCTAssertFalse(callbackMatcher([components URL]));
  386. // `completion` is at index 5
  387. [invocation getArgument:&unretainedArgument atIndex:5];
  388. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  389. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  390. completion([NSURL URLWithString:kFakeRedirectURLStringWithReCAPTCHAToken], nil);
  391. });
  392. });
  393. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  394. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  395. FIRSendVerificationCodeResponseCallback callback) {
  396. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  397. XCTAssertNil(request.appCredential);
  398. XCTAssertEqualObjects(request.reCAPTCHAToken, kFakeReCAPTCHAToken);
  399. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  400. id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
  401. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  402. callback(mockSendVerificationCodeResponse, nil);
  403. });
  404. });
  405. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  406. [_provider verifyPhoneNumber:kTestPhoneNumber
  407. UIDelegate:mockUIDelegate
  408. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  409. XCTAssertTrue([NSThread isMainThread]);
  410. XCTAssertNil(error);
  411. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  412. [expectation fulfill];
  413. }];
  414. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  415. OCMVerifyAll(_mockBackend);
  416. OCMVerifyAll(_mockNotificationManager);
  417. }
  418. /** @fn testVerifyPhoneNumberUIDelegateInvalidClientID
  419. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  420. invalid client ID error.
  421. */
  422. - (void)testVerifyPhoneNumberUIDelegateInvalidClientID {
  423. id mockBundle = OCMClassMock([NSBundle class]);
  424. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  425. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  426. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  427. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  428. // Simulate missing app token error.
  429. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  430. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  431. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  432. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  433. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  434. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  435. code:FIRAuthErrorCodeMissingAppToken
  436. userInfo:nil];
  437. callback(nil, error);
  438. });
  439. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  440. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  441. FIRGetProjectConfigResponseCallback callback) {
  442. XCTAssertNotNil(request);
  443. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  444. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  445. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  446. andReturn(@[ kFakeAuthorizedDomain]);
  447. callback(mockGetProjectConfigResponse, nil);
  448. });
  449. });
  450. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  451. // Expect view controller presentation by UIDelegate.
  452. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  453. UIDelegate:mockUIDelegate
  454. callbackMatcher:OCMOCK_ANY
  455. completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
  456. __unsafe_unretained id unretainedArgument;
  457. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  458. // `completion` is at index 5
  459. [invocation getArgument:&unretainedArgument atIndex:5];
  460. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  461. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  462. completion([NSURL URLWithString:kFakeRedirectURLStringInvalidClientID], nil);
  463. });
  464. });
  465. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  466. [_provider verifyPhoneNumber:kTestPhoneNumber
  467. UIDelegate:mockUIDelegate
  468. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  469. XCTAssertTrue([NSThread isMainThread]);
  470. XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidClientID);
  471. XCTAssertNil(verificationID);
  472. [expectation fulfill];
  473. }];
  474. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  475. OCMVerifyAll(_mockBackend);
  476. OCMVerifyAll(_mockNotificationManager);
  477. }
  478. /** @fn testVerifyPhoneNumberUIDelegateWebNetworkRequestFailed
  479. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in a web
  480. network request failed error.
  481. */
  482. - (void)testVerifyPhoneNumberUIDelegateNetworkRequestFailed {
  483. id mockBundle = OCMClassMock([NSBundle class]);
  484. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  485. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  486. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  487. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  488. // Simulate missing app token error.
  489. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  490. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  491. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  492. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  493. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  494. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  495. code:FIRAuthErrorCodeMissingAppToken
  496. userInfo:nil];
  497. callback(nil, error);
  498. });
  499. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  500. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  501. FIRGetProjectConfigResponseCallback callback) {
  502. XCTAssertNotNil(request);
  503. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  504. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  505. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  506. andReturn(@[ kFakeAuthorizedDomain]);
  507. callback(mockGetProjectConfigResponse, nil);
  508. });
  509. });
  510. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  511. // Expect view controller presentation by UIDelegate.
  512. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  513. UIDelegate:mockUIDelegate
  514. callbackMatcher:OCMOCK_ANY
  515. completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
  516. __unsafe_unretained id unretainedArgument;
  517. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  518. // `completion` is at index 5
  519. [invocation getArgument:&unretainedArgument atIndex:5];
  520. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  521. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  522. completion([NSURL URLWithString:kFakeRedirectURLStringWebNetworkRequestFailed], nil);
  523. });
  524. });
  525. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  526. [_provider verifyPhoneNumber:kTestPhoneNumber
  527. UIDelegate:mockUIDelegate
  528. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  529. XCTAssertTrue([NSThread isMainThread]);
  530. XCTAssertEqual(error.code, FIRAuthErrorCodeWebNetworkRequestFailed);
  531. XCTAssertNil(verificationID);
  532. [expectation fulfill];
  533. }];
  534. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  535. OCMVerifyAll(_mockBackend);
  536. OCMVerifyAll(_mockNotificationManager);
  537. }
  538. /** @fn testVerifyPhoneNumberUIDelegateWebInternalError
  539. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in a web
  540. internal error.
  541. */
  542. - (void)testVerifyPhoneNumberUIDelegateWebInternalError {
  543. id mockBundle = OCMClassMock([NSBundle class]);
  544. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  545. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  546. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  547. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  548. // Simulate missing app token error.
  549. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  550. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  551. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  552. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  553. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  554. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  555. code:FIRAuthErrorCodeMissingAppToken
  556. userInfo:nil];
  557. callback(nil, error);
  558. });
  559. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  560. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  561. FIRGetProjectConfigResponseCallback callback) {
  562. XCTAssertNotNil(request);
  563. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  564. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  565. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  566. andReturn(@[ kFakeAuthorizedDomain]);
  567. callback(mockGetProjectConfigResponse, nil);
  568. });
  569. });
  570. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  571. // Expect view controller presentation by UIDelegate.
  572. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  573. UIDelegate:mockUIDelegate
  574. callbackMatcher:OCMOCK_ANY
  575. completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
  576. __unsafe_unretained id unretainedArgument;
  577. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  578. // `completion` is at index 5
  579. [invocation getArgument:&unretainedArgument atIndex:5];
  580. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  581. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  582. completion([NSURL URLWithString:kFakeRedirectURLStringWebInternalError], nil);
  583. });
  584. });
  585. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  586. [_provider verifyPhoneNumber:kTestPhoneNumber
  587. UIDelegate:mockUIDelegate
  588. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  589. XCTAssertTrue([NSThread isMainThread]);
  590. XCTAssertEqual(error.code, FIRAuthErrorCodeWebInternalError);
  591. XCTAssertNil(verificationID);
  592. [expectation fulfill];
  593. }];
  594. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  595. OCMVerifyAll(_mockBackend);
  596. OCMVerifyAll(_mockNotificationManager);
  597. }
  598. /** @fn testVerifyPhoneNumberUIDelegateUnexpectedError
  599. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  600. invalid client ID.
  601. */
  602. - (void)testVerifyPhoneNumberUIDelegateUnexpectedError {
  603. id mockBundle = OCMClassMock([NSBundle class]);
  604. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  605. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  606. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ kFakeReverseClientID ] } ]);
  607. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  608. // Simulate missing app token error.
  609. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  610. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  611. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  612. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  613. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  614. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  615. code:FIRAuthErrorCodeMissingAppToken
  616. userInfo:nil];
  617. callback(nil, error);
  618. });
  619. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  620. .andCallBlock2(^(FIRGetProjectConfigRequest *request,
  621. FIRGetProjectConfigResponseCallback callback) {
  622. XCTAssertNotNil(request);
  623. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  624. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  625. OCMStub([mockGetProjectConfigResponse authorizedDomains]).
  626. andReturn(@[ kFakeAuthorizedDomain]);
  627. callback(mockGetProjectConfigResponse, nil);
  628. });
  629. });
  630. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  631. // Expect view controller presentation by UIDelegate.
  632. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  633. UIDelegate:mockUIDelegate
  634. callbackMatcher:OCMOCK_ANY
  635. completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
  636. __unsafe_unretained id unretainedArgument;
  637. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  638. // `completion` is at index 5
  639. [invocation getArgument:&unretainedArgument atIndex:5];
  640. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  641. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  642. completion([NSURL URLWithString:kFakeRedirectURLStringUnknownError], nil);
  643. });
  644. });
  645. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  646. [_provider verifyPhoneNumber:kTestPhoneNumber
  647. UIDelegate:mockUIDelegate
  648. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  649. XCTAssertTrue([NSThread isMainThread]);
  650. XCTAssertEqual(error.code, FIRAuthErrorCodeAppVerificationUserInteractionFailure);
  651. XCTAssertNil(verificationID);
  652. [expectation fulfill];
  653. }];
  654. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  655. OCMVerifyAll(_mockBackend);
  656. OCMVerifyAll(_mockNotificationManager);
  657. }
  658. /** @fn testVerifyPhoneNumberUIDelegateRaiseException
  659. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  660. exception.
  661. */
  662. - (void)testVerifyPhoneNumberUIDelegateRaiseException {
  663. id mockBundle = OCMClassMock([NSBundle class]);
  664. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  665. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"])
  666. .andReturn(@[ @{ @"CFBundleURLSchemes" : @[ @"badscheme" ] } ]);
  667. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  668. XCTAssertThrows([_provider verifyPhoneNumber:kTestPhoneNumber
  669. UIDelegate:mockUIDelegate
  670. completion:^(NSString *_Nullable verificationID,
  671. NSError *_Nullable error) {
  672. XCTFail(@"Shouldn't call completion here.");
  673. }]);
  674. }
  675. /** @fn testNotForwardingNotification
  676. @brief Tests returning an error for the app failing to forward notification.
  677. */
  678. - (void)testNotForwardingNotification {
  679. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  680. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(NO); });
  681. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  682. [_provider verifyPhoneNumber:kTestPhoneNumber
  683. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  684. XCTAssertTrue([NSThread isMainThread]);
  685. XCTAssertNil(verificationID);
  686. XCTAssertEqual(error.code, FIRAuthErrorCodeNotificationNotForwarded);
  687. [expectation fulfill];
  688. }];
  689. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  690. OCMVerifyAll(_mockNotificationManager);
  691. }
  692. /** @fn testMissingAPNSToken
  693. @brief Tests returning an error for the app failing to provide an APNS device token.
  694. */
  695. - (void)testMissingAPNSToken {
  696. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  697. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  698. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  699. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  700. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(nil, nil); });
  701. // Expect verify client request to the backend wth empty token.
  702. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  703. .andCallBlock2(^(FIRVerifyClientRequest *request,
  704. FIRVerifyClientResponseCallback callback) {
  705. XCTAssertNil(request.appToken);
  706. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  707. // The backend is supposed to return an error.
  708. callback(nil, [NSError errorWithDomain:FIRAuthErrorDomain
  709. code:FIRAuthErrorCodeMissingAppToken
  710. userInfo:nil]);
  711. });
  712. });
  713. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  714. [_provider verifyPhoneNumber:kTestPhoneNumber
  715. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  716. XCTAssertTrue([NSThread isMainThread]);
  717. XCTAssertNil(verificationID);
  718. XCTAssertEqualObjects(error.domain, FIRAuthErrorDomain);
  719. XCTAssertEqual(error.code, FIRAuthErrorCodeMissingAppToken);
  720. [expectation fulfill];
  721. }];
  722. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  723. OCMVerifyAll(_mockNotificationManager);
  724. OCMVerifyAll(_mockAppCredentialManager);
  725. OCMVerifyAll(_mockAPNSTokenManager);
  726. }
  727. /** @fn testVerifyClient
  728. @brief Tests verifying client before sending verification code.
  729. */
  730. - (void)testVerifyClient {
  731. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  732. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  733. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  734. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  735. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  736. type:FIRAuthAPNSTokenTypeProd];
  737. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  738. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token, nil); });
  739. // Expect verify client request to the backend.
  740. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  741. .andCallBlock2(^(FIRVerifyClientRequest *request,
  742. FIRVerifyClientResponseCallback callback) {
  743. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  744. XCTAssertFalse(request.isSandbox);
  745. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  746. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  747. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  748. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  749. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  750. callback(mockVerifyClientResponse, nil);
  751. });
  752. });
  753. // Mock receiving of push notification.
  754. OCMExpect([[_mockAppCredentialManager ignoringNonObjectArgs]
  755. didStartVerificationWithReceipt:OCMOCK_ANY timeout:0 callback:OCMOCK_ANY])
  756. .andCallIdDoubleIdBlock(^(NSString *receipt,
  757. NSTimeInterval timeout,
  758. FIRAuthAppCredentialCallback callback) {
  759. XCTAssertEqualObjects(receipt, kTestReceipt);
  760. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get passed
  761. // into the block either, so we can't verify it here.
  762. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  763. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  764. });
  765. });
  766. // Expect send verification code request to the backend.
  767. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  768. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  769. FIRSendVerificationCodeResponseCallback callback) {
  770. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  771. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  772. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  773. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  774. id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
  775. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  776. callback(mockSendVerificationCodeResponse, nil);
  777. });
  778. });
  779. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  780. [_provider verifyPhoneNumber:kTestPhoneNumber
  781. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  782. XCTAssertTrue([NSThread isMainThread]);
  783. XCTAssertNil(error);
  784. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  785. [expectation fulfill];
  786. }];
  787. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  788. OCMVerifyAll(_mockBackend);
  789. OCMVerifyAll(_mockNotificationManager);
  790. OCMVerifyAll(_mockAppCredentialManager);
  791. OCMVerifyAll(_mockAPNSTokenManager);
  792. }
  793. /** @fn testSendVerificationCodeFailedRetry
  794. @brief Tests failed retry after failing to send verification code.
  795. */
  796. - (void)testSendVerificationCodeFailedRetry {
  797. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  798. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  799. // Expect twice due to null check consumes one expectation.
  800. OCMExpect([_mockAppCredentialManager credential])
  801. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  802. secret:kTestOldSecret]);
  803. OCMExpect([_mockAppCredentialManager credential])
  804. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  805. secret:kTestOldSecret]);
  806. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  807. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  808. type:FIRAuthAPNSTokenTypeProd];
  809. // Expect first sendVerificationCode request to the backend, with request containing old app
  810. // credential.
  811. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  812. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  813. FIRSendVerificationCodeResponseCallback callback) {
  814. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  815. XCTAssertEqualObjects(request.appCredential.receipt, kTestOldReceipt);
  816. XCTAssertEqualObjects(request.appCredential.secret, kTestOldSecret);
  817. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  818. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  819. });
  820. });
  821. // Expect send verification code request to the backend, with request containing new app
  822. // credential data.
  823. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  824. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  825. FIRSendVerificationCodeResponseCallback callback) {
  826. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  827. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  828. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  829. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  830. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  831. });
  832. });
  833. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  834. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token, nil); });
  835. // Expect verify client request to the backend.
  836. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  837. .andCallBlock2(^(FIRVerifyClientRequest *request,
  838. FIRVerifyClientResponseCallback callback) {
  839. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  840. XCTAssertFalse(request.isSandbox);
  841. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  842. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  843. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  844. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  845. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  846. callback(mockVerifyClientResponse, nil);
  847. });
  848. });
  849. // Mock receiving of push notification.
  850. OCMStub([[_mockAppCredentialManager ignoringNonObjectArgs]
  851. didStartVerificationWithReceipt:OCMOCK_ANY timeout:0 callback:OCMOCK_ANY])
  852. .andCallIdDoubleIdBlock(^(NSString *receipt,
  853. NSTimeInterval timeout,
  854. FIRAuthAppCredentialCallback callback) {
  855. XCTAssertEqualObjects(receipt, kTestReceipt);
  856. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get passed
  857. // into the block either, so we can't verify it here.
  858. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  859. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  860. });
  861. });
  862. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  863. [_provider verifyPhoneNumber:kTestPhoneNumber
  864. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  865. XCTAssertTrue([NSThread isMainThread]);
  866. XCTAssertNil(verificationID);
  867. XCTAssertEqual(error.code, FIRAuthErrorCodeInternalError);
  868. [expectation fulfill];
  869. }];
  870. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  871. OCMVerifyAll(_mockBackend);
  872. OCMVerifyAll(_mockNotificationManager);
  873. OCMVerifyAll(_mockAppCredentialManager);
  874. OCMVerifyAll(_mockAPNSTokenManager);
  875. }
  876. /** @fn testSendVerificationCodeSuccessFulRetry
  877. @brief Tests successful retry after failing to send verification code.
  878. */
  879. - (void)testSendVerificationCodeSuccessFulRetry {
  880. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  881. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
  882. // Expect twice due to null check consumes one expectation.
  883. OCMExpect([_mockAppCredentialManager credential])
  884. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  885. secret:kTestOldSecret]);
  886. OCMExpect([_mockAppCredentialManager credential])
  887. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  888. secret:kTestOldSecret]);
  889. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  890. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  891. type:FIRAuthAPNSTokenTypeProd];
  892. // Expect first sendVerificationCode request to the backend, with request containing old app
  893. // credential.
  894. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  895. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  896. FIRSendVerificationCodeResponseCallback callback) {
  897. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  898. XCTAssertEqualObjects(request.appCredential.receipt, kTestOldReceipt);
  899. XCTAssertEqualObjects(request.appCredential.secret, kTestOldSecret);
  900. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  901. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  902. });
  903. });
  904. // Expect send verification code request to the backend, with request containing new app
  905. // credential data.
  906. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  907. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  908. FIRSendVerificationCodeResponseCallback callback) {
  909. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  910. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  911. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  912. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  913. id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
  914. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  915. callback(mockSendVerificationCodeResponse, nil);
  916. });
  917. });
  918. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  919. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token, nil); });
  920. // Expect verify client request to the backend.
  921. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  922. .andCallBlock2(^(FIRVerifyClientRequest *request,
  923. FIRVerifyClientResponseCallback callback) {
  924. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  925. XCTAssertFalse(request.isSandbox);
  926. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  927. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  928. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  929. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  930. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  931. callback(mockVerifyClientResponse, nil);
  932. });
  933. });
  934. // Mock receiving of push notification.
  935. OCMStub([[_mockAppCredentialManager ignoringNonObjectArgs]
  936. didStartVerificationWithReceipt:OCMOCK_ANY timeout:0 callback:OCMOCK_ANY])
  937. .andCallIdDoubleIdBlock(^(NSString *receipt,
  938. NSTimeInterval timeout,
  939. FIRAuthAppCredentialCallback callback) {
  940. XCTAssertEqualObjects(receipt, kTestReceipt);
  941. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get passed
  942. // into the block either, so we can't verify it here.
  943. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  944. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  945. });
  946. });
  947. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  948. [_provider verifyPhoneNumber:kTestPhoneNumber
  949. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  950. XCTAssertNil(error);
  951. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  952. [expectation fulfill];
  953. }];
  954. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  955. OCMVerifyAll(_mockBackend);
  956. OCMVerifyAll(_mockNotificationManager);
  957. OCMVerifyAll(_mockAppCredentialManager);
  958. OCMVerifyAll(_mockAPNSTokenManager);
  959. }
  960. #pragma clang diagnostic pop // ignored "-Wdeprecated-declarations"
  961. @end
  962. NS_ASSUME_NONNULL_END