FIRPhoneAuthProviderTests.m 76 KB

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