4141// It should be larger than kTimeAccuracy which is used in the method `XCTAssertEqualWithAccuracy`.
4242static NSTimeInterval const kTimeIncrement = 100 ;
4343
44+ // List of observed properties of the class being tested.
45+ static NSString *const kObservedProperties [] = {
46+ @" accessToken" ,
47+ @" refreshToken" ,
48+ @" idToken" ,
49+ };
50+ static const NSUInteger kNumberOfObservedProperties =
51+ sizeof (kObservedProperties ) / sizeof (*kObservedProperties );
52+
53+ // Bit position for notification change type bitmask flags.
54+ // Must match the list of observed properties above.
55+ typedef NS_ENUM (NSUInteger , ChangeType) {
56+ kChangeTypeAccessTokenPrior ,
57+ kChangeTypeAccessToken ,
58+ kChangeTypeRefreshTokenPrior ,
59+ kChangeTypeRefreshToken ,
60+ kChangeTypeIDTokenPrior ,
61+ kChangeTypeIDToken ,
62+ kChangeTypeEnd // not a real change type but an end mark for calculating |kChangeAll|
63+ };
64+
65+ static const NSUInteger kChangeAll = (1u << kChangeTypeEnd ) - 1u ;
66+
67+ #if __has_feature(c_static_assert) || __has_extension(c_static_assert)
68+ _Static_assert (kChangeTypeEnd == (sizeof (kObservedProperties ) / sizeof (*kObservedProperties )) * 2 ,
69+ " List of observed properties must match list of change notification enums" );
70+ #endif
71+
4472@interface GIDGoogleUserTest : XCTestCase
4573@end
4674
47- @implementation GIDGoogleUserTest
75+ @implementation GIDGoogleUserTest {
76+ // Bitmask flags for observed changes, as specified in |ChangeType|.
77+ NSUInteger _changesObserved;
78+ }
79+
80+ - (void )setUp {
81+ _changesObserved = 0 ;
82+ }
4883
4984#pragma mark - Tests
5085
@@ -102,12 +137,8 @@ - (void)testUpdateAuthState {
102137 NSTimeInterval accessTokenExpireTime = [NSDate timeIntervalSinceReferenceDate ];
103138 NSTimeInterval idTokenExpireTime = accessTokenExpireTime + kTimeIncrement ;
104139
105- NSString *idToken = [self idTokenWithExpireTime: idTokenExpireTime];
106- OIDAuthState *authState = [OIDAuthState testInstanceWithIDToken: idToken
107- accessToken: kAccessToken
108- accessTokenExpireTime: accessTokenExpireTime];
109-
110- GIDGoogleUser *user = [[GIDGoogleUser alloc ] initWithAuthState: authState profileData: nil ];
140+ GIDGoogleUser *user = [self observedGoogleUserWithAccessTokenExpireTime: accessTokenExpireTime
141+ idTokenExpireTime: idTokenExpireTime];
111142
112143 NSTimeInterval updatedAccessTokenExpireTime = idTokenExpireTime + kTimeIncrement ;
113144 NSTimeInterval updatedIDTokenExpireTime = updatedAccessTokenExpireTime + kTimeIncrement ;
@@ -126,12 +157,72 @@ - (void)testUpdateAuthState {
126157 XCTAssertEqualWithAccuracy ([user.idToken.expirationDate timeIntervalSinceReferenceDate ],
127158 updatedIDTokenExpireTime, kTimeAccuracy );
128159 XCTAssertEqual (user.profile , updatedProfileData);
160+ XCTAssertEqual (_changesObserved, kChangeAll );
129161}
130162
131163#pragma mark - Helpers
132164
165+ - (GIDGoogleUser *)observedGoogleUserWithAccessTokenExpireTime : (NSTimeInterval )accessTokenExpireTime
166+ idTokenExpireTime : (NSTimeInterval )idTokenExpireTime {
167+ GIDGoogleUser *user = [self googleUserWithAccessTokenExpireTime: accessTokenExpireTime
168+ idTokenExpireTime: idTokenExpireTime];
169+ for (unsigned int i = 0 ; i < kNumberOfObservedProperties ; ++i) {
170+ [user addObserver: self
171+ forKeyPath: kObservedProperties [i]
172+ options: NSKeyValueObservingOptionPrior
173+ context: NULL ];
174+ }
175+ return user;
176+ }
177+
178+ - (GIDGoogleUser *)googleUserWithAccessTokenExpireTime : (NSTimeInterval )accessTokenExpireTime
179+ idTokenExpireTime : (NSTimeInterval )idTokenExpireTime {
180+
181+ NSString *idToken = [self idTokenWithExpireTime: idTokenExpireTime];
182+ OIDAuthState *authState = [OIDAuthState testInstanceWithIDToken: idToken
183+ accessToken: kAccessToken
184+ accessTokenExpireTime: accessTokenExpireTime];
185+
186+ return [[GIDGoogleUser alloc ] initWithAuthState: authState profileData: nil ];
187+ }
188+
133189- (NSString *)idTokenWithExpireTime : (NSTimeInterval )expireTime {
134190 return [OIDTokenResponse idTokenWithSub: kUserID exp: @(expireTime + NSTimeIntervalSince1970)];
135191}
136192
193+ #pragma mark - NSKeyValueObserving
194+
195+ - (void )observeValueForKeyPath : (NSString *)keyPath
196+ ofObject : (id )object
197+ change : (NSDictionary <NSKeyValueChangeKey,id> *)change
198+ context : (void *)context {
199+ ChangeType changeType;
200+ if ([keyPath isEqualToString: @" accessToken" ]) {
201+ if (change[NSKeyValueChangeNotificationIsPriorKey ]) {
202+ changeType = kChangeTypeAccessTokenPrior ;
203+ } else {
204+ changeType = kChangeTypeAccessToken ;
205+ }
206+ } else if ([keyPath isEqualToString: @" refreshToken" ]) {
207+ if (change[NSKeyValueChangeNotificationIsPriorKey ]) {
208+ changeType = kChangeTypeRefreshTokenPrior ;
209+ } else {
210+ changeType = kChangeTypeRefreshToken ;
211+ }
212+ } else if ([keyPath isEqualToString: @" idToken" ]) {
213+ if (change[NSKeyValueChangeNotificationIsPriorKey ]) {
214+ changeType = kChangeTypeIDTokenPrior ;
215+ } else {
216+ changeType = kChangeTypeIDToken ;
217+ }
218+ } else {
219+ XCTFail (@" unexpected keyPath" );
220+ return ; // so compiler knows |changeType| is always assigned
221+ }
222+
223+ NSInteger changeMask = 1 << changeType;
224+ XCTAssertFalse (_changesObserved & changeMask); // each change type should only fire once
225+ _changesObserved |= changeMask;
226+ }
227+
137228@end
0 commit comments