Exemple #1
0
        public void TestIsUserInExperimentReturnsFalseIfNoAudienceInORConditionPass()
        {
            var experiment     = Config.GetExperimentFromKey("feat_with_var_test");
            var userAttributes = new UserAttributes
            {
                { "house", "Ravenclaw" },
                { "lasers", 50 },
                { "should_do_it", false }
            };

            Assert.False(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger));

            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206642"",""3988293898"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206642"" evaluated to FALSE"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3988293899"" with conditions: [""and"",[""or"",[""or"",{""name"":""favorite_ice_cream"",""type"":""custom_attribute"",""match"":""exists""}]]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3988293899"" evaluated to FALSE"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206646"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""exact"",""value"":45.5}]]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206646"" evaluated to FALSE"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206647"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""gt"",""value"":70}]]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206647"" evaluated to FALSE"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206644"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""lt"",""value"":1.0}]]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206644"" evaluated to FALSE"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206643"" with conditions: [""and"",[""or"",[""or"",{""name"":""should_do_it"",""type"":""custom_attribute"",""match"":""exact"",""value"":true}]]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206643"" evaluated to FALSE"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to FALSE"), Times.Once);
        }
Exemple #2
0
        public void TestIsUserInExperimentReturnsFalseIfAnyAudienceInANDConditionDoesNotPass()
        {
            var experiment     = Config.GetExperimentFromKey("audience_combinations_experiment");
            var userAttributes = new UserAttributes
            {
                { "house", "Gryffindor" },
                { "lasers", 50 }
            };

            Assert.False(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger));

            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""audience_combinations_experiment"": [""and"",[""or"",""3468206642"",""3988293898""],[""or"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206642"" evaluated to TRUE"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3988293899"" with conditions: [""and"",[""or"",[""or"",{""name"":""favorite_ice_cream"",""type"":""custom_attribute"",""match"":""exists""}]]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3988293899"" evaluated to FALSE"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206646"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""exact"",""value"":45.5}]]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206646"" evaluated to FALSE"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206647"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""gt"",""value"":70}]]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206647"" evaluated to FALSE"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206644"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""lt"",""value"":1.0}]]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206644"" evaluated to FALSE"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206643"" with conditions: [""and"",[""or"",[""or"",{""name"":""should_do_it"",""type"":""custom_attribute"",""match"":""exact"",""value"":true}]]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""should_do_it"",""value"":true} evaluated to UNKNOWN because no value was passed for user attribute ""should_do_it"""), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206643"" evaluated to UNKNOWN"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""audience_combinations_experiment"" collectively evaluated to FALSE"), Times.Once);
        }
        public void TestUserInExperimentAudienceMatch()
        {
            var userAttributes = new UserAttributes
            {
                { "device_type", "iPhone" },
                { "location", "San Francisco" }
            };

            Assert.IsTrue(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("test_experiment"), userAttributes));
        }
        public void TestIsUserInExperimentAudienceNoMatch()
        {
            var userAttributes = new UserAttributes
            {
                { "device_type", "Android" },
                { "location", "San Francisco" }
            };

            Assert.IsFalse(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("test_experiment"), null));
        }
Exemple #5
0
        public void TestIsUserInExperimentReturnsTrueWithNoAudience()
        {
            var experiment = Config.GetExperimentFromKey("feat_with_var_test");

            experiment.AudienceIds        = new string[] { };
            experiment.AudienceConditions = null;

            Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, null, Logger));

            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": []"), Times.Once);
        }
Exemple #6
0
        public void TestIsUserInExperimentReturnsTrueIfAllAudiencesInANDConditionPass()
        {
            var experiment     = Config.GetExperimentFromKey("audience_combinations_experiment");
            var userAttributes = new UserAttributes
            {
                { "house", "Gryffindor" },
                { "favorite_ice_cream", "walls" }
            };

            Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger));
        }
Exemple #7
0
        public void TestIsUserInExperimentReturnsFalseIfAudienceInNOTConditionGetsPassed()
        {
            var experiment = Config.GetExperimentFromKey("feat_with_var_test");

            experiment.AudienceIds = new string[] { "3468206645" };
            var userAttributes = new UserAttributes
            {
                { "browser_type", "Chrome" }
            };

            Assert.False(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger));
        }
