mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
388 lines
18 KiB
Objective-C
388 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/FIRInstanceIDKeyPair.h"
|
|
#import "Firebase/InstanceID/FIRInstanceIDKeyPairStore.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>
|
|
|
|
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";
|
|
|
|
static NSString *const kPrivateKeyPairTag = @"com.iid.regclient.test.private";
|
|
static NSString *const kPublicKeyPairTag = @"com.iid.regclient.test.public";
|
|
|
|
@interface FIRInstanceIDKeyPairStore (ExposedForTest)
|
|
+ (void)deleteKeyPairWithPrivateTag:(NSString *)privateTag
|
|
publicTag:(NSString *)publicTag
|
|
handler:(void (^)(NSError *))handler;
|
|
@end
|
|
|
|
@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) FIRInstanceIDKeyPair *keyPair;
|
|
|
|
@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];
|
|
// Create a temporary keypair in Keychain
|
|
_keyPair =
|
|
[[FIRInstanceIDKeychain sharedInstance] generateKeyPairWithPrivateTag:kPrivateKeyPairTag
|
|
publicTag:kPublicKeyPairTag];
|
|
}
|
|
|
|
- (void)tearDown {
|
|
[FIRInstanceIDKeyPairStore deleteKeyPairWithPrivateTag:kPrivateKeyPairTag
|
|
publicTag:kPublicKeyPairTag
|
|
handler:nil];
|
|
[super tearDown];
|
|
}
|
|
|
|
- (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
|
|
keyPair:self.keyPair];
|
|
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
|
|
keyPair:self.keyPair];
|
|
[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
|
|
keyPair:self.keyPair];
|
|
[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
|
|
keyPair:self.keyPair];
|
|
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
|
|
keyPair:self.keyPair];
|
|
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)testTokenFetchOperationFirebaseUserAgentHeader {
|
|
XCTestExpectation *completionExpectation =
|
|
[self expectationWithDescription:@"completionExpectation"];
|
|
|
|
FIRInstanceIDCheckinPreferences *checkinPreferences =
|
|
[self setCheckinPreferencesWithLastCheckinTime:0];
|
|
|
|
FIRInstanceIDTokenFetchOperation *operation =
|
|
[[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
|
|
scope:kScope
|
|
options:nil
|
|
checkinPreferences:checkinPreferences
|
|
keyPair:self.keyPair];
|
|
operation.testBlock =
|
|
^(NSURLRequest *request, FIRInstanceIDURLRequestTestResponseBlock response) {
|
|
NSString *userAgentValue = request.allHTTPHeaderFields[kFIRInstanceIDFirebaseUserAgentKey];
|
|
XCTAssertEqualObjects(userAgentValue, [FIRApp firebaseUserAgent]);
|
|
|
|
// 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
|