diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYAudience.m b/OptimizelySDKCore/OptimizelySDKCore/OPTLYAudience.m index 63c4e5cd7..801480d74 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYAudience.m +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYAudience.m @@ -46,7 +46,7 @@ - (void)setConditionsWithNSString:(NSString *)string { } } -- (BOOL)evaluateConditionsWithAttributes:(NSDictionary *)attributes { +- (BOOL)evaluateConditionsWithAttributes:(NSDictionary *)attributes { for (NSObject *condition in self.conditions) { if ([condition evaluateConditionsWithAttributes:attributes]) { // if user satisfies any conditions, return true. diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.h b/OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.h index 00a1885b1..0cafb7373 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.h +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.h @@ -31,7 +31,7 @@ /// Condition type @property (nonatomic, strong) NSString *type; /// Condition value -@property (nonatomic, strong) NSString *value; +@property (nonatomic, strong) NSObject *value; +(BOOL)isBaseConditionJSON:(NSData *)jsonData; diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.m b/OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.m index ed1e0d7c5..56e2ed0a0 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.m +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.m @@ -35,14 +35,14 @@ + (BOOL) isBaseConditionJSON:(NSData *)jsonData { } } -- (BOOL)evaluateConditionsWithAttributes:(NSDictionary *)attributes { +- (BOOL)evaluateConditionsWithAttributes:(NSDictionary *)attributes { if (attributes == nil) { // if the user did not pass in attributes, return false return false; } else { - // check user attribute value for the condition against our condition value - return [self.value isEqualToString:attributes[self.name]]; + // check user attribute value for the condition against our condition value + return [self.value isEqual:attributes[self.name]]; } } diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYCondition.h b/OptimizelySDKCore/OptimizelySDKCore/OPTLYCondition.h index 0995f851b..bc8d16b8e 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYCondition.h +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYCondition.h @@ -21,7 +21,7 @@ /** * Evaluate the condition against the user attributes. */ -- (BOOL)evaluateConditionsWithAttributes:(NSDictionary *)attributes; +- (BOOL)evaluateConditionsWithAttributes:(NSDictionary *)attributes; @end diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYCondition.m b/OptimizelySDKCore/OptimizelySDKCore/OPTLYCondition.m index a8de641a2..4a998c694 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYCondition.m +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYCondition.m @@ -115,7 +115,7 @@ @implementation OPTLYCondition @implementation OPTLYAndCondition -- (BOOL)evaluateConditionsWithAttributes:(NSDictionary *)attributes { +- (BOOL)evaluateConditionsWithAttributes:(NSDictionary *)attributes { for (NSObject *condition in self.subConditions) { // if any of our sub conditions are false if (![condition evaluateConditionsWithAttributes:attributes]) { @@ -131,7 +131,7 @@ - (BOOL)evaluateConditionsWithAttributes:(NSDictionary *) @implementation OPTLYOrCondition -- (BOOL)evaluateConditionsWithAttributes:(NSDictionary *)attributes { +- (BOOL)evaluateConditionsWithAttributes:(NSDictionary *)attributes { for (NSObject *condition in self.subConditions) { // if any of our sub conditions are true if ([condition evaluateConditionsWithAttributes:attributes]) { @@ -147,7 +147,7 @@ - (BOOL)evaluateConditionsWithAttributes:(NSDictionary *) @implementation OPTLYNotCondition -- (BOOL)evaluateConditionsWithAttributes:(NSDictionary *)attributes { +- (BOOL)evaluateConditionsWithAttributes:(NSDictionary *)attributes { // return the negative of the subcondition return ![self.subCondition evaluateConditionsWithAttributes:attributes]; } diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYDecisionService.h b/OptimizelySDKCore/OptimizelySDKCore/OPTLYDecisionService.h index d35b4cce9..92e519006 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYDecisionService.h +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYDecisionService.h @@ -54,7 +54,7 @@ */ - (nullable OPTLYVariation *)getVariation:(nonnull NSString *)userId experiment:(nonnull OPTLYExperiment *)experiment - attributes:(nullable NSDictionary *)attributes; + attributes:(nullable NSDictionary *)attributes; /** * Get a variation the user is bucketed into for the given FeatureFlag @@ -65,6 +65,6 @@ */ - (nullable OPTLYFeatureDecision *)getVariationForFeature:(nonnull OPTLYFeatureFlag *)featureFlag userId:(nonnull NSString *)userId - attributes:(nullable NSDictionary *)attributes; + attributes:(nullable NSDictionary *)attributes; @end diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYDecisionService.m b/OptimizelySDKCore/OptimizelySDKCore/OPTLYDecisionService.m index 6eb8c1564..1f133181e 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYDecisionService.m +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYDecisionService.m @@ -52,7 +52,7 @@ - (instancetype) initWithProjectConfig:(OPTLYProjectConfig *)config - (OPTLYVariation *)getVariation:(NSString *)userId experiment:(OPTLYExperiment *)experiment - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes { NSDictionary *userProfileDict = nil; OPTLYVariation *bucketedVariation = nil; @@ -125,7 +125,7 @@ - (OPTLYVariation *)getVariation:(NSString *)userId - (OPTLYFeatureDecision *)getVariationForFeature:(OPTLYFeatureFlag *)featureFlag userId:(NSString *)userId - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { //Evaluate in this order: @@ -157,7 +157,7 @@ - (OPTLYFeatureDecision *)getVariationForFeature:(OPTLYFeatureFlag *)featureFlag # pragma mark - Helper Methods - (NSString *)getBucketingId:(NSString *)userId - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { // By default, the bucketing ID should be the user ID . NSString *bucketingId = userId; @@ -189,7 +189,7 @@ - (OPTLYExperiment *)getExperimentInGroup:(OPTLYGroup *)group bucketingId:(NSStr - (OPTLYFeatureDecision *)getVariationForFeatureGroup:(OPTLYFeatureFlag *)featureFlag groupId:(NSString *)groupId userId:(NSString *)userId - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { OPTLYFeatureDecision *decision = nil; NSString *logMessage = nil; @@ -225,7 +225,7 @@ - (OPTLYFeatureDecision *)getVariationForFeatureGroup:(OPTLYFeatureFlag *)featur - (OPTLYFeatureDecision *)getVariationForFeatureExperiment:(OPTLYFeatureFlag *)featureFlag userId:(NSString *)userId - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { NSString *featureFlagKey = featureFlag.key; NSArray *experimentIds = featureFlag.experimentIds; @@ -261,7 +261,7 @@ - (OPTLYFeatureDecision *)getVariationForFeatureExperiment:(OPTLYFeatureFlag *)f - (OPTLYFeatureDecision *)getVariationForFeatureRollout:(OPTLYFeatureFlag *)featureFlag userId:(NSString *)userId - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { NSString *bucketing_id = [self getBucketingId:userId attributes:attributes]; NSString *featureFlagKey = featureFlag.key; @@ -447,7 +447,7 @@ - (NSString *)getVariationIdFromUserProfile:(NSDictionary *)userProfileDict - (BOOL)userPassesTargeting:(OPTLYProjectConfig *)config experiment:(OPTLYExperiment *)experiment userId:(NSString *)userId - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes { // check if the user is in the experiment BOOL isUserInExperiment = [self isUserInExperiment:config experiment:experiment attributes:attributes]; @@ -476,7 +476,7 @@ - (BOOL)isExperimentActive:(OPTLYProjectConfig *)config - (BOOL)isUserInExperiment:(OPTLYProjectConfig *)config experiment:(OPTLYExperiment *)experiment - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes { NSArray *audiences = experiment.audienceIds; diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYEventBuilder.h b/OptimizelySDKCore/OptimizelySDKCore/OPTLYEventBuilder.h index 360cbf730..0de75d715 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYEventBuilder.h +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYEventBuilder.h @@ -43,7 +43,7 @@ NS_ASSUME_NONNULL_END - (nullable NSDictionary *)buildImpressionEventForUser:(nonnull NSString *)userId experiment:(nonnull OPTLYExperiment *)experiment variation:(nonnull OPTLYVariation *)variation - attributes:(nullable NSDictionary *)attributes; + attributes:(nullable NSDictionary *)attributes; /** * Create the parameters for a conversion event. @@ -59,7 +59,7 @@ NS_ASSUME_NONNULL_END event:(nonnull OPTLYEvent *)event decisions:(nonnull NSArray *)decisions eventTags:(nullable NSDictionary *)eventTags - attributes:(nullable NSDictionary *)attributes; + attributes:(nullable NSDictionary *)attributes; @end @interface OPTLYEventBuilderDefault : NSObject diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYEventBuilder.m b/OptimizelySDKCore/OptimizelySDKCore/OPTLYEventBuilder.m index b889ad243..f3fb02741 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYEventBuilder.m +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYEventBuilder.m @@ -55,7 +55,7 @@ -(instancetype)initWithConfig:(OPTLYProjectConfig *)config { -(NSDictionary *)buildImpressionEventForUser:(NSString *)userId experiment:(OPTLYExperiment *)experiment variation:(OPTLYVariation *)variation - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { if (!self.config) { return nil; } @@ -71,7 +71,7 @@ -(NSDictionary *)buildConversionEventForUser:(NSString *)userId event:(OPTLYEvent *)event decisions:(NSArray *)decisions eventTags:(NSDictionary *)eventTags - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { if (!self.config) { return nil; @@ -87,7 +87,7 @@ -(NSDictionary *)buildConversionEventForUser:(NSString *)userId } - (NSDictionary *)createCommonParamsForUser:(NSString *)userId - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { NSMutableDictionary *commonParams = [NSMutableDictionary new]; NSMutableDictionary *visitor = [NSMutableDictionary new]; @@ -199,15 +199,15 @@ - (NSDictionary *)filterEventTags:(NSDictionary *)eventTags { } - (NSArray *)createUserFeatures:(OPTLYProjectConfig *)config - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { NSNumber *botFiltering = config.botFiltering; NSMutableArray *features = [NSMutableArray new]; NSArray *attributeKeys = [attributes allKeys]; for (NSString *attributeKey in attributeKeys) { - NSString *attributeValue = attributes[attributeKey]; - if ([OPTLYEventBuilderDefault isEmptyString:attributeValue]) { + NSObject *attributeValue = attributes[attributeKey]; + if (![OPTLYEventBuilderDefault isValidAttributeValue:attributeValue]) { NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAttributeValueInvalidFormat, attributeKey]; [config.logger logMessage:logMessage withLevel:OptimizelyLogLevelDebug]; continue; @@ -252,10 +252,43 @@ + (NSString *)stringOrEmpty:(NSString *)str { return string; } -+ (BOOL)isEmptyString:(NSString*)str { - return (str == nil - || [str isKindOfClass:[NSNull class]] - || [str length] == 0); ++ (BOOL)isEmptyString:(NSObject *)str { + return (!str + || ![str isKindOfClass:[NSString class]] + || [(NSString *)str isEqualToString:@""]); +} + ++ (BOOL)isValidAttributeValue:(NSObject *)value { + // check value is NSObject + if (!value || [value isEqual:[NSNull null]]) { + return false; + } + // check value is NSString + if ([value isKindOfClass:[NSString class]]) { + return true; + } + NSNumber *number = (NSNumber *)value; + // check value is NSNumber + if (number && [number isKindOfClass:[NSNumber class]]) { + const char *objCType = [number objCType]; + // check NSNumber is of type int, double, bool + return (strcmp(objCType, @encode(short)) == 0) + || (strcmp(objCType, @encode(unsigned short)) == 0) + || (strcmp(objCType, @encode(int)) == 0) + || (strcmp(objCType, @encode(unsigned int)) == 0) + || (strcmp(objCType, @encode(long)) == 0) + || (strcmp(objCType, @encode(unsigned long)) == 0) + || (strcmp(objCType, @encode(long long)) == 0) + || (strcmp(objCType, @encode(unsigned long long)) == 0) + || (strcmp(objCType, @encode(float)) == 0) + || (strcmp(objCType, @encode(double)) == 0) + || (strcmp(objCType, @encode(char)) == 0) + || (strcmp(objCType, @encode(unsigned char)) == 0) + || (strcmp(objCType, @encode(bool)) == 0) + || [number isEqual:@YES] + || [number isEqual:@NO]; + } + return false; } @end diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYNotificationCenter.h b/OptimizelySDKCore/OptimizelySDKCore/OPTLYNotificationCenter.h index 172e4af48..ac374686b 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYNotificationCenter.h +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYNotificationCenter.h @@ -26,13 +26,13 @@ typedef NS_ENUM(NSUInteger, OPTLYNotificationType) { typedef void (^ActivateListener)(OPTLYExperiment * _Nonnull experiment, NSString * _Nonnull userId, - NSDictionary * _Nonnull attributes, + NSDictionary * _Nonnull attributes, OPTLYVariation * _Nonnull variation, NSDictionary * _Nonnull event); typedef void (^TrackListener)(NSString * _Nonnull eventKey, NSString * _Nonnull userId, - NSDictionary * _Nonnull attributes, + NSDictionary * _Nonnull attributes, NSDictionary * _Nonnull eventTags, NSDictionary * _Nonnull event); diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYProjectConfig.h b/OptimizelySDKCore/OptimizelySDKCore/OPTLYProjectConfig.h index 550e87d19..f1366d76b 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYProjectConfig.h +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYProjectConfig.h @@ -172,7 +172,7 @@ __attribute((deprecated("Use OPTLYProjectConfig initWithBuilder method instead." */ - (nullable OPTLYVariation *)getVariationForExperiment:(nonnull NSString *)experimentKey userId:(nonnull NSString *)userId - attributes:(nullable NSDictionary *)attributes + attributes:(nullable NSDictionary *)attributes bucketer:(nullable id)bucketer; @end diff --git a/OptimizelySDKCore/OptimizelySDKCore/OPTLYProjectConfig.m b/OptimizelySDKCore/OptimizelySDKCore/OPTLYProjectConfig.m index 7310485f7..229d44e51 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/OPTLYProjectConfig.m +++ b/OptimizelySDKCore/OptimizelySDKCore/OPTLYProjectConfig.m @@ -600,7 +600,7 @@ - (NSDictionary *)generateAttributeToKeyMap // TODO: Remove bucketer from parameters -- this is not needed - (OPTLYVariation *)getVariationForExperiment:(NSString *)experimentKey userId:(NSString *)userId - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes bucketer:(id)bucketer { OPTLYExperiment *experiment = [self getExperimentForKey:experimentKey]; diff --git a/OptimizelySDKCore/OptimizelySDKCore/Optimizely.h b/OptimizelySDKCore/OptimizelySDKCore/Optimizely.h index 164d732dc..2cc9850eb 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/Optimizely.h +++ b/OptimizelySDKCore/OptimizelySDKCore/Optimizely.h @@ -63,7 +63,7 @@ typedef NS_ENUM(NSInteger, OPTLYLiveVariableError) { */ - (nullable OPTLYVariation *)activate:(nonnull NSString *)experimentKey userId:(nonnull NSString *)userId - attributes:(nullable NSDictionary *)attributes; + attributes:(nullable NSDictionary *)attributes; #pragma mark - getVariation methods /** @@ -90,7 +90,7 @@ typedef NS_ENUM(NSInteger, OPTLYLiveVariableError) { */ - (nullable OPTLYVariation *)variation:(nonnull NSString *)experimentKey userId:(nonnull NSString *)userId - attributes:(nullable NSDictionary *)attributes; + attributes:(nullable NSDictionary *)attributes; #pragma mark - Forced Variation Methods /** @@ -135,7 +135,7 @@ typedef NS_ENUM(NSInteger, OPTLYLiveVariableError) { * @param attributes The user's attributes. * @return YES if feature is enabled, false otherwise. */ -- (BOOL)isFeatureEnabled:(nullable NSString *)featureKey userId:(nullable NSString *)userId attributes:(nullable NSDictionary *)attributes; +- (BOOL)isFeatureEnabled:(nullable NSString *)featureKey userId:(nullable NSString *)userId attributes:(nullable NSDictionary *)attributes; /** * Gets boolean feature variable value. @@ -148,7 +148,7 @@ typedef NS_ENUM(NSInteger, OPTLYLiveVariableError) { - (nullable NSNumber *)getFeatureVariableBoolean:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes; + attributes:(nullable NSDictionary *)attributes; /** * Gets double feature variable value. @@ -161,7 +161,7 @@ typedef NS_ENUM(NSInteger, OPTLYLiveVariableError) { - (nullable NSNumber *)getFeatureVariableDouble:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes; + attributes:(nullable NSDictionary *)attributes; /** * Gets integer feature variable value. @@ -174,7 +174,7 @@ typedef NS_ENUM(NSInteger, OPTLYLiveVariableError) { - (nullable NSNumber *)getFeatureVariableInteger:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes; + attributes:(nullable NSDictionary *)attributes; /** * Gets string feature variable value. @@ -187,7 +187,7 @@ typedef NS_ENUM(NSInteger, OPTLYLiveVariableError) { - (nullable NSString *)getFeatureVariableString:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes; + attributes:(nullable NSDictionary *)attributes; /** * Get array of features that are enabled for the user. @@ -196,7 +196,7 @@ typedef NS_ENUM(NSInteger, OPTLYLiveVariableError) { * @return NSArray Array of feature keys that are enabled for the user. */ - (NSArray *_Nonnull)getEnabledFeatures:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes; + attributes:(nullable NSDictionary *)attributes; #pragma mark - trackEvent methods /** @@ -215,7 +215,7 @@ typedef NS_ENUM(NSInteger, OPTLYLiveVariableError) { */ - (void)track:(nonnull NSString *)eventKey userId:(nonnull NSString *)userId - attributes:(nonnull NSDictionary *)attributes; + attributes:(nonnull NSDictionary *)attributes; /** * Track an event @@ -236,7 +236,7 @@ typedef NS_ENUM(NSInteger, OPTLYLiveVariableError) { */ - (void)track:(nonnull NSString *)eventKey userId:(nonnull NSString *)userId - attributes:(nullable NSDictionary *)attributes + attributes:(nullable NSDictionary *)attributes eventTags:(nullable NSDictionary *)eventTags; //////////////////////////////////////////////////////////////// @@ -584,7 +584,7 @@ __attribute((deprecated("Use Optimizely initWithBuilder method instead."))); */ - (void)track:(nonnull NSString *)eventKey userId:(nonnull NSString *)userId - attributes:(nullable NSDictionary *)attributes + attributes:(nullable NSDictionary *)attributes eventTags:(nullable NSDictionary *)eventTags; @end diff --git a/OptimizelySDKCore/OptimizelySDKCore/Optimizely.m b/OptimizelySDKCore/OptimizelySDKCore/Optimizely.m index 6fd34ba74..90409e6d4 100644 --- a/OptimizelySDKCore/OptimizelySDKCore/Optimizely.m +++ b/OptimizelySDKCore/OptimizelySDKCore/Optimizely.m @@ -102,14 +102,14 @@ - (OPTLYVariation *)activate:(NSString *)experimentKey - (OPTLYVariation *)activate:(NSString *)experimentKey userId:(NSString *)userId - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes { return [self activate:experimentKey userId:userId attributes:attributes callback:nil]; } - (OPTLYVariation *)activate:(NSString *)experimentKey userId:(NSString *)userId - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes callback:(void (^)(NSError *))callback { __weak void (^_callback)(NSError *) = callback ? : ^(NSError *error) {}; @@ -180,7 +180,7 @@ - (OPTLYVariation *)variation:(NSString *)experimentKey - (OPTLYVariation *)variation:(NSString *)experimentKey userId:(NSString *)userId - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes { OPTLYVariation *bucketedVariation = [self.config getVariationForExperiment:experimentKey userId:userId @@ -206,7 +206,7 @@ - (BOOL)setForcedVariation:(nonnull NSString *)experimentKey #pragma mark - Feature Flag Methods -- (BOOL)isFeatureEnabled:(NSString *)featureKey userId:(NSString *)userId attributes:(nullable NSDictionary *)attributes { +- (BOOL)isFeatureEnabled:(NSString *)featureKey userId:(NSString *)userId attributes:(nullable NSDictionary *)attributes { if ([Optimizely isEmptyString:userId]) { [self.logger logMessage:OPTLYLoggerMessagesFeatureDisabledUserIdInvalid withLevel:OptimizelyLogLevelError]; return false; @@ -251,7 +251,7 @@ - (NSString *)getFeatureVariableValueForType:(NSString *)variableType featureKey:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes { + attributes:(nullable NSDictionary *)attributes { if ([Optimizely isEmptyString:featureKey]) { [self.logger logMessage:OPTLYLoggerMessagesFeatureVariableValueFlagKeyInvalid withLevel:OptimizelyLogLevelError]; return nil; @@ -307,7 +307,7 @@ - (NSString *)getFeatureVariableValueForType:(NSString *)variableType - (NSNumber *)getFeatureVariableBoolean:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes { + attributes:(nullable NSDictionary *)attributes { NSString *variableValue = [self getFeatureVariableValueForType:FeatureVariableTypeBoolean featureKey:featureKey @@ -324,7 +324,7 @@ - (NSNumber *)getFeatureVariableBoolean:(nullable NSString *)featureKey - (NSNumber *)getFeatureVariableDouble:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes { + attributes:(nullable NSDictionary *)attributes { NSString *variableValue = [self getFeatureVariableValueForType:FeatureVariableTypeDouble featureKey:featureKey @@ -342,7 +342,7 @@ - (NSNumber *)getFeatureVariableDouble:(nullable NSString *)featureKey - (NSNumber *)getFeatureVariableInteger:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes { + attributes:(nullable NSDictionary *)attributes { NSString *variableValue = [self getFeatureVariableValueForType:FeatureVariableTypeInteger featureKey:featureKey @@ -359,7 +359,7 @@ - (NSNumber *)getFeatureVariableInteger:(nullable NSString *)featureKey - (NSString *)getFeatureVariableString:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes { + attributes:(nullable NSDictionary *)attributes { return [self getFeatureVariableValueForType:FeatureVariableTypeString featureKey:featureKey variableKey:variableKey @@ -368,7 +368,7 @@ - (NSString *)getFeatureVariableString:(nullable NSString *)featureKey } -(NSArray *)getEnabledFeatures:(NSString *)userId - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { NSMutableArray *enabledFeatures = [NSMutableArray new]; for (OPTLYFeatureFlag *feature in self.config.featureFlags) { @@ -388,7 +388,7 @@ - (void)track:(NSString *)eventKey userId:(NSString *)userId { - (void)track:(NSString *)eventKey userId:(NSString *)userId - attributes:(NSDictionary * )attributes { + attributes:(NSDictionary * )attributes { [self track:eventKey userId:userId attributes:attributes eventTags:nil]; } @@ -400,7 +400,7 @@ - (void)track:(NSString *)eventKey - (void)track:(NSString *)eventKey userId:(NSString *)userId - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes eventTags:(NSDictionary *)eventTags { if ([Optimizely isEmptyString:eventKey]) { @@ -838,7 +838,7 @@ - (NSString *)description { - (OPTLYVariation *)sendImpressionEventFor:(OPTLYExperiment *)experiment variation:(OPTLYVariation *)variation userId:(NSString *)userId - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes callback:(void (^)(NSError *))callback { // send impression event @@ -882,7 +882,7 @@ - (OPTLYVariation *)sendImpressionEventFor:(OPTLYExperiment *)experiment */ - (NSArray *)decisionsFor:(OPTLYEvent *)event userId:(NSString *)userId - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { NSArray *experimentIds = event.experimentIds; diff --git a/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYConditionTest.m b/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYConditionTest.m index 1dc0efda8..06ba0a8dc 100644 --- a/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYConditionTest.m +++ b/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYConditionTest.m @@ -21,7 +21,7 @@ @interface OPTLYConditionTest : XCTestCase -@property NSDictionary *testUserAttributes; +@property NSDictionary *testUserAttributes; @end @@ -175,7 +175,7 @@ - (void)testDeserializeConditions { OPTLYBaseCondition *baseCondition = orCondition.subConditions[0]; XCTAssertTrue([baseCondition.name isEqualToString:@"browser_type"]); XCTAssertTrue([baseCondition.type isEqualToString:@"custom_dimension"]); - XCTAssertTrue([baseCondition.value isEqualToString:@"chrome"]); + XCTAssertTrue([baseCondition.value isEqual:@"chrome"]); XCTAssertTrue([conditionsArray[0] evaluateConditionsWithAttributes:self.testUserAttributes]); } diff --git a/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYDecisionServiceTest.m b/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYDecisionServiceTest.m index cda019864..fdddef6c5 100644 --- a/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYDecisionServiceTest.m +++ b/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYDecisionServiceTest.m @@ -59,6 +59,11 @@ static NSString * const kAttributeKey = @"browser_type"; static NSString * const kAttributeValue = @"firefox"; static NSString * const kAttributeValueChrome = @"chrome"; +static NSString * const kAttributeKeyBrowserBuildNumberInt = @"browser_buildnumber"; +static NSString * const kAttributeKeyBrowserVersionNumberInt = @"browser_version"; +static NSString * const kAttributeKeyIsBetaVersionBool = @"browser_isbeta"; + + // experiment with no audience static NSString * const kExperimentNoAudienceKey = @"testExperiment4"; @@ -283,6 +288,27 @@ - (void)testGetVariationNoAudience [userProfileServiceMock stopMocking]; } +// if bucketingId attribute is not a string. Defaulted to userId +- (void)testGetVariationWithInvalidBucketingId { + NSDictionary *attributes = @{OptimizelyBucketId: @YES}; + OPTLYExperiment *experiment = [self.config getExperimentForKey:kExperimentNoAudienceKey]; + OPTLYVariation *variation = [self.decisionService getVariation:kUserId experiment:experiment attributes:attributes]; + XCTAssertNotNil(variation, @"Get variation with invalid bucketing Id should use userId for bucketing."); + XCTAssertEqualObjects(variation.variationKey, kExperimentWithAudienceVariationKey, + @"Get variation with invalid bucketing Id should return: %@, but instead returns: %@.", + kExperimentWithAudienceVariationKey, variation.variationKey); +} + +- (void)testGetVariationAcceptAllTypeAttributes { + + OPTLYExperiment *experiment = [self.config getExperimentForKey:kExperimentNoAudienceKey]; + OPTLYVariation *variation = [self.decisionService getVariation:kUserId experiment:experiment attributes:@{kAttributeKey: kAttributeValue, + kAttributeKeyBrowserBuildNumberInt: @(10), kAttributeKeyBrowserVersionNumberInt: @(0.23), kAttributeKeyIsBetaVersionBool: @(YES)}]; + + XCTAssertNotNil(variation, @"Get variation with supported types should return valid variation."); + XCTAssertEqualObjects(variation.variationKey, kExperimentWithAudienceVariationKey); +} + #pragma mark - setForcedVariation // whitelisted user should return the whitelisted variation for getVariation overridden by call to setForcedVariation diff --git a/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYEventBuilderTest.m b/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYEventBuilderTest.m index b5d44dcc7..e9f252ebf 100644 --- a/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYEventBuilderTest.m +++ b/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYEventBuilderTest.m @@ -42,10 +42,12 @@ static NSInteger kEventRevenue = 88; static double kEventValue = 123.456; static NSString * const kTotalRevenueId = @"6316734272"; -static NSString * const kAttributeId = @"6359881003"; static NSString * const kAttributeKeyBrowserType = @"browser_type"; static NSString * const kAttributeValueFirefox = @"firefox"; static NSString * const kAttributeValueChrome = @"chrome"; +static NSString * const kAttributeKeyBrowserVersion = @"browser_version"; +static NSString * const kAttributeKeyBrowserBuildNumber = @"browser_build_number"; +static NSString * const kAttributeKeyBrowserIsDefault = @"browser_is_default"; // events with experiment, but no audiences static NSString * const kEventWithoutAudienceName = @"testEvent"; @@ -106,6 +108,7 @@ @interface OPTLYEventBuilderTest : XCTestCase @property (nonatomic, strong) OPTLYBucketer *bucketer; @property (nonatomic, strong) NSDate *begTimestamp; @property (nonatomic, strong) NSMutableDictionary *attributes; +@property (nonatomic, strong) NSMutableDictionary *attributeIds; @property (nonatomic, strong) NSMutableDictionary *reservedAttributes; @end @@ -122,8 +125,17 @@ - (void)setUp { self.config = [[OPTLYProjectConfig alloc] initWithDatafile:datafile]; self.eventBuilder = [[OPTLYEventBuilderDefault alloc] initWithConfig:self.config]; self.bucketer = [[OPTLYBucketer alloc] initWithConfig:self.config]; - self.attributes = [NSMutableDictionary dictionaryWithDictionary:@{kAttributeKeyBrowserType : kAttributeValueFirefox}]; - self.reservedAttributes = [NSMutableDictionary dictionaryWithDictionary:@{OptimizelyUserAgent : @YES}]; + self.attributes = [NSMutableDictionary dictionaryWithDictionary:@{kAttributeKeyBrowserType : kAttributeValueFirefox, + kAttributeKeyBrowserVersion : @(68.1), + kAttributeKeyBrowserBuildNumber : @(106), + kAttributeKeyBrowserIsDefault : @YES + }]; + self.attributeIds = [NSMutableDictionary dictionaryWithDictionary:@{kAttributeKeyBrowserType : @"6359881003", + kAttributeKeyBrowserVersion : @"6359881004", + kAttributeKeyBrowserBuildNumber : @"6359881005", + kAttributeKeyBrowserIsDefault : @"6359881006" + }]; + self.reservedAttributes = [NSMutableDictionary dictionaryWithDictionary:@{OptimizelyBotFiltering : @YES}]; eventWithoutAudience = [self.config getEventForKey:kEventWithoutAudienceName]; eventWithAudience = [self.config getEventForKey:kEventWithAudienceName]; experimentWithAudience = [self.config getExperimentForKey:kExperimentWithAudienceKey]; @@ -681,7 +693,7 @@ - (void)testBuildConversionTicketWithAnonymizeIPFalse { #pragma mark - Test Invalid Attribute - (void)testCreateConversionEventWithEmptyAttributeValue { - NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithDictionary:@{OptimizelyBucketId : @""}]; + NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithDictionary:@{kAttributeKeyBrowserType : @""}]; NSArray *decisions = [self.optimizely decisionsFor:eventWithoutAudience userId:kUserId attributes:attributes]; NSDictionary *params = [self.eventBuilder buildConversionEventForUser:kUserId event:eventWithoutAudience @@ -780,7 +792,7 @@ - (void)testImpressionEventWithBotFilteringFalse { variation:bucketedVariation attributes:self.attributes]; - [self.reservedAttributes setObject:@(NO) forKey:OptimizelyUserAgent]; + [self.reservedAttributes setObject:@(NO) forKey:OptimizelyBotFiltering]; [self.attributes addEntriesFromDictionary:self.reservedAttributes]; [self checkTicket:ImpressionTicket forParams:impressionEventTicketParams @@ -831,7 +843,7 @@ - (void)testConversionEventWithBotFilteringFalse { eventTags:nil attributes:self.attributes]; - [self.reservedAttributes setObject:@(NO) forKey:OptimizelyUserAgent]; + [self.reservedAttributes setObject:@(NO) forKey:OptimizelyBotFiltering]; [self.attributes addEntriesFromDictionary:self.reservedAttributes]; [self checkTicket:ConversionTicket forParams:params @@ -921,6 +933,26 @@ - (void)testBuildImpressionEventTicketWithNoConfig { XCTAssert([impressionEventTicketParams count] == 0, @"parameters should not be created with no config."); } +- (void)testBuildImpressionEventTicketWithAllTypeAttributes { + OPTLYVariation *bucketedVariation = [self.config getVariationForExperiment:kExperimentWithAudienceKey + userId:kUserId + attributes:self.attributes + bucketer:self.bucketer]; + + NSDictionary *impressionEventTicketParams = [self.eventBuilder buildImpressionEventForUser:kUserId + experiment:experimentWithAudience + variation:bucketedVariation + attributes:self.attributes]; + [self.attributes addEntriesFromDictionary:self.reservedAttributes]; + [self checkTicket:ImpressionTicket + forParams:impressionEventTicketParams + config:self.config + experimentKeys:@[kExperimentWithAudienceKey] + variationId:bucketedVariation.variationId + attributes:self.attributes + eventKey:nil eventTags:nil tags:nil bucketer:nil userId:kUserId]; +} + #pragma mark - Helper Methods - (void)commonBuildConversionTicketTest:(NSDictionary*)eventTags @@ -1179,36 +1211,26 @@ - (void)checkVisitors:(NSArray *)visitors withAttributes:(NSDictionary *)attribu } - (void)checkUserFeatures:(NSArray *)userFeatures - withAttributes:(NSDictionary *)attributes -{ - NSMutableDictionary *filteredAttributes = [[NSMutableDictionary alloc] initWithDictionary:attributes]; - - for (NSString *attributeKey in [attributes allKeys]) { - id value = attributes[attributeKey]; - NSString *attributeValue = [NSString stringWithFormat:@"%@", value]; - if ([attributeValue length] ==0) { - [filteredAttributes removeObjectForKey:attributeKey]; - } - } + withAttributes:(NSDictionary *)attributes { NSUInteger numberOfFeatures = [userFeatures count]; - NSUInteger numberOfAttributes = [filteredAttributes count]; + NSUInteger numberOfAttributes = [attributes count]; XCTAssert(numberOfFeatures == numberOfAttributes, @"Incorrect number of user features."); if (numberOfFeatures == numberOfAttributes) { -// NSSortDescriptor *featureNameDescriptor = [[NSSortDescriptor alloc] initWithKey:OPTLYEventParameterKeysFeaturesName ascending:YES]; -// NSArray *sortedUserFeaturesByName = [userFeatures sortedArrayUsingDescriptors:@[featureNameDescriptor]]; -// -// NSSortDescriptor *attributeKeyDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES]; -// NSArray *sortedAttributeKeys = [[filteredAttributes allKeys] sortedArrayUsingDescriptors:@[attributeKeyDescriptor]]; + NSSortDescriptor *featureNameDescriptor = [[NSSortDescriptor alloc] initWithKey:OPTLYEventParameterKeysFeaturesKey ascending:YES]; + NSArray *sortedUserFeaturesByName = [userFeatures sortedArrayUsingDescriptors:@[featureNameDescriptor]]; + + NSSortDescriptor *attributeKeyDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES]; + NSArray *sortedAttributeKeys = [[attributes allKeys] sortedArrayUsingDescriptors:@[attributeKeyDescriptor]]; for (NSUInteger i = 0; i < numberOfAttributes; i++) { - NSDictionary *params = userFeatures[i]; + NSDictionary *params = sortedUserFeaturesByName[i]; - NSString *anAttributeKey = [filteredAttributes allKeys][i]; - id anAttributeValue = [filteredAttributes objectForKey:anAttributeKey]; + NSString *anAttributeKey = sortedAttributeKeys[i]; + id anAttributeValue = [attributes objectForKey:anAttributeKey]; NSString *featureName = params[OPTLYEventParameterKeysFeaturesKey]; NSString *featureID = params[OPTLYEventParameterKeysFeaturesId]; @@ -1219,7 +1241,7 @@ - (void)checkUserFeatures:(NSArray *)userFeatures // check name XCTAssert([featureName isEqualToString:anAttributeKey ], @"Incorrect feature name."); // check id - XCTAssert([featureID isEqualToString:kAttributeId], @"Incorrect feature id: %@.", featureID); + XCTAssert([featureID isEqualToString:self.attributeIds[featureName]], @"Incorrect feature id: %@.", featureID); } // check type diff --git a/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYNotificationCenterTest.m b/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYNotificationCenterTest.m index 02709a679..37af87424 100644 --- a/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYNotificationCenterTest.m +++ b/OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYNotificationCenterTest.m @@ -30,6 +30,12 @@ static NSString *const kExperimentKey = @"testExperimentWithFirefoxAudience"; static NSString *const kVariationId = @"6362476365"; +static NSString *const kAttributeKeyBrowserName = @"browser_name"; +static NSString *const kAttributeValueBrowserValue = @"firefox"; +static NSString *const kAttributeKeyBrowserBuildNo = @"browser_buildno"; +static NSString *const kAttributeKeyBrowserVersion = @"browser_version"; +static NSString *const kAttributeKeyObject = @"dummy_object"; + @interface OPTLYNotificationCenter() // notification Count represeting total number of notifications. @property (nonatomic, readonly) NSUInteger notificationsCount; @@ -57,19 +63,19 @@ - (void)setUp { }]]; self.notificationCenter = [[OPTLYNotificationCenter alloc] initWithProjectConfig:self.projectConfig]; __weak typeof(self) weakSelf = self; - weakSelf.activateNotification = ^(OPTLYExperiment *experiment, NSString *userId, NSDictionary *attributes, OPTLYVariation *variation, NSDictionary *event) { + weakSelf.activateNotification = ^(OPTLYExperiment *experiment, NSString *userId, NSDictionary *attributes, OPTLYVariation *variation, NSDictionary *event) { NSString *logMessage = @"activate notification called with %@"; [weakSelf.projectConfig.logger logMessage:[NSString stringWithFormat:logMessage, experiment.experimentKey] withLevel:OptimizelyLogLevelInfo]; [weakSelf.projectConfig.logger logMessage:[NSString stringWithFormat:logMessage, userId] withLevel:OptimizelyLogLevelInfo]; [weakSelf.projectConfig.logger logMessage:[NSString stringWithFormat:logMessage, variation.variationKey] withLevel:OptimizelyLogLevelInfo]; }; - weakSelf.anotherActivateNotification = ^(OPTLYExperiment *experiment, NSString *userId, NSDictionary *attributes, OPTLYVariation *variation, NSDictionary *event) { + weakSelf.anotherActivateNotification = ^(OPTLYExperiment *experiment, NSString *userId, NSDictionary *attributes, OPTLYVariation *variation, NSDictionary *event) { NSString *logMessage = @"activate notification called with %@"; [weakSelf.projectConfig.logger logMessage:[NSString stringWithFormat:logMessage, experiment.experimentKey] withLevel:OptimizelyLogLevelInfo]; [weakSelf.projectConfig.logger logMessage:[NSString stringWithFormat:logMessage, userId] withLevel:OptimizelyLogLevelInfo]; [weakSelf.projectConfig.logger logMessage:[NSString stringWithFormat:logMessage, variation.variationKey] withLevel:OptimizelyLogLevelInfo]; }; - weakSelf.trackNotification = ^(NSString *eventKey, NSString *userId, NSDictionary *attributes, NSDictionary *eventTags, NSDictionary *event) { + weakSelf.trackNotification = ^(NSString *eventKey, NSString *userId, NSDictionary *attributes, NSDictionary *eventTags, NSDictionary *event) { NSString *logMessage = @"track notification called with %@"; [weakSelf.projectConfig.logger logMessage:[NSString stringWithFormat:logMessage, eventKey] withLevel:OptimizelyLogLevelInfo]; [weakSelf.projectConfig.logger logMessage:[NSString stringWithFormat:logMessage, userId] withLevel:OptimizelyLogLevelInfo]; @@ -203,6 +209,37 @@ - (void)testSendNotifications { OCMReject(_anotherActivateNotification); } +- (void) testSendNotificationWithAnyAttributes { + // Add activate notifications. + [_notificationCenter addActivateNotificationListener:_activateNotification]; + + // Add track notification. + [_notificationCenter addTrackNotificationListener:_trackNotification]; + + // Fire decision type notifications. + OPTLYExperiment *experiment = [_projectConfig getExperimentForKey:kExperimentKey]; + OPTLYVariation *variation = [experiment getVariationForVariationId:kVariationId]; + NSDictionary *attributes = @{ + kAttributeKeyBrowserName: kAttributeValueBrowserValue, + kAttributeKeyBrowserBuildNo: @(10), + kAttributeKeyBrowserVersion: @(0.3), + kAttributeKeyObject: @{ + kAttributeKeyBrowserName: kAttributeValueBrowserValue, + } + }; + NSDictionary *logEvent = [NSDictionary new]; + NSString *userId = [NSString stringWithFormat:@"%@", kUserId]; + + // Verify that only the registered notifications of decision type are called. + [_notificationCenter sendNotifications:OPTLYNotificationTypeActivate args:@[experiment, userId, attributes, variation, logEvent]]; + + NSString *eventKey = [NSString stringWithFormat:@"%@", kUserId]; + NSDictionary *eventTags = [NSDictionary new]; + + // Verify that only the registered notifications of track type are called. + [_notificationCenter sendNotifications:OPTLYNotificationTypeTrack args:@[eventKey, userId, attributes, eventTags, logEvent]]; +} + - (void)testSendNotificationsWithInvalidArgs { // Add activate notifications. diff --git a/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m b/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m index af7d7fd94..53469ad35 100644 --- a/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m +++ b/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m @@ -52,15 +52,21 @@ // variation IDs static NSString * const kVariationIDForWhitelisting = @"variation4"; +// attribute keys +static NSString * const kAttributeKeyBrowserType = @"browser_type"; +static NSString * const kAttributeKeyBrowserVersion = @"browser_version"; +static NSString * const kAttributeKeyBrowserBuildNumber = @"browser_build_number"; +static NSString * const kAttributeKeyBrowserIsDefault = @"browser_is_default"; + @interface OPTLYNotificationTest : NSObject @end @implementation OPTLYNotificationTest --(void)onActivate:(OPTLYExperiment *)experiment userId:(NSString *)userId attributes:(NSDictionary *)attributes variation:(OPTLYVariation *)variation event:(NSDictionary *)event { +-(void)onActivate:(OPTLYExperiment *)experiment userId:(NSString *)userId attributes:(NSDictionary *)attributes variation:(OPTLYVariation *)variation event:(NSDictionary *)event { } --(void)onTrack:(NSString *)eventKey userId:(NSString *)userId attributes:(NSDictionary *)attributes eventTags:(NSDictionary *)eventTags event:(NSDictionary *)event { +-(void)onTrack:(NSString *)eventKey userId:(NSString *)userId attributes:(NSDictionary *)attributes eventTags:(NSDictionary *)eventTags event:(NSDictionary *)event { } @end @@ -68,18 +74,18 @@ -(void)onTrack:(NSString *)eventKey userId:(NSString *)userId attributes:(NSDict @interface Optimizely(test) - (OPTLYVariation *)activate:(NSString *)experimentKey userId:(NSString *)userId - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes callback:(void (^)(NSError *))callback; - (OPTLYVariation *)sendImpressionEventFor:(OPTLYExperiment *)experiment variation:(OPTLYVariation *)variation userId:(NSString *)userId - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes callback:(void (^)(NSError *))callback; - (NSString *)getFeatureVariableValueForType:(NSString *)variableType featureKey:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes; + attributes:(nullable NSDictionary *)attributes; @end @interface OptimizelyTest : XCTestCase @@ -104,7 +110,12 @@ - (void)setUp { XCTAssertNotNil(self.optimizely); - self.attributes = @{@"browser_type": @"firefox"}; + self.attributes = @{ + kAttributeKeyBrowserType : @"firefox", + kAttributeKeyBrowserVersion : @(68.1), + kAttributeKeyBrowserBuildNumber : @(106), + kAttributeKeyBrowserIsDefault : @YES + }; } - (void)tearDown { @@ -158,6 +169,30 @@ - (void)testVariationWithAudience { XCTAssertNotNil(variation); } +- (void)testVariationWithAudienceTypeInteger { + NSString *experimentKey = @"testExperimentWithFirefoxAudience"; + OPTLYExperiment *experiment = [self.optimizely.config getExperimentForKey:experimentKey]; + XCTAssertNotNil(experiment); + OPTLYVariation *variation; + NSDictionary *attributesWithUserNotInAudience = @{kAttributeKeyBrowserBuildNumber : @(601)}; + NSDictionary *attributesWithUserInAudience = @{kAttributeKeyBrowserBuildNumber : @(106)}; + + // test get experiment without attributes + variation = [self.optimizely variation:experimentKey userId:kUserId]; + XCTAssertNil(variation); + // test get experiment with bad attributes + variation = [self.optimizely variation:experimentKey + userId:kUserId + attributes:attributesWithUserNotInAudience]; + XCTAssertNil(variation); + // test get experiment with good attributes + variation = [self.optimizely variation:experimentKey + userId:kUserId + attributes:attributesWithUserInAudience]; + XCTAssertNotNil(variation); +} + + // Test initializing with older V2 datafile - (void)testOlderV2Datafile { NSData *datafile = [OPTLYTestHelper loadJSONDatafileIntoDataObject:kV2TestDatafileName]; @@ -280,7 +315,7 @@ - (void)testOptimizelyPostsActivateExperimentNotification { OPTLYExperiment *experiment = [self.optimizely.config getExperimentForKey:kExperimentKeyForWhitelisting]; __block NSString *notificationExperimentKey = nil; - [self.optimizely.notificationCenter addActivateNotificationListener:^(OPTLYExperiment *experiment, NSString *userId, NSDictionary *attributes, OPTLYVariation *variation, NSDictionary *event) { + [self.optimizely.notificationCenter addActivateNotificationListener:^(OPTLYExperiment *experiment, NSString *userId, NSDictionary *attributes, OPTLYVariation *variation, NSDictionary *event) { notificationExperimentKey = experiment.experimentId; }]; @@ -358,7 +393,7 @@ - (void)testOptimizelyPostEventTrackNotification { NSString *eventKey = @"testEvent"; __block NSString *notificationEventKey = nil; - [self.optimizely.notificationCenter addTrackNotificationListener:^(NSString * _Nonnull eventKey, NSString * _Nonnull userId, NSDictionary * _Nonnull attributes, NSDictionary * _Nonnull eventTags, NSDictionary * _Nonnull event) { + [self.optimizely.notificationCenter addTrackNotificationListener:^(NSString * _Nonnull eventKey, NSString * _Nonnull userId, NSDictionary * _Nonnull attributes, NSDictionary * _Nonnull eventTags, NSDictionary * _Nonnull event) { notificationEventKey = eventKey; }]; @@ -373,7 +408,7 @@ - (void)testOptimizelyPostEventTrackNotificationWithEventTags { __block NSString *notificationEventKey = nil; __block NSDictionary *notificationEventTags = nil; - [self.optimizely.notificationCenter addTrackNotificationListener:^(NSString * _Nonnull eventKey, NSString * _Nonnull userId, NSDictionary * _Nonnull attributes, NSDictionary * _Nonnull eventTags, NSDictionary * _Nonnull event) { + [self.optimizely.notificationCenter addTrackNotificationListener:^(NSString * _Nonnull eventKey, NSString * _Nonnull userId, NSDictionary * _Nonnull attributes, NSDictionary * _Nonnull eventTags, NSDictionary * _Nonnull event) { notificationEventKey = eventKey; notificationEventTags = eventTags; }]; diff --git a/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyV3Test.m b/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyV3Test.m index 65605f41c..997b5427d 100644 --- a/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyV3Test.m +++ b/OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyV3Test.m @@ -80,11 +80,11 @@ @interface OPTLYNotificationV3Test : NSObject @end @implementation OPTLYNotificationV3Test --(void)onActivate:(OPTLYExperiment *)experiment userId:(NSString *)userId attributes:(NSDictionary *)attributes variation:(OPTLYVariation *)variation event:(NSDictionary *)event { +-(void)onActivate:(OPTLYExperiment *)experiment userId:(NSString *)userId attributes:(NSDictionary *)attributes variation:(OPTLYVariation *)variation event:(NSDictionary *)event { } --(void)onTrack:(NSString *)eventKey userId:(NSString *)userId attributes:(NSDictionary *)attributes eventTags:(NSDictionary *)eventTags event:(NSDictionary *)event { +-(void)onTrack:(NSString *)eventKey userId:(NSString *)userId attributes:(NSDictionary *)attributes eventTags:(NSDictionary *)eventTags event:(NSDictionary *)event { } @end @@ -97,18 +97,18 @@ - (nullable NSString *)variableString:(nonnull NSString *)variableKey callback:(void (^)(NSError *))callback; - (OPTLYVariation *)activate:(NSString *)experimentKey userId:(NSString *)userId - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes callback:(void (^)(NSError *))callback; - (OPTLYVariation *)sendImpressionEventFor:(OPTLYExperiment *)experiment variation:(OPTLYVariation *)variation userId:(NSString *)userId - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes callback:(void (^)(NSError *))callback; - (NSString *)getFeatureVariableValueForType:(NSString *)variableType featureKey:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes; + attributes:(nullable NSDictionary *)attributes; @end @interface OptimizelyV3Test : XCTestCase diff --git a/OptimizelySDKCore/OptimizelySDKCoreTests/TestData/test_data_10_experiments.json b/OptimizelySDKCore/OptimizelySDKCoreTests/TestData/test_data_10_experiments.json index c358acd5d..a9d7ffcdc 100644 --- a/OptimizelySDKCore/OptimizelySDKCoreTests/TestData/test_data_10_experiments.json +++ b/OptimizelySDKCore/OptimizelySDKCoreTests/TestData/test_data_10_experiments.json @@ -284,7 +284,8 @@ } ], "audienceIds": [ - "6369992312" + "6369992312", + "6378191387" ], "variations": [ { @@ -444,12 +445,29 @@ "conditions": "[\"and\", [\"or\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_dimension\", \"value\": \"ie\"}]]]", "id": "6378191386", "name": "IE users" + }, + { + "conditions": "[\"and\", [\"or\", [\"or\", {\"name\": \"browser_build_number\", \"type\": \"custom_dimension\", \"value\": 106}]]]", + "id": "6378191387", + "name": "Custom users" } ], "attributes": [ { "id": "6359881003", "key": "browser_type" + }, + { + "id": "6359881004", + "key": "browser_version" + }, + { + "id": "6359881005", + "key": "browser_build_number" + }, + { + "id": "6359881006", + "key": "browser_is_default" } ], "groups": [ diff --git a/OptimizelySDKShared/OptimizelySDKShared/OPTLYClient.m b/OptimizelySDKShared/OptimizelySDKShared/OPTLYClient.m index 290ad2769..d91b901ae 100644 --- a/OptimizelySDKShared/OptimizelySDKShared/OPTLYClient.m +++ b/OptimizelySDKShared/OptimizelySDKShared/OPTLYClient.m @@ -65,7 +65,7 @@ - (nullable OPTLYVariation *)activate:(nonnull NSString *)experimentKey - (nullable OPTLYVariation *)activate:(NSString *)experimentKey userId:(NSString *)userId - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { if (self.optimizely == nil) { [self.logger logMessage:OPTLYLoggerMessagesClientDummyOptimizelyError withLevel:OptimizelyLogLevelError]; @@ -88,7 +88,7 @@ - (nullable OPTLYVariation *)variation:(NSString *)experimentKey - (nullable OPTLYVariation *)variation:(NSString *)experimentKey userId:(NSString *)userId - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { if (self.optimizely == nil) { [self.logger logMessage:OPTLYLoggerMessagesClientDummyOptimizelyError withLevel:OptimizelyLogLevelError]; @@ -135,7 +135,7 @@ - (BOOL)setForcedVariation:(nonnull NSString *)experimentKey - (BOOL)isFeatureEnabled:(nullable NSString *)featureKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes { + attributes:(nullable NSDictionary *)attributes { if (self.optimizely == nil) { [self.logger logMessage:OPTLYLoggerMessagesClientDummyOptimizelyError withLevel:OptimizelyLogLevelError]; @@ -149,7 +149,7 @@ - (BOOL)isFeatureEnabled:(nullable NSString *)featureKey -(NSNumber *)getFeatureVariableBoolean:(NSString *)featureKey variableKey:(NSString *)variableKey userId:(NSString *)userId - attributes:(NSDictionary *)attributes { + attributes:(NSDictionary *)attributes { if (self.optimizely == nil) { [self.logger logMessage:OPTLYLoggerMessagesClientDummyOptimizelyError withLevel:OptimizelyLogLevelError]; @@ -166,7 +166,7 @@ -(NSNumber *)getFeatureVariableBoolean:(NSString *)featureKey - (NSNumber *)getFeatureVariableDouble:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes { + attributes:(nullable NSDictionary *)attributes { if (self.optimizely == nil) { [self.logger logMessage:OPTLYLoggerMessagesClientDummyOptimizelyError withLevel:OptimizelyLogLevelError]; @@ -184,7 +184,7 @@ - (NSNumber *)getFeatureVariableDouble:(nullable NSString *)featureKey - (NSNumber *)getFeatureVariableInteger:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes { + attributes:(nullable NSDictionary *)attributes { if (self.optimizely == nil) { [self.logger logMessage:OPTLYLoggerMessagesClientDummyOptimizelyError withLevel:OptimizelyLogLevelError]; @@ -199,7 +199,7 @@ - (NSNumber *)getFeatureVariableInteger:(nullable NSString *)featureKey } - (NSArray *)getEnabledFeatures:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes { + attributes:(nullable NSDictionary *)attributes { if (self.optimizely == nil) { [self.logger logMessage:OPTLYLoggerMessagesClientDummyOptimizelyError withLevel:OptimizelyLogLevelError]; @@ -215,7 +215,7 @@ - (NSNumber *)getFeatureVariableInteger:(nullable NSString *)featureKey - (NSString * _Nullable)getFeatureVariableString:(nullable NSString *)featureKey variableKey:(nullable NSString *)variableKey userId:(nullable NSString *)userId - attributes:(nullable NSDictionary *)attributes { + attributes:(nullable NSDictionary *)attributes { if (self.optimizely == nil) { [self.logger logMessage:OPTLYLoggerMessagesClientDummyOptimizelyError withLevel:OptimizelyLogLevelError]; @@ -236,7 +236,7 @@ - (void)track:(NSString *)eventKey userId:(NSString *)userId { - (void)track:(NSString *)eventKey userId:(NSString *)userId - attributes:(NSDictionary * )attributes { + attributes:(NSDictionary * )attributes { [self track:eventKey userId:userId attributes:attributes eventTags:nil]; } @@ -248,7 +248,7 @@ - (void)track:(NSString *)eventKey - (void)track:(NSString *)eventKey userId:(NSString *)userId - attributes:(NSDictionary *)attributes + attributes:(NSDictionary *)attributes eventTags:(NSDictionary *)eventTags { if (self.optimizely == nil) { [self.logger logMessage:OPTLYLoggerMessagesClientDummyOptimizelyError