GIDGoogleUserTest.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h"
  15. #import <XCTest/XCTest.h>
  16. #import <TargetConditionals.h>
  17. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h"
  18. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDProfileData.h"
  19. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h"
  20. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDToken.h"
  21. #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h"
  22. #import "GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h"
  23. #import "GoogleSignIn/Tests/Unit/GIDProfileData+Testing.h"
  24. #import "GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h"
  25. #import "GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.h"
  26. #import "GoogleSignIn/Tests/Unit/OIDTokenRequest+Testing.h"
  27. #import "GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h"
  28. @import GTMAppAuth;
  29. #ifdef SWIFT_PACKAGE
  30. @import AppAuth;
  31. @import GoogleUtilities_MethodSwizzler;
  32. @import GoogleUtilities_SwizzlerTestHelpers;
  33. @import GTMAppAuth;
  34. @import OCMock;
  35. #else
  36. #import <AppAuth/OIDAuthState.h>
  37. #import <AppAuth/OIDAuthorizationRequest.h>
  38. #import <AppAuth/OIDAuthorizationResponse.h>
  39. #import <AppAuth/OIDAuthorizationService.h>
  40. #import <AppAuth/OIDError.h>
  41. #import <AppAuth/OIDIDToken.h>
  42. #import <AppAuth/OIDTokenRequest.h>
  43. #import <AppAuth/OIDTokenResponse.h>
  44. #import <GoogleUtilities/GULSwizzler.h>
  45. #import <GoogleUtilities/GULSwizzler+Unswizzle.h>
  46. #import <OCMock/OCMock.h>
  47. #endif
  48. static NSString *const kNewAccessToken = @"new_access_token";
  49. static NSString *const kNewRefreshToken = @"new_refresh_token";
  50. static NSTimeInterval const kTimeAccuracy = 10;
  51. static NSTimeInterval const kIDTokenExpiresIn = 100;
  52. static NSTimeInterval const kNewIDTokenExpiresIn = 200;
  53. static NSString *const kNewScope = @"newScope";
  54. @interface GIDGoogleUserTest : XCTestCase
  55. @end
  56. @implementation GIDGoogleUserTest {
  57. // The saved token fetch handler.
  58. OIDTokenCallback _tokenFetchHandler;
  59. }
  60. - (void)setUp {
  61. _tokenFetchHandler = nil;
  62. // We need to use swizzle here because OCMock can not stub class method with arguments.
  63. [GULSwizzler swizzleClass:[OIDAuthorizationService class]
  64. selector:@selector(performTokenRequest:originalAuthorizationResponse:callback:)
  65. isClassSelector:YES
  66. withBlock:^(id sender,
  67. OIDTokenRequest *request,
  68. OIDAuthorizationResponse *authorizationResponse,
  69. OIDTokenCallback callback) {
  70. // Save the OIDTokenCallback.
  71. self->_tokenFetchHandler = [callback copy];
  72. }];
  73. }
  74. - (void)tearDown {
  75. [GULSwizzler unswizzleClass:[OIDAuthorizationService class]
  76. selector:@selector(performTokenRequest:originalAuthorizationResponse:callback:)
  77. isClassSelector:YES];
  78. }
  79. #pragma mark - Tests
  80. - (void)testInitWithAuthState {
  81. OIDAuthState *authState = [OIDAuthState testInstance];
  82. GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState
  83. profileData:[GIDProfileData testInstance]];
  84. XCTAssertEqualObjects(user.grantedScopes, @[ OIDAuthorizationRequestTestingScope2 ]);
  85. XCTAssertEqualObjects(user.userID, kUserID);
  86. XCTAssertEqualObjects(user.configuration.hostedDomain, kHostedDomain);
  87. XCTAssertEqualObjects(user.configuration.clientID, OIDAuthorizationRequestTestingClientID);
  88. XCTAssertEqualObjects(user.profile, [GIDProfileData testInstance]);
  89. XCTAssertEqualObjects(user.accessToken.tokenString, kAccessToken);
  90. XCTAssertEqualObjects(user.refreshToken.tokenString, kRefreshToken);
  91. OIDIDToken *idToken = [[OIDIDToken alloc]
  92. initWithIDTokenString:authState.lastTokenResponse.idToken];
  93. XCTAssertEqualObjects(user.idToken.expirationDate, [idToken expiresAt]);
  94. }
  95. - (void)testCoding {
  96. if (@available(iOS 11, macOS 10.13, *)) {
  97. GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:[OIDAuthState testInstance]
  98. profileData:[GIDProfileData testInstance]];
  99. NSData *data = [NSKeyedArchiver archivedDataWithRootObject:user
  100. requiringSecureCoding:YES
  101. error:nil];
  102. GIDGoogleUser *newUser = [NSKeyedUnarchiver unarchivedObjectOfClass:[GIDGoogleUser class]
  103. fromData:data
  104. error:nil];
  105. XCTAssertEqualObjects(user, newUser);
  106. XCTAssertTrue(GIDGoogleUser.supportsSecureCoding);
  107. } else {
  108. XCTSkip(@"Required API is not available for this test.");
  109. }
  110. }
  111. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  112. // Deprecated in iOS 13 and macOS 10.14
  113. - (void)testLegacyCoding {
  114. GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:[OIDAuthState testInstance]
  115. profileData:[GIDProfileData testInstance]];
  116. NSError *archiveError;
  117. NSData *data = [NSKeyedArchiver archivedDataWithRootObject:user
  118. requiringSecureCoding:NO
  119. error:&archiveError];
  120. XCTAssertNil(archiveError);
  121. NSError *unarchiveError;
  122. GIDGoogleUser *newUser = [NSKeyedUnarchiver unarchivedObjectOfClass:[GIDGoogleUser class]
  123. fromData:data
  124. error:&unarchiveError];
  125. XCTAssertNil(unarchiveError);
  126. XCTAssertEqualObjects(user, newUser);
  127. XCTAssertTrue(GIDGoogleUser.supportsSecureCoding);
  128. }
  129. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  130. // Test the old encoding format for backword compatability.
  131. - (void)testOldFormatCoding {
  132. if (@available(iOS 11, macOS 10.13, *)) {
  133. OIDAuthState *authState = [OIDAuthState testInstance];
  134. GIDProfileData *profileDate = [GIDProfileData testInstance];
  135. GIDGoogleUserOldFormat *user = [[GIDGoogleUserOldFormat alloc] initWithAuthState:authState
  136. profileData:profileDate];
  137. NSData *data = [NSKeyedArchiver archivedDataWithRootObject:user
  138. requiringSecureCoding:YES
  139. error:nil];
  140. GIDGoogleUser *newUser = [NSKeyedUnarchiver unarchivedObjectOfClass:[GIDGoogleUser class]
  141. fromData:data
  142. error:nil];
  143. XCTAssertEqualObjects(user, newUser);
  144. }
  145. }
  146. - (void)testUpdateAuthState {
  147. GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:kAccessTokenExpiresIn
  148. idTokenExpiresIn:kIDTokenExpiresIn];
  149. NSString *updatedIDToken = [self idTokenWithExpiresIn:kNewIDTokenExpiresIn];
  150. OIDAuthState *updatedAuthState = [OIDAuthState testInstanceWithIDToken:updatedIDToken
  151. accessToken:kNewAccessToken
  152. accessTokenExpiresIn:kAccessTokenExpiresIn
  153. refreshToken:kNewRefreshToken];
  154. GIDProfileData *updatedProfileData = [GIDProfileData testInstance];
  155. [user updateWithTokenResponse:updatedAuthState.lastTokenResponse
  156. authorizationResponse:updatedAuthState.lastAuthorizationResponse
  157. profileData:updatedProfileData];
  158. XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken);
  159. [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn];
  160. XCTAssertEqualObjects(user.idToken.tokenString, updatedIDToken);
  161. [self verifyUser:user idTokenExpiresIn:kNewIDTokenExpiresIn];
  162. XCTAssertEqualObjects(user.refreshToken.tokenString, kNewRefreshToken);
  163. XCTAssertEqual(user.profile, updatedProfileData);
  164. }
  165. // When updating with a new OIDAuthState in which token information is not changed, the token objects
  166. // should remain the same.
  167. - (void)testUpdateAuthState_tokensAreNotChanged {
  168. NSString *idToken = [self idTokenWithExpiresIn:kIDTokenExpiresIn];
  169. OIDAuthState *authState = [OIDAuthState testInstanceWithIDToken:idToken
  170. accessToken:kAccessToken
  171. accessTokenExpiresIn:kAccessTokenExpiresIn
  172. refreshToken:kRefreshToken];
  173. GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState profileData:nil];
  174. GIDToken *accessTokenBeforeUpdate = user.accessToken;
  175. GIDToken *refreshTokenBeforeUpdate = user.refreshToken;
  176. GIDToken *idTokenBeforeUpdate = user.idToken;
  177. [user updateWithTokenResponse:authState.lastTokenResponse
  178. authorizationResponse:authState.lastAuthorizationResponse
  179. profileData:nil];
  180. XCTAssertIdentical(user.accessToken, accessTokenBeforeUpdate);
  181. XCTAssertIdentical(user.idToken, idTokenBeforeUpdate);
  182. XCTAssertIdentical(user.refreshToken, refreshTokenBeforeUpdate);
  183. }
  184. - (void)testFetcherAuthorizer {
  185. // This is really hard to test without assuming how GTMAppAuthFetcherAuthorization works
  186. // internally, so let's just take the shortcut here by asserting we get a
  187. // GTMAppAuthFetcherAuthorization object.
  188. GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:kAccessTokenExpiresIn
  189. idTokenExpiresIn:kIDTokenExpiresIn];
  190. #pragma clang diagnostic push
  191. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  192. id<GTMFetcherAuthorizationProtocol> fetcherAuthorizer = user.fetcherAuthorizer;
  193. #pragma clang diagnostic pop
  194. XCTAssertTrue([fetcherAuthorizer isKindOfClass:[GTMAuthSession class]]);
  195. XCTAssertTrue([fetcherAuthorizer canAuthorize]);
  196. }
  197. - (void)testFetcherAuthorizer_returnTheSameInstance {
  198. GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:kAccessTokenExpiresIn
  199. idTokenExpiresIn:kIDTokenExpiresIn];
  200. #pragma clang diagnostic push
  201. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  202. id<GTMFetcherAuthorizationProtocol> fetcherAuthorizer = user.fetcherAuthorizer;
  203. id<GTMFetcherAuthorizationProtocol> fetcherAuthorizer2 = user.fetcherAuthorizer;
  204. #pragma clang diagnostic pop
  205. XCTAssertIdentical(fetcherAuthorizer, fetcherAuthorizer2);
  206. }
  207. #pragma mark - Test `refreshTokensIfNeededWithCompletion:`
  208. - (void)testRefreshTokensIfNeededWithCompletion_refresh_givenBothTokensExpired {
  209. // Both tokens expired 10 seconds ago.
  210. GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:-10 idTokenExpiresIn:-10];
  211. NSString *newIdToken = [self idTokenWithExpiresIn:kNewIDTokenExpiresIn];
  212. XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"];
  213. // Save the intermediate states.
  214. [user refreshTokensIfNeededWithCompletion:^(GIDGoogleUser * _Nullable user,
  215. NSError * _Nullable error) {
  216. [expectation fulfill];
  217. XCTAssertNil(error);
  218. XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken);
  219. [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn];
  220. XCTAssertEqualObjects(user.idToken.tokenString, newIdToken);
  221. [self verifyUser:user idTokenExpiresIn:kNewIDTokenExpiresIn];
  222. }];
  223. // Creates a fake response.
  224. OIDTokenResponse *fakeResponse = [OIDTokenResponse testInstanceWithIDToken:newIdToken
  225. accessToken:kNewAccessToken
  226. expiresIn:@(kAccessTokenExpiresIn)
  227. refreshToken:kRefreshToken
  228. tokenRequest:nil];
  229. _tokenFetchHandler(fakeResponse, nil);
  230. [self waitForExpectationsWithTimeout:1 handler:nil];
  231. }
  232. - (void)testRefreshTokens_refresh_givenBothTokensExpired_NoNewIDToken {
  233. // Both tokens expired 10 seconds ago.
  234. GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:-10 idTokenExpiresIn:-10];
  235. // Creates a fake response without ID token.
  236. OIDTokenResponse *fakeResponse = [OIDTokenResponse testInstanceWithIDToken:nil
  237. accessToken:kNewAccessToken
  238. expiresIn:@(kAccessTokenExpiresIn)
  239. refreshToken:kRefreshToken
  240. tokenRequest:nil];
  241. XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"];
  242. // Save the intermediate states.
  243. [user refreshTokensIfNeededWithCompletion:^(GIDGoogleUser * _Nullable user,
  244. NSError * _Nullable error) {
  245. [expectation fulfill];
  246. XCTAssertNil(error);
  247. XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken);
  248. [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn];
  249. XCTAssertNil(user.idToken);
  250. }];
  251. _tokenFetchHandler(fakeResponse, nil);
  252. [self waitForExpectationsWithTimeout:1 handler:nil];
  253. }
  254. - (void)testRefreshTokensIfNeededWithCompletion_refresh_givenAccessTokenExpired {
  255. // Access token expired 10 seconds ago. ID token will expire in 10 minutes.
  256. GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:-10 idTokenExpiresIn:10 * 60];
  257. // Creates a fake response.
  258. NSString *newIdToken = [self idTokenWithExpiresIn:kNewIDTokenExpiresIn];
  259. OIDTokenResponse *fakeResponse = [OIDTokenResponse testInstanceWithIDToken:newIdToken
  260. accessToken:kNewAccessToken
  261. expiresIn:@(kAccessTokenExpiresIn)
  262. refreshToken:kRefreshToken
  263. tokenRequest:nil];
  264. XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"];
  265. // Save the intermediate states.
  266. [user refreshTokensIfNeededWithCompletion:^(GIDGoogleUser * _Nullable user,
  267. NSError * _Nullable error) {
  268. [expectation fulfill];
  269. XCTAssertNil(error);
  270. XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken);
  271. [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn];
  272. XCTAssertEqualObjects(user.idToken.tokenString, newIdToken);
  273. [self verifyUser:user idTokenExpiresIn:kNewIDTokenExpiresIn];
  274. }];
  275. _tokenFetchHandler(fakeResponse, nil);
  276. [self waitForExpectationsWithTimeout:1 handler:nil];
  277. }
  278. - (void)testRefreshTokensIfNeededWithCompletion_refresh_givenIDTokenExpired {
  279. // ID token expired 10 seconds ago. Access token will expire in 10 minutes.
  280. GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:10 * 60 idTokenExpiresIn:-10];
  281. // Creates a fake response.
  282. NSString *newIdToken = [self idTokenWithExpiresIn:kNewIDTokenExpiresIn];
  283. OIDTokenResponse *fakeResponse = [OIDTokenResponse testInstanceWithIDToken:newIdToken
  284. accessToken:kNewAccessToken
  285. expiresIn:@(kAccessTokenExpiresIn)
  286. refreshToken:kRefreshToken
  287. tokenRequest:nil];
  288. XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"];
  289. // Save the intermediate states.
  290. [user refreshTokensIfNeededWithCompletion:^(GIDGoogleUser * _Nullable user,
  291. NSError * _Nullable error) {
  292. [expectation fulfill];
  293. XCTAssertNil(error);
  294. XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken);
  295. [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn];
  296. XCTAssertEqualObjects(user.idToken.tokenString, newIdToken);
  297. [self verifyUser:user idTokenExpiresIn:kNewIDTokenExpiresIn];
  298. }];
  299. _tokenFetchHandler(fakeResponse, nil);
  300. [self waitForExpectationsWithTimeout:1 handler:nil];
  301. }
  302. - (void)testRefreshTokensIfNeededWithCompletion_noRefresh_givenBothTokensNotExpired {
  303. // Both tokens will expire in 10 min.
  304. NSTimeInterval expiresIn = 10 * 60;
  305. GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:expiresIn
  306. idTokenExpiresIn:expiresIn];
  307. NSString *accessTokenStringBeforeRefresh = user.accessToken.tokenString;
  308. NSString *idTokenStringBeforeRefresh = user.idToken.tokenString;
  309. XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"];
  310. // Save the intermediate states.
  311. [user refreshTokensIfNeededWithCompletion:^(GIDGoogleUser * _Nullable user,
  312. NSError * _Nullable error) {
  313. [expectation fulfill];
  314. XCTAssertNil(error);
  315. }];
  316. [self waitForExpectationsWithTimeout:1 handler:nil];
  317. XCTAssertEqualObjects(user.accessToken.tokenString, accessTokenStringBeforeRefresh);
  318. [self verifyUser:user accessTokenExpiresIn:expiresIn];
  319. XCTAssertEqualObjects(user.idToken.tokenString, idTokenStringBeforeRefresh);
  320. [self verifyUser:user idTokenExpiresIn:expiresIn];
  321. }
  322. - (void)testRefreshTokensIfNeededWithCompletion_noRefresh_givenRefreshErrors {
  323. // Both tokens expired 10 second ago.
  324. NSTimeInterval expiresIn = -10;
  325. GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:expiresIn
  326. idTokenExpiresIn:expiresIn];
  327. NSString *accessTokenStringBeforeRefresh = user.accessToken.tokenString;
  328. NSString *idTokenStringBeforeRefresh = user.idToken.tokenString;
  329. XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"];
  330. // Save the intermediate states.
  331. [user refreshTokensIfNeededWithCompletion:^(GIDGoogleUser * _Nullable user,
  332. NSError * _Nullable error) {
  333. [expectation fulfill];
  334. XCTAssertNotNil(error);
  335. XCTAssertNil(user);
  336. }];
  337. _tokenFetchHandler(nil, [self fakeError]);
  338. [self waitForExpectationsWithTimeout:1 handler:nil];
  339. XCTAssertEqualObjects(user.accessToken.tokenString, accessTokenStringBeforeRefresh);
  340. [self verifyUser:user accessTokenExpiresIn:expiresIn];
  341. XCTAssertEqualObjects(user.idToken.tokenString, idTokenStringBeforeRefresh);
  342. [self verifyUser:user idTokenExpiresIn:expiresIn];
  343. }
  344. - (void)testRefreshTokensIfNeededWithCompletion_handleConcurrentRefresh {
  345. // Both tokens expired 10 second ago.
  346. NSTimeInterval expiresIn = -10;
  347. GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:expiresIn
  348. idTokenExpiresIn:expiresIn];
  349. // Creates a fake response.
  350. NSString *newIdToken = [self idTokenWithExpiresIn:kNewIDTokenExpiresIn];
  351. OIDTokenResponse *fakeResponse = [OIDTokenResponse testInstanceWithIDToken:newIdToken
  352. accessToken:kNewAccessToken
  353. expiresIn:@(kAccessTokenExpiresIn)
  354. refreshToken:kRefreshToken
  355. tokenRequest:nil];
  356. XCTestExpectation *firstExpectation =
  357. [self expectationWithDescription:@"First callback is called"];
  358. [user refreshTokensIfNeededWithCompletion:^(GIDGoogleUser *user, NSError *error) {
  359. [firstExpectation fulfill];
  360. XCTAssertNil(error);
  361. XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken);
  362. [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn];
  363. XCTAssertEqualObjects(user.idToken.tokenString, newIdToken);
  364. [self verifyUser:user idTokenExpiresIn:kNewIDTokenExpiresIn];
  365. }];
  366. XCTestExpectation *secondExpectation =
  367. [self expectationWithDescription:@"Second callback is called"];
  368. [user refreshTokensIfNeededWithCompletion:^(GIDGoogleUser *user, NSError *error) {
  369. [secondExpectation fulfill];
  370. XCTAssertNil(error);
  371. XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken);
  372. [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn];
  373. XCTAssertEqualObjects(user.idToken.tokenString, newIdToken);
  374. [self verifyUser:user idTokenExpiresIn:kNewIDTokenExpiresIn];
  375. }];
  376. _tokenFetchHandler(fakeResponse, nil);
  377. [self waitForExpectationsWithTimeout:1 handler:nil];
  378. }
  379. # pragma mark - Test `addScopes:`
  380. - (void)testAddScopes_success {
  381. id signIn = OCMClassMock([GIDSignIn class]);
  382. OCMStub([signIn sharedInstance]).andReturn(signIn);
  383. [[signIn expect] addScopes:OCMOCK_ANY
  384. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  385. presentingViewController:OCMOCK_ANY
  386. #elif TARGET_OS_OSX
  387. presentingWindow:OCMOCK_ANY
  388. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  389. completion:OCMOCK_ANY];
  390. GIDGoogleUser *currentUser = [self googleUserWithAccessTokenExpiresIn:kAccessTokenExpiresIn
  391. idTokenExpiresIn:kIDTokenExpiresIn];
  392. OCMStub([signIn currentUser]).andReturn(currentUser);
  393. [currentUser addScopes:@[kNewScope]
  394. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  395. presentingViewController:[[UIViewController alloc] init]
  396. #elif TARGET_OS_OSX
  397. presentingWindow:[[NSWindow alloc] init]
  398. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  399. completion:nil];
  400. [signIn verify];
  401. }
  402. - (void)testAddScopes_failure_addScopesToPreviousUser {
  403. id signIn = OCMClassMock([GIDSignIn class]);
  404. OCMStub([signIn sharedInstance]).andReturn(signIn);
  405. GIDGoogleUser *currentUser = [self googleUserWithAccessTokenExpiresIn:kAccessTokenExpiresIn
  406. idTokenExpiresIn:kIDTokenExpiresIn];
  407. OCMStub([signIn currentUser]).andReturn(currentUser);
  408. GIDGoogleUser *previousUser = [self googleUserWithAccessTokenExpiresIn:kAccessTokenExpiresIn
  409. idTokenExpiresIn:kNewIDTokenExpiresIn];
  410. XCTestExpectation *expectation =
  411. [self expectationWithDescription:@"Completion is called."];
  412. [previousUser addScopes:@[kNewScope]
  413. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  414. presentingViewController:[[UIViewController alloc] init]
  415. #elif TARGET_OS_OSX
  416. presentingWindow:[[NSWindow alloc] init]
  417. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  418. completion:^(GIDSignInResult *signInResult, NSError *error) {
  419. [expectation fulfill];
  420. XCTAssertNil(signInResult);
  421. XCTAssertEqual(error.code, kGIDSignInErrorCodeMismatchWithCurrentUser);
  422. }];
  423. [self waitForExpectationsWithTimeout:1 handler:nil];
  424. }
  425. - (void)testAddScopes_failure_addScopesToPreviousUser_currentUserIsNull {
  426. id signIn = OCMClassMock([GIDSignIn class]);
  427. OCMStub([signIn sharedInstance]).andReturn(signIn);
  428. GIDGoogleUser *currentUser = nil;
  429. OCMStub([signIn currentUser]).andReturn(currentUser);
  430. GIDGoogleUser *previousUser = [self googleUserWithAccessTokenExpiresIn:kAccessTokenExpiresIn
  431. idTokenExpiresIn:kNewIDTokenExpiresIn];
  432. XCTestExpectation *expectation =
  433. [self expectationWithDescription:@"Completion is called."];
  434. [previousUser addScopes:@[kNewScope]
  435. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  436. presentingViewController:[[UIViewController alloc] init]
  437. #elif TARGET_OS_OSX
  438. presentingWindow:[[NSWindow alloc] init]
  439. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  440. completion:^(GIDSignInResult *signInResult, NSError *error) {
  441. [expectation fulfill];
  442. XCTAssertNil(signInResult);
  443. XCTAssertEqual(error.code, kGIDSignInErrorCodeMismatchWithCurrentUser);
  444. }];
  445. [self waitForExpectationsWithTimeout:1 handler:nil];
  446. }
  447. #pragma mark - Helpers
  448. // Returns a GIDGoogleUser with different tokens expiresIn time. The token strings are constants.
  449. - (GIDGoogleUser *)googleUserWithAccessTokenExpiresIn:(NSTimeInterval)accessTokenExpiresIn
  450. idTokenExpiresIn:(NSTimeInterval)idTokenExpiresIn {
  451. NSString *idToken = [self idTokenWithExpiresIn:idTokenExpiresIn];
  452. OIDAuthState *authState = [OIDAuthState testInstanceWithIDToken:idToken
  453. accessToken:kAccessToken
  454. accessTokenExpiresIn:accessTokenExpiresIn
  455. refreshToken:kRefreshToken];
  456. return [[GIDGoogleUser alloc] initWithAuthState:authState profileData:nil];
  457. }
  458. - (NSString *)idTokenWithExpiresIn:(NSTimeInterval)expiresIn {
  459. // The expireTime should be based on 1970.
  460. NSTimeInterval expireTime = [[NSDate date] timeIntervalSince1970] + expiresIn;
  461. return [OIDTokenResponse idTokenWithSub:kUserID exp:@(expireTime)];
  462. }
  463. - (void)verifyUser:(GIDGoogleUser *)user accessTokenExpiresIn:(NSTimeInterval)expiresIn {
  464. NSDate *expectedAccessTokenExpirationDate = [[NSDate date] dateByAddingTimeInterval:expiresIn];
  465. XCTAssertEqualWithAccuracy([user.accessToken.expirationDate timeIntervalSince1970],
  466. [expectedAccessTokenExpirationDate timeIntervalSince1970],
  467. kTimeAccuracy);
  468. }
  469. - (void)verifyUser:(GIDGoogleUser *)user idTokenExpiresIn:(NSTimeInterval)expiresIn {
  470. NSDate *expectedIDTokenExpirationDate = [[NSDate date] dateByAddingTimeInterval:expiresIn];
  471. XCTAssertEqualWithAccuracy([user.idToken.expirationDate timeIntervalSince1970],
  472. [expectedIDTokenExpirationDate timeIntervalSince1970], kTimeAccuracy);
  473. }
  474. - (NSError *)fakeError {
  475. return [NSError errorWithDomain:@"fake.domain" code:-1 userInfo:nil];
  476. }
  477. @end