FIRPhoneAuthProviderTests.m 83 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739
  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. id mockBundle = OCMClassMock([NSBundle class]);
  284. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  285. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  286. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  287. ]);
  288. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  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. id mockBundle = OCMClassMock([NSBundle class]);
  308. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  309. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  310. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  311. ]);
  312. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  313. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  314. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  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. id mockBundle = OCMClassMock([NSBundle class]);
  350. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  351. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  352. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  353. ]);
  354. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  355. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  356. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  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. id mockBundle = OCMClassMock([NSBundle class]);
  396. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  397. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  398. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  399. ]);
  400. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  401. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  402. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  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. id mockBundle = OCMClassMock([NSBundle class]);
  444. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  445. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  446. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  447. ]);
  448. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  449. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  450. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  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. id mockBundle = OCMClassMock([NSBundle class]);
  489. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  490. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  491. @{@"CFBundleURLSchemes" : @[ kFakeEncodedFirebaseAppID ]}
  492. ]);
  493. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  494. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  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 testVerifyPhoneNumberUIDelegateFirebaseAppIdWhileClientIdPresentFlow
  609. @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion: when the
  610. client ID is present in the plist file, but the encoded app ID is the registered custom URL
  611. scheme.
  612. */
  613. - (void)testVerifyPhoneNumberUIDelegateFirebaseAppIdWhileClientIdPresentFlow {
  614. id mockBundle = OCMClassMock([NSBundle class]);
  615. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  616. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  617. @{@"CFBundleURLSchemes" : @[ kFakeEncodedFirebaseAppID ]}
  618. ]);
  619. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  620. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  621. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  622. // Simulate missing app token error.
  623. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  624. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  625. callback(YES);
  626. });
  627. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  628. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  629. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  630. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  631. code:FIRAuthErrorCodeMissingAppToken
  632. userInfo:nil];
  633. callback(nil, error);
  634. });
  635. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  636. .andCallBlock2(
  637. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  638. XCTAssertNotNil(request);
  639. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  640. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  641. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  642. kFakeAuthorizedDomain
  643. ]);
  644. callback(mockGetProjectConfigResponse, nil);
  645. });
  646. });
  647. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  648. // Expect view controller presentation by UIDelegate.
  649. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  650. UIDelegate:mockUIDelegate
  651. callbackMatcher:OCMOCK_ANY
  652. completion:OCMOCK_ANY])
  653. .andDo(^(NSInvocation *invocation) {
  654. __unsafe_unretained id unretainedArgument;
  655. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  656. // `presentURL` is at index 2.
  657. [invocation getArgument:&unretainedArgument atIndex:2];
  658. NSURL *presentURL = unretainedArgument;
  659. XCTAssertEqualObjects(presentURL.scheme, @"https");
  660. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  661. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  662. NSURLComponents *actualURLComponents = [NSURLComponents componentsWithURL:presentURL
  663. resolvingAgainstBaseURL:NO];
  664. NSArray<NSURLQueryItem *> *queryItems = [actualURLComponents queryItems];
  665. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"ibi" from:queryItems],
  666. kFakeBundleID);
  667. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"appId" from:queryItems],
  668. kFakeFirebaseAppID);
  669. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"apiKey" from:queryItems],
  670. kFakeAPIKey);
  671. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"authType" from:queryItems],
  672. @"verifyApp");
  673. XCTAssertNotNil([FIRAuthWebUtils queryItemValue:@"v" from:queryItems]);
  674. // `callbackMatcher` is at index 4
  675. [invocation getArgument:&unretainedArgument atIndex:4];
  676. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  677. NSMutableString *redirectURL = [NSMutableString
  678. stringWithString:[kFakeEncodedFirebaseAppID
  679. stringByAppendingString:kFakeRedirectURLStringWithReCAPTCHAToken]];
  680. // Verify that the URL is rejected by the callback matcher without the event ID.
  681. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  682. [redirectURL appendString:@"%26eventId%3D"];
  683. [redirectURL appendString:[FIRAuthWebUtils queryItemValue:@"eventId" from:queryItems]];
  684. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  685. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  686. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  687. NSURLComponents *components = [originalComponents copy];
  688. components.query = @"https";
  689. XCTAssertFalse(callbackMatcher([components URL]));
  690. components = [originalComponents copy];
  691. components.host = @"badhost";
  692. XCTAssertFalse(callbackMatcher([components URL]));
  693. components = [originalComponents copy];
  694. components.path = @"badpath";
  695. XCTAssertFalse(callbackMatcher([components URL]));
  696. components = [originalComponents copy];
  697. components.query = @"badquery";
  698. XCTAssertFalse(callbackMatcher([components URL]));
  699. // `completion` is at index 5
  700. [invocation getArgument:&unretainedArgument atIndex:5];
  701. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  702. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  703. completion([NSURL URLWithString:[kFakeEncodedFirebaseAppID
  704. stringByAppendingString:
  705. kFakeRedirectURLStringWithReCAPTCHAToken]],
  706. nil);
  707. });
  708. });
  709. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  710. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  711. FIRSendVerificationCodeResponseCallback callback) {
  712. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  713. XCTAssertNil(request.appCredential);
  714. XCTAssertEqualObjects(request.reCAPTCHAToken, kFakeReCAPTCHAToken);
  715. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  716. id mockSendVerificationCodeResponse =
  717. OCMClassMock([FIRSendVerificationCodeResponse class]);
  718. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  719. callback(mockSendVerificationCodeResponse, nil);
  720. });
  721. });
  722. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  723. [_provider verifyPhoneNumber:kTestPhoneNumber
  724. UIDelegate:mockUIDelegate
  725. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  726. XCTAssertTrue([NSThread isMainThread]);
  727. XCTAssertNil(error);
  728. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  729. [expectation fulfill];
  730. }];
  731. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  732. OCMVerifyAll(_mockBackend);
  733. OCMVerifyAll(_mockNotificationManager);
  734. }
  735. /** @fn testVerifyPhoneNumberUIDelegateClientIdFlow
  736. @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion:.
  737. */
  738. - (void)testVerifyPhoneNumberUIDelegateClientIdFlow {
  739. id mockBundle = OCMClassMock([NSBundle class]);
  740. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  741. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  742. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  743. ]);
  744. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  745. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  746. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  747. // Simulate missing app token error.
  748. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  749. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  750. callback(YES);
  751. });
  752. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  753. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  754. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  755. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  756. code:FIRAuthErrorCodeMissingAppToken
  757. userInfo:nil];
  758. callback(nil, error);
  759. });
  760. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  761. .andCallBlock2(
  762. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  763. XCTAssertNotNil(request);
  764. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  765. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  766. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  767. kFakeAuthorizedDomain
  768. ]);
  769. callback(mockGetProjectConfigResponse, nil);
  770. });
  771. });
  772. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  773. // Expect view controller presentation by UIDelegate.
  774. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  775. UIDelegate:mockUIDelegate
  776. callbackMatcher:OCMOCK_ANY
  777. completion:OCMOCK_ANY])
  778. .andDo(^(NSInvocation *invocation) {
  779. __unsafe_unretained id unretainedArgument;
  780. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  781. // `presentURL` is at index 2.
  782. [invocation getArgument:&unretainedArgument atIndex:2];
  783. NSURL *presentURL = unretainedArgument;
  784. XCTAssertEqualObjects(presentURL.scheme, @"https");
  785. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  786. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  787. NSURLComponents *actualURLComponents = [NSURLComponents componentsWithURL:presentURL
  788. resolvingAgainstBaseURL:NO];
  789. NSArray<NSURLQueryItem *> *queryItems = [actualURLComponents queryItems];
  790. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"ibi" from:queryItems],
  791. kFakeBundleID);
  792. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"clientId" from:queryItems],
  793. kFakeClientID);
  794. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"apiKey" from:queryItems],
  795. kFakeAPIKey);
  796. XCTAssertEqualObjects([FIRAuthWebUtils queryItemValue:@"authType" from:queryItems],
  797. @"verifyApp");
  798. XCTAssertNotNil([FIRAuthWebUtils queryItemValue:@"v" from:queryItems]);
  799. // `callbackMatcher` is at index 4
  800. [invocation getArgument:&unretainedArgument atIndex:4];
  801. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  802. NSMutableString *redirectURL = [NSMutableString
  803. stringWithString:[kFakeReverseClientID
  804. stringByAppendingString:kFakeRedirectURLStringWithReCAPTCHAToken]];
  805. // Verify that the URL is rejected by the callback matcher without the event ID.
  806. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  807. [redirectURL appendString:@"%26eventId%3D"];
  808. [redirectURL appendString:[FIRAuthWebUtils queryItemValue:@"eventId" from:queryItems]];
  809. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  810. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  811. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  812. NSURLComponents *components = [originalComponents copy];
  813. components.query = @"https";
  814. XCTAssertFalse(callbackMatcher([components URL]));
  815. components = [originalComponents copy];
  816. components.host = @"badhost";
  817. XCTAssertFalse(callbackMatcher([components URL]));
  818. components = [originalComponents copy];
  819. components.path = @"badpath";
  820. XCTAssertFalse(callbackMatcher([components URL]));
  821. components = [originalComponents copy];
  822. components.query = @"badquery";
  823. XCTAssertFalse(callbackMatcher([components URL]));
  824. // `completion` is at index 5
  825. [invocation getArgument:&unretainedArgument atIndex:5];
  826. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  827. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  828. completion([NSURL URLWithString:[kFakeReverseClientID
  829. stringByAppendingString:
  830. kFakeRedirectURLStringWithReCAPTCHAToken]],
  831. nil);
  832. });
  833. });
  834. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  835. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  836. FIRSendVerificationCodeResponseCallback callback) {
  837. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  838. XCTAssertNil(request.appCredential);
  839. XCTAssertEqualObjects(request.reCAPTCHAToken, kFakeReCAPTCHAToken);
  840. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  841. id mockSendVerificationCodeResponse =
  842. OCMClassMock([FIRSendVerificationCodeResponse class]);
  843. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  844. callback(mockSendVerificationCodeResponse, nil);
  845. });
  846. });
  847. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  848. [_provider verifyPhoneNumber:kTestPhoneNumber
  849. UIDelegate:mockUIDelegate
  850. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  851. XCTAssertTrue([NSThread isMainThread]);
  852. XCTAssertNil(error);
  853. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  854. [expectation fulfill];
  855. }];
  856. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  857. OCMVerifyAll(_mockBackend);
  858. OCMVerifyAll(_mockNotificationManager);
  859. }
  860. /** @fn testVerifyPhoneNumberUIDelegateInvalidClientID
  861. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  862. invalid client ID error.
  863. */
  864. - (void)testVerifyPhoneNumberUIDelegateInvalidClientID {
  865. id mockBundle = OCMClassMock([NSBundle class]);
  866. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  867. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  868. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  869. ]);
  870. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  871. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  872. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  873. // Simulate missing app token error.
  874. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  875. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  876. callback(YES);
  877. });
  878. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  879. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  880. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  881. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  882. code:FIRAuthErrorCodeMissingAppToken
  883. userInfo:nil];
  884. callback(nil, error);
  885. });
  886. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  887. .andCallBlock2(
  888. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  889. XCTAssertNotNil(request);
  890. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  891. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  892. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  893. kFakeAuthorizedDomain
  894. ]);
  895. callback(mockGetProjectConfigResponse, nil);
  896. });
  897. });
  898. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  899. // Expect view controller presentation by UIDelegate.
  900. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  901. UIDelegate:mockUIDelegate
  902. callbackMatcher:OCMOCK_ANY
  903. completion:OCMOCK_ANY])
  904. .andDo(^(NSInvocation *invocation) {
  905. __unsafe_unretained id unretainedArgument;
  906. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  907. // `completion` is at index 5
  908. [invocation getArgument:&unretainedArgument atIndex:5];
  909. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  910. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  911. completion([NSURL URLWithString:kFakeRedirectURLStringInvalidClientID], nil);
  912. });
  913. });
  914. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  915. [_provider verifyPhoneNumber:kTestPhoneNumber
  916. UIDelegate:mockUIDelegate
  917. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  918. XCTAssertTrue([NSThread isMainThread]);
  919. XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidClientID);
  920. XCTAssertNil(verificationID);
  921. [expectation fulfill];
  922. }];
  923. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  924. OCMVerifyAll(_mockBackend);
  925. OCMVerifyAll(_mockNotificationManager);
  926. }
  927. /** @fn testVerifyPhoneNumberUIDelegateWebNetworkRequestFailed
  928. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in a web
  929. network request failed error.
  930. */
  931. - (void)testVerifyPhoneNumberUIDelegateNetworkRequestFailed {
  932. id mockBundle = OCMClassMock([NSBundle class]);
  933. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  934. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  935. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  936. ]);
  937. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  938. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  939. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  940. // Simulate missing app token error.
  941. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  942. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  943. callback(YES);
  944. });
  945. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  946. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  947. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  948. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  949. code:FIRAuthErrorCodeMissingAppToken
  950. userInfo:nil];
  951. callback(nil, error);
  952. });
  953. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  954. .andCallBlock2(
  955. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  956. XCTAssertNotNil(request);
  957. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  958. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  959. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  960. kFakeAuthorizedDomain
  961. ]);
  962. callback(mockGetProjectConfigResponse, nil);
  963. });
  964. });
  965. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  966. // Expect view controller presentation by UIDelegate.
  967. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  968. UIDelegate:mockUIDelegate
  969. callbackMatcher:OCMOCK_ANY
  970. completion:OCMOCK_ANY])
  971. .andDo(^(NSInvocation *invocation) {
  972. __unsafe_unretained id unretainedArgument;
  973. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  974. // `completion` is at index 5
  975. [invocation getArgument:&unretainedArgument atIndex:5];
  976. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  977. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  978. completion([NSURL URLWithString:kFakeRedirectURLStringWebNetworkRequestFailed], nil);
  979. });
  980. });
  981. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  982. [_provider verifyPhoneNumber:kTestPhoneNumber
  983. UIDelegate:mockUIDelegate
  984. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  985. XCTAssertTrue([NSThread isMainThread]);
  986. XCTAssertEqual(error.code, FIRAuthErrorCodeWebNetworkRequestFailed);
  987. XCTAssertNil(verificationID);
  988. [expectation fulfill];
  989. }];
  990. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  991. OCMVerifyAll(_mockBackend);
  992. OCMVerifyAll(_mockNotificationManager);
  993. }
  994. /** @fn testVerifyPhoneNumberUIDelegateWebInternalError
  995. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in a web
  996. internal error.
  997. */
  998. - (void)testVerifyPhoneNumberUIDelegateWebInternalError {
  999. id mockBundle = OCMClassMock([NSBundle class]);
  1000. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1001. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1002. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  1003. ]);
  1004. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1005. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1006. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  1007. // Simulate missing app token error.
  1008. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  1009. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  1010. callback(YES);
  1011. });
  1012. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  1013. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  1014. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  1015. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  1016. code:FIRAuthErrorCodeMissingAppToken
  1017. userInfo:nil];
  1018. callback(nil, error);
  1019. });
  1020. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  1021. .andCallBlock2(
  1022. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  1023. XCTAssertNotNil(request);
  1024. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1025. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  1026. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  1027. kFakeAuthorizedDomain
  1028. ]);
  1029. callback(mockGetProjectConfigResponse, nil);
  1030. });
  1031. });
  1032. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  1033. // Expect view controller presentation by UIDelegate.
  1034. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  1035. UIDelegate:mockUIDelegate
  1036. callbackMatcher:OCMOCK_ANY
  1037. completion:OCMOCK_ANY])
  1038. .andDo(^(NSInvocation *invocation) {
  1039. __unsafe_unretained id unretainedArgument;
  1040. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  1041. // `completion` is at index 5
  1042. [invocation getArgument:&unretainedArgument atIndex:5];
  1043. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  1044. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1045. completion([NSURL URLWithString:kFakeRedirectURLStringWebInternalError], nil);
  1046. });
  1047. });
  1048. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1049. [_provider verifyPhoneNumber:kTestPhoneNumber
  1050. UIDelegate:mockUIDelegate
  1051. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1052. XCTAssertTrue([NSThread isMainThread]);
  1053. XCTAssertEqual(error.code, FIRAuthErrorCodeWebInternalError);
  1054. XCTAssertNil(verificationID);
  1055. [expectation fulfill];
  1056. }];
  1057. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1058. OCMVerifyAll(_mockBackend);
  1059. OCMVerifyAll(_mockNotificationManager);
  1060. }
  1061. /** @fn testVerifyPhoneNumberUIDelegateUnexpectedError
  1062. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  1063. invalid client ID.
  1064. */
  1065. - (void)testVerifyPhoneNumberUIDelegateUnexpectedError {
  1066. id mockBundle = OCMClassMock([NSBundle class]);
  1067. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1068. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1069. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  1070. ]);
  1071. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1072. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1073. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  1074. // Simulate missing app token error.
  1075. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  1076. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  1077. callback(YES);
  1078. });
  1079. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  1080. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  1081. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  1082. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  1083. code:FIRAuthErrorCodeMissingAppToken
  1084. userInfo:nil];
  1085. callback(nil, error);
  1086. });
  1087. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  1088. .andCallBlock2(
  1089. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  1090. XCTAssertNotNil(request);
  1091. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1092. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  1093. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  1094. kFakeAuthorizedDomain
  1095. ]);
  1096. callback(mockGetProjectConfigResponse, nil);
  1097. });
  1098. });
  1099. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  1100. // Expect view controller presentation by UIDelegate.
  1101. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  1102. UIDelegate:mockUIDelegate
  1103. callbackMatcher:OCMOCK_ANY
  1104. completion:OCMOCK_ANY])
  1105. .andDo(^(NSInvocation *invocation) {
  1106. __unsafe_unretained id unretainedArgument;
  1107. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  1108. // `completion` is at index 5
  1109. [invocation getArgument:&unretainedArgument atIndex:5];
  1110. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  1111. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1112. completion([NSURL URLWithString:kFakeRedirectURLStringUnknownError], nil);
  1113. });
  1114. });
  1115. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1116. [_provider
  1117. verifyPhoneNumber:kTestPhoneNumber
  1118. UIDelegate:mockUIDelegate
  1119. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1120. XCTAssertTrue([NSThread isMainThread]);
  1121. XCTAssertEqual(error.code, FIRAuthErrorCodeAppVerificationUserInteractionFailure);
  1122. XCTAssertNil(verificationID);
  1123. [expectation fulfill];
  1124. }];
  1125. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1126. OCMVerifyAll(_mockBackend);
  1127. OCMVerifyAll(_mockNotificationManager);
  1128. }
  1129. /** @fn testVerifyPhoneNumberUIDelegateUnstructuredError
  1130. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  1131. error being surfaced with a default NSLocalizedFailureReasonErrorKey due to an unexpected
  1132. structure of the error response.
  1133. */
  1134. - (void)testVerifyPhoneNumberUIDelegateUnstructuredError {
  1135. id mockBundle = OCMClassMock([NSBundle class]);
  1136. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1137. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1138. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  1139. ]);
  1140. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1141. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1142. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  1143. // Simulate missing app token error.
  1144. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  1145. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  1146. callback(YES);
  1147. });
  1148. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  1149. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  1150. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  1151. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  1152. code:FIRAuthErrorCodeMissingAppToken
  1153. userInfo:nil];
  1154. callback(nil, error);
  1155. });
  1156. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  1157. .andCallBlock2(
  1158. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  1159. XCTAssertNotNil(request);
  1160. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1161. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  1162. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  1163. kFakeAuthorizedDomain
  1164. ]);
  1165. callback(mockGetProjectConfigResponse, nil);
  1166. });
  1167. });
  1168. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  1169. // Expect view controller presentation by UIDelegate.
  1170. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  1171. UIDelegate:mockUIDelegate
  1172. callbackMatcher:OCMOCK_ANY
  1173. completion:OCMOCK_ANY])
  1174. .andDo(^(NSInvocation *invocation) {
  1175. __unsafe_unretained id unretainedArgument;
  1176. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  1177. // `completion` is at index 5
  1178. [invocation getArgument:&unretainedArgument atIndex:5];
  1179. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  1180. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1181. completion([NSURL URLWithString:kFakeRedirectURLStringUnstructuredError], nil);
  1182. });
  1183. });
  1184. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1185. [_provider
  1186. verifyPhoneNumber:kTestPhoneNumber
  1187. UIDelegate:mockUIDelegate
  1188. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1189. XCTAssertTrue([NSThread isMainThread]);
  1190. XCTAssertEqual(error.code, FIRAuthErrorCodeAppVerificationUserInteractionFailure);
  1191. XCTAssertNil(verificationID);
  1192. [expectation fulfill];
  1193. }];
  1194. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1195. OCMVerifyAll(_mockBackend);
  1196. OCMVerifyAll(_mockNotificationManager);
  1197. }
  1198. /** @fn testVerifyPhoneNumberUIDelegateRaiseException
  1199. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  1200. exception.
  1201. */
  1202. - (void)testVerifyPhoneNumberUIDelegateRaiseException {
  1203. id mockBundle = OCMClassMock([NSBundle class]);
  1204. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1205. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1206. @{@"CFBundleURLSchemes" : @[ @"badscheme" ]}
  1207. ]);
  1208. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1209. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  1210. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  1211. XCTAssertThrows([_provider
  1212. verifyPhoneNumber:kTestPhoneNumber
  1213. UIDelegate:mockUIDelegate
  1214. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1215. XCTFail(@"Shouldn't call completion here.");
  1216. }]);
  1217. }
  1218. /** @fn testNotForwardingNotification
  1219. @brief Tests returning an error for the app failing to forward notification.
  1220. */
  1221. - (void)testNotForwardingNotification {
  1222. id mockBundle = OCMClassMock([NSBundle class]);
  1223. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1224. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1225. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  1226. ]);
  1227. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1228. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1229. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  1230. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  1231. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  1232. callback(NO);
  1233. });
  1234. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1235. [_provider verifyPhoneNumber:kTestPhoneNumber
  1236. UIDelegate:nil
  1237. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1238. XCTAssertTrue([NSThread isMainThread]);
  1239. XCTAssertNil(verificationID);
  1240. XCTAssertEqual(error.code, FIRAuthErrorCodeNotificationNotForwarded);
  1241. [expectation fulfill];
  1242. }];
  1243. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1244. OCMVerifyAll(_mockNotificationManager);
  1245. }
  1246. /** @fn testMissingAPNSToken
  1247. @brief Tests returning an error for the app failing to provide an APNS device token.
  1248. */
  1249. - (void)testMissingAPNSToken {
  1250. id mockBundle = OCMClassMock([NSBundle class]);
  1251. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1252. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1253. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  1254. ]);
  1255. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1256. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1257. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  1258. // Simulate missing app token error.
  1259. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  1260. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  1261. callback(YES);
  1262. });
  1263. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  1264. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  1265. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  1266. NSError *error = [NSError errorWithDomain:FIRAuthErrorDomain
  1267. code:FIRAuthErrorCodeMissingAppToken
  1268. userInfo:nil];
  1269. callback(nil, error);
  1270. });
  1271. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  1272. .andCallBlock2(
  1273. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  1274. XCTAssertNotNil(request);
  1275. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1276. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  1277. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  1278. kFakeAuthorizedDomain
  1279. ]);
  1280. callback(mockGetProjectConfigResponse, nil);
  1281. });
  1282. });
  1283. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  1284. // Expect view controller presentation by UIDelegate.
  1285. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  1286. UIDelegate:mockUIDelegate
  1287. callbackMatcher:OCMOCK_ANY
  1288. completion:OCMOCK_ANY])
  1289. .andDo(^(NSInvocation *invocation) {
  1290. __unsafe_unretained id unretainedArgument;
  1291. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  1292. // `completion` is at index 5
  1293. [invocation getArgument:&unretainedArgument atIndex:5];
  1294. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  1295. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1296. completion(nil, [NSError errorWithDomain:FIRAuthErrorDomain
  1297. code:FIRAuthErrorCodeMissingAppToken
  1298. userInfo:nil]);
  1299. });
  1300. });
  1301. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1302. [_provider verifyPhoneNumber:kTestPhoneNumber
  1303. UIDelegate:mockUIDelegate
  1304. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1305. XCTAssertTrue([NSThread isMainThread]);
  1306. XCTAssertEqual(error.code, FIRAuthErrorCodeMissingAppToken);
  1307. XCTAssertNil(verificationID);
  1308. [expectation fulfill];
  1309. }];
  1310. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1311. OCMVerifyAll(_mockBackend);
  1312. OCMVerifyAll(_mockNotificationManager);
  1313. }
  1314. /** @fn testVerifyClient
  1315. @brief Tests verifying client before sending verification code.
  1316. */
  1317. - (void)testVerifyClient {
  1318. id mockBundle = OCMClassMock([NSBundle class]);
  1319. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1320. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1321. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  1322. ]);
  1323. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1324. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1325. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  1326. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  1327. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  1328. callback(YES);
  1329. });
  1330. OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
  1331. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  1332. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  1333. type:FIRAuthAPNSTokenTypeProd];
  1334. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  1335. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  1336. callback(token, nil);
  1337. });
  1338. // Expect verify client request to the backend.
  1339. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  1340. .andCallBlock2(^(FIRVerifyClientRequest *request, FIRVerifyClientResponseCallback callback) {
  1341. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  1342. XCTAssertFalse(request.isSandbox);
  1343. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1344. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  1345. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  1346. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  1347. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  1348. callback(mockVerifyClientResponse, nil);
  1349. });
  1350. });
  1351. // Mock receiving of push notification.
  1352. OCMExpect([_mockAppCredentialManager didStartVerificationWithReceipt:OCMOCK_ANY
  1353. timeout:0
  1354. callback:OCMOCK_ANY])
  1355. .ignoringNonObjectArgs()
  1356. .andCallIdDoubleIdBlock(^(NSString *receipt, NSTimeInterval timeout,
  1357. FIRAuthAppCredentialCallback callback) {
  1358. XCTAssertEqualObjects(receipt, kTestReceipt);
  1359. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get
  1360. // passed into the block either, so we can't verify it here.
  1361. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1362. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  1363. });
  1364. });
  1365. // Expect send verification code request to the backend.
  1366. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1367. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1368. FIRSendVerificationCodeResponseCallback callback) {
  1369. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1370. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  1371. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  1372. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1373. id mockSendVerificationCodeResponse =
  1374. OCMClassMock([FIRSendVerificationCodeResponse class]);
  1375. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  1376. callback(mockSendVerificationCodeResponse, nil);
  1377. });
  1378. });
  1379. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1380. [_provider verifyPhoneNumber:kTestPhoneNumber
  1381. UIDelegate:nil
  1382. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1383. XCTAssertTrue([NSThread isMainThread]);
  1384. XCTAssertNil(error);
  1385. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  1386. [expectation fulfill];
  1387. }];
  1388. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1389. OCMVerifyAll(_mockBackend);
  1390. OCMVerifyAll(_mockNotificationManager);
  1391. OCMVerifyAll(_mockAppCredentialManager);
  1392. OCMVerifyAll(_mockAPNSTokenManager);
  1393. }
  1394. /** @fn testSendVerificationCodeFailedRetry
  1395. @brief Tests failed retry after failing to send verification code.
  1396. */
  1397. - (void)testSendVerificationCodeFailedRetry {
  1398. id mockBundle = OCMClassMock([NSBundle class]);
  1399. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1400. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1401. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  1402. ]);
  1403. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1404. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1405. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  1406. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  1407. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  1408. callback(YES);
  1409. });
  1410. // Expect twice due to null check consumes one expectation.
  1411. OCMExpect([_mockAppCredentialManager credential])
  1412. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  1413. secret:kTestOldSecret]);
  1414. OCMExpect([_mockAppCredentialManager credential])
  1415. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  1416. secret:kTestOldSecret]);
  1417. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  1418. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  1419. type:FIRAuthAPNSTokenTypeProd];
  1420. // Expect first sendVerificationCode request to the backend, with request containing old app
  1421. // credential.
  1422. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1423. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1424. FIRSendVerificationCodeResponseCallback callback) {
  1425. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1426. XCTAssertEqualObjects(request.appCredential.receipt, kTestOldReceipt);
  1427. XCTAssertEqualObjects(request.appCredential.secret, kTestOldSecret);
  1428. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1429. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  1430. });
  1431. });
  1432. // Expect send verification code request to the backend, with request containing new app
  1433. // credential data.
  1434. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1435. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1436. FIRSendVerificationCodeResponseCallback callback) {
  1437. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1438. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  1439. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  1440. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1441. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  1442. });
  1443. });
  1444. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  1445. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  1446. callback(token, nil);
  1447. });
  1448. // Expect verify client request to the backend.
  1449. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  1450. .andCallBlock2(^(FIRVerifyClientRequest *request, FIRVerifyClientResponseCallback callback) {
  1451. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  1452. XCTAssertFalse(request.isSandbox);
  1453. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1454. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  1455. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  1456. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  1457. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  1458. callback(mockVerifyClientResponse, nil);
  1459. });
  1460. });
  1461. // Mock receiving of push notification.
  1462. OCMStub([_mockAppCredentialManager didStartVerificationWithReceipt:OCMOCK_ANY
  1463. timeout:0
  1464. callback:OCMOCK_ANY])
  1465. .ignoringNonObjectArgs()
  1466. .andCallIdDoubleIdBlock(^(NSString *receipt, NSTimeInterval timeout,
  1467. FIRAuthAppCredentialCallback callback) {
  1468. XCTAssertEqualObjects(receipt, kTestReceipt);
  1469. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get
  1470. // passed into the block either, so we can't verify it here.
  1471. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1472. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  1473. });
  1474. });
  1475. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1476. [_provider verifyPhoneNumber:kTestPhoneNumber
  1477. UIDelegate:nil
  1478. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1479. XCTAssertTrue([NSThread isMainThread]);
  1480. XCTAssertNil(verificationID);
  1481. XCTAssertEqual(error.code, FIRAuthErrorCodeInternalError);
  1482. [expectation fulfill];
  1483. }];
  1484. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1485. OCMVerifyAll(_mockBackend);
  1486. OCMVerifyAll(_mockNotificationManager);
  1487. OCMVerifyAll(_mockAppCredentialManager);
  1488. OCMVerifyAll(_mockAPNSTokenManager);
  1489. }
  1490. /** @fn testSendVerificationCodeSuccessFulRetry
  1491. @brief Tests successful retry after failing to send verification code.
  1492. */
  1493. - (void)testSendVerificationCodeSuccessFulRetry {
  1494. id mockBundle = OCMClassMock([NSBundle class]);
  1495. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1496. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1497. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  1498. ]);
  1499. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1500. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1501. _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
  1502. OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
  1503. .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) {
  1504. callback(YES);
  1505. });
  1506. // Expect twice due to null check consumes one expectation.
  1507. OCMExpect([_mockAppCredentialManager credential])
  1508. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  1509. secret:kTestOldSecret]);
  1510. OCMExpect([_mockAppCredentialManager credential])
  1511. .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
  1512. secret:kTestOldSecret]);
  1513. NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
  1514. FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
  1515. type:FIRAuthAPNSTokenTypeProd];
  1516. // Expect first sendVerificationCode request to the backend, with request containing old app
  1517. // credential.
  1518. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1519. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1520. FIRSendVerificationCodeResponseCallback callback) {
  1521. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1522. XCTAssertEqualObjects(request.appCredential.receipt, kTestOldReceipt);
  1523. XCTAssertEqualObjects(request.appCredential.secret, kTestOldSecret);
  1524. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1525. callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
  1526. });
  1527. });
  1528. // Expect send verification code request to the backend, with request containing new app
  1529. // credential data.
  1530. OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
  1531. .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
  1532. FIRSendVerificationCodeResponseCallback callback) {
  1533. XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
  1534. XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
  1535. XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
  1536. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1537. id mockSendVerificationCodeResponse =
  1538. OCMClassMock([FIRSendVerificationCodeResponse class]);
  1539. OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
  1540. callback(mockSendVerificationCodeResponse, nil);
  1541. });
  1542. });
  1543. OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
  1544. .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) {
  1545. callback(token, nil);
  1546. });
  1547. // Expect verify client request to the backend.
  1548. OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
  1549. .andCallBlock2(^(FIRVerifyClientRequest *request, FIRVerifyClientResponseCallback callback) {
  1550. XCTAssertEqualObjects(request.appToken, @"21402324255E");
  1551. XCTAssertFalse(request.isSandbox);
  1552. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1553. id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
  1554. OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
  1555. OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
  1556. .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
  1557. callback(mockVerifyClientResponse, nil);
  1558. });
  1559. });
  1560. // Mock receiving of push notification.
  1561. OCMStub([_mockAppCredentialManager didStartVerificationWithReceipt:OCMOCK_ANY
  1562. timeout:0
  1563. callback:OCMOCK_ANY])
  1564. .ignoringNonObjectArgs()
  1565. .andCallIdDoubleIdBlock(^(NSString *receipt, NSTimeInterval timeout,
  1566. FIRAuthAppCredentialCallback callback) {
  1567. XCTAssertEqualObjects(receipt, kTestReceipt);
  1568. // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get
  1569. // passed into the block either, so we can't verify it here.
  1570. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1571. callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
  1572. });
  1573. });
  1574. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1575. [_provider verifyPhoneNumber:kTestPhoneNumber
  1576. UIDelegate:nil
  1577. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  1578. XCTAssertNil(error);
  1579. XCTAssertEqualObjects(verificationID, kTestVerificationID);
  1580. [expectation fulfill];
  1581. }];
  1582. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1583. OCMVerifyAll(_mockBackend);
  1584. OCMVerifyAll(_mockNotificationManager);
  1585. OCMVerifyAll(_mockAppCredentialManager);
  1586. OCMVerifyAll(_mockAPNSTokenManager);
  1587. }
  1588. #pragma clang diagnostic pop // ignored "-Wdeprecated-declarations"
  1589. @end
  1590. NS_ASSUME_NONNULL_END
  1591. #endif