FIROAuthProviderTests.m 63 KB

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