Files
firebase-ios-sdk/FirebaseMessaging/Tests/UnitTests/FIRMessagingTest.m
T

1004 lines
46 KiB
Objective-C

/*
* Copyright 2017 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 <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import <GoogleUtilities/GULUserDefaults.h>
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
#import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
#import "FirebaseMessaging/Sources/FIRMessagingConstants.h"
#import "FirebaseMessaging/Sources/FIRMessagingPubSub.h"
#import "FirebaseMessaging/Sources/FIRMessagingTopicOperation.h"
#import "FirebaseMessaging/Sources/FIRMessagingUtilities.h"
#import "FirebaseMessaging/Sources/FIRMessaging_Private.h"
#import "FirebaseMessaging/Sources/NSError+FIRMessaging.h"
#import "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h"
#import "FirebaseMessaging/Sources/Token/FIRMessagingAPNSInfo.h"
#import "FirebaseMessaging/Sources/Token/FIRMessagingFIDRegisterOperation.h"
#import "FirebaseMessaging/Sources/Token/FIRMessagingFIDUnregisterOperation.h"
#import "FirebaseMessaging/Sources/Token/FIRMessagingTokenInfo.h"
#import "FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.h"
#import "FirebaseMessaging/Sources/Token/FIRMessagingTokenStore.h"
#import "FirebaseMessaging/Tests/UnitTests/FIRMessagingTestUtilities.h"
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
#import "SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.h"
@interface FIRMessagingFIDRegisterOperation (ExposedForTest)
+ (void)resetSharedSession;
@end
@interface FIRMessagingFIDUnregisterOperation (ExposedForTest)
+ (void)resetSharedSession;
@end
@interface FIRInstallationsAuthTokenResult (Tests)
- (instancetype)initWithToken:(NSString *)token expirationDate:(NSDate *)expirationDate;
@end
@interface FIRMessagingTokenManager (ExposedForTest)
- (void)deleteAllTokensLocallyWithHandler:(void (^)(NSError *error))handler;
@end
extern NSString *const kFIRMessagingFCMTokenFetchAPNSOption;
@interface FIRMessaging ()
@property(nonatomic, readwrite, strong) NSString *defaultFcmToken;
@property(nonatomic, readwrite, strong) NSData *apnsTokenData;
@property(nonatomic, readwrite, strong) FIRMessagingTokenManager *tokenManager;
// Expose autoInitEnabled static method for IID.
+ (BOOL)isAutoInitEnabledWithUserDefaults:(NSUserDefaults *)userDefaults;
// Direct Channel Methods
- (void)updateAutomaticClientConnection;
- (BOOL)shouldBeConnectedAutomatically;
- (void)configureMessagingWithOptions:(FIROptions *)options;
- (void)retrieveTokenOrFidForSenderID:(nonnull NSString *)senderID
completion:(nullable void (^)(NSString *_Nullable token,
NSError *_Nullable error))completion;
- (void)handleInstallationIDDidChangeNotification:(NSNotification *)notification;
@end
@interface FIRMessagingTest : XCTestCase
@property(nonatomic, readonly, strong) FIRMessaging *messaging;
@property(nonatomic, readwrite, strong) id mockMessaging;
@property(nonatomic, readwrite, strong) id mockInstanceID;
@property(nonatomic, readwrite, strong) id mockFirebaseApp;
@property(nonatomic, readwrite, strong) id mockTokenManager;
@property(nonatomic, strong) FIRMessagingTestUtilities *testUtil;
@property(nonatomic, readwrite, strong) id urlSessionMock;
@property(nonatomic, readwrite, strong) id bundleMock;
@end
@implementation FIRMessagingTest
- (void)clearPendingTopicSubscriptions {
[[GULUserDefaults standardUserDefaults]
removeObjectForKey:@"com.firebase.messaging.pending-subscriptions"];
}
- (void)setUp {
[super setUp];
[self clearPendingTopicSubscriptions];
[FIRMessagingFIDRegisterOperation resetSharedSession];
[FIRMessagingFIDUnregisterOperation resetSharedSession];
// Create the messaging instance with all the necessary dependencies.
NSUserDefaults *defaults =
[[NSUserDefaults alloc] initWithSuiteName:kFIRMessagingDefaultsTestDomain];
_testUtil = [[FIRMessagingTestUtilities alloc] initWithUserDefaults:defaults withRMQManager:NO];
_mockMessaging = _testUtil.mockMessaging;
_messaging = _testUtil.messaging;
_mockTokenManager = _testUtil.mockTokenManager;
_mockFirebaseApp = OCMClassMock([FIRApp class]);
OCMStub([_mockFirebaseApp defaultApp]).andReturn(_mockFirebaseApp);
[[NSUserDefaults standardUserDefaults]
removePersistentDomainForName:[NSBundle mainBundle].bundleIdentifier];
}
- (void)tearDown {
[_testUtil cleanupAfterTest:self];
[_mockFirebaseApp stopMocking];
[self.urlSessionMock stopMocking];
self.urlSessionMock = nil;
[self.bundleMock stopMocking];
self.bundleMock = nil;
_messaging = nil;
[self clearPendingTopicSubscriptions];
[[[NSUserDefaults alloc] initWithSuiteName:kFIRMessagingDefaultsTestDomain]
removePersistentDomainForName:kFIRMessagingDefaultsTestDomain];
[FIRMessagingFIDRegisterOperation resetSharedSession];
[FIRMessagingFIDUnregisterOperation resetSharedSession];
[super tearDown];
}
- (void)testAutoInitEnableFlag {
// Should read from Info.plist
XCTAssertFalse(_messaging.isAutoInitEnabled);
// Now set the flag should overwrite Info.plist value.
_messaging.autoInitEnabled = YES;
XCTAssertTrue(_messaging.isAutoInitEnabled);
}
- (void)testAutoInitEnableFlagOverrideGlobalTrue {
OCMStub([_mockFirebaseApp isDataCollectionDefaultEnabled]).andReturn(YES);
self.bundleMock = OCMPartialMock([NSBundle mainBundle]);
OCMStub([self.bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled])
.andReturn(nil);
XCTAssertTrue(self.messaging.isAutoInitEnabled);
self.messaging.autoInitEnabled = NO;
XCTAssertFalse(self.messaging.isAutoInitEnabled);
}
- (void)testAutoInitEnableFlagOverrideGlobalFalse {
OCMStub([_mockFirebaseApp isDataCollectionDefaultEnabled]).andReturn(YES);
self.bundleMock = OCMPartialMock([NSBundle mainBundle]);
OCMStub([self.bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled])
.andReturn(nil);
XCTAssertTrue(self.messaging.isAutoInitEnabled);
self.messaging.autoInitEnabled = NO;
XCTAssertFalse(self.messaging.isAutoInitEnabled);
}
- (void)testAutoInitEnableGlobalDefaultTrue {
OCMStub([_mockFirebaseApp isDataCollectionDefaultEnabled]).andReturn(YES);
self.bundleMock = OCMPartialMock([NSBundle mainBundle]);
OCMStub([self.bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled])
.andReturn(nil);
XCTAssertTrue(self.messaging.isAutoInitEnabled);
}
- (void)testAutoInitEnableGlobalDefaultFalse {
OCMStub([_mockFirebaseApp isDataCollectionDefaultEnabled]).andReturn(NO);
self.bundleMock = OCMPartialMock([NSBundle mainBundle]);
OCMStub([self.bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled])
.andReturn(nil);
XCTAssertFalse(self.messaging.isAutoInitEnabled);
}
- (void)testAutoInitEnabledMatchesStaticMethod {
// Flag is set to YES in user defaults.
NSUserDefaults *defaults = self.messaging.messagingUserDefaults;
[defaults setObject:@YES forKey:kFIRMessagingUserDefaultsKeyAutoInitEnabled];
XCTAssertTrue(self.messaging.isAutoInitEnabled);
XCTAssertEqual(self.messaging.isAutoInitEnabled,
[FIRMessaging isAutoInitEnabledWithUserDefaults:defaults]);
}
- (void)testAutoInitDisabledMatchesStaticMethod {
// Flag is set to NO in user defaults.
NSUserDefaults *defaults = self.messaging.messagingUserDefaults;
[defaults setObject:@NO forKey:kFIRMessagingUserDefaultsKeyAutoInitEnabled];
XCTAssertFalse(self.messaging.isAutoInitEnabled);
XCTAssertEqual(self.messaging.isAutoInitEnabled,
[FIRMessaging isAutoInitEnabledWithUserDefaults:defaults]);
}
#pragma mark - FCM Token Fetching and Deleting
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// TODO(chliang) mock tokenManager
- (void)x_testAPNSTokenIncludedInOptionsIfAvailableDuringTokenFetch {
self.messaging.apnsTokenData =
[@"PRETENDING_TO_BE_A_DEVICE_TOKEN" dataUsingEncoding:NSUTF8StringEncoding];
XCTestExpectation *expectation =
[self expectationWithDescription:@"Included APNS Token data in options dict."];
// Inspect the 'options' dictionary to tell whether our expectation was fulfilled
[[[self.mockInstanceID stub] andDo:^(NSInvocation *invocation) {
__unsafe_unretained NSDictionary *options;
[invocation getArgument:&options atIndex:4];
if (options[@"apns_token"] != nil) {
[expectation fulfill];
}
}] tokenWithAuthorizedEntity:OCMOCK_ANY scope:OCMOCK_ANY options:OCMOCK_ANY handler:OCMOCK_ANY];
[self.messaging
retrieveFCMTokenForSenderID:@"123456"
completion:^(NSString *_Nullable FCMToken, NSError *_Nullable error){
}];
[self waitForExpectationsWithTimeout:0.1 handler:nil];
}
- (void)x_testAPNSTokenNotIncludedIfUnavailableDuringTokenFetch {
XCTestExpectation *expectation =
[self expectationWithDescription:@"Included APNS Token data not included in options dict."];
// Inspect the 'options' dictionary to tell whether our expectation was fulfilled
[[[self.mockInstanceID stub] andDo:^(NSInvocation *invocation) {
__unsafe_unretained NSDictionary *options;
[invocation getArgument:&options atIndex:4];
if (options[@"apns_token"] == nil) {
[expectation fulfill];
}
}] tokenWithAuthorizedEntity:OCMOCK_ANY scope:OCMOCK_ANY options:OCMOCK_ANY handler:OCMOCK_ANY];
[self.messaging
retrieveFCMTokenForSenderID:@"123456"
completion:^(NSString *_Nullable FCMToken, NSError *_Nullable error){
}];
[self waitForExpectationsWithTimeout:0.1 handler:nil];
}
- (void)testReturnsErrorWhenFetchingTokenWithoutSenderID {
XCTestExpectation *expectation =
[self expectationWithDescription:@"Returned an error fetching token without Sender ID"];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
[self.messaging
retrieveFCMTokenForSenderID:nil
completion:^(NSString *_Nullable FCMToken, NSError *_Nullable error) {
if (error != nil) {
[expectation fulfill];
}
}];
#pragma clang diagnostic pop
[self waitForExpectationsWithTimeout:0.1 handler:nil];
}
- (void)testReturnsErrorWhenFetchingTokenWithoutAPNSToken {
XCTestExpectation *expectation =
[self expectationWithDescription:@"Returned an error fetching token without APNS Token"];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
[self.messaging
retrieveFCMTokenForSenderID:@"12345"
completion:^(NSString *_Nullable FCMToken, NSError *_Nullable error) {
if (error != nil) {
[expectation fulfill];
}
}];
#pragma clang diagnostic pop
[self waitForExpectationsWithTimeout:0.1 handler:nil];
}
- (void)testReturnsErrorWhenFetchingTokenWithEmptySenderID {
XCTestExpectation *expectation =
[self expectationWithDescription:@"Returned an error fetching token with empty Sender ID"];
[self.messaging
retrieveFCMTokenForSenderID:@""
completion:^(NSString *_Nullable FCMToken, NSError *_Nullable error) {
if (error != nil) {
[expectation fulfill];
}
}];
[self waitForExpectationsWithTimeout:0.1 handler:nil];
}
- (void)testReturnsErrorWhenDeletingTokenWithoutSenderID {
XCTestExpectation *expectation =
[self expectationWithDescription:@"Returned an error deleting token without Sender ID"];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
[self.messaging deleteFCMTokenForSenderID:nil
completion:^(NSError *_Nullable error) {
if (error != nil) {
[expectation fulfill];
}
}];
#pragma clang diagnostic pop
[self waitForExpectationsWithTimeout:0.1 handler:nil];
}
- (void)testReturnsErrorWhenDeletingTokenWithEmptySenderID {
XCTestExpectation *expectation =
[self expectationWithDescription:@"Returned an error deleting token with empty Sender ID"];
[self.messaging deleteFCMTokenForSenderID:@""
completion:^(NSError *_Nullable error) {
if (error != nil) {
[expectation fulfill];
}
}];
[self waitForExpectationsWithTimeout:0.1 handler:nil];
}
#pragma clang diagnostic pop
- (void)setupV1RegistrationHttpCallWithMethod:(NSString *)httpMethod
responseBody:(NSData *)responseBody
requestValidationBlock:(BOOL (^)(NSURLRequest *))requestValidationBlock {
// Setup installation ID.
id installationIDArg = [OCMArg invokeBlockWithArgs:@"fake-fid", [NSNull null], nil];
OCMStub([(FIRInstallations *)self.testUtil.mockInstallations
installationIDWithCompletion:installationIDArg]);
// Setup FIS auth token.
FIRInstallationsAuthTokenResult *mockAuthTokenResult =
[[FIRInstallationsAuthTokenResult alloc] initWithToken:@"fis-auth-token"
expirationDate:[NSDate distantFuture]];
id authTokenArg = [OCMArg invokeBlockWithArgs:mockAuthTokenResult, [NSNull null], nil];
OCMStub(
[(FIRInstallations *)self.testUtil.mockInstallations authTokenWithCompletion:authTokenArg]);
self.urlSessionMock = OCMClassMock([NSURLSession class]);
OCMStub(ClassMethod([self.urlSessionMock sessionWithConfiguration:[OCMArg any]]))
.andReturn(self.urlSessionMock);
// Setup the HTTP response.
NSHTTPURLResponse *response =
[[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"https://example.com"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:nil];
id mockDataTask =
[FIRURLSessionOCMockStub stubURLSessionDataTaskWithResponse:response
body:responseBody
error:nil
URLSessionMock:self.urlSessionMock
requestValidationBlock:requestValidationBlock];
OCMStub([mockDataTask setTaskDescription:OCMOCK_ANY]);
}
- (void)testRegisterNotifiesDelegateWhenInstallationIdEnabled {
// FirebaseMessaging.isInstallationIdEnabled should return YES.
OCMStub([self.mockMessaging isInstallationIdEnabled]).andReturn(YES);
id mockOptions = OCMClassMock([FIROptions class]);
OCMStub([mockOptions GCMSenderID]).andReturn(@"123456789123");
OCMStub([mockOptions projectID]).andReturn(@"test-project-id");
OCMStub([mockOptions APIKey]).andReturn(@"test-api-key");
OCMStub([mockOptions bundleID]).andReturn(@"com.google.FirebaseMessagingTest");
OCMStub([(FIRApp *)self.mockFirebaseApp options]).andReturn(mockOptions);
[self.messaging.tokenManager setValue:@"123456789123" forKey:@"fcmSenderID"];
[self.messaging.tokenManager deleteAllTokensLocallyWithHandler:nil];
[self.messaging.tokenManager setValue:nil forKey:@"defaultFCMToken"];
// Setup APNs token.
self.messaging.apnsTokenData = [@"fakeAPNSToken" dataUsingEncoding:NSUTF8StringEncoding];
FIRMessagingAPNSInfo *mockAPNSInfo =
[[FIRMessagingAPNSInfo alloc] initWithDeviceToken:self.messaging.apnsTokenData isSandbox:YES];
[self.messaging.tokenManager setValue:mockAPNSInfo forKey:@"currentAPNSInfo"];
NSData *responseBody = [@"{\"name\":\"projects/test-project-id/registrations/fake-fid\"}"
dataUsingEncoding:NSUTF8StringEncoding];
[self
setupV1RegistrationHttpCallWithMethod:@"POST"
responseBody:responseBody
requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
// Assert the HTTP request is correct.
XCTAssertEqualObjects(sentRequest.URL.absoluteString,
@"https://fcmregistrations.googleapis.com/v1/projects/"
@"test-project-id/registrations");
XCTAssertEqualObjects(sentRequest.HTTPMethod, @"POST");
XCTAssertEqualObjects(sentRequest.allHTTPHeaderFields[@"X-Goog-Api-Key"],
@"test-api-key");
XCTAssertEqualObjects(
sentRequest.allHTTPHeaderFields[@"X-Goog-Firebase-Installations-Auth"],
@"fis-auth-token");
NSDictionary *body =
[NSJSONSerialization JSONObjectWithData:sentRequest.HTTPBody
options:0
error:nil];
XCTAssertNotNil(body[@"ios"]);
XCTAssertEqualObjects(
body[@"ios"][@"apns_token"],
FIRMessagingStringForAPNSDeviceToken(self.messaging.apnsTokenData));
return YES;
}];
// Setup message delegate.
id mockDelegate = OCMProtocolMock(@protocol(FIRMessagingDelegate));
self.messaging.delegate = mockDelegate;
XCTestExpectation *delegateExpectation =
[self expectationWithDescription:@"Delegate received registration notification."];
OCMStub([mockDelegate messaging:OCMOCK_ANY didReceiveRegistration:OCMOCK_ANY])
.andDo(^(NSInvocation *invocation) {
__unsafe_unretained NSString *installationId;
[invocation getArgument:&installationId atIndex:3];
XCTAssertEqualObjects(installationId, @"fake-fid");
[delegateExpectation fulfill];
});
XCTestExpectation *completionExpectation =
[self expectationWithDescription:@"Register completion called."];
[self.messaging registerWithCompletion:^(NSError *error) {
XCTAssertNil(error);
[completionExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}
- (void)testRegisterNotifiesDelegateWhenCachedFidExists {
// FirebaseMessaging.isInstallationIdEnabled should return YES.
OCMStub([self.mockMessaging isInstallationIdEnabled]).andReturn(YES);
id mockOptions = OCMClassMock([FIROptions class]);
OCMStub([mockOptions GCMSenderID]).andReturn(@"123456789123");
OCMStub([(FIRApp *)self.mockFirebaseApp options]).andReturn(mockOptions);
[self.messaging.tokenManager setValue:@"123456789123" forKey:@"fcmSenderID"];
FIRMessagingTokenInfo *cachedTokenInfo =
[[FIRMessagingTokenInfo alloc] initWithAuthorizedEntity:@"123456789123"
scope:kFIRMessagingDefaultTokenScope
token:@"fake-cached-fid"
appVersion:@"1.0"
firebaseAppID:@"app-id"
tokenType:@"FID"];
OCMStub([self.mockTokenManager
cachedTokenInfoWithAuthorizedEntity:@"123456789123"
scope:kFIRMessagingDefaultTokenScope])
.andReturn(cachedTokenInfo);
OCMStub([self.mockTokenManager defaultFCMToken]).andReturn(@"fake-cached-fid");
// Setup message delegate.
id mockDelegate = OCMProtocolMock(@protocol(FIRMessagingDelegate));
self.messaging.delegate = mockDelegate;
XCTestExpectation *delegateExpectation =
[self expectationWithDescription:@"Delegate received registration notification."];
OCMStub([mockDelegate messaging:OCMOCK_ANY didReceiveRegistration:OCMOCK_ANY])
.andDo(^(NSInvocation *invocation) {
__unsafe_unretained NSString *installationId;
[invocation getArgument:&installationId atIndex:3];
XCTAssertEqualObjects(installationId, @"fake-cached-fid");
[delegateExpectation fulfill];
});
XCTestExpectation *completionExpectation =
[self expectationWithDescription:@"Register completion called."];
[self.messaging registerWithCompletion:^(NSError *error) {
XCTAssertNil(error);
[completionExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}
- (void)testRegisterCallsRetrieveTokenOrFidWhenCachedTokenIsNotFid {
OCMStub([self.mockMessaging isInstallationIdEnabled]).andReturn(YES);
id mockOptions = OCMClassMock([FIROptions class]);
OCMStub([mockOptions GCMSenderID]).andReturn(@"123456789123");
OCMStub([(FIRApp *)self.mockFirebaseApp options]).andReturn(mockOptions);
[self.messaging.tokenManager setValue:@"123456789123" forKey:@"fcmSenderID"];
FIRMessagingTokenInfo *cachedTokenInfo =
[[FIRMessagingTokenInfo alloc] initWithAuthorizedEntity:@"123456789123"
scope:kFIRMessagingDefaultTokenScope
token:@"old-v4-token"
appVersion:@"1.0"
firebaseAppID:@"app-id"
tokenType:@"V4"];
OCMStub([self.mockTokenManager
cachedTokenInfoWithAuthorizedEntity:@"123456789123"
scope:kFIRMessagingDefaultTokenScope])
.andReturn(cachedTokenInfo);
// retrieveTokenOrFidForSenderID should be called because the cached token is not "FID".
XCTestExpectation *retrieveExpectation =
[self expectationWithDescription:@"retrieveTokenOrFidForSenderID should be called"];
[[[self.mockMessaging expect] andDo:^(NSInvocation *invocation) {
[retrieveExpectation fulfill];
}] retrieveTokenOrFidForSenderID:@"123456789123" completion:OCMOCK_ANY];
[self.messaging registerWithCompletion:^(NSError *error){
}];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}
- (void)testRegisterWithCompletionFailsWhenInstallationIdDisabled {
// FirebaseMessaging.isInstallationIdEnabled should return NO.
OCMStub([self.mockMessaging isInstallationIdEnabled]).andReturn(NO);
XCTestExpectation *completionExpectation =
[self expectationWithDescription:@"Register completion called."];
[self.messaging registerWithCompletion:^(NSError *error) {
XCTAssertNotNil(error);
XCTAssertEqual(error.code, kFIRMessagingErrorCodeInvalidRequest);
[completionExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}
- (void)testRegisterWithCompletionFailsWhenSenderIDMissing {
// FirebaseMessaging.isInstallationIdEnabled should return YES.
OCMStub([self.mockMessaging isInstallationIdEnabled]).andReturn(YES);
id mockOptions = OCMClassMock([FIROptions class]);
OCMStub([mockOptions GCMSenderID]).andReturn(nil);
OCMStub([(FIRApp *)self.mockFirebaseApp options]).andReturn(mockOptions);
XCTestExpectation *completionExpectation =
[self expectationWithDescription:@"Register completion called."];
[self.messaging registerWithCompletion:^(NSError *error) {
XCTAssertNotNil(error);
XCTAssertEqual(error.code, kFIRMessagingErrorCodeMissingAuthorizedEntity);
[completionExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}
- (void)testUnregisterNotifiesDelegateWhenInstallationIdEnabled {
// FirebaseMessaging.isInstallationIdEnabled should return YES.
OCMStub([self.mockMessaging isInstallationIdEnabled]).andReturn(YES);
id mockOptions = OCMClassMock([FIROptions class]);
OCMStub([mockOptions GCMSenderID]).andReturn(@"123456789123");
OCMStub([mockOptions projectID]).andReturn(@"test-project-id");
OCMStub([mockOptions APIKey]).andReturn(@"test-api-key");
OCMStub([mockOptions bundleID]).andReturn(@"com.google.FirebaseMessagingTest");
OCMStub([(FIRApp *)self.mockFirebaseApp options]).andReturn(mockOptions);
[self
setupV1RegistrationHttpCallWithMethod:@"DELETE"
responseBody:nil
requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
// Assert the HTTP request is correct.
XCTAssertEqualObjects(sentRequest.URL.absoluteString,
@"https://fcmregistrations.googleapis.com/v1/projects/"
@"test-project-id/registrations/fake-fid");
XCTAssertEqualObjects(sentRequest.HTTPMethod, @"DELETE");
XCTAssertEqualObjects(sentRequest.allHTTPHeaderFields[@"X-Goog-Api-Key"],
@"test-api-key");
XCTAssertEqualObjects(
sentRequest.allHTTPHeaderFields[@"X-Goog-Firebase-Installations-Auth"],
@"fis-auth-token");
return YES;
}];
// Setup message delegate.
id mockDelegate = OCMProtocolMock(@protocol(FIRMessagingDelegate));
self.messaging.delegate = mockDelegate;
XCTestExpectation *delegateExpectation =
[self expectationWithDescription:@"Delegate received unregister notification."];
OCMStub([mockDelegate messaging:OCMOCK_ANY didUnregister:OCMOCK_ANY])
.andDo(^(NSInvocation *invocation) {
__unsafe_unretained NSString *installationId;
[invocation getArgument:&installationId atIndex:3];
XCTAssertEqualObjects(installationId, @"fake-fid");
[delegateExpectation fulfill];
});
XCTestExpectation *completionExpectation =
[self expectationWithDescription:@"Unregister completion called."];
[self.messaging unregisterWithCompletion:^(NSError *error) {
XCTAssertNil(error);
[completionExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}
- (void)testUnregisterWithCompletionFailsWhenInstallationIdDisabled {
// FirebaseMessaging.isInstallationIdEnabled should return NO.
OCMStub([self.mockMessaging isInstallationIdEnabled]).andReturn(NO);
XCTestExpectation *completionExpectation =
[self expectationWithDescription:@"Unregister completion called."];
[self.messaging unregisterWithCompletion:^(NSError *error) {
XCTAssertNotNil(error);
XCTAssertEqual(error.code, kFIRMessagingErrorCodeInvalidRequest);
[completionExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}
- (void)testUnregisterWithCompletionFailsWhenSenderIDMissing {
// FirebaseMessaging.isInstallationIdEnabled should return YES.
OCMStub([self.mockMessaging isInstallationIdEnabled]).andReturn(YES);
id mockOptions = OCMClassMock([FIROptions class]);
OCMStub([mockOptions GCMSenderID]).andReturn(nil);
OCMStub([(FIRApp *)self.mockFirebaseApp options]).andReturn(mockOptions);
XCTestExpectation *completionExpectation =
[self expectationWithDescription:@"Unregister completion called."];
[self.messaging unregisterWithCompletion:^(NSError *error) {
XCTAssertNotNil(error);
XCTAssertEqual(error.code, kFIRMessagingErrorCodeMissingAuthorizedEntity);
[completionExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}
- (void)testAppStartNotifiesDelegateWhenBothAutoInitAndInstallationIdEnabled {
// Both isInstallationIdEnabled and autoInitEnabled are YES.
OCMStub([self.mockMessaging isInstallationIdEnabled]).andReturn(YES);
OCMStub([self.mockMessaging isAutoInitEnabled]).andReturn(YES);
id mockOptions = OCMClassMock([FIROptions class]);
OCMStub([mockOptions GCMSenderID]).andReturn(@"123456789123");
OCMStub([mockOptions projectID]).andReturn(@"test-project-id");
OCMStub([mockOptions APIKey]).andReturn(@"test-api-key");
OCMStub([mockOptions bundleID]).andReturn(@"com.google.FirebaseMessagingTest");
OCMStub([mockOptions googleAppID]).andReturn(@"test-app-id");
OCMStub([(FIRApp *)self.mockFirebaseApp options]).andReturn(mockOptions);
NSData *responseBody = [@"{\"name\":\"projects/test-project-id/registrations/fake-fid\"}"
dataUsingEncoding:NSUTF8StringEncoding];
[self setupV1RegistrationHttpCallWithMethod:@"POST"
responseBody:responseBody
requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
return YES;
}];
// Setup message delegate.
id mockDelegate = OCMProtocolMock(@protocol(FIRMessagingDelegate));
self.messaging.delegate = mockDelegate;
XCTestExpectation *expectation = [self
expectationWithDescription:@"Delegate received registration notification on app start."];
OCMStub([mockDelegate messaging:OCMOCK_ANY didReceiveRegistration:OCMOCK_ANY])
.andDo(^(NSInvocation *invocation) {
__unsafe_unretained NSString *installationId;
[invocation getArgument:&installationId atIndex:3];
XCTAssertEqualObjects(installationId, @"fake-fid");
[expectation fulfill];
});
// Configure messaging and set APNs token to simulate app start.
[self.messaging configureMessagingWithOptions:mockOptions];
self.messaging.APNSToken = [@"fakeAPNSToken" dataUsingEncoding:NSUTF8StringEncoding];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}
- (void)testAppStartNotifiesDelegateWhenCachedV4TokenExists {
// FirebaseMessaging.isInstallationIdEnabled should return NO.
OCMStub([self.mockMessaging isInstallationIdEnabled]).andReturn(NO);
id mockOptions = OCMClassMock([FIROptions class]);
OCMStub([mockOptions GCMSenderID]).andReturn(@"123456789123");
OCMStub([(FIRApp *)self.mockFirebaseApp options]).andReturn(mockOptions);
FIRMessagingTokenInfo *cachedTokenInfo =
[[FIRMessagingTokenInfo alloc] initWithAuthorizedEntity:@"123456789123"
scope:kFIRMessagingDefaultTokenScope
token:@"fake-cached-v4-token"
appVersion:@"1.0"
firebaseAppID:@"app-id"
tokenType:@"V4"];
OCMStub([self.mockTokenManager
cachedTokenInfoWithAuthorizedEntity:@"123456789123"
scope:kFIRMessagingDefaultTokenScope])
.andReturn(cachedTokenInfo);
// Setup installations ID.
id installationIDArg = [OCMArg invokeBlockWithArgs:@"fake-iid", [NSNull null], nil];
OCMStub([(FIRInstallations *)self.testUtil.mockInstallations
installationIDWithCompletion:installationIDArg]);
// Setup message delegate.
id mockDelegate = OCMProtocolMock(@protocol(FIRMessagingDelegate));
self.messaging.delegate = mockDelegate;
XCTestExpectation *expectation =
[self expectationWithDescription:@"Delegate received registration token on app start."];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
OCMStub([mockDelegate messaging:OCMOCK_ANY didReceiveRegistrationToken:OCMOCK_ANY])
.andDo(^(NSInvocation *invocation) {
__unsafe_unretained NSString *token;
[invocation getArgument:&token atIndex:3];
XCTAssertEqualObjects(token, @"fake-cached-v4-token");
[expectation fulfill];
});
#pragma clang diagnostic pop
// Configure messaging to simulate app start with cached token.
[self.messaging configureMessagingWithOptions:mockOptions];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
- (void)testAppStartNotifiesDelegateWhenCachedFidExists {
// FirebaseMessaging.isInstallationIdEnabled should return YES.
OCMStub([self.mockMessaging isInstallationIdEnabled]).andReturn(YES);
id mockOptions = OCMClassMock([FIROptions class]);
OCMStub([mockOptions GCMSenderID]).andReturn(@"123456789123");
OCMStub([(FIRApp *)self.mockFirebaseApp options]).andReturn(mockOptions);
FIRMessagingTokenInfo *cachedTokenInfo =
[[FIRMessagingTokenInfo alloc] initWithAuthorizedEntity:@"123456789123"
scope:kFIRMessagingDefaultTokenScope
token:@"fake-cached-fid"
appVersion:@"1.0"
firebaseAppID:@"app-id"
tokenType:@"FID"];
OCMStub([self.mockTokenManager
cachedTokenInfoWithAuthorizedEntity:@"123456789123"
scope:kFIRMessagingDefaultTokenScope])
.andReturn(cachedTokenInfo);
// Setup installations ID.
id installationIDArg = [OCMArg invokeBlockWithArgs:@"fake-fid", [NSNull null], nil];
OCMStub([(FIRInstallations *)self.testUtil.mockInstallations
installationIDWithCompletion:installationIDArg]);
// Setup message delegate.
id mockDelegate = OCMProtocolMock(@protocol(FIRMessagingDelegate));
self.messaging.delegate = mockDelegate;
XCTestExpectation *expectation =
[self expectationWithDescription:@"Delegate received registration on app start."];
OCMStub([mockDelegate messaging:OCMOCK_ANY didReceiveRegistration:OCMOCK_ANY])
.andDo(^(NSInvocation *invocation) {
__unsafe_unretained NSString *installationId;
[invocation getArgument:&installationId atIndex:3];
XCTAssertEqualObjects(installationId, @"fake-cached-fid");
[expectation fulfill];
});
// Configure messaging to simulate app start with cached FID token.
[self.messaging configureMessagingWithOptions:mockOptions];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
- (void)testSubscribeToTopicWhenInstallationIdEnabled {
// FirebaseMessaging.isInstallationIdEnabled should return YES.
OCMStub([self.mockMessaging isInstallationIdEnabled]).andReturn(YES);
id mockOptions = OCMClassMock([FIROptions class]);
OCMStub([mockOptions GCMSenderID]).andReturn(@"123456789123");
OCMStub([mockOptions projectID]).andReturn(@"test-project-id");
OCMStub([mockOptions APIKey]).andReturn(@"test-api-key");
OCMStub([mockOptions bundleID]).andReturn(@"com.google.FirebaseMessagingTest");
OCMStub([(FIRApp *)self.mockFirebaseApp options]).andReturn(mockOptions);
[self.messaging.tokenManager setValue:@"123456789123" forKey:@"fcmSenderID"];
[self.messaging.tokenManager deleteAllTokensLocallyWithHandler:nil];
[self.messaging.tokenManager setValue:nil forKey:@"defaultFCMToken"];
// Configure messaging and set APNs token.
[self.messaging configureMessagingWithOptions:mockOptions];
self.messaging.APNSToken = [@"fakeAPNSToken" dataUsingEncoding:NSUTF8StringEncoding];
// Setup installations ID & auth token.
id installationIDArg = [OCMArg invokeBlockWithArgs:@"fake-fid", [NSNull null], nil];
OCMStub([(FIRInstallations *)self.testUtil.mockInstallations
installationIDWithCompletion:installationIDArg]);
FIRInstallationsAuthTokenResult *mockAuthTokenResult =
[[FIRInstallationsAuthTokenResult alloc] initWithToken:@"fis-auth-token"
expirationDate:[NSDate distantFuture]];
id authTokenArg = [OCMArg invokeBlockWithArgs:mockAuthTokenResult, [NSNull null], nil];
OCMStub(
[(FIRInstallations *)self.testUtil.mockInstallations authTokenWithCompletion:authTokenArg]);
id registrationURLSessionMock = OCMClassMock([NSURLSession class]);
id registrationOperationMock = OCMClassMock([FIRMessagingFIDRegisterOperation class]);
OCMStub(ClassMethod([registrationOperationMock sharedSession]))
.andReturn(registrationURLSessionMock);
id topicURLSessionMock = OCMClassMock([NSURLSession class]);
id topicOperationMock = OCMClassMock([FIRMessagingTopicOperation class]);
OCMStub(ClassMethod([topicOperationMock sharedSession])).andReturn(topicURLSessionMock);
// Setup the registration HTTP response.
NSHTTPURLResponse *registrationResponse =
[[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"https://example.com"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:nil];
NSData *registrationResponseBody =
[@"{\"name\":\"projects/test-project-id/registrations/fake-fid\"}"
dataUsingEncoding:NSUTF8StringEncoding];
__block NSInteger callOrder = 0;
__block NSInteger registrationCallIndex = -1;
__block NSInteger subscriptionCallIndex = -1;
id mockRegistrationDataTask = [FIRURLSessionOCMockStub
stubURLSessionDataTaskWithResponse:registrationResponse
body:registrationResponseBody
error:nil
URLSessionMock:registrationURLSessionMock
requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
registrationCallIndex = ++callOrder;
XCTAssertEqualObjects(sentRequest.URL.absoluteString,
@"https://fcmregistrations.googleapis.com/v1/projects/"
@"test-project-id/registrations");
XCTAssertEqualObjects(sentRequest.HTTPMethod, @"POST");
XCTAssertEqualObjects(sentRequest.allHTTPHeaderFields[@"X-Goog-Api-Key"],
@"test-api-key");
XCTAssertEqualObjects(
sentRequest.allHTTPHeaderFields[@"X-Goog-Firebase-Installations-Auth"],
@"fis-auth-token");
return YES;
}];
OCMStub([mockRegistrationDataTask setTaskDescription:OCMOCK_ANY]);
// Setup the topic subscription HTTP response.
NSHTTPURLResponse *response =
[[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"https://example.com"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:nil];
NSData *responseBody = [@"{\"topicName\":\"foobar\"}" dataUsingEncoding:NSUTF8StringEncoding];
XCTestExpectation *expectation =
[self expectationWithDescription:@"Topic subscription HTTP request validated and completed."];
id mockDataTask = [FIRURLSessionOCMockStub
stubURLSessionDataTaskWithResponse:response
body:responseBody
error:nil
URLSessionMock:topicURLSessionMock
requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
subscriptionCallIndex = ++callOrder;
XCTAssertEqualObjects(sentRequest.URL.absoluteString,
@"https://fcmregistrations.googleapis.com/v1/projects/"
@"test-project-id/registrations/fake-fid/"
@"topicSubscriptions/foobar:subscribe");
XCTAssertEqualObjects(sentRequest.HTTPMethod, @"POST");
XCTAssertEqualObjects(sentRequest.allHTTPHeaderFields[@"X-Goog-Api-Key"],
@"test-api-key");
XCTAssertEqualObjects(
sentRequest.allHTTPHeaderFields[@"X-Goog-Firebase-Installations-Auth"],
@"fis-auth-token");
XCTAssertEqualObjects(sentRequest.allHTTPHeaderFields[@"Accept"],
@"application/json");
XCTAssertNil(sentRequest.HTTPBody);
return YES;
}];
OCMStub([mockDataTask setTaskDescription:OCMOCK_ANY]);
[self.messaging subscribeToTopic:@"foobar"
completion:^(NSError *_Nullable error) {
XCTAssertNil(error);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
// Registration should be called first, then the topic subscription.
XCTAssertEqual(registrationCallIndex, 1);
XCTAssertEqual(subscriptionCallIndex, 2);
[topicOperationMock stopMocking];
[registrationOperationMock stopMocking];
}
- (void)testUnsubscribeFromTopicWhenInstallationIdEnabled {
// FirebaseMessaging.isInstallationIdEnabled should return YES.
OCMStub([self.mockMessaging isInstallationIdEnabled]).andReturn(YES);
id mockOptions = OCMClassMock([FIROptions class]);
OCMStub([mockOptions GCMSenderID]).andReturn(@"123456789123");
OCMStub([mockOptions projectID]).andReturn(@"test-project-id");
OCMStub([mockOptions APIKey]).andReturn(@"test-api-key");
OCMStub([mockOptions bundleID]).andReturn(@"com.google.FirebaseMessagingTest");
OCMStub([(FIRApp *)self.mockFirebaseApp options]).andReturn(mockOptions);
[self.messaging.tokenManager setValue:@"123456789123" forKey:@"fcmSenderID"];
[self.messaging.tokenManager deleteAllTokensLocallyWithHandler:nil];
[self.messaging.tokenManager setValue:nil forKey:@"defaultFCMToken"];
// Setup installations ID & auth token.
id installationIDArg = [OCMArg invokeBlockWithArgs:@"fake-fid", [NSNull null], nil];
OCMStub([(FIRInstallations *)self.testUtil.mockInstallations
installationIDWithCompletion:installationIDArg]);
FIRInstallationsAuthTokenResult *mockAuthTokenResult =
[[FIRInstallationsAuthTokenResult alloc] initWithToken:@"fis-auth-token"
expirationDate:[NSDate distantFuture]];
id authTokenArg = [OCMArg invokeBlockWithArgs:mockAuthTokenResult, [NSNull null], nil];
OCMStub(
[(FIRInstallations *)self.testUtil.mockInstallations authTokenWithCompletion:authTokenArg]);
id URLSessionMock = OCMClassMock([NSURLSession class]);
id topicOperationMock = OCMClassMock([FIRMessagingTopicOperation class]);
OCMStub(ClassMethod([topicOperationMock sharedSession])).andReturn(URLSessionMock);
// Setup the HTTP response.
NSHTTPURLResponse *response =
[[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"https://example.com"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:nil];
NSData *responseBody = [@"{}" dataUsingEncoding:NSUTF8StringEncoding];
XCTestExpectation *expectation = [self
expectationWithDescription:@"Topic unsubscription HTTP request validated and completed."];
id mockDataTask = [FIRURLSessionOCMockStub
stubURLSessionDataTaskWithResponse:response
body:responseBody
error:nil
URLSessionMock:URLSessionMock
requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
XCTAssertEqualObjects(sentRequest.URL.absoluteString,
@"https://fcmregistrations.googleapis.com/v1/projects/"
@"test-project-id/registrations/fake-fid/"
@"topicSubscriptions/foobar:unsubscribe");
XCTAssertEqualObjects(sentRequest.HTTPMethod, @"POST");
XCTAssertEqualObjects(sentRequest.allHTTPHeaderFields[@"X-Goog-Api-Key"],
@"test-api-key");
XCTAssertEqualObjects(
sentRequest.allHTTPHeaderFields[@"X-Goog-Firebase-Installations-Auth"],
@"fis-auth-token");
XCTAssertEqualObjects(sentRequest.allHTTPHeaderFields[@"Accept"],
@"application/json");
XCTAssertNil(sentRequest.HTTPBody);
return YES;
}];
OCMStub([mockDataTask setTaskDescription:OCMOCK_ANY]);
[self.messaging unsubscribeFromTopic:@"foobar"
completion:^(NSError *_Nullable error) {
XCTAssertNil(error);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
[topicOperationMock stopMocking];
}
- (void)testFIDChangeNotificationWhenDefaultFCMTokenIsNil {
OCMStub([self.mockTokenManager defaultFCMToken]).andReturn(nil);
id installationIDArg = [OCMArg invokeBlockWithArgs:@"fake-fid", [NSNull null], nil];
OCMStub([(FIRInstallations *)self.testUtil.mockInstallations
installationIDWithCompletion:installationIDArg]);
// `defaultFCMToken` is nil, meaning the app hasn't registered with FCM yet.
// Expect `retrieveTokenOrFidForSenderID` NOT to be called.
[[self.mockMessaging reject] retrieveTokenOrFidForSenderID:OCMOCK_ANY completion:OCMOCK_ANY];
[self.messaging handleInstallationIDDidChangeNotification:
[NSNotification notificationWithName:FIRInstallationIDDidChangeNotification
object:nil]];
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for main queue"];
dispatch_async(dispatch_get_main_queue(), ^{
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)testFIDChangeNotificationWhenDefaultFCMTokenIsNotNil {
OCMStub([self.mockTokenManager defaultFCMToken]).andReturn(@"old-fake-fid");
id installationIDArg = [OCMArg invokeBlockWithArgs:@"new-fake-fid", [NSNull null], nil];
OCMStub([(FIRInstallations *)self.testUtil.mockInstallations
installationIDWithCompletion:installationIDArg]);
// `defaultFCMToken` is not nil, meaning the app has already registered with FCM.
// Expect `retrieveTokenOrFidForSenderID` to be called to retrieve the new FID.
XCTestExpectation *retrieveTokenExpectation =
[self expectationWithDescription:@"retrieveTokenOrFidForSenderID should be called"];
[[[self.mockMessaging expect] andDo:^(NSInvocation *invocation) {
[retrieveTokenExpectation fulfill];
}] retrieveTokenOrFidForSenderID:OCMOCK_ANY completion:OCMOCK_ANY];
[self.messaging handleInstallationIDDidChangeNotification:
[NSNotification notificationWithName:FIRInstallationIDDidChangeNotification
object:nil]];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
@end