mirror of
https://github.com/google/app-check
synced 2026-11-09 22:05:52 +05:45
refactor: migrate GACAppCheckTests off OCMock (Milestone 1) (#107)
This commit is contained in:
1 parent
170b466a06
commit
3c6d658bbb
16 files changed
+556
-230
No files matched your search
@@ -16,7 +16,11 @@
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
#import "AppCheckCore/Tests/Unit/Utils/GACAppCheckProviderFake.h"
|
||||
#import "AppCheckCore/Tests/Unit/Utils/GACAppCheckSettingsFake.h"
|
||||
#import "AppCheckCore/Tests/Unit/Utils/GACAppCheckStorageFake.h"
|
||||
#import "AppCheckCore/Tests/Unit/Utils/GACAppCheckTokenDelegateFake.h"
|
||||
#import "AppCheckCore/Tests/Unit/Utils/GACAppCheckTokenRefresherFake.h"
|
||||
|
||||
#import "FBLPromise+Testing.h"
|
||||
|
||||
@@ -54,15 +58,13 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
|
||||
@interface GACAppCheckTests : XCTestCase
|
||||
|
||||
@property(nonatomic) OCMockObject<GACAppCheckStorageProtocol> *mockStorage;
|
||||
@property(nonatomic) OCMockObject<GACAppCheckProvider> *mockAppCheckProvider;
|
||||
@property(nonatomic) OCMockObject<GACAppCheckTokenRefresherProtocol> *mockTokenRefresher;
|
||||
@property(nonatomic) OCMockObject<GACAppCheckSettingsProtocol> *mockSettings;
|
||||
@property(nonatomic) OCMockObject<GACAppCheckTokenDelegate> *mockTokenDelegate;
|
||||
@property(nonatomic) GACAppCheckStorageFake *fakeStorage;
|
||||
@property(nonatomic) GACAppCheckProviderFake *fakeAppCheckProvider;
|
||||
@property(nonatomic) GACAppCheckTokenRefresherFake *fakeTokenRefresher;
|
||||
@property(nonatomic) GACAppCheckSettingsFake *fakeSettings;
|
||||
@property(nonatomic) GACAppCheckTokenDelegateFake *fakeTokenDelegate;
|
||||
@property(nonatomic) GACAppCheck *appCheck;
|
||||
|
||||
@property(nonatomic, copy, nullable) GACAppCheckTokenRefreshBlock tokenRefreshHandler;
|
||||
|
||||
@end
|
||||
|
||||
@implementation GACAppCheckTests
|
||||
@@ -70,102 +72,33 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
|
||||
self.mockStorage = OCMStrictProtocolMock(@protocol(GACAppCheckStorageProtocol));
|
||||
self.mockAppCheckProvider = OCMStrictProtocolMock(@protocol(GACAppCheckProvider));
|
||||
self.mockTokenRefresher = OCMStrictProtocolMock(@protocol(GACAppCheckTokenRefresherProtocol));
|
||||
self.mockSettings = OCMStrictProtocolMock(@protocol(GACAppCheckSettingsProtocol));
|
||||
self.mockTokenDelegate = OCMStrictProtocolMock(@protocol(GACAppCheckTokenDelegate));
|
||||
|
||||
[self stubSetTokenRefreshHandler];
|
||||
self.fakeStorage = [[GACAppCheckStorageFake alloc] init];
|
||||
self.fakeAppCheckProvider = [[GACAppCheckProviderFake alloc] init];
|
||||
self.fakeTokenRefresher = [[GACAppCheckTokenRefresherFake alloc] init];
|
||||
self.fakeSettings = [[GACAppCheckSettingsFake alloc] init];
|
||||
self.fakeTokenDelegate = [[GACAppCheckTokenDelegateFake alloc] init];
|
||||
|
||||
self.appCheck = [[GACAppCheck alloc] initWithServiceName:kAppName
|
||||
appCheckProvider:self.mockAppCheckProvider
|
||||
storage:self.mockStorage
|
||||
tokenRefresher:self.mockTokenRefresher
|
||||
settings:self.mockSettings
|
||||
tokenDelegate:self.mockTokenDelegate];
|
||||
appCheckProvider:self.fakeAppCheckProvider
|
||||
storage:self.fakeStorage
|
||||
tokenRefresher:self.fakeTokenRefresher
|
||||
settings:self.fakeSettings
|
||||
tokenDelegate:self.fakeTokenDelegate];
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
self.appCheck = nil;
|
||||
[self.mockAppCheckProvider stopMocking];
|
||||
self.mockAppCheckProvider = nil;
|
||||
[self.mockStorage stopMocking];
|
||||
self.mockStorage = nil;
|
||||
[self.mockTokenRefresher stopMocking];
|
||||
self.mockTokenRefresher = nil;
|
||||
self.fakeAppCheckProvider = nil;
|
||||
self.fakeStorage = nil;
|
||||
self.fakeTokenRefresher = nil;
|
||||
self.fakeSettings = nil;
|
||||
self.fakeTokenDelegate = nil;
|
||||
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
#pragma mark - Public Init
|
||||
|
||||
- (void)testAppCheckInit {
|
||||
NSString *tokenKey =
|
||||
[NSString stringWithFormat:@"app_check_token.%@.%@", kAppName, kResourceName];
|
||||
|
||||
// 1. Stub GACAppCheckTokenRefresher and validate usage.
|
||||
id mockTokenRefresher = OCMClassMock([GACAppCheckTokenRefresher class]);
|
||||
OCMExpect([mockTokenRefresher alloc]).andReturn(mockTokenRefresher);
|
||||
|
||||
id refresherDateValidator =
|
||||
[OCMArg checkWithBlock:^BOOL(GACAppCheckTokenRefreshResult *refreshResult) {
|
||||
XCTAssertEqual(refreshResult.status, GACAppCheckTokenRefreshStatusNever);
|
||||
XCTAssertEqual(refreshResult.tokenExpirationDate, nil);
|
||||
XCTAssertEqual(refreshResult.tokenReceivedAtDate, nil);
|
||||
return YES;
|
||||
}];
|
||||
|
||||
id settingsValidator = [OCMArg checkWithBlock:^BOOL(id obj) {
|
||||
XCTAssert([obj conformsToProtocol:@protocol(GACAppCheckSettingsProtocol)]);
|
||||
return YES;
|
||||
}];
|
||||
|
||||
OCMExpect([mockTokenRefresher initWithRefreshResult:refresherDateValidator
|
||||
settings:settingsValidator])
|
||||
.andReturn(mockTokenRefresher);
|
||||
OCMExpect([mockTokenRefresher setTokenRefreshHandler:[OCMArg any]]);
|
||||
|
||||
// 2. Stub GACAppCheckStorage and validate usage.
|
||||
id mockStorage = OCMStrictClassMock([GACAppCheckStorage class]);
|
||||
OCMExpect([mockStorage alloc]).andReturn(mockStorage);
|
||||
OCMExpect([mockStorage initWithTokenKey:tokenKey accessGroup:kAppGroupID]).andReturn(mockStorage);
|
||||
|
||||
// 3. Stub attestation provider.
|
||||
OCMockObject<GACAppCheckProvider> *mockProvider =
|
||||
OCMStrictProtocolMock(@protocol(GACAppCheckProvider));
|
||||
|
||||
// 4. Stub GACAppCheckSettingsProtocol.
|
||||
OCMockObject<GACAppCheckSettingsProtocol> *mockSettings =
|
||||
OCMStrictProtocolMock(@protocol(GACAppCheckSettingsProtocol));
|
||||
|
||||
// 5. Stub GACAppCheckTokenDelegate.
|
||||
OCMockObject<GACAppCheckTokenDelegate> *mockTokenDelegate =
|
||||
OCMStrictProtocolMock(@protocol(GACAppCheckTokenDelegate));
|
||||
|
||||
// 6. Call init.
|
||||
GACAppCheck *appCheck = [[GACAppCheck alloc] initWithServiceName:kAppName
|
||||
resourceName:kResourceName
|
||||
appCheckProvider:mockProvider
|
||||
settings:mockSettings
|
||||
tokenDelegate:mockTokenDelegate
|
||||
keychainAccessGroup:kAppGroupID];
|
||||
XCTAssert([appCheck isKindOfClass:[GACAppCheck class]]);
|
||||
|
||||
// 7. Verify mocks.
|
||||
OCMVerifyAll(mockTokenRefresher);
|
||||
OCMVerifyAll(mockStorage);
|
||||
OCMVerifyAll(mockProvider);
|
||||
OCMVerifyAll(mockSettings);
|
||||
OCMVerifyAll(mockTokenDelegate);
|
||||
|
||||
// 8. Stop mocking real class mocks.
|
||||
[mockTokenRefresher stopMocking];
|
||||
mockTokenRefresher = nil;
|
||||
[mockStorage stopMocking];
|
||||
mockStorage = nil;
|
||||
}
|
||||
|
||||
#pragma mark - Public Get Token
|
||||
|
||||
- (void)testGetToken_WhenNoCache_Success {
|
||||
@@ -185,7 +118,11 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
|
||||
// 3. Wait for expectations and validate mocks.
|
||||
[self waitForExpectations:@[ expectation ] timeout:0.5];
|
||||
[self verifyAllMocks];
|
||||
|
||||
XCTAssertEqual(self.fakeAppCheckProvider.getTokenCallCount, 1);
|
||||
XCTAssertEqualObjects(self.fakeStorage.lastSetToken, expectedToken);
|
||||
XCTAssertEqual(self.fakeTokenRefresher.updateWithRefreshResultCallCount, 1);
|
||||
XCTAssertEqualObjects(self.fakeTokenDelegate.lastToken, expectedToken);
|
||||
}
|
||||
|
||||
- (void)testGetToken_WhenCachedTokenIsValid_Success {
|
||||
@@ -209,7 +146,11 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
|
||||
// 3. Wait for expectations and validate mocks.
|
||||
[self waitForExpectations:@[ expectation ] timeout:0.5];
|
||||
[self verifyAllMocks];
|
||||
|
||||
XCTAssertEqual(self.fakeAppCheckProvider.getTokenCallCount, 1);
|
||||
XCTAssertEqualObjects(self.fakeStorage.lastSetToken, expectedToken);
|
||||
XCTAssertEqual(self.fakeTokenRefresher.updateWithRefreshResultCallCount, 1);
|
||||
XCTAssertEqualObjects(self.fakeTokenDelegate.lastToken, expectedToken);
|
||||
}
|
||||
|
||||
- (void)testGetToken_WhenCachedTokenExpired_Success {
|
||||
@@ -229,7 +170,11 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
|
||||
// 3. Wait for expectations and validate mocks.
|
||||
[self waitForExpectations:@[ expectation ] timeout:0.5];
|
||||
[self verifyAllMocks];
|
||||
|
||||
XCTAssertEqual(self.fakeAppCheckProvider.getTokenCallCount, 1);
|
||||
XCTAssertEqualObjects(self.fakeStorage.lastSetToken, expectedToken);
|
||||
XCTAssertEqual(self.fakeTokenRefresher.updateWithRefreshResultCallCount, 1);
|
||||
XCTAssertEqualObjects(self.fakeTokenDelegate.lastToken, expectedToken);
|
||||
}
|
||||
|
||||
- (void)testGetToken_AppCheckProviderError {
|
||||
@@ -253,70 +198,66 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
|
||||
// 3. Wait for expectations and validate mocks.
|
||||
[self waitForExpectations:@[ expectation ] timeout:0.5];
|
||||
[self verifyAllMocks];
|
||||
|
||||
XCTAssertEqual(self.fakeAppCheckProvider.getTokenCallCount, 1);
|
||||
XCTAssertEqual(self.fakeTokenDelegate.tokenDidUpdateCallCount, 0);
|
||||
XCTAssertNil(self.fakeStorage.lastSetToken);
|
||||
XCTAssertEqual(self.fakeTokenRefresher.updateWithRefreshResultCallCount, 0);
|
||||
}
|
||||
|
||||
#pragma mark - Token refresher
|
||||
|
||||
- (void)testTokenRefreshTriggeredAndRefreshSuccess {
|
||||
// 1. Expect token to be requested from storage.
|
||||
OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
|
||||
self.fakeStorage.getTokenPromise = [FBLPromise resolvedWith:nil];
|
||||
|
||||
// 2. Expect token requested from app check provider.
|
||||
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:10000];
|
||||
GACAppCheckToken *tokenToReturn = [[GACAppCheckToken alloc] initWithToken:@"valid"
|
||||
expirationDate:expirationDate];
|
||||
id completionArg = [OCMArg invokeBlockWithArgs:tokenToReturn, [NSNull null], nil];
|
||||
OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
|
||||
self.fakeAppCheckProvider.tokenToReturn = tokenToReturn;
|
||||
|
||||
// 3. Expect new token to be stored.
|
||||
OCMExpect([self.mockStorage setToken:tokenToReturn])
|
||||
.andReturn([FBLPromise resolvedWith:tokenToReturn]);
|
||||
OCMExpect([self.mockTokenRefresher updateWithRefreshResult:[OCMArg any]]);
|
||||
self.fakeStorage.setTokenPromise = [FBLPromise resolvedWith:tokenToReturn];
|
||||
|
||||
// 4. Expect token update notification to be sent.
|
||||
OCMExpect([self.mockTokenDelegate tokenDidUpdate:tokenToReturn serviceName:kAppName]);
|
||||
|
||||
// 5. Trigger refresh and expect the result.
|
||||
if (self.tokenRefreshHandler == nil) {
|
||||
// 4. Trigger refresh and expect the result.
|
||||
if (self.fakeTokenRefresher.tokenRefreshHandler == nil) {
|
||||
XCTFail(@"`tokenRefreshHandler` must be not `nil`.");
|
||||
return;
|
||||
}
|
||||
|
||||
XCTestExpectation *completionExpectation = [self expectationWithDescription:@"completion"];
|
||||
self.tokenRefreshHandler(^(GACAppCheckTokenRefreshResult *refreshResult) {
|
||||
self.fakeTokenRefresher.tokenRefreshHandler(^(GACAppCheckTokenRefreshResult *refreshResult) {
|
||||
[completionExpectation fulfill];
|
||||
XCTAssertEqualObjects(refreshResult.tokenExpirationDate, expirationDate);
|
||||
XCTAssertEqual(refreshResult.status, GACAppCheckTokenRefreshStatusSuccess);
|
||||
});
|
||||
|
||||
[self waitForExpectations:@[ completionExpectation ] timeout:0.5];
|
||||
[self verifyAllMocks];
|
||||
|
||||
XCTAssertEqual(self.fakeAppCheckProvider.getTokenCallCount, 1);
|
||||
XCTAssertEqualObjects(self.fakeStorage.lastSetToken, tokenToReturn);
|
||||
XCTAssertEqual(self.fakeTokenRefresher.updateWithRefreshResultCallCount, 1);
|
||||
XCTAssertEqual(self.fakeTokenDelegate.tokenDidUpdateCallCount, 1);
|
||||
XCTAssertEqualObjects(self.fakeTokenDelegate.lastToken, tokenToReturn);
|
||||
}
|
||||
|
||||
- (void)testTokenRefreshTriggeredAndRefreshError {
|
||||
// 1. Expect token to be requested from storage.
|
||||
OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
|
||||
self.fakeStorage.getTokenPromise = [FBLPromise resolvedWith:nil];
|
||||
|
||||
// 2. Expect token requested from app check provider.
|
||||
NSError *providerError = [self internalError];
|
||||
id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], providerError, nil];
|
||||
OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
|
||||
|
||||
// 3. Don't expect token requested from app check provider.
|
||||
OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]);
|
||||
|
||||
// 4. Don't expect token update notification to be sent.
|
||||
OCMReject([self.mockTokenDelegate tokenDidUpdate:OCMOCK_ANY serviceName:OCMOCK_ANY]);
|
||||
self.fakeAppCheckProvider.errorToReturn = providerError;
|
||||
|
||||
// 5. Trigger refresh and expect the result.
|
||||
if (self.tokenRefreshHandler == nil) {
|
||||
if (self.fakeTokenRefresher.tokenRefreshHandler == nil) {
|
||||
XCTFail(@"`tokenRefreshHandler` must be not `nil`.");
|
||||
return;
|
||||
}
|
||||
|
||||
XCTestExpectation *completionExpectation = [self expectationWithDescription:@"completion"];
|
||||
self.tokenRefreshHandler(^(GACAppCheckTokenRefreshResult *refreshResult) {
|
||||
self.fakeTokenRefresher.tokenRefreshHandler(^(GACAppCheckTokenRefreshResult *refreshResult) {
|
||||
[completionExpectation fulfill];
|
||||
XCTAssertEqual(refreshResult.status, GACAppCheckTokenRefreshStatusFailure);
|
||||
XCTAssertNil(refreshResult.tokenExpirationDate);
|
||||
@@ -324,23 +265,17 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
});
|
||||
|
||||
[self waitForExpectations:@[ completionExpectation ] timeout:0.5];
|
||||
[self verifyAllMocks];
|
||||
|
||||
XCTAssertEqual(self.fakeAppCheckProvider.getTokenCallCount, 1);
|
||||
XCTAssertEqual(self.fakeTokenDelegate.tokenDidUpdateCallCount, 0);
|
||||
XCTAssertNil(self.fakeStorage.lastSetToken);
|
||||
XCTAssertEqual(self.fakeTokenRefresher.updateWithRefreshResultCallCount, 0);
|
||||
}
|
||||
|
||||
- (void)testLimitedUseTokenWithSuccess {
|
||||
// 1. Don't expect token to be requested from storage.
|
||||
OCMReject([self.mockStorage getToken]);
|
||||
|
||||
// 2. Expect token requested from app check provider.
|
||||
// 1. Expect token requested from app check provider.
|
||||
GACAppCheckToken *expectedToken = [self validToken];
|
||||
id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil];
|
||||
OCMExpect([self.mockAppCheckProvider getLimitedUseTokenWithCompletion:completionArg]);
|
||||
|
||||
// 3. Don't expect token requested from storage.
|
||||
OCMReject([self.mockStorage setToken:expectedToken]);
|
||||
|
||||
// 4. Don't expect token update notification to be sent.
|
||||
OCMReject([self.mockTokenDelegate tokenDidUpdate:OCMOCK_ANY serviceName:OCMOCK_ANY]);
|
||||
self.fakeAppCheckProvider.limitedUseTokenToReturn = expectedToken;
|
||||
|
||||
// 5. Expect token request to be completed.
|
||||
XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
|
||||
@@ -351,23 +286,16 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
XCTAssertNil(result.error);
|
||||
}];
|
||||
[self waitForExpectations:@[ getTokenExpectation ] timeout:0.5];
|
||||
[self verifyAllMocks];
|
||||
|
||||
XCTAssertEqual(self.fakeAppCheckProvider.getLimitedUseTokenCallCount, 1);
|
||||
XCTAssertEqualObjects(self.fakeStorage.lastSetToken, nil);
|
||||
XCTAssertEqual(self.fakeTokenDelegate.tokenDidUpdateCallCount, 0);
|
||||
}
|
||||
|
||||
- (void)testLimitedUseToken_WhenTokenGenerationErrors {
|
||||
// 1. Don't expect token to be requested from storage.
|
||||
OCMReject([self.mockStorage getToken]);
|
||||
|
||||
// 2. Expect error when requesting token from app check provider.
|
||||
NSError *providerError = [_GACAppCheckErrorUtil keychainErrorWithError:[self internalError]];
|
||||
id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], providerError, nil];
|
||||
OCMExpect([self.mockAppCheckProvider getLimitedUseTokenWithCompletion:completionArg]);
|
||||
|
||||
// 3. Don't expect token requested from app check provider.
|
||||
OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]);
|
||||
|
||||
// 4. Don't expect token update notification to be sent.
|
||||
OCMReject([self.mockTokenDelegate tokenDidUpdate:OCMOCK_ANY serviceName:OCMOCK_ANY]);
|
||||
self.fakeAppCheckProvider.limitedUseErrorToReturn = providerError;
|
||||
|
||||
// 5. Expect token request to be completed.
|
||||
XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
|
||||
@@ -381,7 +309,12 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ getTokenExpectation ] timeout:0.5];
|
||||
[self verifyAllMocks];
|
||||
|
||||
XCTAssertEqual(self.fakeAppCheckProvider.getLimitedUseTokenCallCount, 1);
|
||||
XCTAssertEqual(self.fakeAppCheckProvider.getTokenCallCount, 0);
|
||||
XCTAssertNil(self.fakeStorage.lastSetToken);
|
||||
XCTAssertEqual(self.fakeTokenDelegate.tokenDidUpdateCallCount, 0);
|
||||
XCTAssertEqual(self.fakeTokenRefresher.updateWithRefreshResultCallCount, 0);
|
||||
}
|
||||
|
||||
#pragma mark - Merging multiple get token requests
|
||||
@@ -392,10 +325,6 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
[self expectTokenRequestFromAppCheckProvider];
|
||||
GACAppCheckToken *expectedToken = expectedTokenAndPromise.firstObject;
|
||||
FBLPromise *storeTokenPromise = expectedTokenAndPromise.lastObject;
|
||||
OCMExpect([self.mockTokenRefresher updateWithRefreshResult:[OCMArg any]]);
|
||||
|
||||
// 2. Expect token update notification to be sent.
|
||||
OCMExpect([self.mockTokenDelegate tokenDidUpdate:expectedToken serviceName:kAppName]);
|
||||
|
||||
// 3. Request token several times.
|
||||
NSInteger getTokenCallsCount = 10;
|
||||
@@ -422,7 +351,10 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
|
||||
// 4. Wait for expectations and validate mocks.
|
||||
[self waitForExpectations:getTokenCompletionExpectations timeout:0.5];
|
||||
[self verifyAllMocks];
|
||||
|
||||
XCTAssertEqual(self.fakeAppCheckProvider.getTokenCallCount, 1);
|
||||
XCTAssertEqual(self.fakeTokenRefresher.updateWithRefreshResultCallCount, 1);
|
||||
XCTAssertEqual(self.fakeTokenDelegate.tokenDidUpdateCallCount, 1);
|
||||
|
||||
// 5. Check a get token call after.
|
||||
[self assertGetToken_WhenCachedTokenIsValid_Success];
|
||||
@@ -437,9 +369,6 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
// 1.1. Create an expected error to be reject the store token promise with later.
|
||||
NSError *storageError = [NSError errorWithDomain:self.name code:0 userInfo:nil];
|
||||
|
||||
// 2. Don't expect token update notification to be sent.
|
||||
OCMReject([self.mockTokenDelegate tokenDidUpdate:OCMOCK_ANY serviceName:OCMOCK_ANY]);
|
||||
|
||||
// 3. Request token several times.
|
||||
NSInteger getTokenCallsCount = 10;
|
||||
NSMutableArray *getTokenCompletionExpectations =
|
||||
@@ -467,7 +396,12 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
|
||||
// 4. Wait for expectations and validate mocks.
|
||||
[self waitForExpectations:getTokenCompletionExpectations timeout:0.5];
|
||||
[self verifyAllMocks];
|
||||
|
||||
// After the first token generation fails and caches the result, the call count will be 1
|
||||
XCTAssertEqual(self.fakeAppCheckProvider.getTokenCallCount, 1);
|
||||
XCTAssertEqual(self.fakeTokenDelegate.tokenDidUpdateCallCount, 0); // No updates on error
|
||||
XCTAssertEqualObjects(self.fakeStorage.lastSetToken, expectedTokenAndPromise.firstObject);
|
||||
XCTAssertEqual(self.fakeTokenRefresher.updateWithRefreshResultCallCount, 0);
|
||||
|
||||
// 5. Check a get token call after.
|
||||
[self assertGetToken_WhenCachedTokenIsValid_Success];
|
||||
@@ -489,15 +423,9 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
return [[GACAppCheckToken alloc] initWithToken:@"valid" expirationDate:soonExpiringTokenDate];
|
||||
}
|
||||
|
||||
- (void)stubSetTokenRefreshHandler {
|
||||
id arg = [OCMArg checkWithBlock:^BOOL(id handler) {
|
||||
self.tokenRefreshHandler = handler;
|
||||
return YES;
|
||||
}];
|
||||
OCMExpect([self.mockTokenRefresher setTokenRefreshHandler:arg]);
|
||||
}
|
||||
|
||||
- (void)assertGetToken_WhenCachedTokenIsValid_Success {
|
||||
NSInteger initialCallCount = self.fakeAppCheckProvider.getTokenCallCount;
|
||||
|
||||
// 1. Create expected token and configure expectations.
|
||||
GACAppCheckToken *cachedToken = [self validToken];
|
||||
|
||||
@@ -514,25 +442,20 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
|
||||
// 3. Wait for expectations and validate mocks.
|
||||
[self waitForExpectations:@[ expectation ] timeout:0.5];
|
||||
[self verifyAllMocks];
|
||||
|
||||
XCTAssertEqual(self.fakeAppCheckProvider.getTokenCallCount, initialCallCount);
|
||||
}
|
||||
|
||||
- (XCTestExpectation *)configuredExpectations_GetTokenWhenNoCache_withExpectedToken:
|
||||
(GACAppCheckToken *)expectedToken {
|
||||
// 1. Expect token to be requested from storage.
|
||||
OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
|
||||
self.fakeStorage.getTokenPromise = [FBLPromise resolvedWith:nil];
|
||||
|
||||
// 2. Expect token requested from app check provider.
|
||||
id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil];
|
||||
OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
|
||||
self.fakeAppCheckProvider.tokenToReturn = expectedToken;
|
||||
|
||||
// 3. Expect new token to be stored.
|
||||
OCMExpect([self.mockStorage setToken:expectedToken])
|
||||
.andReturn([FBLPromise resolvedWith:expectedToken]);
|
||||
OCMExpect([self.mockTokenRefresher updateWithRefreshResult:[OCMArg any]]);
|
||||
|
||||
// 4. Expect token update notification to be sent.
|
||||
OCMExpect([self.mockTokenDelegate tokenDidUpdate:expectedToken serviceName:kAppName]);
|
||||
self.fakeStorage.setTokenPromise = [FBLPromise resolvedWith:expectedToken];
|
||||
|
||||
// 5. Expect token request to be completed.
|
||||
XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
|
||||
@@ -543,13 +466,7 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
- (XCTestExpectation *)configuredExpectation_GetTokenWhenCacheTokenIsValid_withExpectedToken:
|
||||
(GACAppCheckToken *)expectedToken {
|
||||
// 1. Expect token to be requested from storage.
|
||||
OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:expectedToken]);
|
||||
|
||||
// 2. Don't expect token requested from app check provider.
|
||||
OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]);
|
||||
|
||||
// 3. Don't expect token update notification to be sent.
|
||||
OCMReject([self.mockTokenDelegate tokenDidUpdate:OCMOCK_ANY serviceName:OCMOCK_ANY]);
|
||||
self.fakeStorage.getTokenPromise = [FBLPromise resolvedWith:expectedToken];
|
||||
|
||||
// 4. Expect token request to be completed.
|
||||
return [self expectationWithDescription:@"getToken"];
|
||||
@@ -558,20 +475,11 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
- (XCTestExpectation *)
|
||||
configuredExpectations_GetTokenForcingRefreshWhenCacheIsValid_withExpectedToken:
|
||||
(GACAppCheckToken *)expectedToken {
|
||||
// 1. Don't expect token to be requested from storage.
|
||||
OCMReject([self.mockStorage getToken]);
|
||||
|
||||
// 2. Expect token requested from app check provider.
|
||||
id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil];
|
||||
OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
|
||||
self.fakeAppCheckProvider.tokenToReturn = expectedToken;
|
||||
|
||||
// 3. Expect new token to be stored.
|
||||
OCMExpect([self.mockStorage setToken:expectedToken])
|
||||
.andReturn([FBLPromise resolvedWith:expectedToken]);
|
||||
OCMExpect([self.mockTokenRefresher updateWithRefreshResult:[OCMArg any]]);
|
||||
|
||||
// 4. Expect token update notification to be sent.
|
||||
OCMExpect([self.mockTokenDelegate tokenDidUpdate:expectedToken serviceName:kAppName]);
|
||||
self.fakeStorage.setTokenPromise = [FBLPromise resolvedWith:expectedToken];
|
||||
|
||||
// 5. Expect token request to be completed.
|
||||
XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
|
||||
@@ -584,19 +492,13 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
// 1. Expect token to be requested from storage.
|
||||
GACAppCheckToken *cachedToken = [[GACAppCheckToken alloc] initWithToken:@"expired"
|
||||
expirationDate:[NSDate date]];
|
||||
OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:cachedToken]);
|
||||
self.fakeStorage.getTokenPromise = [FBLPromise resolvedWith:cachedToken];
|
||||
|
||||
// 2. Expect token requested from app check provider.
|
||||
id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil];
|
||||
OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
|
||||
self.fakeAppCheckProvider.tokenToReturn = expectedToken;
|
||||
|
||||
// 3. Expect new token to be stored.
|
||||
OCMExpect([self.mockStorage setToken:expectedToken])
|
||||
.andReturn([FBLPromise resolvedWith:expectedToken]);
|
||||
OCMExpect([self.mockTokenRefresher updateWithRefreshResult:[OCMArg any]]);
|
||||
|
||||
// 4. Expect token update notification to be sent.
|
||||
OCMExpect([self.mockTokenDelegate tokenDidUpdate:expectedToken serviceName:kAppName]);
|
||||
self.fakeStorage.setTokenPromise = [FBLPromise resolvedWith:expectedToken];
|
||||
|
||||
// 5. Expect token request to be completed.
|
||||
XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
|
||||
@@ -608,17 +510,10 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
configuredExpectations_GetTokenWhenError_withError:(NSError *_Nonnull)error
|
||||
andToken:(GACAppCheckToken *_Nullable)token {
|
||||
// 1. Expect token to be requested from storage.
|
||||
OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:token]);
|
||||
self.fakeStorage.getTokenPromise = [FBLPromise resolvedWith:token];
|
||||
|
||||
// 2. Expect token requested from app check provider.
|
||||
id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], error, nil];
|
||||
OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
|
||||
|
||||
// 3. Don't expect token requested from app check provider.
|
||||
OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]);
|
||||
|
||||
// 4. Expect token update notification to be sent.
|
||||
OCMReject([self.mockTokenDelegate tokenDidUpdate:OCMOCK_ANY serviceName:OCMOCK_ANY]);
|
||||
self.fakeAppCheckProvider.errorToReturn = error;
|
||||
|
||||
// 5. Expect token request to be completed.
|
||||
XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
|
||||
@@ -628,28 +523,19 @@ static NSString *const kAppGroupID = @"app_group_id";
|
||||
|
||||
- (NSArray *)expectTokenRequestFromAppCheckProvider {
|
||||
// 1. Expect token to be requested from storage.
|
||||
OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
|
||||
self.fakeStorage.getTokenPromise = [FBLPromise resolvedWith:nil];
|
||||
|
||||
// 2. Expect token requested from app check provider.
|
||||
GACAppCheckToken *expectedToken = [self validToken];
|
||||
id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil];
|
||||
OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
|
||||
self.fakeAppCheckProvider.tokenToReturn = expectedToken;
|
||||
|
||||
// 3. Expect new token to be stored.
|
||||
// 3.1. Create a pending promise to resolve later.
|
||||
FBLPromise<GACAppCheckToken *> *storeTokenPromise = [FBLPromise pendingPromise];
|
||||
// 3.2. Stub storage set token method.
|
||||
OCMExpect([self.mockStorage setToken:expectedToken]).andReturn(storeTokenPromise);
|
||||
self.fakeStorage.setTokenPromise = storeTokenPromise;
|
||||
|
||||
return @[ expectedToken, storeTokenPromise ];
|
||||
}
|
||||
|
||||
- (void)verifyAllMocks {
|
||||
OCMVerifyAll(self.mockAppCheckProvider);
|
||||
OCMVerifyAll(self.mockStorage);
|
||||
OCMVerifyAll(self.mockSettings);
|
||||
OCMVerifyAll(self.mockTokenDelegate);
|
||||
OCMVerifyAll(self.mockTokenRefresher);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckProvider.h"
|
||||
#import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckToken.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface GACAppCheckProviderFake : NSObject <GACAppCheckProvider>
|
||||
|
||||
@property(atomic, nullable) GACAppCheckToken *tokenToReturn;
|
||||
@property(atomic, nullable) NSError *errorToReturn;
|
||||
@property(nonatomic) NSInteger getTokenCallCount;
|
||||
|
||||
@property(atomic, nullable) GACAppCheckToken *limitedUseTokenToReturn;
|
||||
@property(atomic, nullable) NSError *limitedUseErrorToReturn;
|
||||
@property(nonatomic) NSInteger getLimitedUseTokenCallCount;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "AppCheckCore/Tests/Unit/Utils/GACAppCheckProviderFake.h"
|
||||
|
||||
@implementation GACAppCheckProviderFake
|
||||
|
||||
@synthesize getTokenCallCount = _getTokenCallCount;
|
||||
@synthesize getLimitedUseTokenCallCount = _getLimitedUseTokenCallCount;
|
||||
|
||||
- (NSInteger)getTokenCallCount {
|
||||
@synchronized(self) {
|
||||
return _getTokenCallCount;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setGetTokenCallCount:(NSInteger)getTokenCallCount {
|
||||
@synchronized(self) {
|
||||
_getTokenCallCount = getTokenCallCount;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)getLimitedUseTokenCallCount {
|
||||
@synchronized(self) {
|
||||
return _getLimitedUseTokenCallCount;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setGetLimitedUseTokenCallCount:(NSInteger)getLimitedUseTokenCallCount {
|
||||
@synchronized(self) {
|
||||
_getLimitedUseTokenCallCount = getLimitedUseTokenCallCount;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)getTokenWithCompletion:(void (^)(GACAppCheckToken *_Nullable token,
|
||||
NSError *_Nullable error))handler {
|
||||
GACAppCheckToken *token;
|
||||
NSError *error;
|
||||
@synchronized(self) {
|
||||
_getTokenCallCount++;
|
||||
token = self.tokenToReturn;
|
||||
error = self.errorToReturn;
|
||||
}
|
||||
if (handler) {
|
||||
handler(token, error);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)getLimitedUseTokenWithCompletion:(void (^)(GACAppCheckToken *_Nullable token,
|
||||
NSError *_Nullable error))handler {
|
||||
GACAppCheckToken *token;
|
||||
NSError *error;
|
||||
@synchronized(self) {
|
||||
_getLimitedUseTokenCallCount++;
|
||||
token = self.limitedUseTokenToReturn;
|
||||
error = self.limitedUseErrorToReturn;
|
||||
}
|
||||
if (handler) {
|
||||
handler(token, error);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckSettings.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface GACAppCheckSettingsFake : NSObject <GACAppCheckSettingsProtocol>
|
||||
|
||||
@property(nonatomic) BOOL isTokenAutoRefreshEnabled;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "AppCheckCore/Tests/Unit/Utils/GACAppCheckSettingsFake.h"
|
||||
|
||||
@implementation GACAppCheckSettingsFake
|
||||
|
||||
@synthesize isTokenAutoRefreshEnabled = _isTokenAutoRefreshEnabled;
|
||||
|
||||
- (BOOL)isTokenAutoRefreshEnabled {
|
||||
@synchronized(self) {
|
||||
return _isTokenAutoRefreshEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setIsTokenAutoRefreshEnabled:(BOOL)isTokenAutoRefreshEnabled {
|
||||
@synchronized(self) {
|
||||
_isTokenAutoRefreshEnabled = isTokenAutoRefreshEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AppCheckCore/Sources/Core/Storage/GACAppCheckStorage.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface GACAppCheckStorageFake : NSObject <GACAppCheckStorageProtocol>
|
||||
|
||||
@property(atomic, nullable) FBLPromise<GACAppCheckToken *> *getTokenPromise;
|
||||
@property(atomic, nullable) FBLPromise<GACAppCheckToken *> *setTokenPromise;
|
||||
@property(atomic, nullable) GACAppCheckToken *lastSetToken;
|
||||
@property(atomic, nullable) FBLPromise<NSNull *> *removeTokenPromise;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "AppCheckCore/Tests/Unit/Utils/GACAppCheckStorageFake.h"
|
||||
#import "FBLPromise+Testing.h"
|
||||
|
||||
@implementation GACAppCheckStorageFake
|
||||
|
||||
- (FBLPromise<GACAppCheckToken *> *)getToken {
|
||||
return self.getTokenPromise ?: [FBLPromise resolvedWith:nil];
|
||||
}
|
||||
|
||||
- (FBLPromise<GACAppCheckToken *> *)setToken:(GACAppCheckToken *)token {
|
||||
@synchronized(self) {
|
||||
self.lastSetToken = token;
|
||||
}
|
||||
return self.setTokenPromise ?: [FBLPromise resolvedWith:token];
|
||||
}
|
||||
|
||||
- (FBLPromise<NSNull *> *)removeToken {
|
||||
return self.removeTokenPromise ?: [FBLPromise resolvedWith:[NSNull null]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckTokenDelegate.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface GACAppCheckTokenDelegateFake : NSObject <GACAppCheckTokenDelegate>
|
||||
|
||||
@property(atomic) NSInteger tokenDidUpdateCallCount;
|
||||
@property(atomic, nullable) GACAppCheckToken *lastToken;
|
||||
@property(atomic, copy, nullable) NSString *lastServiceName;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "AppCheckCore/Tests/Unit/Utils/GACAppCheckTokenDelegateFake.h"
|
||||
|
||||
@implementation GACAppCheckTokenDelegateFake
|
||||
|
||||
- (void)tokenDidUpdate:(GACAppCheckToken *)token serviceName:(NSString *)serviceName {
|
||||
@synchronized(self) {
|
||||
self.tokenDidUpdateCallCount++;
|
||||
self.lastToken = token;
|
||||
self.lastServiceName = serviceName;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AppCheckCore/Sources/Core/TokenRefresh/GACAppCheckTokenRefresher.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface GACAppCheckTokenRefresherFake : NSObject <GACAppCheckTokenRefresherProtocol>
|
||||
|
||||
@property(nonatomic, copy, nullable) GACAppCheckTokenRefreshBlock tokenRefreshHandler;
|
||||
@property(atomic) NSInteger updateWithRefreshResultCallCount;
|
||||
@property(atomic, nullable) GACAppCheckTokenRefreshResult *lastRefreshResult;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "AppCheckCore/Tests/Unit/Utils/GACAppCheckTokenRefresherFake.h"
|
||||
|
||||
@implementation GACAppCheckTokenRefresherFake
|
||||
|
||||
@synthesize tokenRefreshHandler = _tokenRefreshHandler;
|
||||
|
||||
- (GACAppCheckTokenRefreshBlock)tokenRefreshHandler {
|
||||
@synchronized(self) {
|
||||
return _tokenRefreshHandler;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTokenRefreshHandler:(GACAppCheckTokenRefreshBlock)tokenRefreshHandler {
|
||||
@synchronized(self) {
|
||||
_tokenRefreshHandler = [tokenRefreshHandler copy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateWithRefreshResult:(GACAppCheckTokenRefreshResult *)refreshResult {
|
||||
@synchronized(self) {
|
||||
self.updateWithRefreshResultCallCount++;
|
||||
self.lastRefreshResult = refreshResult;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -28,12 +28,12 @@ typedef void (^GACFakeTimerCreateHandler)(NSDate *fireDate);
|
||||
|
||||
/// `createHandler` is called each time the timer provider returned by `fakeTimerProvider` is asked
|
||||
/// to create a timer.
|
||||
@property(nonatomic, copy, nullable) GACFakeTimerCreateHandler createHandler;
|
||||
@property(atomic, copy, nullable) GACFakeTimerCreateHandler createHandler;
|
||||
|
||||
@property(nonatomic, copy, nullable) dispatch_block_t invalidationHandler;
|
||||
@property(atomic, copy, nullable) dispatch_block_t invalidationHandler;
|
||||
|
||||
/// The timer handler passed in the timer provider returned by `fakeTimerProvider` method.
|
||||
@property(nonatomic, copy, nullable) dispatch_block_t handler;
|
||||
@property(atomic, copy, nullable) dispatch_block_t handler;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -19,20 +19,30 @@
|
||||
@implementation GACFakeTimer
|
||||
|
||||
- (GACTimerProvider)fakeTimerProvider {
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
return ^id<GACAppCheckTimerProtocol> _Nullable(NSDate *fireDate, dispatch_queue_t queue,
|
||||
dispatch_block_t handler) {
|
||||
self.handler = handler;
|
||||
if (self.createHandler) {
|
||||
self.createHandler(fireDate);
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
@synchronized(strongSelf) {
|
||||
strongSelf.handler = handler;
|
||||
void (^createHandler)(NSDate *) = strongSelf.createHandler;
|
||||
if (createHandler) {
|
||||
createHandler(fireDate);
|
||||
}
|
||||
}
|
||||
|
||||
return strongSelf;
|
||||
};
|
||||
}
|
||||
|
||||
- (void)invalidate {
|
||||
if (self.invalidationHandler) {
|
||||
self.invalidationHandler();
|
||||
void (^invalidationHandler)(void) = self.invalidationHandler;
|
||||
if (invalidationHandler) {
|
||||
invalidationHandler();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (fileURL == nil) {
|
||||
NSLog(@"Fixture named %@ not found", fileName);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
NSData *data = [NSData dataWithContentsOfURL:fileURL options:0 error:&error];
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Review Guidelines
|
||||
|
||||
## Concurrency & Thread Safety
|
||||
|
||||
When reviewing Objective-C code, particularly test fakes, you MUST strictly enforce the following thread safety rules for concurrent test execution:
|
||||
|
||||
1. **State Mutation Synchronization**: All state mutations (e.g., incrementing counters like `counter++`, setting properties, or adding to arrays) MUST be wrapped in a `@synchronized(self)` block to prevent data races and TSAN (Thread Sanitizer) failures.
|
||||
2. **Proper Use of `atomic` vs `nonatomic`**: The `atomic` property modifier does NOT make read-modify-write operations (like `++`) thread-safe. Properties that require compound operations should be declared `nonatomic` to avoid misleading expectations, and their getters/setters must be manually synchronized in the implementation file (or accessed directly via ivar within a synchronized block).
|
||||
@@ -210,3 +210,47 @@ Perform self-reflection after completing the task. You MUST update this file
|
||||
(`agents.md`) with any new learnings, context, or troubleshooting steps that
|
||||
were needed and will be needed again to refine the process for future agents.
|
||||
Alternatively, create or update a Knowledge Item.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 OCMock & Objective-C Testing Migration Guidelines
|
||||
When migrating Objective-C tests away from `OCMock` (to support future Swift
|
||||
interoperability), you MUST follow these patterns:
|
||||
- **Use Protocol-Based Fakes**: Replace `OCMock` with manually implemented
|
||||
`Fake` classes that conform to the required protocols (e.g.,
|
||||
`id<GACAppCheckProvider>`).
|
||||
- **Strict Black-Box Testing (No Test Categories)**: Do NOT use
|
||||
`@interface TargetClass (Tests)` categories to expose internal properties or
|
||||
standard `alloc/init` overrides just to verify "wiring". Inject Fakes via
|
||||
designated initializers and verify behavior, not state.
|
||||
- **Thread-Safety & Locks**: Avoid redundant recursive locks
|
||||
(`@synchronized(self)`). If a property uses `@synchronized(self)` in its
|
||||
getter/setter, use direct instance variable access (e.g. `_ivar`) when
|
||||
reading/writing it internally from within another `@synchronized(self)` block
|
||||
to reduce overhead.
|
||||
- **Concurrent Independent PRs**: When migrating multiple test suites, separate
|
||||
them into independent branches (e.g. Core AppCheck vs AppAttest) and open
|
||||
concurrent PRs rather than a single monolithic PR, provided they don't share
|
||||
Fake implementations.
|
||||
|
||||
---
|
||||
|
||||
## 🤖 AI Subagent Personas
|
||||
|
||||
### Objective Code Verifier
|
||||
Your role is to act as a strict execution engine. When invoked, you must
|
||||
strictly run builds and test suites using this repository's specific test
|
||||
commands (as defined in the Verification section above). You must report binary
|
||||
pass/fail results. Do not attempt to fix or diagnose the failures yourself;
|
||||
simply output the error logs and failure details back to the caller.
|
||||
|
||||
### Rigorous Code Reviewer
|
||||
Your role is to perform subjective, rigorous code reviews on proposed changes.
|
||||
You must focus heavily on concurrency, memory management, and strict adherence
|
||||
to the project's guidelines. You MUST read and enforce the rules defined in
|
||||
`REVIEW_GUIDELINES.md` located at the root of this repository. Flag any missing
|
||||
synchronization or thread-safety violations immediately.
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user