FIRPhoneAuthProviderTests.m 66 KB

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