/* * 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. */ #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 || \ __TV_OS_VERSION_MAX_ALLOWED >= __TV_10_0 || __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_14 #import #endif #import #import #import #import #import "Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h" #pragma mark - Invalid App Delegate or UNNotificationCenter @interface RandomObject : NSObject @property(nonatomic, weak) id delegate; @end @implementation RandomObject - (void)application:(GULApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { } - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler: (void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(ios(10.0), macos(10.14), tvos(10.0)) { } #if TARGET_OS_IOS || TARGET_OS_OSX - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(macos(10.14), ios(10.0)) { } #endif @end #pragma mark - Incomplete App Delegate @interface IncompleteAppDelegate : NSObject @end @implementation IncompleteAppDelegate @end #pragma mark - Fake AppDelegate @interface FakeAppDelegate : NSObject @property(nonatomic) BOOL remoteNotificationMethodWasCalled; @property(nonatomic) BOOL remoteNotificationWithFetchHandlerWasCalled; @property(nonatomic, strong) NSData *deviceToken; @property(nonatomic, strong) NSError *registerForRemoteNotificationsError; @end @implementation FakeAppDelegate #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (void)application:(GULApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { self.remoteNotificationMethodWasCalled = YES; } #pragma clang diagnostic pop #if TARGET_OS_IOS || TARGET_OS_TV - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { self.remoteNotificationWithFetchHandlerWasCalled = YES; } #endif - (void)application:(GULApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { self.deviceToken = deviceToken; } - (void)application:(GULApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { self.registerForRemoteNotificationsError = error; } @end #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 || \ __TV_OS_VERSION_MAX_ALLOWED >= __TV_10_0 || __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_14 #pragma mark - Incompete UNUserNotificationCenterDelegate @interface IncompleteUserNotificationCenterDelegate : NSObject @end @implementation IncompleteUserNotificationCenterDelegate @end #pragma mark - Fake UNUserNotificationCenterDelegate @interface FakeUserNotificationCenterDelegate : NSObject @property(nonatomic) BOOL willPresentWasCalled; @property(nonatomic) BOOL didReceiveResponseWasCalled; @end @implementation FakeUserNotificationCenterDelegate - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler: (void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(ios(10.0), macos(10.14), tvos(10.0)) { self.willPresentWasCalled = YES; } #if TARGET_OS_IOS || TARGET_OS_OSX - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0), macos(10.14)) { self.didReceiveResponseWasCalled = YES; } #endif @end #endif @interface GULAppDelegateSwizzler (FIRMessagingRemoteNotificationsProxyTest) + (void)resetProxyOriginalDelegateOnceToken; @end #pragma mark - Local, Per-Test Properties @interface FIRMessagingRemoteNotificationsProxyTest : XCTestCase @property(nonatomic, strong) FIRMessagingRemoteNotificationsProxy *proxy; @property(nonatomic, strong) id mockProxyClass; @property(nonatomic, strong) id mockMessaging; @property(nonatomic, strong) id mockUserNotificationCenter; @end @implementation FIRMessagingRemoteNotificationsProxyTest - (void)setUp { [super setUp]; [GULAppDelegateSwizzler resetProxyOriginalDelegateOnceToken]; _mockMessaging = OCMClassMock([FIRMessaging class]); OCMStub([_mockMessaging messaging]).andReturn(_mockMessaging); _proxy = [[FIRMessagingRemoteNotificationsProxy alloc] init]; _mockProxyClass = OCMClassMock([FIRMessagingRemoteNotificationsProxy class]); // Update +sharedProxy to always return our test instance OCMStub([_mockProxyClass sharedProxy]).andReturn(self.proxy); if (@available(macOS 10.14, iOS 10.0, *)) { _mockUserNotificationCenter = OCMPartialMock([UNUserNotificationCenter currentNotificationCenter]); } } - (void)tearDown { [_mockProxyClass stopMocking]; _mockProxyClass = nil; [_mockMessaging stopMocking]; _mockMessaging = nil; if (@available(macOS 10.14, iOS 10.0, *)) { [_mockUserNotificationCenter stopMocking]; _mockUserNotificationCenter = nil; } _proxy = nil; [super tearDown]; } #pragma mark - Method Swizzling Tests - (void)testSwizzlingNonAppDelegate { RandomObject *invalidAppDelegate = [[RandomObject alloc] init]; [[GULAppDelegateSwizzler sharedApplication] setDelegate:(id)invalidAppDelegate]; [self.proxy swizzleMethodsIfPossible]; OCMReject([self.mockMessaging appDidReceiveMessage:[OCMArg any]]); [invalidAppDelegate application:[GULAppDelegateSwizzler sharedApplication] didReceiveRemoteNotification:@{}]; } - (void)testSwizzledIncompleteAppDelegateRemoteNotificationMethod { IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init]; [[GULAppDelegateSwizzler sharedApplication] setDelegate:incompleteAppDelegate]; [self.proxy swizzleMethodsIfPossible]; NSDictionary *notification = @{@"test" : @""}; OCMExpect([self.mockMessaging appDidReceiveMessage:notification]); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [incompleteAppDelegate application:[GULAppDelegateSwizzler sharedApplication] didReceiveRemoteNotification:notification]; #pragma clang diagnostic pop [self.mockMessaging verify]; } - (void)testIncompleteAppDelegateRemoteNotificationWithFetchHandlerMethod { IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init]; [[GULAppDelegateSwizzler sharedApplication] setDelegate:incompleteAppDelegate]; [self.proxy swizzleMethodsIfPossible]; #if TARGET_OS_IOS || TARGET_OS_TV SEL remoteNotificationWithFetchHandler = @selector(application: didReceiveRemoteNotification:fetchCompletionHandler:); XCTAssertFalse([incompleteAppDelegate respondsToSelector:remoteNotificationWithFetchHandler]); #endif SEL remoteNotification = @selector(application:didReceiveRemoteNotification:); XCTAssertTrue([incompleteAppDelegate respondsToSelector:remoteNotification]); } - (void)testSwizzledAppDelegateRemoteNotificationMethods { FakeAppDelegate *appDelegate = [[FakeAppDelegate alloc] init]; [[GULAppDelegateSwizzler sharedApplication] setDelegate:appDelegate]; [self.proxy swizzleMethodsIfPossible]; NSDictionary *notification = @{@"test" : @""}; // Test application:didReceiveRemoteNotification: // Verify our swizzled method was called OCMExpect([self.mockMessaging appDidReceiveMessage:notification]); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [appDelegate application:[GULAppDelegateSwizzler sharedApplication] didReceiveRemoteNotification:notification]; #pragma clang diagnostic pop // Verify our original method was called XCTAssertTrue(appDelegate.remoteNotificationMethodWasCalled); [self.mockMessaging verify]; // Test application:didReceiveRemoteNotification:fetchCompletionHandler: #if TARGET_OS_IOS || TARGET_OS_TV // Verify our swizzled method was called OCMExpect([self.mockMessaging appDidReceiveMessage:notification]); [appDelegate application:[GULAppDelegateSwizzler sharedApplication] didReceiveRemoteNotification:notification fetchCompletionHandler:^(UIBackgroundFetchResult result){ }]; // Verify our original method was called XCTAssertTrue(appDelegate.remoteNotificationWithFetchHandlerWasCalled); [self.mockMessaging verify]; #endif // Verify application:didRegisterForRemoteNotificationsWithDeviceToken: NSData *deviceToken = [NSData data]; OCMExpect([self.mockMessaging setAPNSToken:deviceToken]); [appDelegate application:[GULAppDelegateSwizzler sharedApplication] didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; XCTAssertEqual(appDelegate.deviceToken, deviceToken); [self.mockMessaging verify]; // Verify application:didFailToRegisterForRemoteNotificationsWithError: NSError *error = [NSError errorWithDomain:@"tests" code:-1 userInfo:nil]; [appDelegate application:[GULAppDelegateSwizzler sharedApplication] didFailToRegisterForRemoteNotificationsWithError:error]; XCTAssertEqual(appDelegate.registerForRemoteNotificationsError, error); } - (void)testListeningForDelegateChangesOnInvalidUserNotificationCenter { if (@available(macOS 10.14, iOS 10.0, *)) { RandomObject *invalidNotificationCenter = [[RandomObject alloc] init]; OCMStub([self.mockUserNotificationCenter currentNotificationCenter]) .andReturn(invalidNotificationCenter); [self.proxy swizzleMethodsIfPossible]; OCMReject([self.mockMessaging appDidReceiveMessage:[OCMArg any]]); [(id)invalidNotificationCenter.delegate userNotificationCenter:self.mockUserNotificationCenter willPresentNotification:OCMClassMock([UNNotification class]) withCompletionHandler:^(UNNotificationPresentationOptions options){ }]; } } - (void)testSwizzlingInvalidUserNotificationCenterDelegate { if (@available(macOS 10.14, iOS 10.0, *)) { RandomObject *invalidDelegate = [[RandomObject alloc] init]; OCMStub([self.mockUserNotificationCenter delegate]).andReturn(invalidDelegate); [self.proxy swizzleMethodsIfPossible]; OCMReject([self.mockMessaging appDidReceiveMessage:[OCMArg any]]); [invalidDelegate userNotificationCenter:self.mockUserNotificationCenter willPresentNotification:OCMClassMock([UNNotification class]) withCompletionHandler:^(UNNotificationPresentationOptions options){ }]; } } - (void)testSwizzlingUserNotificationsCenterDelegate { #if TARGET_OS_IOS || TARGET_OS_OSX if (@available(macOS 10.14, iOS 10.0, *)) { FakeUserNotificationCenterDelegate *delegate = [[FakeUserNotificationCenterDelegate alloc] init]; OCMStub([self.mockUserNotificationCenter delegate]).andReturn(delegate); [self.proxy swizzleMethodsIfPossible]; NSDictionary *message = @{@"message" : @""}; id notification = [self userNotificationWithMessage:message]; OCMExpect([self.mockMessaging appDidReceiveMessage:message]); [delegate userNotificationCenter:self.mockUserNotificationCenter willPresentNotification:notification withCompletionHandler:^(UNNotificationPresentationOptions options){ }]; [self.mockMessaging verify]; } #endif } // Use a fake delegate that doesn't actually implement the needed delegate method. // Our swizzled method should not be called. - (void)testIncompleteUserNotificationCenterDelegateMethod { if (@available(macOS 10.14, iOS 10.0, *)) { IncompleteUserNotificationCenterDelegate *delegate = [[IncompleteUserNotificationCenterDelegate alloc] init]; OCMStub([self.mockUserNotificationCenter delegate]).andReturn(delegate); [self.proxy swizzleMethodsIfPossible]; // Because the incomplete delete does not implement either of the optional delegate methods, we // should swizzle nothing. If we had swizzled them, then respondsToSelector: would return YES // even though the delegate does not implement the methods. SEL willPresentSelector = @selector(userNotificationCenter: willPresentNotification:withCompletionHandler:); XCTAssertFalse([delegate respondsToSelector:willPresentSelector]); SEL didReceiveResponseSelector = @selector(userNotificationCenter: didReceiveNotificationResponse:withCompletionHandler:); XCTAssertFalse([delegate respondsToSelector:didReceiveResponseSelector]); } } // Use an object that does actually implement the optional methods. Both should be called. - (void)testSwizzledUserNotificationsCenterDelegate { #if TARGET_OS_IOS || TARGET_OS_OSX FakeUserNotificationCenterDelegate *delegate = [[FakeUserNotificationCenterDelegate alloc] init]; OCMStub([self.mockUserNotificationCenter delegate]).andReturn(delegate); [self.proxy swizzleMethodsIfPossible]; NSDictionary *message = @{@"message" : @""}; // Verify userNotificationCenter:willPresentNotification:withCompletionHandler: OCMExpect([self.mockMessaging appDidReceiveMessage:message]); if (@available(macOS 10.14, iOS 10.0, tvOS 10.0, *)) { // Invoking delegate method should also invoke our swizzled method // The swizzled method uses the +sharedProxy, which should be // returning our proxy. // Use non-nil, proper classes, otherwise our SDK bails out. [delegate userNotificationCenter:self.mockUserNotificationCenter willPresentNotification:[self userNotificationWithMessage:message] withCompletionHandler:^(NSUInteger options){ }]; // Verify our original method was called XCTAssertTrue(delegate.willPresentWasCalled); // Verify our swizzled method was called [self.mockMessaging verify]; } if (@available(macOS 10.14, iOS 10.0, *)) { // Verify userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: OCMExpect([self.mockMessaging appDidReceiveMessage:message]); [delegate userNotificationCenter:self.mockUserNotificationCenter didReceiveNotificationResponse:[self userNotificationResponseWithMessage:message] withCompletionHandler:^{ }]; // Verify our original method was called XCTAssertTrue(delegate.didReceiveResponseWasCalled); // Verify our swizzled method was called [self.mockMessaging verify]; } #endif } - (id)userNotificationResponseWithMessage:(NSDictionary *)message { #if TARGET_OS_IOS || TARGET_OS_OSX if (@available(macOS 10.14, iOS 10.0, *)) { // Stub out: response.[mock notification above] id mockNotificationResponse = OCMClassMock([UNNotificationResponse class]); id mockNotification = [self userNotificationWithMessage:message]; OCMStub([mockNotificationResponse notification]).andReturn(mockNotification); return mockNotificationResponse; } #endif return nil; } - (UNNotification *)userNotificationWithMessage:(NSDictionary *)message API_AVAILABLE(macos(10.14), ios(10.0)) { UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; #if TARGET_OS_IOS || TARGET_OS_OSX content.userInfo = message; #endif id notificationRequest = OCMClassMock([UNNotificationRequest class]); OCMStub([notificationRequest content]).andReturn(content); id notification = OCMClassMock([UNNotification class]); OCMStub([notification request]).andReturn(notificationRequest); return notification; } @end