From 882b2cc89270a925fe7c6d7e4013d9da36b6beb9 Mon Sep 17 00:00:00 2001 From: Yasir Ali <> Date: Thu, 10 Jan 2019 16:30:59 +0500 Subject: [PATCH 1/3] Added support for empty userId in track and activate calls. --- .../OptimizelySDKCore/OPTLYBaseCondition.m | 2 +- .../OptimizelySDKCore/OPTLYEventBuilder.m | 2 +- .../OptimizelySDKCore/OPTLYEventTagUtil.m | 5 +++-- .../OPTLYNSObject+Validation.h | 7 ++++++ .../OPTLYNSObject+Validation.m | 22 +++++++++++++------ .../OPTLYNotificationCenter.m | 7 +++--- .../OptimizelySDKCore/Optimizely.m | 4 ++-- .../OptimizelySDKCoreTests/OptimizelyTest.m | 8 +++++++ 8 files changed, 41 insertions(+), 16 deletions(-) diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.m b/OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.m index faa4689a4..bf66bbfa9 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.m +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.m @@ -52,7 +52,7 @@ -(nullable NSNumber *)evaluateMatchTypeExact:(NSDictionary) answer = nil; [logger logMessage:OPTLYLoggerMessagesRevenueValueInvalid withLevel:OptimizelyLogLevelWarning]; } - } else if ([value isKindOfClass:[NSString class]]) { + } else if ([value isValidStringType]) { // cast strings to long long answer = @([(NSString*)value longLongValue]); [logger logMessage:[NSString stringWithFormat:OPTLYLoggerMessagesRevenueValueString, value] withLevel:OptimizelyLogLevelWarning]; @@ -138,7 +139,7 @@ + (NSNumber *)getNumericValue:(NSDictionary *)eventTags logger:(id) [logger logMessage:[NSString stringWithFormat:OPTLYLoggerMessagesNumericValueInvalidFloat, value] withLevel:OptimizelyLogLevelWarning]; } } - } else if ([value isKindOfClass:[NSString class]]) { + } else if ([value isValidStringType]) { // cast strings to double double doubleValue = [(NSString*)value doubleValue]; if (isfinite(doubleValue)) { diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYNSObject+Validation.h b/OptimizelySDKCore/OptimizelySDKCore/OPTLYNSObject+Validation.h index ccc7039f4..1612771ff 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYNSObject+Validation.h +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYNSObject+Validation.h @@ -20,6 +20,13 @@ NS_ASSUME_NONNULL_BEGIN @interface NSObject (Validation) +/** + * Returns if object is a valid string type + * + * @returns A Bool whether object is a valid string type. + **/ +- (BOOL)isValidStringType; + /** * Determines if object is a valid non-empty string type. * diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYNSObject+Validation.m b/OptimizelySDKCore/OptimizelySDKCore/OPTLYNSObject+Validation.m index 38e8ae42f..9ab3884f0 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYNSObject+Validation.m +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYNSObject+Validation.m @@ -19,9 +19,17 @@ @implementation NSObject (Validation) +- (BOOL)isValidStringType { + if (self) { + // check value is NSString + return [self isKindOfClass:[NSString class]]; + } + return false; +} + - (nullable NSString *)getValidString { if (self) { - if ([self isKindOfClass:[NSString class]] && ![(NSString *)self isEqualToString:@""]) { + if ([self isValidStringType] && ![(NSString *)self isEqualToString:@""]) { return (NSString *)self; } } @@ -49,7 +57,7 @@ - (nullable NSDictionary *)getValidDictionary { - (NSString *)getStringOrEmpty { NSString *string = @""; if (self) { - if ([self isKindOfClass:[NSString class]]) { + if ([self isValidStringType]) { string = [string stringByAppendingString:((NSString *)self)]; } } @@ -58,7 +66,7 @@ - (NSString *)getStringOrEmpty { - (nullable NSArray *)getValidAudienceConditionsArray { if(self) { - if ([self isKindOfClass:[NSString class]]) { + if ([self isValidStringType]) { //Check if string is a valid json NSError *error = nil; NSData *data = [(NSString *)self dataUsingEncoding:NSUTF8StringEncoding]; @@ -82,7 +90,7 @@ - (nullable NSArray *)getValidAudienceConditionsArray { - (nullable NSArray *)getValidConditionsArray { if(self) { - if ([self isKindOfClass:[NSString class]]) { + if ([self isValidStringType]) { NSError *error = nil; NSData *data = [(NSString *)self dataUsingEncoding:NSUTF8StringEncoding]; NSArray *conditionsArray = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error]; @@ -100,7 +108,7 @@ - (BOOL)isValidAttributeValue { return false; } // check value is NSString - if ([self isKindOfClass:[NSString class]]) { + if ([self isValidStringType]) { return true; } // check value is Boolean @@ -115,7 +123,7 @@ - (BOOL)isValidAttributeValue { return false; } --(BOOL)isValidBooleanAttributeValue { +- (BOOL)isValidBooleanAttributeValue { if (self) { // check value is NSNumber NSNumber *number = (NSNumber *)self; @@ -126,7 +134,7 @@ -(BOOL)isValidBooleanAttributeValue { return false; } --(BOOL)isValidNumericAttributeValue { +- (BOOL)isValidNumericAttributeValue { if (self) { NSNumber *number = (NSNumber *)self; // check value is NSNumber diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYNotificationCenter.m b/OptimizelySDKCore/OptimizelySDKCore/OPTLYNotificationCenter.m index 654609c72..083883037 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYNotificationCenter.m +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYNotificationCenter.m @@ -19,6 +19,7 @@ #import "OPTLYLogger.h" #import "OPTLYExperiment.h" #import "OPTLYVariation.h" +#import "OPTLYNSObject+Validation.h" #import NSString *const OPTLYNotificationExperimentKey = @"experiment"; @@ -149,7 +150,7 @@ - (void)notifyActivateListener:(ActivateListener)listener args:(NSDictionary *)a NSString *userId = (NSString *)[args objectForKey:OPTLYNotificationUserIdKey]; assert(userId); - assert([userId isKindOfClass:[NSString class]]); + assert([userId isValidStringType]); NSDictionary *attributes = (NSDictionary *)[args objectForKey:OPTLYNotificationAttributesKey]; @@ -178,11 +179,11 @@ - (void)notifyTrackListener:(TrackListener)listener args:(NSDictionary *)args { NSString *eventKey = (NSString *)[args objectForKey:OPTLYNotificationEventKey]; assert(eventKey); - assert([eventKey isKindOfClass:[NSString class]]); + assert([eventKey isValidStringType]); NSString *userId = (NSString *)[args objectForKey:OPTLYNotificationUserIdKey]; assert(userId); - assert([userId isKindOfClass:[NSString class]]); + assert([userId isValidStringType]); NSDictionary *attributes = (NSDictionary *)[args objectForKey:OPTLYNotificationAttributesKey]; if (attributes != nil && ![attributes isEqual:[NSNull null]]) { diff --git a/OptimizelySDKCore/OptimizelySDKCore/Optimizely.m b/OptimizelySDKCore/OptimizelySDKCore/Optimizely.m index 595a50fea..e48056cf1 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/Optimizely.m +++ b/OptimizelySDKCore/OptimizelySDKCore/Optimizely.m @@ -123,7 +123,7 @@ - (OPTLYVariation *)activate:(NSString *)experimentKey return nil; } - if ([userId getValidString] == nil) { + if (![userId isValidStringType]) { NSError *error = [self handleErrorLogsForActivate:OPTLYLoggerMessagesUserIdInvalid ofLevel:OptimizelyLogLevelError]; _callback(error); return nil; @@ -434,7 +434,7 @@ - (void)track:(NSString *)eventKey return; } - if ([userId getValidString] == nil) { + if (![userId isValidStringType]) { [self handleErrorLogsForTrack:OPTLYLoggerMessagesUserIdInvalid ofLevel:OptimizelyLogLevelError]; return; } diff --git a/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m b/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m index 684ce79e6..01b5ee097 100644 --- a/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m +++ b/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m @@ -244,6 +244,14 @@ - (void)testVariationWhitelisting { # pragma mark - Integration Tests +- (void)testOptimizelyActivateWithEmptyUserId { + OPTLYVariation *_variation = [self.optimizely activate:@"testExperimentMultivariate" + userId:@""]; + XCTAssertNotNil(_variation); + XCTAssertEqualObjects(@"Feorge", _variation.variationKey); +} + + - (void)testOptimizelyActivateWithNoExperiment { __weak XCTestExpectation *expectation = [self expectationWithDescription:@"getActivatedVariation"]; From a9e4a7707f5fc191297f76f6d7c91d4d2fe98012 Mon Sep 17 00:00:00 2001 From: Yasir Ali <> Date: Thu, 10 Jan 2019 16:52:22 +0500 Subject: [PATCH 2/3] Test case added for forced variation with empty userId. --- OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m b/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m index 01b5ee097..ef56530a6 100644 --- a/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m +++ b/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m @@ -1588,6 +1588,8 @@ - (void)testSetForcedVariationWithNullAndEmptyUserId XCTAssertTrue([self.optimizely setForcedVariation:kExperimentKeyForFV userId:@"" variationKey:kVariationKeyForFV]); + OPTLYVariation *variation = [self.optimizely getForcedVariation:kExperimentKeyForFV userId:@""]; + XCTAssertEqualObjects(variation.variationKey, kVariationKeyForFV); } - (void)testSetForcedVariationWithInvalidExperimentKey From 41e23fc499dfa43f85dbdab0830fed5effef4606 Mon Sep 17 00:00:00 2001 From: Yasir Ali <> Date: Thu, 10 Jan 2019 17:52:19 +0500 Subject: [PATCH 3/3] Testcase added for track with EmptyUserId. --- .../OptimizelySDKCoreTests/OptimizelyTest.m | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m b/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m index ef56530a6..18734761b 100644 --- a/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m +++ b/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m @@ -431,6 +431,28 @@ - (void)testOptimizelyTrackWithNoUser { [loggerMock stopMocking]; } +- (void)testOptimizelyTrackWithEmptyUserId { + + NSString *eventKey = @"testEvent"; + __block NSString *_userId = nil; + __block NSString *notificationEventKey = nil; + __block NSDictionary *actualAttributes; + __block NSDictionary *actualEventTags; + + [self.optimizely.notificationCenter addTrackNotificationListener:^(NSString * _Nonnull eventKey, NSString * _Nonnull userId, NSDictionary * _Nonnull attributes, NSDictionary * _Nonnull eventTags, NSDictionary * _Nonnull event) { + _userId = userId; + notificationEventKey = eventKey; + actualAttributes = attributes; + actualEventTags = eventTags; + }]; + + [self.optimizely track:eventKey userId:@""]; + XCTAssertEqualObjects(@"", _userId); + XCTAssertEqual(eventKey, notificationEventKey); + XCTAssertEqualObjects(nil, actualAttributes); + XCTAssertEqualObjects(nil, actualEventTags); +} + - (void)testOptimizelyTrackWithInvalidEvent { NSString *invalidEventKey = @"invalid";