Skip to content

Commit 6fd7b35

Browse files
authored
SDK-72 iOS push types (#393)
* Send push types on notifications registration * Unit tests for push types * Fix and refactor Push Manager unit test
1 parent e589fc1 commit 6fd7b35

File tree

2 files changed

+197
-37
lines changed

2 files changed

+197
-37
lines changed

Example/Tests/Classes/LPPushNotificationsManagerTest.m

Lines changed: 135 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ + (void)setUp
3939
- (void)setUp
4040
{
4141
[super setUp];
42-
// Automatically sets up AppId and AccessKey for development mode.
43-
[LeanplumHelper setup_development_test];
4442
}
4543

4644
- (void)tearDown
@@ -49,16 +47,48 @@ - (void)tearDown
4947
[LeanplumHelper clean_up];
5048
}
5149

52-
- (void)test_push_token
50+
- (LPPushNotificationsManager*)mockManager
5351
{
54-
XCTAssertTrue([LeanplumHelper start_production_test]);
55-
56-
// Partial mock Action Manager.
5752
LPPushNotificationsManager *manager = [LPPushNotificationsManager sharedManager];
5853
id pushNotificationsManagerMock = OCMPartialMock(manager);
5954
OCMStub([LPPushNotificationsManager sharedManager]).andReturn(pushNotificationsManagerMock);
6055
OCMStub([pushNotificationsManagerMock respondsToSelector:
6156
@selector(leanplum_application:didRegisterForRemoteNotificationsWithDeviceToken:)]).andReturn(NO);
57+
return manager;
58+
}
59+
60+
- (NSString*)formatToken:(NSData*)token
61+
{
62+
NSString *formattedToken = [[LPNotificationsManager shared] hexadecimalStringFromData:token];
63+
formattedToken = [[[formattedToken stringByReplacingOccurrencesOfString:@"<" withString:@""]
64+
stringByReplacingOccurrencesOfString:@">" withString:@""]
65+
stringByReplacingOccurrencesOfString:@" " withString:@""];
66+
return formattedToken;
67+
}
68+
69+
- (void)removePushTypes
70+
{
71+
NSString *settingsKey = [[LPPushNotificationsManager sharedManager] leanplum_createUserNotificationSettingsKey];
72+
[[NSUserDefaults standardUserDefaults] removeObjectForKey:settingsKey];
73+
[[NSUserDefaults standardUserDefaults] synchronize];
74+
}
75+
76+
- (void)mockUserNotificationSettings:(UIUserNotificationType)type withCategoryId:(NSString *)categoryId
77+
{
78+
id mockApplication = OCMClassMock([UIApplication class]);
79+
OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
80+
UIMutableUserNotificationCategory *cat = [[UIMutableUserNotificationCategory alloc] init];
81+
cat.identifier = categoryId;
82+
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:type categories:[NSSet setWithObject:cat]];
83+
OCMStub([mockApplication currentUserNotificationSettings]).andReturn(settings);
84+
}
85+
86+
- (void)test_push_token
87+
{
88+
XCTAssertTrue([LeanplumHelper start_production_test]);
89+
90+
// Partial mock Action Manager.
91+
LPPushNotificationsManager *manager = [self mockManager];
6292

6393
// Remove Push Token.
6494
[manager removePushToken];
@@ -67,14 +97,10 @@ - (void)test_push_token
6797
UIApplication *app = [UIApplication sharedApplication];
6898
XCTestExpectation *expectNewToken = [self expectationWithDescription:@"expectNewToken"];
6999
NSData *token = [@"sample" dataUsingEncoding:NSUTF8StringEncoding];
70-
NSString *formattedToken = [[LPNotificationsManager shared] hexadecimalStringFromData:token];
71-
formattedToken = [[[formattedToken stringByReplacingOccurrencesOfString:@"<" withString:@""]
72-
stringByReplacingOccurrencesOfString:@">" withString:@""]
73-
stringByReplacingOccurrencesOfString:@" " withString:@""];
74100
[LeanplumRequest validate_request:^BOOL(NSString *method, NSString *apiMethod,
75101
NSDictionary *params) {
76102
XCTAssertTrue([apiMethod isEqual:@"setDeviceAttributes"]);
77-
XCTAssertTrue([params[@"iosPushToken"] isEqual:formattedToken]);
103+
XCTAssertTrue([params[@"iosPushToken"] isEqual:[self formatToken:token]]);
78104
[expectNewToken fulfill];
79105
return YES;
80106
}];
@@ -91,15 +117,11 @@ - (void)test_push_token
91117

92118
// Test push token is sent if the token changes.
93119
token = [@"sample2" dataUsingEncoding:NSUTF8StringEncoding];
94-
formattedToken = [[LPNotificationsManager shared] hexadecimalStringFromData:token];
95-
formattedToken = [[[formattedToken stringByReplacingOccurrencesOfString:@"<" withString:@""]
96-
stringByReplacingOccurrencesOfString:@">" withString:@""]
97-
stringByReplacingOccurrencesOfString:@" " withString:@""];
98120
XCTestExpectation *expectUpdatedToken = [self expectationWithDescription:@"expectUpdatedToken"];
99121
[LeanplumRequest validate_request:^BOOL(NSString *method, NSString *apiMethod,
100122
NSDictionary *params) {
101123
XCTAssertTrue([apiMethod isEqual:@"setDeviceAttributes"]);
102-
XCTAssertTrue([params[@"iosPushToken"] isEqual:formattedToken]);
124+
XCTAssertTrue([params[LP_PARAM_DEVICE_PUSH_TOKEN] isEqual:[self formatToken:token]]);
103125
[expectUpdatedToken fulfill];
104126
return YES;
105127
}];
@@ -108,6 +130,103 @@ - (void)test_push_token
108130
[self waitForExpectationsWithTimeout:2 handler:nil];
109131
}
110132