Exemple #8
0
        public void TestIsUserInExperimentReturnsTrueWhenAudienceUsedInExperimentNoAttributesProvided()
        {
            var experiment = Config.GetExperimentFromKey("feat_with_var_test");

            experiment.AudienceIds = new string[] { "3468206648" };

            Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, null, Logger));
            Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, new UserAttributes {
            }, Logger));

            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206648""]"), Times.Exactly(2));
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206648"" with conditions: [""not"",{""name"":""input_value"",""type"":""custom_attribute"",""match"":""exists""}]"), Times.Exactly(2));
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Audience ""3468206648"" evaluated to TRUE"), Times.Exactly(2));
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to TRUE"), Times.Exactly(2));
        }
Exemple #9
0
        public void TestIsUserInExperimentReturnsTrueIfAnyAudienceInORConditionPass()
        {
            var experiment     = Config.GetExperimentFromKey("feat_with_var_test");
            var userAttributes = new UserAttributes
            {
                { "house", "Gryffindor" },
                { "lasers", 50 },
                { "should_do_it", false }
            };

            Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger));

            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206642"",""3988293898"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audience ""3468206642"" evaluated to TRUE"), Times.Once);
            LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to TRUE"), Times.Once);
        }
Exemple #10
0
        /// <summary>
        /// Helper function to validate all required conditions before performing activate or track.
        /// </summary>
        /// <param name="experiment">Experiment Object representing experiment</param>
        /// <param name="userId">string ID for user</param>
        /// <param name="userAttributes">associative array of Attributes for the user</param>
        private bool ValidatePreconditions(Experiment experiment, string userId, UserAttributes userAttributes = null)
        {
            if (!experiment.IsExperimentRunning)
            {
                Logger.Log(LogLevel.INFO, string.Format("Experiment {0} is not running.", experiment.Key));
                return(false);
            }

            if (experiment.IsUserInForcedVariation(userId))
            {
                return(true);
            }

            if (!ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger))
            {
                Logger.Log(LogLevel.INFO, string.Format("User \"{0}\" does not meet conditions to be in experiment \"{1}\".", userId, experiment.Key));
                return(false);
            }

            return(true);
        }
        /// <summary>
        /// Get a Variation of an Experiment for a user to be allocated into.
        /// </summary>
        /// <param name = "experiment" > The Experiment the user will be bucketed into.</param>
        /// <param name = "userId" > The userId of the user.
        /// <param name = "filteredAttributes" > The user's attributes. This should be filtered to just attributes in the Datafile.</param>
        /// <returns>The Variation the user is allocated into.</returns>
        public virtual Variation GetVariation(Experiment experiment, string userId, ProjectConfig config, UserAttributes filteredAttributes)
        {
            if (!ExperimentUtils.IsExperimentActive(experiment, Logger))
            {
                return(null);
            }

            // check if a forced variation is set
            var forcedVariation = GetForcedVariation(experiment.Key, userId, config);

            if (forcedVariation != null)
            {
                return(forcedVariation);
            }

            var variation = GetWhitelistedVariation(experiment, userId);

            if (variation != null)
            {
                return(variation);
            }
            UserProfile userProfile = null;

            if (UserProfileService != null)
            {
                try
                {
                    Dictionary <string, object> userProfileMap = UserProfileService.Lookup(userId);
                    if (userProfileMap != null && UserProfileUtil.IsValidUserProfileMap(userProfileMap))
                    {
                        userProfile = UserProfileUtil.ConvertMapToUserProfile(userProfileMap);
                        variation   = GetStoredVariation(experiment, userProfile, config);
                        if (variation != null)
                        {
                            return(variation);
                        }
                    }
                    else if (userProfileMap == null)
                    {
                        Logger.Log(LogLevel.INFO, "We were unable to get a user profile map from the UserProfileService.");
                    }
                    else
                    {
                        Logger.Log(LogLevel.ERROR, "The UserProfileService returned an invalid map.");
                    }
                }
                catch (Exception exception)
                {
                    Logger.Log(LogLevel.ERROR, exception.Message);
                    ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message));
                }
            }

            if (ExperimentUtils.IsUserInExperiment(config, experiment, filteredAttributes, Logger))
            {
                // Get Bucketing ID from user attributes.
                string bucketingId = GetBucketingId(userId, filteredAttributes);

                variation = Bucketer.Bucket(config, experiment, bucketingId, userId);

                if (variation != null && variation.Key != null)
                {
                    if (UserProfileService != null)
                    {
                        var bucketerUserProfile = userProfile ?? new UserProfile(userId, new Dictionary <string, Decision>());
                        SaveVariation(experiment, variation, bucketerUserProfile);
                    }
                    else
                    {
                        Logger.Log(LogLevel.INFO, "This decision will not be saved since the UserProfileService is null.");
                    }
                }

                return(variation);
            }
            Logger.Log(LogLevel.INFO, $"User \"{userId}\" does not meet conditions to be in experiment \"{experiment.Key}\".");

            return(null);
        }
        /// <summary>
        /// Try to bucket the user into a rollout rule.
        /// Evaluate the user for rules in priority order by seeing if the user satisfies the audience.
        /// Fall back onto the everyone else rule if the user is ever excluded from a rule due to traffic allocation.
        /// </summary>
        /// <param name = "featureFlag" >The feature flag the user wants to access.</param>
        /// <param name = "userId" >User Identifier</param>
        /// <param name = "filteredAttributes" >The user's attributes. This should be filtered to just attributes in the Datafile.</param>
        /// <returns>null if the user is not bucketed into the rollout or if the feature flag was not attached to a rollout.
        /// otherwise the FeatureDecision entity</returns>
        public virtual FeatureDecision GetVariationForFeatureRollout(FeatureFlag featureFlag, string userId, UserAttributes filteredAttributes, ProjectConfig config)
        {
            if (featureFlag == null)
            {
                Logger.Log(LogLevel.ERROR, "Invalid feature flag provided.");
                return(null);
            }

            if (string.IsNullOrEmpty(featureFlag.RolloutId))
            {
                Logger.Log(LogLevel.INFO, $"The feature flag \"{featureFlag.Key}\" is not used in a rollout.");
                return(null);
            }

            Rollout rollout = config.GetRolloutFromId(featureFlag.RolloutId);

            if (string.IsNullOrEmpty(rollout.Id))
            {
                Logger.Log(LogLevel.ERROR, $"The rollout with id \"{featureFlag.RolloutId}\" is not found in the datafile for feature flag \"{featureFlag.Key}\"");
                return(null);
            }

            if (rollout.Experiments == null || rollout.Experiments.Count == 0)
            {
                return(null);
            }

            Variation variation          = null;
            var       rolloutRulesLength = rollout.Experiments.Count;

            // Get Bucketing ID from user attributes.
            string bucketingId = GetBucketingId(userId, filteredAttributes);

            // For all rules before the everyone else rule
            for (int i = 0; i < rolloutRulesLength - 1; i++)
            {
                var rolloutRule = rollout.Experiments[i];
                if (ExperimentUtils.IsUserInExperiment(config, rolloutRule, filteredAttributes, Logger))
                {
                    variation = Bucketer.Bucket(config, rolloutRule, bucketingId, userId);
                    if (variation == null || string.IsNullOrEmpty(variation.Id))
                    {
                        break;
                    }

                    return(new FeatureDecision(rolloutRule, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT));
                }
                else
                {
                    var audience = config.GetAudience(rolloutRule.AudienceIds[0]);
                    Logger.Log(LogLevel.DEBUG, $"User \"{userId}\" does not meet the conditions to be in rollout rule for audience \"{audience.Name}\".");
                }
            }

            // Get the last rule which is everyone else rule.
            var everyoneElseRolloutRule = rollout.Experiments[rolloutRulesLength - 1];

            if (ExperimentUtils.IsUserInExperiment(config, everyoneElseRolloutRule, filteredAttributes, Logger))
            {
                variation = Bucketer.Bucket(config, everyoneElseRolloutRule, bucketingId, userId);
                if (variation != null && !string.IsNullOrEmpty(variation.Id))
                {
                    return(new FeatureDecision(everyoneElseRolloutRule, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT));
                }
            }
            else
            {
                var audience = config.GetAudience(everyoneElseRolloutRule.AudienceIds[0]);
                Logger.Log(LogLevel.DEBUG, $"User \"{userId}\" does not meet the conditions to be in rollout rule for audience \"{audience.Name}\".");
            }

            return(null);
        }
        public void TestIsUserInExperimentAudienceUsedInExperimentNoAttributesProvided()
        {
            Assert.IsFalse(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("test_experiment"), new UserAttributes()));

            Assert.IsFalse(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("test_experiment"), null));
        }
 public void TestIsUserInExperimentNoAudienceUsedInExperiment()
 {
     Assert.IsTrue(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("paused_experiment"), new UserAttributes()));
 }