FIROAuthProviderTests.m 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <TargetConditionals.h>
  17. #if TARGET_OS_IOS
  18. #import <OCMock/OCMock.h>
  19. #import <XCTest/XCTest.h>
  20. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuthErrors.h"
  21. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuthUIDelegate.h"
  22. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIROAuthProvider.h"
  23. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  24. #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h"
  25. #import "FirebaseAuth/Sources/Auth/FIRAuth_Internal.h"
  26. #import "FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthCredential_Internal.h"
  27. #import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h"
  28. #import "FirebaseAuth/Sources/Backend/FIRAuthRequestConfiguration.h"
  29. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetProjectConfigRequest.h"
  30. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetProjectConfigResponse.h"
  31. #import "FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h"
  32. #import "FirebaseAuth/Sources/Utilities/FIRAuthURLPresenter.h"
  33. #import "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"
  34. #import "FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.h"
  35. /** @var kExpectationTimeout
  36. @brief The maximum time waiting for expectations to fulfill.
  37. */
  38. static const NSTimeInterval kExpectationTimeout = 1;
  39. /** @var kFakeAuthorizedDomain
  40. @brief A fake authorized domain for the app.
  41. */
  42. static NSString *const kFakeAuthorizedDomain = @"test.firebaseapp.com";
  43. /** @var kFakeBundleID
  44. @brief A fake bundle ID.
  45. */
  46. static NSString *const kFakeBundleID = @"com.firebaseapp.example";
  47. /** @var kFakeAccessToken
  48. @brief A fake access token for testing.
  49. */
  50. static NSString *const kFakeAccessToken = @"fakeAccessToken";
  51. /** @var kFakeIDToken
  52. @brief A fake ID token for testing.
  53. */
  54. static NSString *const kFakeIDToken = @"fakeIDToken";
  55. /** @var kFakeProviderID
  56. @brief A fake provider ID for testing.
  57. */
  58. static NSString *const kFakeProviderID = @"fakeProviderID";
  59. /** @var kFakeProviderID
  60. @brief A fake provider ID for testing.
  61. */
  62. static NSString *const kFakeDisplayName = @"fakeDisplayName";
  63. /** @var kFakeAPIKey
  64. @brief A fake API key.
  65. */
  66. static NSString *const kFakeAPIKey = @"asdfghjkl";
  67. /** @var kFakeEmulatorHost
  68. @brief A fake emulator host.
  69. */
  70. static NSString *const kFakeEmulatorHost = @"emulatorhost";
  71. /** @var kFakeEmulatorPort
  72. @brief A fake emulator port.
  73. */
  74. static NSString *const kFakeEmulatorPort = @"12345";
  75. /** @var kFakeClientID
  76. @brief A fake client ID.
  77. */
  78. static NSString *const kFakeClientID = @"123456.apps.googleusercontent.com";
  79. /** @var kFakeReverseClientID
  80. @brief The dot-reversed version of the fake client ID.
  81. */
  82. static NSString *const kFakeReverseClientID = @"com.googleusercontent.apps.123456";
  83. /** @var kFakeFirebaseAppID
  84. @brief A fake Firebase app ID.
  85. */
  86. static NSString *const kFakeFirebaseAppID = @"1:123456789:ios:123abc456def";
  87. /** @var kFakeEncodedFirebaseAppID
  88. @brief A fake encoded Firebase app ID to be used as a custom URL scheme.
  89. */
  90. static NSString *const kFakeEncodedFirebaseAppID = @"app-1-123456789-ios-123abc456def";
  91. /** @var kFakeTenantID
  92. @brief A fake tenant ID.
  93. */
  94. static NSString *const kFakeTenantID = @"tenantID";
  95. /** @var kFakeOAuthResponseURL
  96. @brief A fake OAuth response URL used in test.
  97. */
  98. static NSString *const kFakeOAuthResponseURL = @"fakeOAuthResponseURL";
  99. /** @var kFakeRedirectURLResponseURL
  100. @brief A fake callback URL (minus the scheme) containing a fake response URL.
  101. */
  102. static NSString *const kFakeRedirectURLResponseURL =
  103. @"://firebaseauth/"
  104. @"link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcallback%3FauthType%"
  105. @"3DsignInWithRedirect%26link%3D";
  106. /** @var kFakeRedirectURLBaseErrorString
  107. @brief The base for a fake redirect URL string that contains an error.
  108. */
  109. static NSString *const kFakeRedirectURLBaseErrorString =
  110. @"com.googleusercontent.apps.123456://fire"
  111. "baseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcallback%3f";
  112. /** @var kNetworkRequestFailedErrorString
  113. @brief The error message returned if a network request failure occurs within the web context.
  114. */
  115. static NSString *const kNetworkRequestFailedErrorString =
  116. @"firebaseError%3D%257B%2522code%2"
  117. "522%253A%2522auth%252Fnetwork-request-failed%2522%252C%2522message%2522%253A%2522The%"
  118. "2520netwo"
  119. "rk%2520request%2520failed%2520.%2522%257D%26authType%3DsignInWithRedirect";
  120. /** @var kInvalidClientIDString
  121. @brief The error message returned if the client ID used is invalid.
  122. */
  123. static NSString *const kInvalidClientIDString =
  124. @"firebaseError%3D%257B%2522code%2522%253A%2522auth"
  125. "%252Finvalid-oauth-client-id%2522%252C%2522message%2522%253A%2522The%2520OAuth%2520client%"
  126. "2520"
  127. "ID%2520provided%2520is%2520either%2520invalid%2520or%2520does%2520not%2520match%2520the%"
  128. "2520sp"
  129. "ecified%2520API%2520key.%2522%257D%26authType%3DsignInWithRedirect";
  130. /** @var kInternalErrorString
  131. @brief The error message returned if there is an internal error within the web context.
  132. */
  133. static NSString *const kInternalErrorString =
  134. @"firebaseError%3D%257B%2522code%2522%253"
  135. "A%2522auth%252Finternal-error%2522%252C%2522message%2522%253A%2522Internal%2520error%2520.%"
  136. "252"
  137. "2%257D%26authType%3DsignInWithRedirect";
  138. /** @var kUnknownErrorString
  139. @brief The error message returned if an unknown error is returned from the web context.
  140. */
  141. static NSString *const kUnknownErrorString =
  142. @"firebaseError%3D%257B%2522code%2522%253A%2522auth%2"
  143. "52Funknown-error-id%2522%252C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%"
  144. "2520pr"
  145. "ovided%2520is%2520either%2520invalid%2520or%2520does%2520not%2520match%2520the%2520specified%"
  146. "2"
  147. "520API%2520key.%2522%257D%26authType%3DsignInWithRedirect";
  148. @interface FIROAuthProviderTests : XCTestCase
  149. @end
  150. @implementation FIROAuthProviderTests {
  151. /** @var _mockBackend
  152. @brief The mock @c FIRAuthBackendImplementation.
  153. */
  154. id _mockBackend;
  155. /** @var _provider
  156. @brief The @c FIROAuthProvider instance under test.
  157. */
  158. FIROAuthProvider *_provider;
  159. /** @var _mockAuth
  160. @brief The mock @c FIRAuth instance associated with @c _provider.
  161. */
  162. id _mockAuth;
  163. /** @var _mockURLPresenter
  164. @brief The mock @c FIRAuthURLPresenter instance associated with @c _mockAuth.
  165. */
  166. id _mockURLPresenter;
  167. /** @var _mockApp
  168. @brief The mock @c FIRApp instance associated with @c _mockAuth.
  169. */
  170. id _mockApp;
  171. /** @var _mockOptions
  172. @brief The mock @c FIROptions instance associated with @c _mockApp.
  173. */
  174. id _mockOptions;
  175. /** @var _mockRequestConfiguration
  176. @brief The mock @c FIRAuthRequestConfiguration instance associated with @c _mockAuth.
  177. */
  178. id _mockRequestConfiguration;
  179. }
  180. - (void)setUp {
  181. [super setUp];
  182. _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation));
  183. [FIRAuthBackend setBackendImplementation:_mockBackend];
  184. _mockAuth = OCMClassMock([FIRAuth class]);
  185. _mockApp = OCMClassMock([FIRApp class]);
  186. OCMStub([_mockAuth app]).andReturn(_mockApp);
  187. _mockOptions = OCMClassMock([FIROptions class]);
  188. OCMStub([(FIRApp *)_mockApp options]).andReturn(_mockOptions);
  189. OCMStub([_mockOptions googleAppID]).andReturn(kFakeFirebaseAppID);
  190. _mockURLPresenter = OCMClassMock([FIRAuthURLPresenter class]);
  191. OCMStub([_mockAuth authURLPresenter]).andReturn(_mockURLPresenter);
  192. _mockRequestConfiguration = OCMClassMock([FIRAuthRequestConfiguration class]);
  193. OCMStub([_mockAuth requestConfiguration]).andReturn(_mockRequestConfiguration);
  194. OCMStub([_mockRequestConfiguration APIKey]).andReturn(kFakeAPIKey);
  195. }
  196. /** @fn testObtainingOAuthCredentialNoIDToken
  197. @brief Tests the correct creation of an OAuthCredential without an IDToken.
  198. */
  199. - (void)testObtainingOAuthCredentialNoIDToken {
  200. FIRAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:kFakeProviderID
  201. accessToken:kFakeAccessToken];
  202. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  203. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  204. XCTAssertEqualObjects(OAuthCredential.accessToken, kFakeAccessToken);
  205. XCTAssertEqualObjects(OAuthCredential.provider, kFakeProviderID);
  206. XCTAssertNil(OAuthCredential.IDToken);
  207. }
  208. /** @fn testObtainingOAuthCredentialWithDisplayName
  209. @brief Tests the correct creation of an OAuthCredential with a displayName.
  210. */
  211. - (void)testObtainingOAuthCredentialWithDisplayName {
  212. FIRAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:kFakeProviderID
  213. IDToken:kFakeIDToken rawNonce:nil accessToken:kFakeAccessToken displayName:kFakeDisplayName];
  214. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  215. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  216. XCTAssertEqualObjects(OAuthCredential.accessToken, kFakeAccessToken);
  217. XCTAssertEqualObjects(OAuthCredential.provider, kFakeProviderID);
  218. XCTAssertEqualObjects(OAuthCredential.IDToken, kFakeIDToken);
  219. XCTAssertEqualObjects(OAuthCredential.displayName,kFakeDisplayName);
  220. }
  221. /** @fn testObtainingOAuthCredentialWithIDToken
  222. @brief Tests the correct creation of an OAuthCredential with an IDToken
  223. */
  224. - (void)testObtainingOAuthCredentialWithIDToken {
  225. FIRAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:kFakeProviderID
  226. IDToken:kFakeIDToken
  227. accessToken:kFakeAccessToken];
  228. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  229. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  230. XCTAssertEqualObjects(OAuthCredential.accessToken, kFakeAccessToken);
  231. XCTAssertEqualObjects(OAuthCredential.provider, kFakeProviderID);
  232. XCTAssertEqualObjects(OAuthCredential.IDToken, kFakeIDToken);
  233. }
  234. /** @fn testObtainingOAuthProvider
  235. @brief Tests the correct creation of an FIROAuthProvider instance.
  236. */
  237. - (void)testObtainingOAuthProvider {
  238. id mockAuth = OCMClassMock([FIRAuth class]);
  239. id mockApp = OCMClassMock([FIRApp class]);
  240. OCMStub([mockAuth app]).andReturn(mockApp);
  241. id mockOptions = OCMClassMock([FIROptions class]);
  242. OCMStub([mockOptions clientID]).andReturn(kFakeClientID);
  243. OCMStub([mockOptions googleAppID]).andReturn(kFakeFirebaseAppID);
  244. OCMStub([(FIRApp *)mockApp options]).andReturn(mockOptions);
  245. FIROAuthProvider *OAuthProvider = [FIROAuthProvider providerWithProviderID:kFakeProviderID
  246. auth:mockAuth];
  247. XCTAssertTrue([OAuthProvider isKindOfClass:[FIROAuthProvider class]]);
  248. XCTAssertEqualObjects(OAuthProvider.providerID, kFakeProviderID);
  249. }
  250. /** @fn testGetCredentialWithUIDelegateWithClientID
  251. @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion:
  252. */
  253. - (void)testGetCredentialWithUIDelegateWithClientID {
  254. id mockBundle = OCMClassMock([NSBundle class]);
  255. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  256. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  257. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  258. ]);
  259. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  260. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  261. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  262. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  263. .andCallBlock2(
  264. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  265. XCTAssertNotNil(request);
  266. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  267. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  268. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  269. kFakeAuthorizedDomain
  270. ]);
  271. callback(mockGetProjectConfigResponse, nil);
  272. });
  273. });
  274. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  275. // Expect view controller presentation by UIDelegate.
  276. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  277. UIDelegate:mockUIDelegate
  278. callbackMatcher:OCMOCK_ANY
  279. completion:OCMOCK_ANY])
  280. .andDo(^(NSInvocation *invocation) {
  281. __unsafe_unretained id unretainedArgument;
  282. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  283. // `presentURL` is at index 2.
  284. [invocation getArgument:&unretainedArgument atIndex:2];
  285. NSURL *presentURL = unretainedArgument;
  286. XCTAssertEqualObjects(presentURL.scheme, @"https");
  287. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  288. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  289. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  290. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  291. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  292. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  293. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  294. XCTAssertNotNil(params[@"v"]);
  295. XCTAssertNil(params[@"tid"]);
  296. // `callbackMatcher` is at index 4
  297. [invocation getArgument:&unretainedArgument atIndex:4];
  298. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  299. NSMutableString *redirectURL = [NSMutableString
  300. stringWithString:[kFakeReverseClientID
  301. stringByAppendingString:kFakeRedirectURLResponseURL]];
  302. // Add fake OAuthResponse to callback.
  303. [redirectURL appendString:kFakeOAuthResponseURL];
  304. // Verify that the URL is rejected by the callback matcher without the event ID.
  305. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  306. [redirectURL appendString:@"%26eventId%3D"];
  307. [redirectURL appendString:params[@"eventId"]];
  308. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  309. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  310. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  311. NSURLComponents *components = [originalComponents copy];
  312. components.query = @"https";
  313. XCTAssertFalse(callbackMatcher([components URL]));
  314. components = [originalComponents copy];
  315. components.host = @"badhost";
  316. XCTAssertFalse(callbackMatcher([components URL]));
  317. components = [originalComponents copy];
  318. components.path = @"badpath";
  319. XCTAssertFalse(callbackMatcher([components URL]));
  320. components = [originalComponents copy];
  321. components.query = @"badquery";
  322. XCTAssertFalse(callbackMatcher([components URL]));
  323. // `completion` is at index 5
  324. [invocation getArgument:&unretainedArgument atIndex:5];
  325. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  326. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  327. completion(originalComponents.URL, nil);
  328. });
  329. });
  330. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  331. [_provider
  332. getCredentialWithUIDelegate:mockUIDelegate
  333. completion:^(FIRAuthCredential *_Nullable credential,
  334. NSError *_Nullable error) {
  335. XCTAssertTrue([NSThread isMainThread]);
  336. XCTAssertNil(error);
  337. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  338. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  339. XCTAssertEqualObjects(kFakeOAuthResponseURL,
  340. OAuthCredential.OAuthResponseURLString);
  341. [expectation fulfill];
  342. }];
  343. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  344. OCMVerifyAll(_mockBackend);
  345. }
  346. /** @fn testGetCredentialWithUIDelegateWithTenantID
  347. @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion:
  348. */
  349. - (void)testGetCredentialWithUIDelegateWithTenantID {
  350. id mockBundle = OCMClassMock([NSBundle class]);
  351. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  352. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  353. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  354. ]);
  355. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  356. OCMStub([_mockAuth tenantID]).andReturn(kFakeTenantID);
  357. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  358. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  359. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  360. .andCallBlock2(
  361. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  362. XCTAssertNotNil(request);
  363. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  364. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  365. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  366. kFakeAuthorizedDomain
  367. ]);
  368. callback(mockGetProjectConfigResponse, nil);
  369. });
  370. });
  371. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  372. // Expect view controller presentation by UIDelegate.
  373. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  374. UIDelegate:mockUIDelegate
  375. callbackMatcher:OCMOCK_ANY
  376. completion:OCMOCK_ANY])
  377. .andDo(^(NSInvocation *invocation) {
  378. __unsafe_unretained id unretainedArgument;
  379. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  380. // `presentURL` is at index 2.
  381. [invocation getArgument:&unretainedArgument atIndex:2];
  382. NSURL *presentURL = unretainedArgument;
  383. XCTAssertEqualObjects(presentURL.scheme, @"https");
  384. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  385. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  386. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  387. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  388. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  389. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  390. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  391. XCTAssertEqualObjects(params[@"tid"], kFakeTenantID);
  392. XCTAssertNotNil(params[@"v"]);
  393. // `callbackMatcher` is at index 4
  394. [invocation getArgument:&unretainedArgument atIndex:4];
  395. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  396. NSMutableString *redirectURL = [NSMutableString
  397. stringWithString:[kFakeReverseClientID
  398. stringByAppendingString:kFakeRedirectURLResponseURL]];
  399. // Add fake OAuthResponse to callback.
  400. [redirectURL appendString:kFakeOAuthResponseURL];
  401. // Verify that the URL is rejected by the callback matcher without the event ID.
  402. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  403. [redirectURL appendString:@"%26eventId%3D"];
  404. [redirectURL appendString:params[@"eventId"]];
  405. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  406. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  407. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  408. NSURLComponents *components = [originalComponents copy];
  409. components.query = @"https";
  410. XCTAssertFalse(callbackMatcher([components URL]));
  411. components = [originalComponents copy];
  412. components.host = @"badhost";
  413. XCTAssertFalse(callbackMatcher([components URL]));
  414. components = [originalComponents copy];
  415. components.path = @"badpath";
  416. XCTAssertFalse(callbackMatcher([components URL]));
  417. components = [originalComponents copy];
  418. components.query = @"badquery";
  419. XCTAssertFalse(callbackMatcher([components URL]));
  420. // `completion` is at index 5
  421. [invocation getArgument:&unretainedArgument atIndex:5];
  422. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  423. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  424. completion(originalComponents.URL, nil);
  425. });
  426. });
  427. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  428. [_provider
  429. getCredentialWithUIDelegate:mockUIDelegate
  430. completion:^(FIRAuthCredential *_Nullable credential,
  431. NSError *_Nullable error) {
  432. XCTAssertTrue([NSThread isMainThread]);
  433. XCTAssertNil(error);
  434. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  435. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  436. XCTAssertEqualObjects(kFakeOAuthResponseURL,
  437. OAuthCredential.OAuthResponseURLString);
  438. [expectation fulfill];
  439. }];
  440. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  441. OCMVerifyAll(_mockBackend);
  442. }
  443. /** @fn testGetCredentialWithUIDelegateUserCancellationWithClientID
  444. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to user
  445. cancelation.
  446. */
  447. - (void)testGetCredentialWithUIDelegateUserCancellationWithClientID {
  448. id mockBundle = OCMClassMock([NSBundle class]);
  449. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  450. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  451. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  452. ]);
  453. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  454. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  455. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  456. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  457. .andCallBlock2(
  458. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  459. XCTAssertNotNil(request);
  460. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  461. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  462. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  463. kFakeAuthorizedDomain
  464. ]);
  465. callback(mockGetProjectConfigResponse, nil);
  466. });
  467. });
  468. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  469. // Expect view controller presentation by UIDelegate.
  470. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  471. UIDelegate:mockUIDelegate
  472. callbackMatcher:OCMOCK_ANY
  473. completion:OCMOCK_ANY])
  474. .andDo(^(NSInvocation *invocation) {
  475. __unsafe_unretained id unretainedArgument;
  476. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  477. // `presentURL` is at index 2.
  478. [invocation getArgument:&unretainedArgument atIndex:2];
  479. NSURL *presentURL = unretainedArgument;
  480. XCTAssertEqualObjects(presentURL.scheme, @"https");
  481. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  482. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  483. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  484. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  485. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  486. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  487. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  488. XCTAssertNotNil(params[@"v"]);
  489. XCTAssertNil(params[@"tid"]);
  490. // `callbackMatcher` is at index 4
  491. [invocation getArgument:&unretainedArgument atIndex:4];
  492. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  493. NSMutableString *redirectURL = [NSMutableString
  494. stringWithString:[kFakeReverseClientID
  495. stringByAppendingString:kFakeRedirectURLResponseURL]];
  496. // Add fake OAuthResponse to callback.
  497. [redirectURL appendString:kFakeOAuthResponseURL];
  498. // Verify that the URL is rejected by the callback matcher without the event ID.
  499. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  500. [redirectURL appendString:@"%26eventId%3D"];
  501. [redirectURL appendString:params[@"eventId"]];
  502. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  503. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  504. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  505. NSURLComponents *components = [originalComponents copy];
  506. components.query = @"https";
  507. XCTAssertFalse(callbackMatcher([components URL]));
  508. components = [originalComponents copy];
  509. components.host = @"badhost";
  510. XCTAssertFalse(callbackMatcher([components URL]));
  511. components = [originalComponents copy];
  512. components.path = @"badpath";
  513. XCTAssertFalse(callbackMatcher([components URL]));
  514. components = [originalComponents copy];
  515. components.query = @"badquery";
  516. XCTAssertFalse(callbackMatcher([components URL]));
  517. // `completion` is at index 5
  518. [invocation getArgument:&unretainedArgument atIndex:5];
  519. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  520. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  521. completion(nil, [FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]);
  522. });
  523. });
  524. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  525. [_provider getCredentialWithUIDelegate:mockUIDelegate
  526. completion:^(FIRAuthCredential *_Nullable credential,
  527. NSError *_Nullable error) {
  528. XCTAssertTrue([NSThread isMainThread]);
  529. XCTAssertNil(credential);
  530. XCTAssertEqual(FIRAuthErrorCodeWebContextCancelled, error.code);
  531. [expectation fulfill];
  532. }];
  533. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  534. OCMVerifyAll(_mockBackend);
  535. }
  536. /** @fn testGetCredentialWithUIDelegateNetworkRequestFailedWithClientID
  537. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to a
  538. failed network request within the web context.
  539. */
  540. - (void)testGetCredentialWithUIDelegateNetworkRequestFailedWithClientID {
  541. id mockBundle = OCMClassMock([NSBundle class]);
  542. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  543. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  544. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  545. ]);
  546. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  547. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  548. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  549. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  550. .andCallBlock2(
  551. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  552. XCTAssertNotNil(request);
  553. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  554. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  555. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  556. kFakeAuthorizedDomain
  557. ]);
  558. callback(mockGetProjectConfigResponse, nil);
  559. });
  560. });
  561. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  562. // Expect view controller presentation by UIDelegate.
  563. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  564. UIDelegate:mockUIDelegate
  565. callbackMatcher:OCMOCK_ANY
  566. completion:OCMOCK_ANY])
  567. .andDo(^(NSInvocation *invocation) {
  568. __unsafe_unretained id unretainedArgument;
  569. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  570. // `presentURL` is at index 2.
  571. [invocation getArgument:&unretainedArgument atIndex:2];
  572. NSURL *presentURL = unretainedArgument;
  573. XCTAssertEqualObjects(presentURL.scheme, @"https");
  574. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  575. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  576. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  577. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  578. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  579. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  580. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  581. XCTAssertNotNil(params[@"v"]);
  582. XCTAssertNil(params[@"tid"]);
  583. // `callbackMatcher` is at index 4
  584. [invocation getArgument:&unretainedArgument atIndex:4];
  585. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  586. NSMutableString *redirectURL =
  587. [NSMutableString stringWithString:kFakeRedirectURLBaseErrorString];
  588. [redirectURL appendString:kNetworkRequestFailedErrorString];
  589. // Verify that the URL is rejected by the callback matcher without the event ID.
  590. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  591. [redirectURL appendString:@"%26eventId%3D"];
  592. [redirectURL appendString:params[@"eventId"]];
  593. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  594. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  595. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  596. NSURLComponents *components = [originalComponents copy];
  597. components.query = @"https";
  598. XCTAssertFalse(callbackMatcher([components URL]));
  599. components = [originalComponents copy];
  600. components.host = @"badhost";
  601. XCTAssertFalse(callbackMatcher([components URL]));
  602. components = [originalComponents copy];
  603. components.path = @"badpath";
  604. XCTAssertFalse(callbackMatcher([components URL]));
  605. components = [originalComponents copy];
  606. components.query = @"badquery";
  607. XCTAssertFalse(callbackMatcher([components URL]));
  608. // `completion` is at index 5
  609. [invocation getArgument:&unretainedArgument atIndex:5];
  610. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  611. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  612. completion(originalComponents.URL, nil);
  613. });
  614. });
  615. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  616. [_provider getCredentialWithUIDelegate:mockUIDelegate
  617. completion:^(FIRAuthCredential *_Nullable credential,
  618. NSError *_Nullable error) {
  619. XCTAssertTrue([NSThread isMainThread]);
  620. XCTAssertNil(credential);
  621. XCTAssertEqual(FIRAuthErrorCodeWebNetworkRequestFailed, error.code);
  622. [expectation fulfill];
  623. }];
  624. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  625. OCMVerifyAll(_mockBackend);
  626. }
  627. /** @fn testGetCredentialWithUIDelegateInternalErrorWithClientID
  628. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to an
  629. internal error within the web context.
  630. */
  631. - (void)testGetCredentialWithUIDelegateInternalErrorWithClientID {
  632. id mockBundle = OCMClassMock([NSBundle class]);
  633. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  634. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  635. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  636. ]);
  637. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  638. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  639. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  640. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  641. .andCallBlock2(
  642. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  643. XCTAssertNotNil(request);
  644. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  645. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  646. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  647. kFakeAuthorizedDomain
  648. ]);
  649. callback(mockGetProjectConfigResponse, nil);
  650. });
  651. });
  652. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  653. // Expect view controller presentation by UIDelegate.
  654. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  655. UIDelegate:mockUIDelegate
  656. callbackMatcher:OCMOCK_ANY
  657. completion:OCMOCK_ANY])
  658. .andDo(^(NSInvocation *invocation) {
  659. __unsafe_unretained id unretainedArgument;
  660. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  661. // `presentURL` is at index 2.
  662. [invocation getArgument:&unretainedArgument atIndex:2];
  663. NSURL *presentURL = unretainedArgument;
  664. XCTAssertEqualObjects(presentURL.scheme, @"https");
  665. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  666. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  667. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  668. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  669. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  670. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  671. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  672. XCTAssertNotNil(params[@"v"]);
  673. XCTAssertNil(params[@"tid"]);
  674. // `callbackMatcher` is at index 4
  675. [invocation getArgument:&unretainedArgument atIndex:4];
  676. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  677. NSMutableString *redirectURL =
  678. [NSMutableString stringWithString:kFakeRedirectURLBaseErrorString];
  679. // Add internal error string to redirect URL.
  680. [redirectURL appendString:kInternalErrorString];
  681. // Verify that the URL is rejected by the callback matcher without the event ID.
  682. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  683. [redirectURL appendString:@"%26eventId%3D"];
  684. [redirectURL appendString:params[@"eventId"]];
  685. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  686. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  687. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  688. NSURLComponents *components = [originalComponents copy];
  689. components.query = @"https";
  690. XCTAssertFalse(callbackMatcher([components URL]));
  691. components = [originalComponents copy];
  692. components.host = @"badhost";
  693. XCTAssertFalse(callbackMatcher([components URL]));
  694. components = [originalComponents copy];
  695. components.path = @"badpath";
  696. XCTAssertFalse(callbackMatcher([components URL]));
  697. components = [originalComponents copy];
  698. components.query = @"badquery";
  699. XCTAssertFalse(callbackMatcher([components URL]));
  700. // `completion` is at index 5
  701. [invocation getArgument:&unretainedArgument atIndex:5];
  702. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  703. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  704. completion(originalComponents.URL, nil);
  705. });
  706. });
  707. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  708. [_provider getCredentialWithUIDelegate:mockUIDelegate
  709. completion:^(FIRAuthCredential *_Nullable credential,
  710. NSError *_Nullable error) {
  711. XCTAssertTrue([NSThread isMainThread]);
  712. XCTAssertNil(credential);
  713. XCTAssertEqual(FIRAuthErrorCodeWebInternalError, error.code);
  714. [expectation fulfill];
  715. }];
  716. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  717. OCMVerifyAll(_mockBackend);
  718. }
  719. /** @fn testGetCredentialWithUIDelegateInvalidClientID
  720. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to an
  721. use of an invalid client ID.
  722. */
  723. - (void)testGetCredentialWithUIDelegateInvalidClientID {
  724. id mockBundle = OCMClassMock([NSBundle class]);
  725. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  726. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  727. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  728. ]);
  729. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  730. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  731. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  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. // `presentURL` is at index 2.
  754. [invocation getArgument:&unretainedArgument atIndex:2];
  755. NSURL *presentURL = unretainedArgument;
  756. XCTAssertEqualObjects(presentURL.scheme, @"https");
  757. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  758. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  759. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  760. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  761. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  762. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  763. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  764. XCTAssertNotNil(params[@"v"]);
  765. XCTAssertNil(params[@"tid"]);
  766. // `callbackMatcher` is at index 4
  767. [invocation getArgument:&unretainedArgument atIndex:4];
  768. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  769. NSMutableString *redirectURL =
  770. [NSMutableString stringWithString:kFakeRedirectURLBaseErrorString];
  771. // Add invalid client ID error to redirect URL.
  772. [redirectURL appendString:kInvalidClientIDString];
  773. // Verify that the URL is rejected by the callback matcher without the event ID.
  774. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  775. [redirectURL appendString:@"%26eventId%3D"];
  776. [redirectURL appendString:params[@"eventId"]];
  777. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  778. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  779. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  780. NSURLComponents *components = [originalComponents copy];
  781. components.query = @"https";
  782. XCTAssertFalse(callbackMatcher([components URL]));
  783. components = [originalComponents copy];
  784. components.host = @"badhost";
  785. XCTAssertFalse(callbackMatcher([components URL]));
  786. components = [originalComponents copy];
  787. components.path = @"badpath";
  788. XCTAssertFalse(callbackMatcher([components URL]));
  789. components = [originalComponents copy];
  790. components.query = @"badquery";
  791. XCTAssertFalse(callbackMatcher([components URL]));
  792. // `completion` is at index 5
  793. [invocation getArgument:&unretainedArgument atIndex:5];
  794. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  795. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  796. completion(originalComponents.URL, nil);
  797. });
  798. });
  799. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  800. [_provider getCredentialWithUIDelegate:mockUIDelegate
  801. completion:^(FIRAuthCredential *_Nullable credential,
  802. NSError *_Nullable error) {
  803. XCTAssertTrue([NSThread isMainThread]);
  804. XCTAssertNil(credential);
  805. XCTAssertEqual(FIRAuthErrorCodeInvalidClientID, error.code);
  806. [expectation fulfill];
  807. }];
  808. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  809. OCMVerifyAll(_mockBackend);
  810. }
  811. /** @fn testGetCredentialWithUIDelegateUnknownErrorWithClientID
  812. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to an
  813. unknown error.
  814. */
  815. - (void)testGetCredentialWithUIDelegateUnknownErrorWithClientID {
  816. id mockBundle = OCMClassMock([NSBundle class]);
  817. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  818. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  819. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  820. ]);
  821. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  822. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  823. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  824. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  825. .andCallBlock2(
  826. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  827. XCTAssertNotNil(request);
  828. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  829. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  830. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  831. kFakeAuthorizedDomain
  832. ]);
  833. callback(mockGetProjectConfigResponse, nil);
  834. });
  835. });
  836. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  837. // Expect view controller presentation by UIDelegate.
  838. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  839. UIDelegate:mockUIDelegate
  840. callbackMatcher:OCMOCK_ANY
  841. completion:OCMOCK_ANY])
  842. .andDo(^(NSInvocation *invocation) {
  843. __unsafe_unretained id unretainedArgument;
  844. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  845. // `presentURL` is at index 2.
  846. [invocation getArgument:&unretainedArgument atIndex:2];
  847. NSURL *presentURL = unretainedArgument;
  848. XCTAssertEqualObjects(presentURL.scheme, @"https");
  849. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  850. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  851. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  852. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  853. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  854. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  855. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  856. XCTAssertNotNil(params[@"v"]);
  857. XCTAssertNil(params[@"tid"]);
  858. // `callbackMatcher` is at index 4
  859. [invocation getArgument:&unretainedArgument atIndex:4];
  860. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  861. NSMutableString *redirectURL =
  862. [NSMutableString stringWithString:kFakeRedirectURLBaseErrorString];
  863. // Add unknown error to redirect URL.
  864. [redirectURL appendString:kUnknownErrorString];
  865. // Verify that the URL is rejected by the callback matcher without the event ID.
  866. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  867. [redirectURL appendString:@"%26eventId%3D"];
  868. [redirectURL appendString:params[@"eventId"]];
  869. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  870. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  871. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  872. NSURLComponents *components = [originalComponents copy];
  873. components.query = @"https";
  874. XCTAssertFalse(callbackMatcher([components URL]));
  875. components = [originalComponents copy];
  876. components.host = @"badhost";
  877. XCTAssertFalse(callbackMatcher([components URL]));
  878. components = [originalComponents copy];
  879. components.path = @"badpath";
  880. XCTAssertFalse(callbackMatcher([components URL]));
  881. components = [originalComponents copy];
  882. components.query = @"badquery";
  883. XCTAssertFalse(callbackMatcher([components URL]));
  884. // `completion` is at index 5
  885. [invocation getArgument:&unretainedArgument atIndex:5];
  886. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  887. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  888. completion(originalComponents.URL, nil);
  889. });
  890. });
  891. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  892. [_provider getCredentialWithUIDelegate:mockUIDelegate
  893. completion:^(FIRAuthCredential *_Nullable credential,
  894. NSError *_Nullable error) {
  895. XCTAssertTrue([NSThread isMainThread]);
  896. XCTAssertNil(credential);
  897. XCTAssertEqual(FIRAuthErrorCodeWebSignInUserInteractionFailure,
  898. error.code);
  899. [expectation fulfill];
  900. }];
  901. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  902. OCMVerifyAll(_mockBackend);
  903. }
  904. /** @fn testGetCredentialWithUIDelegateWithFirebaseAppID
  905. @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion:
  906. */
  907. - (void)testGetCredentialWithUIDelegateWithFirebaseAppID {
  908. id mockBundle = OCMClassMock([NSBundle class]);
  909. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  910. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  911. @{@"CFBundleURLSchemes" : @[ kFakeEncodedFirebaseAppID ]}
  912. ]);
  913. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  914. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  915. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  916. .andCallBlock2(
  917. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  918. XCTAssertNotNil(request);
  919. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  920. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  921. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  922. kFakeAuthorizedDomain
  923. ]);
  924. callback(mockGetProjectConfigResponse, nil);
  925. });
  926. });
  927. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  928. // Expect view controller presentation by UIDelegate.
  929. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  930. UIDelegate:mockUIDelegate
  931. callbackMatcher:OCMOCK_ANY
  932. completion:OCMOCK_ANY])
  933. .andDo(^(NSInvocation *invocation) {
  934. __unsafe_unretained id unretainedArgument;
  935. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  936. // `presentURL` is at index 2.
  937. [invocation getArgument:&unretainedArgument atIndex:2];
  938. NSURL *presentURL = unretainedArgument;
  939. XCTAssertEqualObjects(presentURL.scheme, @"https");
  940. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  941. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  942. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  943. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  944. XCTAssertEqualObjects(params[@"appId"], kFakeFirebaseAppID);
  945. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  946. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  947. XCTAssertNotNil(params[@"v"]);
  948. XCTAssertNil(params[@"tid"]);
  949. // `callbackMatcher` is at index 4
  950. [invocation getArgument:&unretainedArgument atIndex:4];
  951. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  952. NSMutableString *redirectURL = [NSMutableString
  953. stringWithString:[kFakeEncodedFirebaseAppID
  954. stringByAppendingString:kFakeRedirectURLResponseURL]];
  955. // Add fake OAuthResponse to callback.
  956. [redirectURL appendString:kFakeOAuthResponseURL];
  957. // Verify that the URL is rejected by the callback matcher without the event ID.
  958. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  959. [redirectURL appendString:@"%26eventId%3D"];
  960. [redirectURL appendString:params[@"eventId"]];
  961. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  962. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  963. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  964. NSURLComponents *components = [originalComponents copy];
  965. components.query = @"https";
  966. XCTAssertFalse(callbackMatcher([components URL]));
  967. components = [originalComponents copy];
  968. components.host = @"badhost";
  969. XCTAssertFalse(callbackMatcher([components URL]));
  970. components = [originalComponents copy];
  971. components.path = @"badpath";
  972. XCTAssertFalse(callbackMatcher([components URL]));
  973. components = [originalComponents copy];
  974. components.query = @"badquery";
  975. XCTAssertFalse(callbackMatcher([components URL]));
  976. // `completion` is at index 5
  977. [invocation getArgument:&unretainedArgument atIndex:5];
  978. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  979. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  980. completion(originalComponents.URL, nil);
  981. });
  982. });
  983. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  984. [_provider
  985. getCredentialWithUIDelegate:mockUIDelegate
  986. completion:^(FIRAuthCredential *_Nullable credential,
  987. NSError *_Nullable error) {
  988. XCTAssertTrue([NSThread isMainThread]);
  989. XCTAssertNil(error);
  990. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  991. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  992. XCTAssertEqualObjects(kFakeOAuthResponseURL,
  993. OAuthCredential.OAuthResponseURLString);
  994. [expectation fulfill];
  995. }];
  996. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  997. OCMVerifyAll(_mockBackend);
  998. }
  999. /** @fn testGetCredentialWithUIDelegateWithFirebaseAppIDWhileClientIdPresent
  1000. @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion: when the
  1001. client ID is present in the plist file, but the encoded app ID is the registered custom URL
  1002. scheme.
  1003. */
  1004. - (void)testGetCredentialWithUIDelegateWithFirebaseAppIDWhileClientIdPresent {
  1005. id mockBundle = OCMClassMock([NSBundle class]);
  1006. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1007. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1008. @{@"CFBundleURLSchemes" : @[ kFakeEncodedFirebaseAppID ]}
  1009. ]);
  1010. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1011. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1012. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  1013. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  1014. .andCallBlock2(
  1015. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  1016. XCTAssertNotNil(request);
  1017. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1018. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  1019. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  1020. kFakeAuthorizedDomain
  1021. ]);
  1022. callback(mockGetProjectConfigResponse, nil);
  1023. });
  1024. });
  1025. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  1026. // Expect view controller presentation by UIDelegate.
  1027. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  1028. UIDelegate:mockUIDelegate
  1029. callbackMatcher:OCMOCK_ANY
  1030. completion:OCMOCK_ANY])
  1031. .andDo(^(NSInvocation *invocation) {
  1032. __unsafe_unretained id unretainedArgument;
  1033. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  1034. // `presentURL` is at index 2.
  1035. [invocation getArgument:&unretainedArgument atIndex:2];
  1036. NSURL *presentURL = unretainedArgument;
  1037. XCTAssertEqualObjects(presentURL.scheme, @"https");
  1038. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  1039. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  1040. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  1041. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  1042. XCTAssertEqualObjects(params[@"appId"], kFakeFirebaseAppID);
  1043. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  1044. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  1045. XCTAssertNotNil(params[@"v"]);
  1046. XCTAssertNil(params[@"tid"]);
  1047. // `callbackMatcher` is at index 4
  1048. [invocation getArgument:&unretainedArgument atIndex:4];
  1049. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  1050. NSMutableString *redirectURL = [NSMutableString
  1051. stringWithString:[kFakeEncodedFirebaseAppID
  1052. stringByAppendingString:kFakeRedirectURLResponseURL]];
  1053. // Add fake OAuthResponse to callback.
  1054. [redirectURL appendString:kFakeOAuthResponseURL];
  1055. // Verify that the URL is rejected by the callback matcher without the event ID.
  1056. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  1057. [redirectURL appendString:@"%26eventId%3D"];
  1058. [redirectURL appendString:params[@"eventId"]];
  1059. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  1060. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  1061. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  1062. NSURLComponents *components = [originalComponents copy];
  1063. components.query = @"https";
  1064. XCTAssertFalse(callbackMatcher([components URL]));
  1065. components = [originalComponents copy];
  1066. components.host = @"badhost";
  1067. XCTAssertFalse(callbackMatcher([components URL]));
  1068. components = [originalComponents copy];
  1069. components.path = @"badpath";
  1070. XCTAssertFalse(callbackMatcher([components URL]));
  1071. components = [originalComponents copy];
  1072. components.query = @"badquery";
  1073. XCTAssertFalse(callbackMatcher([components URL]));
  1074. // `completion` is at index 5
  1075. [invocation getArgument:&unretainedArgument atIndex:5];
  1076. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  1077. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1078. completion(originalComponents.URL, nil);
  1079. });
  1080. });
  1081. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1082. [_provider
  1083. getCredentialWithUIDelegate:mockUIDelegate
  1084. completion:^(FIRAuthCredential *_Nullable credential,
  1085. NSError *_Nullable error) {
  1086. XCTAssertTrue([NSThread isMainThread]);
  1087. XCTAssertNil(error);
  1088. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  1089. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  1090. XCTAssertEqualObjects(kFakeOAuthResponseURL,
  1091. OAuthCredential.OAuthResponseURLString);
  1092. [expectation fulfill];
  1093. }];
  1094. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1095. OCMVerifyAll(_mockBackend);
  1096. }
  1097. /** @fn testGetCredentialWithUIDelegateUseEmulator
  1098. @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion: when using the
  1099. emulator.
  1100. */
  1101. - (void)testGetCredentialWithUIDelegateUseEmulator {
  1102. id mockBundle = OCMClassMock([NSBundle class]);
  1103. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1104. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1105. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  1106. ]);
  1107. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1108. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1109. NSString *emulatorHostAndPort =
  1110. [NSString stringWithFormat:@"%@:%@", kFakeEmulatorHost, kFakeEmulatorPort];
  1111. OCMStub([_mockRequestConfiguration emulatorHostAndPort]).andReturn(emulatorHostAndPort);
  1112. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  1113. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  1114. // Expect view controller presentation by UIDelegate.
  1115. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  1116. UIDelegate:mockUIDelegate
  1117. callbackMatcher:OCMOCK_ANY
  1118. completion:OCMOCK_ANY])
  1119. .andDo(^(NSInvocation *invocation) {
  1120. __unsafe_unretained id unretainedArgument;
  1121. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  1122. // `presentURL` is at index 2.
  1123. [invocation getArgument:&unretainedArgument atIndex:2];
  1124. NSURL *presentURL = unretainedArgument;
  1125. XCTAssertEqualObjects(presentURL.scheme, @"http");
  1126. XCTAssertEqualObjects(presentURL.host, kFakeEmulatorHost);
  1127. XCTAssertEqualObjects([presentURL.port stringValue], kFakeEmulatorPort);
  1128. XCTAssertEqualObjects(presentURL.path, @"/emulator/auth/handler");
  1129. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  1130. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  1131. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  1132. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  1133. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  1134. XCTAssertNotNil(params[@"v"]);
  1135. XCTAssertNil(params[@"tid"]);
  1136. // `callbackMatcher` is at index 4
  1137. [invocation getArgument:&unretainedArgument atIndex:4];
  1138. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  1139. NSMutableString *redirectURL = [NSMutableString
  1140. stringWithString:[kFakeReverseClientID
  1141. stringByAppendingString:kFakeRedirectURLResponseURL]];
  1142. // Add fake OAuthResponse to callback.
  1143. [redirectURL appendString:kFakeOAuthResponseURL];
  1144. // Verify that the URL is rejected by the callback matcher without the event ID.
  1145. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  1146. [redirectURL appendString:@"%26eventId%3D"];
  1147. [redirectURL appendString:params[@"eventId"]];
  1148. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  1149. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  1150. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  1151. NSURLComponents *components = [originalComponents copy];
  1152. components.query = @"https";
  1153. XCTAssertFalse(callbackMatcher([components URL]));
  1154. components = [originalComponents copy];
  1155. components.host = @"badhost";
  1156. XCTAssertFalse(callbackMatcher([components URL]));
  1157. components = [originalComponents copy];
  1158. components.path = @"badpath";
  1159. XCTAssertFalse(callbackMatcher([components URL]));
  1160. components = [originalComponents copy];
  1161. components.query = @"badquery";
  1162. XCTAssertFalse(callbackMatcher([components URL]));
  1163. // `completion` is at index 5
  1164. [invocation getArgument:&unretainedArgument atIndex:5];
  1165. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  1166. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1167. completion(originalComponents.URL, nil);
  1168. });
  1169. });
  1170. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1171. [_provider
  1172. getCredentialWithUIDelegate:mockUIDelegate
  1173. completion:^(FIRAuthCredential *_Nullable credential,
  1174. NSError *_Nullable error) {
  1175. XCTAssertTrue([NSThread isMainThread]);
  1176. XCTAssertNil(error);
  1177. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  1178. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  1179. XCTAssertEqualObjects(kFakeOAuthResponseURL,
  1180. OAuthCredential.OAuthResponseURLString);
  1181. [expectation fulfill];
  1182. }];
  1183. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1184. OCMVerifyAll(_mockBackend);
  1185. }
  1186. @end
  1187. #endif