133+
/**
134+
* Test iOS Push Types are sent when notifications are registered successfully
135+
*/
136+
- (void)test_push_types
137+
{
138+
// Partial mock Action Manager.
139+
LPPushNotificationsManager *manager = [self mockManager];
140+
141+
// Remove Push Token.
142+
[manager removePushToken];
143+
[self removePushTypes];
144+
145+
[self mockUserNotificationSettings:UIUserNotificationTypeAlert withCategoryId:@"testCategory"];
146+
147+
UIApplication *app = [UIApplication sharedApplication];
148+
XCTestExpectation *expectNewToken = [self expectationWithDescription:@"expectNewToken"];
149+
NSData *token = [@"sample" dataUsingEncoding:NSUTF8StringEncoding];
150+
[LeanplumRequest validate_request:^BOOL(NSString *method, NSString *apiMethod,
151+
NSDictionary *params) {
152+
XCTAssertTrue([apiMethod isEqual:@"setDeviceAttributes"]);
153+
XCTAssertTrue([params[LP_PARAM_DEVICE_PUSH_TOKEN] isEqual:[self formatToken:token]]);
154+
XCTAssertTrue([params[LP_PARAM_DEVICE_USER_NOTIFICATION_TYPES] isEqual:@(UIUserNotificationTypeAlert)]);
155+
XCTAssertTrue([params[LP_PARAM_DEVICE_USER_NOTIFICATION_CATEGORIES] isEqual:[LPJSON stringFromJSON:@[@"testCategory"]?: @""]]);
156+
[expectNewToken fulfill];
157+
return YES;
158+
}];
159+
160+
[manager leanplum_application:app didRegisterForRemoteNotificationsWithDeviceToken:token];
161+
162+
[self mockUserNotificationSettings:UIUserNotificationTypeBadge withCategoryId:@"testCategory2"];
163+
// Test categories will be sent even if token is the same
164+
[LeanplumRequest validate_request:^BOOL(NSString *method, NSString *apiMethod,
165+
NSDictionary *params) {
166+
XCTAssertTrue([apiMethod isEqual:@"setDeviceAttributes"]);
167+
XCTAssertNil(params[LP_PARAM_DEVICE_PUSH_TOKEN]);
168+
XCTAssertTrue([params[LP_PARAM_DEVICE_USER_NOTIFICATION_TYPES] isEqual:@(UIUserNotificationTypeBadge)]);
169+
XCTAssertTrue([params[LP_PARAM_DEVICE_USER_NOTIFICATION_CATEGORIES] isEqual:[LPJSON stringFromJSON:@[@"testCategory2"]?: @""]]);
170+
return YES;
171+
}];
172+
[manager leanplum_application:app didRegisterForRemoteNotificationsWithDeviceToken:token];
173+
174+
[self waitForExpectationsWithTimeout:2 handler:nil];
175+
}
176+
177+
- (void)test_push_types_foreground
178+
{
179+
[self removePushTypes];
180+
181+
LPPushNotificationsManager *manager = [LPPushNotificationsManager sharedManager];
182+
id pushNotificationsManagerMock = OCMPartialMock(manager);
183+
OCMStub([LPPushNotificationsManager sharedManager]).andReturn(pushNotificationsManagerMock);
184+
185+
// Call start to attach the observer for App Resume/Foreground
186+
// Note that multiple start calls will attach multiple observers
187+
if (!Leanplum.hasStarted){
188+
XCTAssertTrue([LeanplumHelper start_production_test]);
189+
}
190+
191+
// Mock Application Notification Settings
192+
[self mockUserNotificationSettings:UIUserNotificationTypeAlert withCategoryId:@"testCategory"];
193+
194+
XCTestExpectation *expectPushTypesSet = [self expectationWithDescription:@"expectPushTypesSet"];
195+
[LeanplumRequest validate_request:^BOOL(NSString *method, NSString *apiMethod,
196+
NSDictionary *params) {
197+
if ([apiMethod isEqual:@"setDeviceAttributes"]) {
198+
// Use the mock object to verify
199+
OCMVerify([[pushNotificationsManagerMock handler] sendUserNotificationSettingsIfChanged:[OCMArg any]]);
200+
XCTAssertTrue([params[LP_PARAM_DEVICE_USER_NOTIFICATION_TYPES] isEqual:@(UIUserNotificationTypeAlert)]);
201+
XCTAssertTrue([params[LP_PARAM_DEVICE_USER_NOTIFICATION_CATEGORIES] isEqual:[LPJSON stringFromJSON:@[@"testCategory"]?: @""]]);
202+
[expectPushTypesSet fulfill];
203+
}
204+
return YES;
205+
}];
206+
207+
// Triggers sendUserNotificationSettingsIfChanged and resumeSession
208+
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification
209+
object:nil];
210+
211+
// Verify no request is made if the settings are the same
212+
[LeanplumRequest validate_request:^BOOL(NSString *method, NSString *apiMethod,
213+
NSDictionary *params) {
214+
if ([apiMethod isEqual:@"setDeviceAttributes"]) {
215+
XCTAssertTrue(NO);
216+
}
217+
return YES;
218+
}];
219+
220+
// Triggers sendUserNotificationSettingsIfChanged and resumeSession
221+
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification
222+
object:nil];
223+
// Use the mock object to verify
224+
OCMVerify([[pushNotificationsManagerMock handler] sendUserNotificationSettingsIfChanged:[OCMArg any]]);
225+
226+
[pushNotificationsManagerMock stopMocking];
227+
[self waitForExpectationsWithTimeout:2 handler:nil];
228+
}
229+
111230
- (void)test_update_and_remove_push_token
112231
{
113232
LPPushNotificationsManager *manager = [LPPushNotificationsManager sharedManager];

Leanplum-SDK/Classes/Notifications/Push/LPPushNotificationsHandler.m

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,39 @@ @interface LPPushNotificationsHandler()
2020
@property (nonatomic, strong) NSDate *notificationHandledTime;
2121
@end
2222

23+
@interface UIUserNotificationSettings (LPUtil)
24+
@property (readonly, nonatomic) NSDictionary *dictionary;
25+
+(NSDictionary *)toRequestParams:(NSDictionary *)settings;
26+
@end
27+
28+
@implementation UIUserNotificationSettings (LPUtil)
29+
30+
- (NSDictionary *)dictionary
31+
{
32+
NSNumber *types = @([self types]);
33+
NSMutableArray *categories = [NSMutableArray array];
34+
for (UIMutableUserNotificationCategory *category in [self categories]) {
35+
if ([category identifier]) {
36+
// Skip categories that have no identifier.
37+
[categories addObject:[category identifier]];
38+
}
39+
}
40+
NSArray *sortedCategories = [categories sortedArrayUsingSelector:@selector(compare:)];
41+
NSDictionary *settings = @{LP_PARAM_DEVICE_USER_NOTIFICATION_TYPES: types,
42+
LP_PARAM_DEVICE_USER_NOTIFICATION_CATEGORIES: sortedCategories};
43+
return settings;
44+
}
45+
46+
+ (NSDictionary *)toRequestParams:(NSDictionary *)settings
47+
{
48+
NSDictionary *params = [@{
49+
LP_PARAM_DEVICE_USER_NOTIFICATION_TYPES: settings[LP_PARAM_DEVICE_USER_NOTIFICATION_TYPES],
50+
LP_PARAM_DEVICE_USER_NOTIFICATION_CATEGORIES:
51+
[LPJSON stringFromJSON:settings[LP_PARAM_DEVICE_USER_NOTIFICATION_CATEGORIES]] ?: @""} mutableCopy];
52+
return params;
53+
}
54+
@end
55+
2356
@implementation LPPushNotificationsHandler
2457

2558
-(instancetype)init
@@ -87,18 +120,29 @@ -(void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token
87120
stringByReplacingOccurrencesOfString:@">" withString:@""]
88121
stringByReplacingOccurrencesOfString:@" " withString:@""];
89122

