| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014 |
- /*
- * Copyright 2020 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 <XCTest/XCTest.h>
- #import <OCMock/OCMock.h>
- #import "FBLPromise+Testing.h"
- #import "AppCheck/Sources/Public/AppCheck/GACAppCheck.h"
- #import "AppCheck/Sources/Public/AppCheck/GACAppCheckErrors.h"
- #import "AppCheck/Sources/Public/AppCheck/GACAppCheckProvider.h"
- #import "AppCheck/Sources/Public/AppCheck/GACAppCheckSettings.h"
- #import "AppCheck/Sources/Core/Errors/GACAppCheckErrorUtil.h"
- #import "AppCheck/Sources/Core/GACAppCheckTokenResult.h"
- #import "AppCheck/Sources/Core/Storage/GACAppCheckStorage.h"
- #import "AppCheck/Sources/Core/TokenRefresh/GACAppCheckTokenRefreshResult.h"
- #import "AppCheck/Sources/Core/TokenRefresh/GACAppCheckTokenRefresher.h"
- #import "AppCheck/Sources/Public/AppCheck/GACAppCheckToken.h"
- #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
- // Since DeviceCheck is the default attestation provider for AppCheck, disable
- // test cases that may be dependent on DeviceCheck being available.
- #if GAC_DEVICE_CHECK_SUPPORTED_TARGETS
- // The FAC token value returned when an error occurs.
- static NSString *const kDummyToken = @"eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ==";
- static NSString *const kResourceName = @"projects/test_project_id/apps/test_app_id";
- static NSString *const kAppName = @"GACAppCheckTests";
- static NSString *const kAppGroupID = @"app_group_id";
- @interface GACAppCheck (Tests) <GACAppCheckInterop>
- - (instancetype)initWithInstanceName:(NSString *)instanceName
- appCheckProvider:(id<GACAppCheckProvider>)appCheckProvider
- storage:(id<GACAppCheckStorageProtocol>)storage
- tokenRefresher:(id<GACAppCheckTokenRefresherProtocol>)tokenRefresher
- notificationCenter:(NSNotificationCenter *)notificationCenter
- settings:(id<GACAppCheckSettingsProtocol>)settings;
- @end
- @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) NSNotificationCenter *notificationCenter;
- @property(nonatomic) GACAppCheck<GACAppCheckInterop> *appCheck;
- @property(nonatomic, copy, nullable) GACAppCheckTokenRefreshBlock tokenRefreshHandler;
- @end
- @implementation GACAppCheckTests
- - (void)setUp {
- [super setUp];
- self.mockStorage = OCMStrictProtocolMock(@protocol(GACAppCheckStorageProtocol));
- self.mockAppCheckProvider = OCMStrictProtocolMock(@protocol(GACAppCheckProvider));
- self.mockTokenRefresher = OCMStrictProtocolMock(@protocol(GACAppCheckTokenRefresherProtocol));
- self.mockSettings = OCMProtocolMock(@protocol(GACAppCheckSettingsProtocol));
- self.notificationCenter = [[NSNotificationCenter alloc] init];
- [self stubSetTokenRefreshHandler];
- self.appCheck = [[GACAppCheck alloc] initWithInstanceName:kAppName
- appCheckProvider:self.mockAppCheckProvider
- storage:self.mockStorage
- tokenRefresher:self.mockTokenRefresher
- notificationCenter:self.notificationCenter
- settings:self.mockSettings];
- }
- - (void)tearDown {
- self.appCheck = nil;
- [self.mockAppCheckProvider stopMocking];
- self.mockAppCheckProvider = nil;
- [self.mockStorage stopMocking];
- self.mockStorage = nil;
- [self.mockTokenRefresher stopMocking];
- self.mockTokenRefresher = 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. Call init.
- GACAppCheck *appCheck = [[GACAppCheck alloc] initWithInstanceName:kAppName
- appCheckProvider:mockProvider
- settings:mockSettings
- resourceName:kResourceName
- keychainAccessGroup:kAppGroupID];
- XCTAssert([appCheck isKindOfClass:[GACAppCheck class]]);
- // 6. Verify mocks.
- OCMVerifyAll(mockTokenRefresher);
- OCMVerifyAll(mockStorage);
- OCMVerifyAll(mockProvider);
- OCMVerifyAll(mockSettings);
- // 7. Stop mocking real class mocks.
- [mockTokenRefresher stopMocking];
- mockTokenRefresher = nil;
- [mockStorage stopMocking];
- mockStorage = nil;
- }
- #pragma mark - Public Get Token
- - (void)testGetToken_WhenNoCache_Success {
- // 1. Create expected token and configure expectations.
- GACAppCheckToken *expectedToken = [self validToken];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenWhenNoCache_withExpectedToken:expectedToken];
- // 2. Request token and verify result.
- [self.appCheck
- tokenForcingRefresh:NO
- completion:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [expectations.lastObject fulfill];
- XCTAssertNotNil(token);
- XCTAssertEqualObjects(token.token, expectedToken.token);
- XCTAssertNil(error);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)testGetToken_WhenCachedTokenIsValid_Success {
- [self assertGetToken_WhenCachedTokenIsValid_Success];
- }
- - (void)testGetTokenForcingRefresh_WhenCachedTokenIsValid_Success {
- // 1. Create expected token and configure expectations.
- GACAppCheckToken *expectedToken = [self validToken];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenForcingRefreshWhenCacheIsValid_withExpectedToken:
- expectedToken];
- // 2. Request token and verify result.
- [self.appCheck
- tokenForcingRefresh:YES
- completion:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [expectations.lastObject fulfill];
- XCTAssertNotNil(token);
- XCTAssertEqualObjects(token.token, expectedToken.token);
- XCTAssertNil(error);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)testGetToken_WhenCachedTokenExpired_Success {
- // 1. Create expected token and configure expectations.
- GACAppCheckToken *expectedToken = [self validToken];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenWhenCachedTokenExpired_withExpectedToken:expectedToken];
- // 2. Request token and verify result.
- [self.appCheck
- tokenForcingRefresh:NO
- completion:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [expectations.lastObject fulfill];
- XCTAssertNotNil(token);
- XCTAssertEqualObjects(token.token, expectedToken.token);
- XCTAssertNil(error);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)testGetToken_AppCheckProviderError {
- // 1. Create expected token and error and configure expectations.
- GACAppCheckToken *cachedToken = [self soonExpiringToken];
- NSError *providerError = [NSError errorWithDomain:@"GACAppCheckTests" code:-1 userInfo:nil];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenWhenError_withError:providerError andToken:cachedToken];
- // 2. Request token and verify result.
- [self.appCheck
- tokenForcingRefresh:NO
- completion:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [expectations.lastObject fulfill];
- XCTAssertNil(token);
- XCTAssertNotNil(error);
- XCTAssertNotEqualObjects(error, providerError);
- XCTAssertEqualObjects(error.domain, GACAppCheckErrorDomain);
- XCTAssertEqualObjects(error.userInfo[NSUnderlyingErrorKey], providerError);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)testGetToken_ServerUnreachableError {
- // 1. Create expected error and configure expectations.
- NSError *serverError = [GACAppCheckErrorUtil APIErrorWithNetworkError:[self internalError]];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenWhenError_withError:serverError andToken:nil];
- // 2. Request token and verify result.
- [self.appCheck
- tokenForcingRefresh:NO
- completion:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [expectations.lastObject fulfill];
- XCTAssertNil(token);
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error, serverError);
- XCTAssertEqualObjects(error.domain, GACAppCheckErrorDomain);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)testGetToken_KeychainError {
- // 1. Expect token to be requested from storage.
- NSError *keychainError = [GACAppCheckErrorUtil keychainErrorWithError:[self internalError]];
- OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:keychainError]);
- // 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]);
- // 3. Expect new token to be stored.
- OCMExpect([self.mockStorage setToken:expectedToken])
- .andReturn([FBLPromise resolvedWith:keychainError]);
- // 4. Don't expect token update notification to be sent.
- XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationWithExpectedToken:@""
- isInverted:YES];
- // 5. Request token and verify result.
- XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
- [self.appCheck
- tokenForcingRefresh:NO
- completion:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [getTokenExpectation fulfill];
- XCTAssertNil(token);
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error, keychainError);
- XCTAssertEqualObjects(error.domain, GACAppCheckErrorDomain);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:@[ notificationExpectation, getTokenExpectation ] timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)testGetToken_UnsupportedError {
- // 1. Create expected error and configure expectations.
- NSError *providerError =
- [GACAppCheckErrorUtil unsupportedAttestationProvider:@"AppAttestProvider"];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenWhenError_withError:providerError andToken:nil];
- // 2. Request token and verify result.
- [self.appCheck
- tokenForcingRefresh:NO
- completion:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [expectations.lastObject fulfill];
- XCTAssertNil(token);
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error, providerError);
- XCTAssertEqualObjects(error.domain, GACAppCheckErrorDomain);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- #pragma mark - GACAppCheckInterop Get Token
- - (void)testInteropGetToken_WhenNoCache_Success {
- // 1. Create expected token and configure expectations.
- GACAppCheckToken *expectedToken = [self validToken];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenWhenNoCache_withExpectedToken:expectedToken];
- // 2. Request token and verify result.
- [self.appCheck getTokenForcingRefresh:NO
- completion:^(id<GACAppCheckTokenResultInterop> tokenResult) {
- [expectations.lastObject fulfill];
- XCTAssertNotNil(tokenResult);
- XCTAssertEqualObjects(tokenResult.token, expectedToken.token);
- XCTAssertNil(tokenResult.error);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)testInteropGetToken_WhenCachedTokenIsValid_Success {
- [self assertInteropGetToken_WhenCachedTokenIsValid_Success];
- }
- - (void)testInteropGetTokenForcingRefresh_WhenCachedTokenIsValid_Success {
- // 1. Create expected token and configure expectations.
- GACAppCheckToken *expectedToken = [self validToken];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenForcingRefreshWhenCacheIsValid_withExpectedToken:
- expectedToken];
- // 2. Request token and verify result.
- [self.appCheck getTokenForcingRefresh:YES
- completion:^(id<GACAppCheckTokenResultInterop> tokenResult) {
- [expectations.lastObject fulfill];
- XCTAssertNotNil(tokenResult);
- XCTAssertEqualObjects(tokenResult.token, expectedToken.token);
- XCTAssertNil(tokenResult.error);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)testInteropGetToken_WhenCachedTokenExpired_Success {
- // 1. Create expected token and configure expectations.
- GACAppCheckToken *expectedToken = [self validToken];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenWhenCachedTokenExpired_withExpectedToken:expectedToken];
- // 2. Request token and verify result.
- [self.appCheck getTokenForcingRefresh:NO
- completion:^(id<GACAppCheckTokenResultInterop> tokenResult) {
- [expectations.lastObject fulfill];
- XCTAssertNotNil(tokenResult);
- XCTAssertEqualObjects(tokenResult.token, expectedToken.token);
- XCTAssertNil(tokenResult.error);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)testInteropGetToken_AppCheckProviderError {
- // 1. Create expected tokens and errors and configure expectations.
- GACAppCheckToken *cachedToken = [self soonExpiringToken];
- NSError *providerError = [NSError errorWithDomain:@"GACAppCheckTests" code:-1 userInfo:nil];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenWhenError_withError:providerError andToken:cachedToken];
- // 2. Request token and verify result.
- [self.appCheck
- getTokenForcingRefresh:NO
- completion:^(id<GACAppCheckTokenResultInterop> result) {
- [expectations.lastObject fulfill];
- XCTAssertNotNil(result);
- XCTAssertEqualObjects(result.token, kDummyToken);
- XCTAssertEqualObjects(result.error, providerError);
- // Interop API does not wrap errors in public domain.
- XCTAssertNotEqualObjects(result.error.domain, GACAppCheckErrorDomain);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- #pragma mark - Token refresher
- - (void)testTokenRefreshTriggeredAndRefreshSuccess {
- // 1. Expect token to be requested from storage.
- OCMExpect([self.mockStorage getToken]).andReturn([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]);
- // 3. Expect new token to be stored.
- OCMExpect([self.mockStorage setToken:tokenToReturn])
- .andReturn([FBLPromise resolvedWith:tokenToReturn]);
- OCMExpect([self.mockTokenRefresher updateWithRefreshResult:[OCMArg any]]);
- // 4. Expect token update notification to be sent.
- XCTestExpectation *notificationExpectation =
- [self tokenUpdateNotificationWithExpectedToken:tokenToReturn.token];
- // 5. Trigger refresh and expect the result.
- if (self.tokenRefreshHandler == nil) {
- XCTFail(@"`tokenRefreshHandler` must be not `nil`.");
- return;
- }
- XCTestExpectation *completionExpectation = [self expectationWithDescription:@"completion"];
- self.tokenRefreshHandler(^(GACAppCheckTokenRefreshResult *refreshResult) {
- [completionExpectation fulfill];
- XCTAssertEqualObjects(refreshResult.tokenExpirationDate, expirationDate);
- XCTAssertEqual(refreshResult.status, GACAppCheckTokenRefreshStatusSuccess);
- });
- [self waitForExpectations:@[ notificationExpectation, completionExpectation ] timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)testTokenRefreshTriggeredAndRefreshError {
- // 1. Expect token to be requested from storage.
- OCMExpect([self.mockStorage getToken]).andReturn([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.
- XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationWithExpectedToken:@""
- isInverted:YES];
- // 5. Trigger refresh and expect the result.
- if (self.tokenRefreshHandler == nil) {
- XCTFail(@"`tokenRefreshHandler` must be not `nil`.");
- return;
- }
- XCTestExpectation *completionExpectation = [self expectationWithDescription:@"completion"];
- self.tokenRefreshHandler(^(GACAppCheckTokenRefreshResult *refreshResult) {
- [completionExpectation fulfill];
- XCTAssertEqual(refreshResult.status, GACAppCheckTokenRefreshStatusFailure);
- XCTAssertNil(refreshResult.tokenExpirationDate);
- XCTAssertNil(refreshResult.tokenReceivedAtDate);
- });
- [self waitForExpectations:@[ notificationExpectation, completionExpectation ] timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)testLimitedUseTokenWithSuccess {
- // 1. Don't expect token to be requested from storage.
- OCMReject([self.mockStorage getToken]);
- // 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]);
- // 3. Don't expect token requested from storage.
- OCMReject([self.mockStorage setToken:expectedToken]);
- // 4. Don't expect token update notification to be sent.
- XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationWithExpectedToken:@""
- isInverted:YES];
- // 5. Expect token request to be completed.
- XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
- [self.appCheck
- limitedUseTokenWithCompletion:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [getTokenExpectation fulfill];
- XCTAssertNotNil(token);
- XCTAssertEqualObjects(token.token, expectedToken.token);
- XCTAssertNil(error);
- }];
- [self waitForExpectations:@[ notificationExpectation, getTokenExpectation ] timeout:0.5];
- [self verifyAllMocks];
- }
- - (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 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.
- XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationWithExpectedToken:@""
- isInverted:YES];
- // 5. Expect token request to be completed.
- XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
- [self.appCheck
- limitedUseTokenWithCompletion:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [getTokenExpectation fulfill];
- XCTAssertNotNil(error);
- XCTAssertNil(token.token);
- XCTAssertEqualObjects(error, providerError);
- XCTAssertEqualObjects(error.domain, GACAppCheckErrorDomain);
- }];
- [self waitForExpectations:@[ notificationExpectation, getTokenExpectation ] timeout:0.5];
- [self verifyAllMocks];
- }
- #pragma mark - Token update notifications
- - (void)testTokenUpdateNotificationKeys {
- XCTAssertEqualObjects([self.appCheck tokenDidChangeNotificationName],
- @"GACAppCheckAppCheckTokenDidChangeNotification");
- XCTAssertEqualObjects([self.appCheck notificationInstanceNameKey],
- @"GACAppCheckInstanceNameNotificationKey");
- XCTAssertEqualObjects([self.appCheck notificationTokenKey], @"GACAppCheckTokenNotificationKey");
- }
- #pragma mark - Merging multiple get token requests
- - (void)testGetToken_WhenCalledSeveralTimesSuccess_ThenThereIsOnlyOneOperation {
- // 1. Expect a token to be requested and stored.
- NSArray * /*[expectedToken, storeTokenPromise]*/ expectedTokenAndPromise =
- [self expectTokenRequestFromAppCheckProvider];
- GACAppCheckToken *expectedToken = expectedTokenAndPromise.firstObject;
- FBLPromise *storeTokenPromise = expectedTokenAndPromise.lastObject;
- OCMExpect([self.mockTokenRefresher updateWithRefreshResult:[OCMArg any]]);
- // 2. Expect token update notification to be sent.
- XCTestExpectation *notificationExpectation =
- [self tokenUpdateNotificationWithExpectedToken:expectedToken.token];
- // 3. Request token several times.
- NSInteger getTokenCallsCount = 10;
- NSMutableArray *getTokenCompletionExpectations =
- [NSMutableArray arrayWithCapacity:getTokenCallsCount];
- for (NSInteger i = 0; i < getTokenCallsCount; i++) {
- // 3.1. Expect a completion to be called for each method call.
- XCTestExpectation *getTokenExpectation =
- [self expectationWithDescription:[NSString stringWithFormat:@"getToken%@", @(i)]];
- [getTokenCompletionExpectations addObject:getTokenExpectation];
- // 3.2. Request token and verify result.
- [self.appCheck
- tokenForcingRefresh:NO
- completion:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [getTokenExpectation fulfill];
- XCTAssertNotNil(token);
- XCTAssertEqualObjects(token.token, expectedToken.token);
- XCTAssertNil(error);
- }];
- }
- // 3.3. Fulfill the pending promise to finish the get token operation.
- [storeTokenPromise fulfill:expectedToken];
- // 4. Wait for expectations and validate mocks.
- NSArray *expectations =
- [getTokenCompletionExpectations arrayByAddingObject:notificationExpectation];
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- // 5. Check a get token call after.
- [self assertGetToken_WhenCachedTokenIsValid_Success];
- }
- - (void)testGetToken_WhenCalledSeveralTimesError_ThenThereIsOnlyOneOperation {
- // 1. Expect a token to be requested and stored.
- NSArray * /*[expectedToken, storeTokenPromise]*/ expectedTokenAndPromise =
- [self expectTokenRequestFromAppCheckProvider];
- GACAppCheckToken *expectedToken = expectedTokenAndPromise.firstObject;
- FBLPromise *storeTokenPromise = expectedTokenAndPromise.lastObject;
- // 1.1. Create an expected error to be rejected with later.
- NSError *storageError = [NSError errorWithDomain:self.name code:0 userInfo:nil];
- // 2. Don't expect token update notification to be sent.
- XCTestExpectation *notificationExpectation =
- [self tokenUpdateNotificationWithExpectedToken:expectedToken.token isInverted:YES];
- // 3. Request token several times.
- NSInteger getTokenCallsCount = 10;
- NSMutableArray *getTokenCompletionExpectations =
- [NSMutableArray arrayWithCapacity:getTokenCallsCount];
- for (NSInteger i = 0; i < getTokenCallsCount; i++) {
- // 3.1. Expect a completion to be called for each method call.
- XCTestExpectation *getTokenExpectation =
- [self expectationWithDescription:[NSString stringWithFormat:@"getToken%@", @(i)]];
- [getTokenCompletionExpectations addObject:getTokenExpectation];
- // 3.2. Request token and verify result.
- [self.appCheck
- tokenForcingRefresh:NO
- completion:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [getTokenExpectation fulfill];
- XCTAssertNil(token);
- XCTAssertNotNil(error);
- XCTAssertNotEqualObjects(error, storageError);
- XCTAssertEqualObjects(error.domain, GACAppCheckErrorDomain);
- XCTAssertEqualObjects(error.userInfo[NSUnderlyingErrorKey], storageError);
- }];
- }
- // 3.3. Reject the pending promise to finish the get token operation.
- [storeTokenPromise reject:storageError];
- // 4. Wait for expectations and validate mocks.
- NSArray *expectations =
- [getTokenCompletionExpectations arrayByAddingObject:notificationExpectation];
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- // 5. Check a get token call after.
- [self assertGetToken_WhenCachedTokenIsValid_Success];
- }
- - (void)testInteropGetToken_WhenCalledSeveralTimesSuccess_ThenThereIsOnlyOneOperation {
- // 1. Expect a token to be requested and stored.
- NSArray * /*[expectedToken, storeTokenPromise]*/ expectedTokenAndPromise =
- [self expectTokenRequestFromAppCheckProvider];
- GACAppCheckToken *expectedToken = expectedTokenAndPromise.firstObject;
- FBLPromise *storeTokenPromise = expectedTokenAndPromise.lastObject;
- OCMExpect([self.mockTokenRefresher updateWithRefreshResult:[OCMArg any]]);
- // 2. Expect token update notification to be sent.
- XCTestExpectation *notificationExpectation =
- [self tokenUpdateNotificationWithExpectedToken:expectedToken.token];
- // 3. Request token several times.
- NSInteger getTokenCallsCount = 10;
- NSMutableArray *getTokenCompletionExpectations =
- [NSMutableArray arrayWithCapacity:getTokenCallsCount];
- for (NSInteger i = 0; i < getTokenCallsCount; i++) {
- // 3.1. Expect a completion to be called for each method call.
- XCTestExpectation *getTokenExpectation =
- [self expectationWithDescription:[NSString stringWithFormat:@"getToken%@", @(i)]];
- [getTokenCompletionExpectations addObject:getTokenExpectation];
- // 3.2. Request token and verify result.
- [self.appCheck getTokenForcingRefresh:NO
- completion:^(id<GACAppCheckTokenResultInterop> tokenResult) {
- [getTokenExpectation fulfill];
- XCTAssertNotNil(tokenResult);
- XCTAssertEqualObjects(tokenResult.token, expectedToken.token);
- XCTAssertNil(tokenResult.error);
- }];
- }
- // 3.3. Fulfill the pending promise to finish the get token operation.
- [storeTokenPromise fulfill:expectedToken];
- // 4. Wait for expectations and validate mocks.
- NSArray *expectations =
- [getTokenCompletionExpectations arrayByAddingObject:notificationExpectation];
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- // 5. Check a get token call after.
- [self assertInteropGetToken_WhenCachedTokenIsValid_Success];
- }
- - (void)testInteropGetToken_WhenCalledSeveralTimesError_ThenThereIsOnlyOneOperation {
- // 1. Expect a token to be requested and stored.
- NSArray * /*[expectedToken, storeTokenPromise]*/ expectedTokenAndPromise =
- [self expectTokenRequestFromAppCheckProvider];
- GACAppCheckToken *expectedToken = expectedTokenAndPromise.firstObject;
- FBLPromise *storeTokenPromise = expectedTokenAndPromise.lastObject;
- // 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.
- XCTestExpectation *notificationExpectation =
- [self tokenUpdateNotificationWithExpectedToken:expectedToken.token isInverted:YES];
- // 3. Request token several times.
- NSInteger getTokenCallsCount = 10;
- NSMutableArray *getTokenCompletionExpectations =
- [NSMutableArray arrayWithCapacity:getTokenCallsCount];
- for (NSInteger i = 0; i < getTokenCallsCount; i++) {
- // 3.1. Expect a completion to be called for each method call.
- XCTestExpectation *getTokenExpectation =
- [self expectationWithDescription:[NSString stringWithFormat:@"getToken%@", @(i)]];
- [getTokenCompletionExpectations addObject:getTokenExpectation];
- // 3.2. Request token and verify result.
- [self.appCheck getTokenForcingRefresh:NO
- completion:^(id<GACAppCheckTokenResultInterop> tokenResult) {
- [getTokenExpectation fulfill];
- XCTAssertNotNil(tokenResult);
- XCTAssertEqualObjects(tokenResult.error, storageError);
- XCTAssertEqualObjects(tokenResult.token, kDummyToken);
- }];
- }
- // 3.3. Reject the pending promise to finish the get token operation.
- [storeTokenPromise reject:storageError];
- // 4. Wait for expectations and validate mocks.
- NSArray *expectations =
- [getTokenCompletionExpectations arrayByAddingObject:notificationExpectation];
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- // 5. Check a get token call after.
- [self assertInteropGetToken_WhenCachedTokenIsValid_Success];
- }
- #pragma mark - Helpers
- - (NSError *)internalError {
- return [NSError errorWithDomain:@"com.internal.error" code:-1 userInfo:nil];
- }
- - (GACAppCheckToken *)validToken {
- return [[GACAppCheckToken alloc] initWithToken:[NSUUID UUID].UUIDString
- expirationDate:[NSDate distantFuture]];
- }
- - (GACAppCheckToken *)soonExpiringToken {
- NSDate *soonExpiringTokenDate = [NSDate dateWithTimeIntervalSinceNow:4.5 * 60];
- 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]);
- }
- - (XCTestExpectation *)tokenUpdateNotificationWithExpectedToken:(NSString *)expectedToken {
- return [self tokenUpdateNotificationWithExpectedToken:expectedToken isInverted:NO];
- }
- - (XCTestExpectation *)tokenUpdateNotificationWithExpectedToken:(NSString *)expectedToken
- isInverted:(BOOL)isInverted {
- XCTestExpectation *expectation = [self
- expectationForNotification:[self.appCheck tokenDidChangeNotificationName]
- object:nil
- notificationCenter:self.notificationCenter
- handler:^BOOL(NSNotification *_Nonnull notification) {
- XCTAssertEqualObjects(
- notification.userInfo[[self.appCheck notificationInstanceNameKey]],
- kAppName);
- XCTAssertEqualObjects(
- notification.userInfo[[self.appCheck notificationTokenKey]],
- expectedToken);
- XCTAssertEqualObjects(notification.object, self.appCheck);
- return YES;
- }];
- expectation.inverted = isInverted;
- return expectation;
- }
- - (void)assertGetToken_WhenCachedTokenIsValid_Success {
- // 1. Create expected token and configure expectations.
- GACAppCheckToken *cachedToken = [self validToken];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenWhenCacheTokenIsValid_withExpectedToken:cachedToken];
- // 2. Request token and verify result.
- [self.appCheck
- tokenForcingRefresh:NO
- completion:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [expectations.lastObject fulfill];
- XCTAssertNotNil(token);
- XCTAssertEqualObjects(token.token, cachedToken.token);
- XCTAssertNil(error);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)assertInteropGetToken_WhenCachedTokenIsValid_Success {
- // 1. Create expected token and configure expectations.
- GACAppCheckToken *cachedToken = [self validToken];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenWhenCacheTokenIsValid_withExpectedToken:cachedToken];
- // 2. Request token and verify result.
- [self.appCheck getTokenForcingRefresh:NO
- completion:^(id<GACAppCheckTokenResultInterop> tokenResult) {
- [expectations.lastObject fulfill];
- XCTAssertNotNil(tokenResult);
- XCTAssertEqualObjects(tokenResult.token, cachedToken.token);
- XCTAssertNil(tokenResult.error);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- - (NSArray<XCTestExpectation *> *)configuredExpectations_GetTokenWhenNoCache_withExpectedToken:
- (GACAppCheckToken *)expectedToken {
- // 1. Expect token to be requested from storage.
- OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
- // 2. Expect token requested from app check provider.
- id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil];
- OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
- // 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.
- XCTestExpectation *tokenNotificationExpectation =
- [self tokenUpdateNotificationWithExpectedToken:expectedToken.token];
- // 5. Expect token request to be completed.
- XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
- return @[ tokenNotificationExpectation, getTokenExpectation ];
- }
- - (NSArray<XCTestExpectation *> *)
- configuredExpectations_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.
- XCTestExpectation *tokenNotificationExpectation =
- [self tokenUpdateNotificationWithExpectedToken:@"" isInverted:YES];
- // 4. Expect token request to be completed.
- XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
- return @[ tokenNotificationExpectation, getTokenExpectation ];
- }
- - (NSArray<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]);
- // 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.
- XCTestExpectation *notificationExpectation =
- [self tokenUpdateNotificationWithExpectedToken:expectedToken.token];
- // 5. Expect token request to be completed.
- XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
- return @[ notificationExpectation, getTokenExpectation ];
- }
- - (NSArray<XCTestExpectation *> *)
- configuredExpectations_GetTokenWhenCachedTokenExpired_withExpectedToken:
- (GACAppCheckToken *)expectedToken {
- // 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]);
- // 2. Expect token requested from app check provider.
- id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil];
- OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
- // 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.
- XCTestExpectation *notificationExpectation =
- [self tokenUpdateNotificationWithExpectedToken:expectedToken.token];
- // 5. Expect token request to be completed.
- XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
- return @[ notificationExpectation, getTokenExpectation ];
- }
- - (NSArray<XCTestExpectation *> *)
- 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]);
- // 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.
- XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationWithExpectedToken:@""
- isInverted:YES];
- // 5. Expect token request to be completed.
- XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
- return @[ notificationExpectation, getTokenExpectation ];
- }
- - (NSArray *)expectTokenRequestFromAppCheckProvider {
- // 1. Expect token to be requested from storage.
- OCMExpect([self.mockStorage getToken]).andReturn([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]);
- // 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);
- return @[ expectedToken, storeTokenPromise ];
- }
- - (void)verifyAllMocks {
- OCMVerifyAll(self.mockAppCheckProvider);
- OCMVerifyAll(self.mockStorage);
- OCMVerifyAll(self.mockSettings);
- OCMVerifyAll(self.mockTokenRefresher);
- }
- @end
- #endif // GAC_DEVICE_CHECK_SUPPORTED_TARGETS
|