| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 |
- /*
- * 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 <FirebaseAppCheckInterop/FirebaseAppCheckInterop.h>
- #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.h"
- #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckErrors.h"
- #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProvider.h"
- #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProviderFactory.h"
- #import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h"
- #import "FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h"
- #import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h"
- #import "FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h"
- #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
- // The FAC token value returned when an error occurs.
- static NSString *const kDummyToken = @"eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ==";
- extern void FIRResetLogger(void);
- @interface FIRAppCheck (Tests) <FIRAppCheckInterop, GACAppCheckTokenDelegate>
- - (instancetype)initWithAppName:(NSString *)appName
- appCheckCore:(GACAppCheck *)appCheckCore
- appCheckProvider:(id<FIRAppCheckProvider>)appCheckProvider
- notificationCenter:(NSNotificationCenter *)notificationCenter
- settings:(FIRAppCheckSettings *)settings;
- - (nullable instancetype)initWithApp:(FIRApp *)app;
- - (void)tokenDidUpdate:(nonnull GACAppCheckToken *)token
- serviceName:(nonnull NSString *)serviceName;
- @end
- @interface FIRAppCheckTests : XCTestCase
- @property(nonatomic) NSString *appName;
- @property(nonatomic) OCMockObject<FIRAppCheckProvider> *mockAppCheckProvider;
- @property(nonatomic) id mockSettings;
- @property(nonatomic) NSNotificationCenter *notificationCenter;
- @property(nonatomic) id mockAppCheckCore;
- @property(nonatomic) FIRAppCheck<FIRAppCheckInterop> *appCheck;
- @end
- @implementation FIRAppCheckTests
- - (void)setUp {
- [super setUp];
- FIRResetLogger();
- self.appName = @"FIRAppCheckTests";
- self.mockAppCheckProvider = OCMStrictProtocolMock(@protocol(FIRAppCheckProvider));
- self.mockSettings = OCMStrictClassMock([FIRAppCheckSettings class]);
- self.notificationCenter = [[NSNotificationCenter alloc] init];
- self.mockAppCheckCore = OCMStrictClassMock([GACAppCheck class]);
- self.appCheck = [[FIRAppCheck alloc] initWithAppName:self.appName
- appCheckCore:self.mockAppCheckCore
- appCheckProvider:self.mockAppCheckProvider
- notificationCenter:self.notificationCenter
- settings:self.mockSettings];
- }
- - (void)tearDown {
- self.appCheck = nil;
- self.mockAppCheckCore = nil;
- self.mockAppCheckProvider = nil;
- [super tearDown];
- }
- - (void)testInitWithApp {
- NSString *projectID = @"testInitWithApp_projectID";
- NSString *googleAppID = @"testInitWithApp_googleAppID";
- NSString *appName = @"testInitWithApp_appName";
- NSString *appGroupID = @"testInitWithApp_appGroupID";
- // 1. Stub FIRApp and validate usage.
- FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:googleAppID GCMSenderID:@""];
- options.projectID = projectID;
- options.appGroupID = appGroupID;
- FIRApp *app = [[FIRApp alloc] initInstanceWithName:appName options:options];
- // The following disables automatic token refresh, which could interfere with tests.
- app.dataCollectionDefaultEnabled = NO;
- // 2. Stub attestation provider.
- OCMockObject<FIRAppCheckProviderFactory> *mockProviderFactory =
- OCMStrictProtocolMock(@protocol(FIRAppCheckProviderFactory));
- OCMockObject<FIRAppCheckProvider> *mockProvider =
- OCMStrictProtocolMock(@protocol(FIRAppCheckProvider));
- OCMExpect([mockProviderFactory createProviderWithApp:app]).andReturn(mockProvider);
- [FIRAppCheck setAppCheckProviderFactory:mockProviderFactory];
- // 3. Set the Firebase logging level to Debug.
- FIRSetLoggerLevel(FIRLoggerLevelDebug);
- // 4. Call init.
- FIRAppCheck *appCheck = [[FIRAppCheck alloc] initWithApp:app];
- XCTAssert([appCheck isKindOfClass:[FIRAppCheck class]]);
- // 5. Verify mocks.
- OCMVerifyAll(mockProviderFactory);
- OCMVerifyAll(mockProvider);
- // 6. Verify that the App Check Core logging level is also Debug.
- XCTAssertEqual(GACAppCheckLogger.logLevel, GACAppCheckLogLevelDebug);
- }
- - (void)testAppCheckInstanceForApp {
- FIROptions *options =
- [[FIROptions alloc] initWithGoogleAppID:@"1:100000000000:ios:aaaaaaaaaaaaaaaaaaaaaaaa"
- GCMSenderID:@"sender_id"];
- options.APIKey = @"api_key";
- options.projectID = @"project_id";
- FIRApp *app = [[FIRApp alloc] initInstanceWithName:@"testAppCheckInstanceForApp" options:options];
- // The following disables automatic token refresh, which could interfere with tests.
- app.dataCollectionDefaultEnabled = NO;
- XCTAssertNotNil(app);
- XCTAssertNotNil([FIRAppCheck appCheckWithApp:app]);
- // Verify that the App Check Core logging level is the default (Warning).
- XCTAssertEqual(GACAppCheckLogger.logLevel, GACAppCheckLogLevelWarning);
- }
- #pragma mark - Public Get Token
- - (void)testGetToken_Success {
- // 1. Create expected token and configure expectations.
- FIRAppCheckToken *expectedToken = [self validToken];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_TokenForcingRefresh_withExpectedToken:expectedToken];
- // 2. Request token and verify result.
- [self.appCheck
- tokenForcingRefresh:NO
- completion:^(FIRAppCheckToken *_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.
- FIRAppCheckToken *cachedToken = [self soonExpiringToken];
- NSError *providerError = [NSError errorWithDomain:@"FIRAppCheckTests" 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:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [expectations.lastObject fulfill];
- XCTAssertNil(token);
- XCTAssertNotNil(error);
- XCTAssertNotEqualObjects(error, providerError);
- XCTAssertEqualObjects(error.domain, FIRAppCheckErrorDomain);
- 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 = [self appCheckCoreErrorWithCode:GACAppCheckErrorCodeServerUnreachable
- failureReason:@"API request error."
- underlyingError:[self internalError]];
- NSError *publicServerError = [FIRAppCheckErrorUtil publicDomainErrorWithError:serverError];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenWhenError_withError:serverError andToken:nil];
- // 2. Request token and verify result.
- [self.appCheck
- tokenForcingRefresh:NO
- completion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [expectations.lastObject fulfill];
- XCTAssertNil(token);
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error, publicServerError);
- XCTAssertEqualObjects(error.domain, FIRAppCheckErrorDomain);
- XCTAssertEqual(error.code, FIRAppCheckErrorCodeServerUnreachable);
- XCTAssertEqualObjects(error.userInfo, serverError.userInfo);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)testGetToken_UnsupportedError {
- // 1. Create expected error and configure expectations.
- NSError *providerError = [self appCheckCoreErrorWithCode:GACAppCheckErrorCodeUnsupported
- failureReason:@"AppAttestProvider unsupported"
- underlyingError:nil];
- NSError *publicProviderError = [FIRAppCheckErrorUtil publicDomainErrorWithError:providerError];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_GetTokenWhenError_withError:providerError andToken:nil];
- // 2. Request token and verify result.
- [self.appCheck
- tokenForcingRefresh:NO
- completion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [expectations.lastObject fulfill];
- XCTAssertNil(token);
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error, publicProviderError);
- XCTAssertEqualObjects(error.domain, FIRAppCheckErrorDomain);
- XCTAssertEqual(error.code, FIRAppCheckErrorCodeUnsupported);
- XCTAssertEqualObjects(error.userInfo, providerError.userInfo);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- #pragma mark - FIRAppCheckInterop Get Token
- - (void)testInteropGetTokenForcingRefresh_Success {
- // 1. Create expected token and configure expectations.
- FIRAppCheckToken *expectedToken = [self validToken];
- NSArray * /*[tokenNotification, getToken]*/ expectations =
- [self configuredExpectations_TokenForcingRefresh_withExpectedToken:expectedToken];
- // 2. Request token and verify result.
- [self.appCheck getTokenForcingRefresh:NO
- completion:^(id<FIRAppCheckTokenResultInterop> 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)testInteropGetTokenForcingRefresh_AppCheckProviderError {
- // 1. Create expected tokens and errors and configure expectations.
- FIRAppCheckToken *cachedToken = [self soonExpiringToken];
- NSError *providerError = [NSError errorWithDomain:@"FIRAppCheckTests" 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<FIRAppCheckTokenResultInterop> 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, FIRAppCheckErrorDomain);
- }];
- // 3. Wait for expectations and validate mocks.
- [self waitForExpectations:expectations timeout:0.5];
- [self verifyAllMocks];
- }
- - (void)testLimitedUseTokenWithSuccess {
- // 1. Expect token requested from app check provider.
- FIRAppCheckToken *expectedToken = [self validToken];
- GACAppCheckToken *expectedInternalToken = [expectedToken internalToken];
- GACAppCheckTokenResult *expectedTokenResult =
- [[GACAppCheckTokenResult alloc] initWithToken:expectedInternalToken];
- id completionArg = [OCMArg invokeBlockWithArgs:expectedTokenResult, nil];
- OCMStub([self.mockAppCheckCore limitedUseTokenWithCompletion:completionArg]);
- // 2. Don't expect token update notification to be sent.
- XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationNotPosted];
- // 3. Expect token request to be completed.
- XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
- [self.appCheck
- limitedUseTokenWithCompletion:^(FIRAppCheckToken *_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. Expect error when requesting token from app check provider.
- NSError *providerError = [self appCheckCoreErrorWithCode:GACAppCheckErrorCodeKeychain
- failureReason:@"Keychain access error."
- underlyingError:[self internalError]];
- NSError *publicProviderError = [FIRAppCheckErrorUtil publicDomainErrorWithError:providerError];
- GACAppCheckTokenResult *expectedTokenResult =
- [[GACAppCheckTokenResult alloc] initWithError:providerError];
- id completionArg = [OCMArg invokeBlockWithArgs:expectedTokenResult, nil];
- OCMStub([self.mockAppCheckCore limitedUseTokenWithCompletion:completionArg]);
- // 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 *notificationExpectation = [self tokenUpdateNotificationNotPosted];
- // 4. Expect token request to be completed.
- XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
- [self.appCheck
- limitedUseTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
- [getTokenExpectation fulfill];
- XCTAssertNotNil(error);
- XCTAssertNil(token.token);
- XCTAssertEqualObjects(error, publicProviderError);
- XCTAssertEqualObjects(error.domain, FIRAppCheckErrorDomain);
- XCTAssertEqual(error.code, FIRAppCheckErrorCodeKeychain);
- XCTAssertEqualObjects(error.userInfo, providerError.userInfo);
- }];
- [self waitForExpectations:@[ notificationExpectation, getTokenExpectation ] timeout:0.5];
- [self verifyAllMocks];
- }
- #pragma mark - Token update notifications
- - (void)testTokenUpdateNotificationKeys {
- XCTAssertEqualObjects([self.appCheck tokenDidChangeNotificationName],
- @"FIRAppCheckAppCheckTokenDidChangeNotification");
- XCTAssertEqualObjects([self.appCheck notificationAppNameKey],
- @"FIRAppCheckAppNameNotificationKey");
- XCTAssertEqualObjects([self.appCheck notificationTokenKey], @"FIRAppCheckTokenNotificationKey");
- }
- #pragma mark - Auto-refresh enabled
- - (void)testIsTokenAutoRefreshEnabled {
- // Expect value from settings to be used.
- [[[self.mockSettings expect] andReturnValue:@(NO)] isTokenAutoRefreshEnabled];
- XCTAssertFalse(self.appCheck.isTokenAutoRefreshEnabled);
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- XCTAssertTrue(self.appCheck.isTokenAutoRefreshEnabled);
- OCMVerifyAll(self.mockSettings);
- }
- - (void)testSetIsTokenAutoRefreshEnabled {
- OCMExpect([self.mockSettings setIsTokenAutoRefreshEnabled:YES]);
- self.appCheck.isTokenAutoRefreshEnabled = YES;
- OCMExpect([self.mockSettings setIsTokenAutoRefreshEnabled:NO]);
- self.appCheck.isTokenAutoRefreshEnabled = NO;
- OCMVerifyAll(self.mockSettings);
- }
- #pragma mark - Helpers
- - (NSError *)internalError {
- return [NSError errorWithDomain:@"com.internal.error" code:-1 userInfo:nil];
- }
- - (NSError *)appCheckCoreErrorWithCode:(GACAppCheckErrorCode)code
- failureReason:(nullable NSString *)failureReason
- underlyingError:(nullable NSError *)underlyingError {
- NSMutableDictionary<NSErrorUserInfoKey, id> *userInfo = [NSMutableDictionary dictionary];
- userInfo[NSUnderlyingErrorKey] = underlyingError;
- userInfo[NSLocalizedFailureReasonErrorKey] = failureReason;
- return [NSError errorWithDomain:GACAppCheckErrorDomain code:code userInfo:userInfo];
- }
- - (FIRAppCheckToken *)validToken {
- return [[FIRAppCheckToken alloc] initWithToken:[NSUUID UUID].UUIDString
- expirationDate:[NSDate distantFuture]];
- }
- - (FIRAppCheckToken *)soonExpiringToken {
- NSDate *soonExpiringTokenDate = [NSDate dateWithTimeIntervalSinceNow:4.5 * 60];
- return [[FIRAppCheckToken alloc] initWithToken:@"valid" expirationDate:soonExpiringTokenDate];
- }
- - (XCTestExpectation *)tokenUpdateNotificationWithExpectedToken:(NSString *)expectedToken {
- XCTestExpectation *expectation =
- [self expectationForNotification:[self.appCheck tokenDidChangeNotificationName]
- object:nil
- notificationCenter:self.notificationCenter
- handler:^BOOL(NSNotification *_Nonnull notification) {
- XCTAssertEqualObjects(
- notification.userInfo[[self.appCheck notificationAppNameKey]],
- self.appName);
- XCTAssertEqualObjects(
- notification.userInfo[[self.appCheck notificationTokenKey]],
- expectedToken);
- XCTAssertEqualObjects(notification.object, self.appCheck);
- return YES;
- }];
- return expectation;
- }
- - (XCTestExpectation *)tokenUpdateNotificationNotPosted {
- XCTNSNotificationExpectation *expectation = [[XCTNSNotificationExpectation alloc]
- initWithName:[self.appCheck tokenDidChangeNotificationName]
- object:nil
- notificationCenter:self.notificationCenter];
- expectation.inverted = YES;
- return expectation;
- }
- - (NSArray<XCTestExpectation *> *)configuredExpectations_TokenForcingRefresh_withExpectedToken:
- (FIRAppCheckToken *)expectedToken {
- // 1. Expect token requested from app check core.
- GACAppCheckToken *expectedInternalToken = [expectedToken internalToken];
- GACAppCheckTokenResult *expectedTokenResult =
- [[GACAppCheckTokenResult alloc] initWithToken:expectedInternalToken];
- id completionArg = [OCMArg invokeBlockWithArgs:expectedTokenResult, nil];
- OCMExpect([self.mockAppCheckCore tokenForcingRefresh:NO completion:completionArg])
- .andDo(^(NSInvocation *invocation) {
- [self.appCheck tokenDidUpdate:expectedInternalToken serviceName:self.appName];
- })
- .ignoringNonObjectArgs();
- // 2. Expect token update notification to be sent.
- XCTestExpectation *tokenNotificationExpectation =
- [self tokenUpdateNotificationWithExpectedToken:expectedToken.token];
- // 3. Expect token request to be completed.
- XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
- return @[ tokenNotificationExpectation, getTokenExpectation ];
- }
- - (NSArray<XCTestExpectation *> *)
- configuredExpectations_GetTokenWhenError_withError:(NSError *_Nonnull)error
- andToken:(FIRAppCheckToken *_Nullable)token {
- // 1. Expect token requested from app check core.
- GACAppCheckTokenResult *expectedTokenResult =
- [[GACAppCheckTokenResult alloc] initWithError:error];
- id completionArg = [OCMArg invokeBlockWithArgs:expectedTokenResult, nil];
- OCMExpect([self.mockAppCheckCore tokenForcingRefresh:NO completion:completionArg])
- .ignoringNonObjectArgs();
- // 2. Don't expect token requested from app check provider.
- OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]);
- // 3. Expect token update notification to be sent.
- XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationNotPosted];
- // 4. Expect token request to be completed.
- XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
- return @[ notificationExpectation, getTokenExpectation ];
- }
- - (void)verifyAllMocks {
- OCMVerifyAll(self.mockAppCheckProvider);
- OCMVerifyAll(self.mockSettings);
- OCMVerifyAll(self.mockAppCheckCore);
- }
- @end
|