123+
NSMutableDictionary* deviceAttributeParams = [[NSMutableDictionary alloc] init];
90124
// Send push token if we don't have one and when the token changed.
91125
// We no longer send in start's response because saved push token will be send in start too.
92126
NSString *existingToken = [[LPPushNotificationsManager sharedManager] pushToken];
93127
if (!existingToken || ![existingToken isEqualToString:formattedToken]) {
94128

95129
[[LPPushNotificationsManager sharedManager] updatePushToken:formattedToken];
96130

131+
deviceAttributeParams[LP_PARAM_DEVICE_PUSH_TOKEN] = formattedToken;
132+
}
133+
// Get the push types if changed
134+
NSDictionary* settings = [[UIApplication sharedApplication].currentUserNotificationSettings dictionary];
135+
if ([self updateUserNotificationSettings:settings]) {
136+
[deviceAttributeParams addEntriesFromDictionary:[UIUserNotificationSettings toRequestParams:settings]];
137+
}
138+
139+
// If there are changes to the push token and/or the push types, send a request
140+
if (deviceAttributeParams.count > 0) {
97141
LPRequestFactory *reqFactory = [[LPRequestFactory alloc]
98142
initWithFeatureFlagManager:[LPFeatureFlagManager sharedManager]];
99143

100144
id<LPRequesting> request = [reqFactory
101-
setDeviceAttributesWithParams:@{LP_PARAM_DEVICE_PUSH_TOKEN: formattedToken}];
145+
setDeviceAttributesWithParams:deviceAttributeParams];
102146
[[LPRequestSender sharedInstance] send:request];
103147
}
104148
LP_END_TRY
@@ -120,31 +164,28 @@ - (void)didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notifi
120164
LP_END_TRY
121165
}
122166

