FIRPhoneAuthProviderTests.m 53 KB

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