FIRPhoneAuthProviderTests.m 78 KB

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