123-
#pragma mark - Push Notifications
124-
- (void)sendUserNotificationSettingsIfChanged:(UIUserNotificationSettings *)notificationSettings
167+
#pragma mark - Notification Settings
168+
- (BOOL)updateUserNotificationSettings:(NSDictionary *)newSettings
125169
{
126-
// Send settings.
127170
NSString *settingsKey = [[LPPushNotificationsManager sharedManager] leanplum_createUserNotificationSettingsKey];
128171
NSDictionary *existingSettings = [[NSUserDefaults standardUserDefaults] dictionaryForKey:settingsKey];
129-
NSNumber *types = @([notificationSettings types]);
130-
NSMutableArray *categories = [NSMutableArray array];
131-
for (UIMutableUserNotificationCategory *category in [notificationSettings categories]) {
132-
if ([category identifier]) {
133-
// Skip categories that have no identifier.
134-
[categories addObject:[category identifier]];
135-
}
136-
}
137-
NSArray *sortedCategories = [categories sortedArrayUsingSelector:@selector(compare:)];
138-
NSDictionary *settings = @{LP_PARAM_DEVICE_USER_NOTIFICATION_TYPES: types,
139-
LP_PARAM_DEVICE_USER_NOTIFICATION_CATEGORIES: sortedCategories};
140-
if (![existingSettings isEqualToDictionary:settings]) {
141-
[[NSUserDefaults standardUserDefaults] setObject:settings forKey:settingsKey];
172+
if (![existingSettings isEqualToDictionary:newSettings]) {
173+
[[NSUserDefaults standardUserDefaults] setObject:newSettings forKey:settingsKey];
142174
[[NSUserDefaults standardUserDefaults] synchronize];
175+
return YES;
176+
}
177+
178+
return NO;
179+
}
180+
181+
#pragma mark - Push Notifications
182+
- (void)sendUserNotificationSettingsIfChanged:(UIUserNotificationSettings *)notificationSettings
183+
{
184+
NSDictionary* settings = [notificationSettings dictionary];
185+
// Send settings.
186+
if ([self updateUserNotificationSettings:settings]) {
143187
NSString *existingToken = [[LPPushNotificationsManager sharedManager] pushToken];
144-
NSMutableDictionary *params = [@{
145-
LP_PARAM_DEVICE_USER_NOTIFICATION_TYPES: types,
146-
LP_PARAM_DEVICE_USER_NOTIFICATION_CATEGORIES:
147-
[LPJSON stringFromJSON:sortedCategories] ?: @""} mutableCopy];
188+
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithDictionary:[UIUserNotificationSettings toRequestParams:settings]];
148189
if (existingToken) {
149190
params[LP_PARAM_DEVICE_PUSH_TOKEN] = existingToken;
150191
}

0 commit comments

Comments
 (0)