Files
firebase-ios-sdk/Example/InstanceID/Tests/FIRInstanceIDTokenOperationsTest.m
T
Maksym MalyhinandChen Liang f62c7ddcec IID: version dependent on FirebaseInstallations (#4533)
* Firebase Installations: Instance ID now uses FIS under the hood. (#3398)

* Fix merge errors

* Use modular import for FIRInstanceID_Private.h

* Use modular imports for FirebaseInstanceID.h (#3509)

* Changes missing during the fix.

* Changes missing during merge.

* Fix header import.

* ./scripts/style.sh

* Fix FCM tests. (#3888)

* Fix IID FCM token check.

* FirebaseInstallation: merge master into FIS integration branch (#4076)

* Fix IID FCM token check.

* Unused constant deleted.

* FCM tests: fix bad merge.

* Remote Config test app: use local IID and FIS. (#4089)

* token request should pass through if token is not fresh (#4104)

* Merge errors fix.

* IID with FIS: send FIS auth token with IID token request (#4152)

* FIRInstanceIDTokenOperation: add FIS auth token to the IID token request.

* IID tests: don't use FIS private headers.

* IID: indication of compatibility with FIS (#4262)

* FIS: IID incompatible version exception.

* FirebaseInstallations.podspec: set preprocessor flags based on env vars.

* FIS Travis: set required env vars

* /scripts/style.sh

* IID: method to indicate FIS usege added.

* FIS: FIRInstantiationTimingEagerInDefaultApp

* FIS: FIRInstantiationTimingAlwaysEager

* IID: make compatibility method static.

* Make -[FIRInstanceID appInstanceID:] safe to be called from any thread (#4482)

* IID: use a cached value of FID for -[FIRInstanceID appInstanceID:]

* TODO comment

* TODO updated

* FirebaseInstallations: prepare IID with FIS dependency for release (#4522)

* Bump FIrebaseInstallations to 1.0.0

* Travis: remove mm/fis-integration-master branch

* Travis: if_changed script updated to reflect IID dependency on FIS.

* Revert "Travis: remove mm/fis-integration-master branch"

This reverts commit 534dff990a.

* Travis: remove mm/fis-integration-master branch (#4526)

* Formatting and spelling.

* Firebase example proj: pod deintegrate

Co-authored-by: Chen Liang <chliang@google.com>
2019-12-20 10:54:11 -05:00

375 lines
18 KiB
Objective-C

/*
* Copyright 2019 Google
*
* 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 <FirebaseInstanceID/FIRInstanceID.h>
#import <OCMock/OCMock.h>
#import "Firebase/InstanceID/FIRInstanceIDAuthService.h"
#import "Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.h"
#import "Firebase/InstanceID/FIRInstanceIDCheckinService.h"
#import "Firebase/InstanceID/FIRInstanceIDConstants.h"
#import "Firebase/InstanceID/FIRInstanceIDKeychain.h"
#import "Firebase/InstanceID/FIRInstanceIDStore.h"
#import "Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.h"
#import "Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.h"
#import "Firebase/InstanceID/FIRInstanceIDTokenOperation+Private.h"
#import "Firebase/InstanceID/FIRInstanceIDTokenOperation.h"
#import "Firebase/InstanceID/NSError+FIRInstanceID.h"
#import <FirebaseCore/FIRAppInternal.h>
#import <GoogleUtilities/GULHeartbeatDateStorage.h>
static NSString *kDeviceID = @"fakeDeviceID";
static NSString *kSecretToken = @"fakeSecretToken";
static NSString *kDigestString = @"test-digest";
static NSString *kVersionInfoString = @"version_info-1.0.0";
static NSString *kAuthorizedEntity = @"sender-1234567";
static NSString *kScope = @"fcm";
static NSString *kRegistrationToken = @"token-12345";
@interface FIRInstanceIDTokenOperation (ExposedForTest)
- (void)performTokenOperation;
@end
@interface FIRInstanceIDTokenOperationsTest : XCTestCase
@property(strong, readonly, nonatomic) FIRInstanceIDAuthService *authService;
@property(strong, readonly, nonatomic) id mockAuthService;
@property(strong, readonly, nonatomic) id mockStore;
@property(strong, readonly, nonatomic) FIRInstanceIDCheckinService *checkinService;
@property(strong, readonly, nonatomic) id mockCheckinService;
@property(strong, readonly, nonatomic) NSString *instanceID;
@property(nonatomic, readwrite, strong) FIRInstanceIDCheckinPreferences *checkinPreferences;
@end
@implementation FIRInstanceIDTokenOperationsTest
- (void)setUp {
[super setUp];
_mockStore = OCMClassMock([FIRInstanceIDStore class]);
_checkinService = [[FIRInstanceIDCheckinService alloc] init];
_mockCheckinService = OCMPartialMock(_checkinService);
_authService = [[FIRInstanceIDAuthService alloc] initWithCheckinService:_mockCheckinService
store:_mockStore];
_instanceID = @"instanceID";
NSString *const kHeartbeatStorageFile = @"HEARTBEAT_INFO_STORAGE";
GULHeartbeatDateStorage *dataStorage =
[[GULHeartbeatDateStorage alloc] initWithFileName:kHeartbeatStorageFile];
[[NSFileManager defaultManager] removeItemAtURL:[dataStorage fileURL] error:nil];
}
- (void)testThatTokenOperationsAuthHeaderStringMatchesCheckin {
int64_t tenHoursAgo = FIRInstanceIDCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
FIRInstanceIDCheckinPreferences *checkin =
[self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
NSString *expectedAuthHeader = [FIRInstanceIDTokenOperation HTTPAuthHeaderFromCheckin:checkin];
XCTestExpectation *authHeaderMatchesCheckinExpectation =
[self expectationWithDescription:@"Auth header string in request matches checkin info"];
FIRInstanceIDTokenFetchOperation *operation =
[[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
scope:kScope
options:nil
checkinPreferences:checkin
instanceID:self.instanceID];
operation.testBlock =
^(NSURLRequest *request, FIRInstanceIDURLRequestTestResponseBlock response) {
NSDictionary<NSString *, NSString *> *headers = request.allHTTPHeaderFields;
NSString *authHeader = headers[@"Authorization"];
if ([authHeader isEqualToString:expectedAuthHeader]) {
[authHeaderMatchesCheckinExpectation fulfill];
}
// Return a response (doesnt matter what the response is)
NSData *responseBody = [self dataForFetchRequest:request returnValidToken:YES];
NSHTTPURLResponse *responseObject = [[NSHTTPURLResponse alloc] initWithURL:request.URL
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:nil];
response(responseBody, responseObject, nil);
};
[operation start];
[self waitForExpectationsWithTimeout:0.25
handler:^(NSError *_Nullable error) {
XCTAssertNil(error.localizedDescription);
}];
}
- (void)testThatTokenOperationWithoutCheckInFails {
// If asserts are enabled, test for the assert to be thrown, otherwise check for the resulting
// error in the completion handler.
XCTestExpectation *failedExpectation =
[self expectationWithDescription:@"Operation failed without checkin info"];
// This will return hasCheckinInfo == NO
FIRInstanceIDCheckinPreferences *emptyCheckinPreferences =
[[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:@"" secretToken:@""];
FIRInstanceIDTokenOperation *operation =
[[FIRInstanceIDTokenOperation alloc] initWithAction:FIRInstanceIDTokenActionFetch
forAuthorizedEntity:kAuthorizedEntity
scope:kScope
options:nil
checkinPreferences:emptyCheckinPreferences
instanceID:self.instanceID];
[operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result,
NSString *_Nullable token, NSError *_Nullable error) {
[failedExpectation fulfill];
}];
@try {
[operation start];
} @catch (NSException *exception) {
if (exception.name == NSInternalInconsistencyException) {
[failedExpectation fulfill];
}
} @finally {
}
[self waitForExpectationsWithTimeout:0.25
handler:^(NSError *_Nullable error) {
XCTAssertNil(error.localizedDescription);
}];
}
- (void)testThatAnAlreadyCancelledOperationFinishesWithoutStarting {
XCTestExpectation *cancelledExpectation =
[self expectationWithDescription:@"Operation finished as cancelled"];
XCTestExpectation *didNotCallPerform =
[self expectationWithDescription:@"Did not call performTokenOperation"];
__block BOOL performWasCalled = NO;
int64_t tenHoursAgo = FIRInstanceIDCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
FIRInstanceIDCheckinPreferences *checkinPreferences =
[self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
FIRInstanceIDTokenOperation *operation =
[[FIRInstanceIDTokenOperation alloc] initWithAction:FIRInstanceIDTokenActionFetch
forAuthorizedEntity:kAuthorizedEntity
scope:kScope
options:nil
checkinPreferences:checkinPreferences
instanceID:self.instanceID];
[operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result,
NSString *_Nullable token, NSError *_Nullable error) {
if (result == FIRInstanceIDTokenOperationCancelled) {
[cancelledExpectation fulfill];
}
if (!performWasCalled) {
[didNotCallPerform fulfill];
}
}];
id mockOperation = OCMPartialMock(operation);
[[[mockOperation stub] andDo:^(NSInvocation *invocation) {
performWasCalled = YES;
}] performTokenOperation];
[operation cancel];
[operation start];
[self waitForExpectationsWithTimeout:0.25
handler:^(NSError *_Nullable error) {
XCTAssertNil(error.localizedDescription);
}];
}
- (void)testThatOptionsDictionaryIsIncludedWithFetchRequest {
XCTestExpectation *optionsIncludedExpectation =
[self expectationWithDescription:@"Options keys were included in token URL request"];
int64_t tenHoursAgo = FIRInstanceIDCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
FIRInstanceIDCheckinPreferences *checkinPreferences =
[self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
NSData *fakeDeviceToken = [@"fakeAPNSToken" dataUsingEncoding:NSUTF8StringEncoding];
BOOL isSandbox = NO;
NSString *apnsTupleString =
FIRInstanceIDAPNSTupleStringForTokenAndServerType(fakeDeviceToken, isSandbox);
NSDictionary *options = @{
kFIRInstanceIDTokenOptionsFirebaseAppIDKey : @"fakeGMPAppID",
kFIRInstanceIDTokenOptionsAPNSKey : fakeDeviceToken,
kFIRInstanceIDTokenOptionsAPNSIsSandboxKey : @(isSandbox),
};
FIRInstanceIDTokenFetchOperation *operation =
[[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
scope:kScope
options:options
checkinPreferences:checkinPreferences
instanceID:self.instanceID];
operation.testBlock =
^(NSURLRequest *request, FIRInstanceIDURLRequestTestResponseBlock response) {
NSString *query = [[NSString alloc] initWithData:request.HTTPBody
encoding:NSUTF8StringEncoding];
NSString *gmpAppIDQueryTuple =
[NSString stringWithFormat:@"%@=%@", kFIRInstanceIDTokenOptionsFirebaseAppIDKey,
options[kFIRInstanceIDTokenOptionsFirebaseAppIDKey]];
NSRange gmpAppIDRange = [query rangeOfString:gmpAppIDQueryTuple];
NSString *apnsQueryTuple = [NSString
stringWithFormat:@"%@=%@", kFIRInstanceIDTokenOptionsAPNSKey, apnsTupleString];
NSRange apnsRange = [query rangeOfString:apnsQueryTuple];
if (gmpAppIDRange.location != NSNotFound && apnsRange.location != NSNotFound) {
[optionsIncludedExpectation fulfill];
}
// Return a response (doesnt matter what the response is)
NSData *responseBody = [self dataForFetchRequest:request returnValidToken:YES];
NSHTTPURLResponse *responseObject = [[NSHTTPURLResponse alloc] initWithURL:request.URL
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:nil];
response(responseBody, responseObject, nil);
};
[operation start];
[self waitForExpectationsWithTimeout:0.25
handler:^(NSError *_Nullable error) {
XCTAssertNil(error.localizedDescription);
}];
}
- (void)testServerResetCommand {
XCTestExpectation *shouldResetIdentityExpectation =
[self expectationWithDescription:
@"When server sends down RST error, clients should return reset identity error."];
int64_t tenHoursAgo = FIRInstanceIDCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
FIRInstanceIDCheckinPreferences *checkinPreferences =
[self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
FIRInstanceIDTokenFetchOperation *operation =
[[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
scope:kScope
options:nil
checkinPreferences:checkinPreferences
instanceID:self.instanceID];
operation.testBlock =
^(NSURLRequest *request, FIRInstanceIDURLRequestTestResponseBlock response) {
// Return a response with Error=RST
NSData *responseBody = [self dataForFetchRequest:request returnValidToken:NO];
NSHTTPURLResponse *responseObject = [[NSHTTPURLResponse alloc] initWithURL:request.URL
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:nil];
response(responseBody, responseObject, nil);
};
[operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result,
NSString *_Nullable token, NSError *_Nullable error) {
XCTAssertEqual(result, FIRInstanceIDTokenOperationError);
XCTAssertNotNil(error);
XCTAssertEqual(error.code, kFIRInstanceIDErrorCodeInvalidIdentity);
[shouldResetIdentityExpectation fulfill];
}];
[operation start];
[self waitForExpectationsWithTimeout:0.25
handler:^(NSError *_Nullable error) {
XCTAssertNil(error.localizedDescription);
}];
}
- (void)testHTTPAuthHeaderGenerationFromCheckin {
FIRInstanceIDCheckinPreferences *checkinPreferences =
[[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:kDeviceID secretToken:kSecretToken];
NSString *expectedHeader =
[NSString stringWithFormat:@"AidLogin %@:%@", checkinPreferences.deviceID,
checkinPreferences.secretToken];
NSString *generatedHeader =
[FIRInstanceIDTokenOperation HTTPAuthHeaderFromCheckin:checkinPreferences];
XCTAssertEqualObjects(generatedHeader, expectedHeader);
}
- (void)testTokenFetchOperationFirebaseUserAgentAndHeartbeatHeader {
XCTestExpectation *completionExpectation =
[self expectationWithDescription:@"completionExpectation"];
FIRInstanceIDCheckinPreferences *checkinPreferences =
[self setCheckinPreferencesWithLastCheckinTime:0];
FIRInstanceIDTokenFetchOperation *operation =
[[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
scope:kScope
options:nil
checkinPreferences:checkinPreferences
instanceID:self.instanceID];
operation.testBlock =
^(NSURLRequest *request, FIRInstanceIDURLRequestTestResponseBlock response) {
NSString *userAgentValue = request.allHTTPHeaderFields[kFIRInstanceIDFirebaseUserAgentKey];
XCTAssertEqualObjects(userAgentValue, [FIRApp firebaseUserAgent]);
NSString *heartBeatCode = request.allHTTPHeaderFields[kFIRInstanceIDFirebaseHeartbeatKey];
XCTAssertEqualObjects(heartBeatCode, @"3");
// Return a response with Error=RST
NSData *responseBody = [self dataForFetchRequest:request returnValidToken:NO];
NSHTTPURLResponse *responseObject = [[NSHTTPURLResponse alloc] initWithURL:request.URL
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:nil];
response(responseBody, responseObject, nil);
};
[operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result,
NSString *_Nullable token, NSError *_Nullable error) {
[completionExpectation fulfill];
}];
[operation start];
[self waitForExpectationsWithTimeout:0.25
handler:^(NSError *_Nullable error) {
XCTAssertNil(error.localizedDescription);
}];
}
#pragma mark - Internal Helpers
- (NSData *)dataForFetchRequest:(NSURLRequest *)request returnValidToken:(BOOL)returnValidToken {
NSString *response;
if (returnValidToken) {
response = [NSString stringWithFormat:@"token=%@", kRegistrationToken];
} else {
response = @"Error=RST";
}
return [response dataUsingEncoding:NSUTF8StringEncoding];
}
- (FIRInstanceIDCheckinPreferences *)setCheckinPreferencesWithLastCheckinTime:(int64_t)time {
FIRInstanceIDCheckinPreferences *checkinPreferences =
[[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:kDeviceID secretToken:kSecretToken];
NSDictionary *checkinPlistContents = @{
kFIRInstanceIDDigestStringKey : kDigestString,
kFIRInstanceIDVersionInfoStringKey : kVersionInfoString,
kFIRInstanceIDLastCheckinTimeKey : @(time)
};
[checkinPreferences updateWithCheckinPlistContents:checkinPlistContents];
// manually initialize the checkin preferences
self.checkinPreferences = checkinPreferences;
return checkinPreferences;
}
@end