@@ -918,6 +918,7 @@ - (void)testRefreshTokensIfNeededWithCompletion_noRefresh_givenRefreshTokenExpir
918918- (void )testRefreshTokensIfNeeded_dropsResponseForReplacedAuthorization {
919919 GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn: -10
920920 idTokenExpiresIn: kIDTokenExpiresIn ];
921+ XCTAssertEqualObjects (user.grantedScopes , @[ OIDAuthorizationRequestTestingScope2 ]);
921922
922923 XCTestExpectation *expectation = [self expectationWithDescription: @" Callback is called" ];
923924 [user refreshTokensIfNeededWithCompletion: ^(GIDGoogleUser *_Nullable refreshedUser,
@@ -927,18 +928,18 @@ - (void)testRefreshTokensIfNeeded_dropsResponseForReplacedAuthorization {
927928 XCTAssertNil (error);
928929 }];
929930
930- NSString *updatedIDToken = [self idTokenWithExpiresIn: kNewIDTokenExpiresIn ];
931+ // The add-scopes flow replaces the authorization while the refresh is in flight.
932+ NSArray <NSString *> *scopesAfterAddScopes = @[ OIDAuthorizationRequestTestingScope2, kNewScope ];
931933 OIDAuthState *updatedAuthState =
932- [OIDAuthState testInstanceWithIDToken: updatedIDToken
933- accessToken: @" access_token_after_add_scopes"
934- accessTokenExpiresIn: kAccessTokenExpiresIn
935- refreshToken: kNewRefreshToken ];
934+ [self authStateWithGrantedScopes: scopesAfterAddScopes
935+ accessToken: @" access_token_after_add_scopes" ];
936936 [user updateWithTokenResponse: updatedAuthState.lastTokenResponse
937937 authorizationResponse: updatedAuthState.lastAuthorizationResponse
938938 profileData: nil ];
939939
940+ // The stale response carries the old grant's scope and a different access token.
940941 OIDTokenResponse *staleResponse =
941- [OIDTokenResponse testInstanceWithIDToken: updatedIDToken
942+ [OIDTokenResponse testInstanceWithIDToken: [ self idTokenWithExpiresIn: kNewIDTokenExpiresIn ]
942943 accessToken: kNewAccessToken
943944 expiresIn: @(kAccessTokenExpiresIn )
944945 refreshToken: kRefreshToken
@@ -947,6 +948,39 @@ - (void)testRefreshTokensIfNeeded_dropsResponseForReplacedAuthorization {
947948 [self waitForExpectationsWithTimeout: 1 handler: nil ];
948949
949950 XCTAssertEqualObjects (user.accessToken .tokenString , @" access_token_after_add_scopes" );
951+ XCTAssertEqualObjects (user.grantedScopes , scopesAfterAddScopes);
952+ }
953+
954+ - (void )testRefreshTokensIfNeeded_dropsErrorForReplacedAuthorization {
955+ GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn: -10
956+ idTokenExpiresIn: kIDTokenExpiresIn ];
957+
958+ XCTestExpectation *expectation = [self expectationWithDescription: @" Callback is called" ];
959+ [user refreshTokensIfNeededWithCompletion: ^(GIDGoogleUser *_Nullable refreshedUser,
960+ NSError *_Nullable error) {
961+ [expectation fulfill ];
962+ XCTAssertNotNil (refreshedUser);
963+ XCTAssertNil (error);
964+ }];
965+
966+ // The add-scopes flow replaces the authorization while the refresh is in flight.
967+ OIDAuthState *updatedAuthState =
968+ [self authStateWithGrantedScopes: @[ OIDAuthorizationRequestTestingScope2, kNewScope ]
969+ accessToken: @" access_token_after_add_scopes" ];
970+ [user updateWithTokenResponse: updatedAuthState.lastTokenResponse
971+ authorizationResponse: updatedAuthState.lastAuthorizationResponse
972+ profileData: nil ];
973+
974+ // A stale OAuth token error for the old grant must not invalidate the new authorization.
975+ NSError *staleError = [NSError errorWithDomain: OIDOAuthTokenErrorDomain
976+ code: OIDErrorCodeOAuthTokenInvalidGrant
977+ userInfo: nil ];
978+ _tokenFetchHandler (nil , staleError);
979+ [self waitForExpectationsWithTimeout: 1 handler: nil ];
980+
981+ XCTAssertTrue (user.authState .isAuthorized );
982+ XCTAssertNil (user.authState .authorizationError );
983+ XCTAssertEqualObjects (user.accessToken .tokenString , @" access_token_after_add_scopes" );
950984}
951985
952986- (void )testWrapperIdentifier_PresentOnRefreshRequestWhenSet {
0 commit comments