diff --git a/.gitignore b/.gitignore index c4d97504..bf807ea3 100644 --- a/.gitignore +++ b/.gitignore @@ -244,3 +244,4 @@ ModelManifest.xml OptimizelySDK.Package/nuget.exe OptimizelySDK.Package/content OptimizelySDK.Package/lib + diff --git a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj index 8dc64e4e..9d3d441d 100644 --- a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj +++ b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj @@ -46,28 +46,28 @@ - + AudienceConditions\AndCondition.cs - + AudienceConditions\AudienceIdCondition.cs - + AudienceConditions\BaseCondition.cs - + AudienceConditions\SemanticVersion.cs - + AudienceConditions\EmptyCondition.cs - + AudienceConditions\ICondition.cs - + AudienceConditions\NotCondition.cs - + AudienceConditions\OrCondition.cs @@ -100,7 +100,7 @@ Entity\IdKeyEntity.cs - + OptimizelyJSON.cs @@ -139,7 +139,7 @@ Event\LogEvent.cs - + Event\ForwardingEventProcessor.cs @@ -178,19 +178,19 @@ Utils\ControlAttributes.cs - + Utils\ExceptionExtensions.cs - + Utils\ConditionParser.cs - + Utils\AttributeMatchTypes.cs - + Utils\DecisionInfoTypes.cs - + Utils\DateTimeUtils.cs @@ -227,108 +227,117 @@ Entity\Rollout - + ProjectConfig - + Config\DatafileProjectConfig - + Config\ProjectConfigManager - - Config\FallbackProjectConfigManager.cs - - - Event\Entity\ConversionEvent.cs - - - Event\Entity\Decision.cs - - - Event\Entity\EventBatch.cs - - - Event\Entity\EventContext.cs - - - Event\Entity\ImpressionEvent.cs - - - Event\Entity\Snapshot.cs - - - Event\Entity\SnapshotEvent.cs - - - Event\Entity\UserEvent.cs - - - Event\Entity\Visitor.cs - - - Event\Entity\DecisionMetadata.cs - - - Event\Entity\VisitorAttribute.cs - - - Event\EventFactory.cs - - - Event\UserEventFactory.cs - - - Event\EventProcessor.cs - - - OptlyConfig\OptimizelyConfig.cs - - - OptlyConfig\OptimizelyAttribute.cs - - - OptlyConfig\OptimizelyEvent.cs - - - OptlyConfig\OptimizelyExperiment.cs - - - OptlyConfig\OptimizelyAudience.cs - - - OptlyConfig\OptimizelyFeature.cs - - - OptlyConfig\OptimizelyVariable.cs - - - OptlyConfig\OptimizelyVariation.cs - - - OptlyConfig\OptimizelyConfigService.cs - - - OptlyConfig\IOptimizelyConfigManager.cs - - + + Config\FallbackProjectConfigManager.cs + + + Event\Entity\ConversionEvent.cs + + + Event\Entity\Decision.cs + + + Event\Entity\EventBatch.cs + + + Event\Entity\EventContext.cs + + + Event\Entity\ImpressionEvent.cs + + + Event\Entity\Snapshot.cs + + + Event\Entity\SnapshotEvent.cs + + + Event\Entity\UserEvent.cs + + + Event\Entity\Visitor.cs + + + Event\Entity\DecisionMetadata.cs + + + Event\Entity\VisitorAttribute.cs + + + Event\EventFactory.cs + + + Event\UserEventFactory.cs + + + Event\EventProcessor.cs + + + OptlyConfig\OptimizelyConfig.cs + + + OptlyConfig\OptimizelyAttribute.cs + + + OptlyConfig\OptimizelyEvent.cs + + + OptlyConfig\OptimizelyExperiment.cs + + + OptlyConfig\OptimizelyAudience.cs + + + OptlyConfig\OptimizelyFeature.cs + + + OptlyConfig\OptimizelyVariable.cs + + + OptlyConfig\OptimizelyVariation.cs + + + OptlyConfig\OptimizelyConfigService.cs + + + OptlyConfig\IOptimizelyConfigManager.cs + + OptimizelyDecisions\DecisionMessage.cs - + OptimizelyDecisions\OptimizelyDecideOption.cs - + OptimizelyDecisions\OptimizelyDecision.cs - + + OptimizelyDecisionContext.cs + + + ForcedDecisionsStore.cs + + + OptimizelyForcedDecision.cs + + OptimizelyUserContext.cs - - Entity\Result.cs - - - OptimizelyDecisions\DecisionReasons.cs - + + Entity\Result.cs + + + OptimizelyDecisions\DecisionReasons.cs + diff --git a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj index e5c64c6c..3dfdfbc9 100644 --- a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj +++ b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj @@ -48,28 +48,28 @@ - + AudienceConditions\AndCondition.cs - + AudienceConditions\AudienceIdCondition.cs - + AudienceConditions\BaseCondition.cs - + AudienceConditions\SemanticVersion.cs - + AudienceConditions\EmptyCondition.cs - + AudienceConditions\ICondition.cs - + AudienceConditions\NotCondition.cs - + AudienceConditions\OrCondition.cs @@ -102,7 +102,7 @@ Entity\IdKeyEntity.cs - + OptimizelyJSON.cs @@ -165,7 +165,7 @@ Utils\ConfigParser.cs - + Utils\ConditionParser.cs @@ -180,16 +180,16 @@ Utils\ControlAttributes.cs - + Utils\ExceptionExtensions.cs - + Utils\AttributeMatchTypes.cs - + Utils\DecisionInfoTypes.cs - + Utils\DateTimeUtils.cs @@ -226,126 +226,135 @@ Entity\Rollout - + ProjectConfig - + Config\DatafileProjectConfig - + Config\ProjectConfigManager - + Config\PollingProjectConfigManager - + ClientConfigHandler - + Config\HttpProjectConfigManager - - Config\FallbackProjectConfigManager.cs - - - OptimizelyFactory.cs - - - Event\Entity\ConversionEvent.cs - - - Event\Entity\Decision.cs - - - Event\Entity\EventBatch.cs - - - Event\Entity\EventContext.cs - - - Event\Entity\ImpressionEvent.cs - - - Event\Entity\Snapshot.cs - - - Event\Entity\SnapshotEvent.cs - - - Event\Entity\UserEvent.cs - - - Event\Entity\Visitor.cs - - - Event\Entity\VisitorAttribute.cs - - - Event\Entity\DecisionMetadata.cs - - - Event\EventFactory.cs - - - Event\UserEventFactory.cs - - - Event\BatchEventProcessor.cs - - - Event\EventProcessor.cs - - + + Config\FallbackProjectConfigManager.cs + + + OptimizelyFactory.cs + + + Event\Entity\ConversionEvent.cs + + + Event\Entity\Decision.cs + + + Event\Entity\EventBatch.cs + + + Event\Entity\EventContext.cs + + + Event\Entity\ImpressionEvent.cs + + + Event\Entity\Snapshot.cs + + + Event\Entity\SnapshotEvent.cs + + + Event\Entity\UserEvent.cs + + + Event\Entity\Visitor.cs + + + Event\Entity\VisitorAttribute.cs + + + Event\Entity\DecisionMetadata.cs + + + Event\EventFactory.cs + + + Event\UserEventFactory.cs + + + Event\BatchEventProcessor.cs + + + Event\EventProcessor.cs + + Event\ForwardingEventProcessor.cs - + OptlyConfig\OptimizelyConfig.cs - + OptlyConfig\OptimizelyAttribute.cs - + OptlyConfig\OptimizelyEvent.cs - + OptlyConfig\OptimizelyExperiment.cs - + OptlyConfig\OptimizelyFeature.cs - + OptlyConfig\OptimizelyVariable.cs - + OptlyConfig\OptimizelyVariation.cs - + OptlyConfig\OptimizelyAudience.cs - + OptlyConfig\OptimizelyConfigService.cs - + OptlyConfig\IOptimizelyConfigManager.cs - + OptimizelyDecisions\DecisionMessage.cs - + OptimizelyDecisions\DecisionReasons.cs - + OptimizelyDecisions\OptimizelyDecideOption.cs - + OptimizelyDecisions\OptimizelyDecision.cs - + OptimizelyUserContext.cs - - Entity\Result.cs - + + OptimizelyDecisionContext.cs + + + ForcedDecisionsStore.cs + + + OptimizelyForcedDecision.cs + + + Entity\Result.cs + diff --git a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj index 7d5e7c81..740e5d4d 100644 --- a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj +++ b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj @@ -1,152 +1,153 @@  - - - netstandard1.6 - 1.2.1 - ..\keypair.snk - false - - - - + + netstandard1.6 + 1.2.1 + ..\keypair.snk + false + + + ;$(DefineConstants);NetStandardIdentifier - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - AtomicProjectConfigManager.cs - - - OptimizelyFactory.cs - - - ConversionEvent.cs - - - EventBatch.cs - - - EventContext.cs - - - ImpressionEvent.cs - - - Snapshot.cs - - - SnapshotEvent.cs - - - UserEvent.cs - - - Visitor.cs - - - VisitorAttribute.cs - - - DecisionMetadata.cs - - - DecisionEvent.cs - - - EventFactory.cs - - - UserEventFactory.cs - - - EventProcessor.cs - - - Result.cs - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OptimizelyForcedDecision.cs + + + OptimizelyFactory.cs + + + ConversionEvent.cs + + + EventBatch.cs + + + EventContext.cs + + + ImpressionEvent.cs + + + Snapshot.cs + + + SnapshotEvent.cs + + + UserEvent.cs + + + Visitor.cs + + + VisitorAttribute.cs + + + DecisionMetadata.cs + + + DecisionEvent.cs + + + EventFactory.cs + + + UserEventFactory.cs + + + EventProcessor.cs + + + Result.cs + diff --git a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj index b43a21cb..0202fe84 100644 --- a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj +++ b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj @@ -304,6 +304,15 @@ OptimizelyUserContext.cs + + OptimizelyDecisionContext.cs + + + ForcedDecisionsStore.cs + + + OptimizelyForcedDecision.cs + Entity\Result.cs diff --git a/OptimizelySDK.Tests/Assertions.cs b/OptimizelySDK.Tests/Assertions.cs new file mode 100644 index 00000000..3f95e42b --- /dev/null +++ b/OptimizelySDK.Tests/Assertions.cs @@ -0,0 +1,728 @@ +/* + * Copyright 2017, 2019-2021, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using NUnit.Framework; +using OptimizelySDK.Entity; +using OptimizelySDK.OptimizelyDecisions; +using OptimizelySDK.OptlyConfig; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OptimizelySDK.Tests +{ + /// + /// Simplifies assertions and provides more insight into which particular piece is failing for a value. + /// Especially helpful for Test Driven Development + /// + [ExcludeFromCodeCoverage] + public class Assertions + { + #region Basic asserts + + public static bool HasItems(IEnumerable expected, IEnumerable actual, bool allowNull = true) + { + if (allowNull && expected == null && actual == null) + return false; + + Assert.AreEqual(expected.Count(), actual.Count()); + + return true; + } + + public static void AreEquivalent(IEnumerable expected, IEnumerable actual) + { + if (HasItems(expected, actual, false)) + { + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + Assert.AreEqual(z.Expected, z.Actual); + }; + } + } + + public static void AreEquivalent(Dictionary expected, Dictionary actual) + { + Assert.AreEqual(expected.Count, actual.Count); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEqual(z.Expected, z.Actual); + }; + } + + public static void AreEqual(KeyValuePair expected, KeyValuePair actual) + { + Assert.AreEqual(expected.Key, actual.Key); + Assert.AreEqual(expected.Value, actual.Value); + } + + public static void AreEqual(OptimizelyJSON expected, OptimizelyJSON actual) + { + Assert.AreEqual(expected.ToString(), actual.ToString()); + } + + private static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + { + Assert.AreEqual(expected.Key, actual.Key); + Assert.AreEqual(expected.Value, actual.Value); + } + + #endregion Basic asserts + + #region + + public static void AreEqual(OptimizelyForcedDecision expected, OptimizelyForcedDecision actual) + { + Assert.AreEqual(expected.VariationKey, actual.VariationKey); + } + + #endregion + + #region OptimizelyAttribute + + public static void AreEquivalent(OptimizelyAttribute[] expected, OptimizelyAttribute[] actual) + { + Assert.AreEqual(expected.Count(), actual.Count()); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEqual(z.Expected, z.Actual); + }; + } + + public static void AreEqual(OptimizelyAttribute expected, OptimizelyAttribute actual) + { + Assert.AreEqual(expected.Id, actual.Id); + Assert.AreEqual(expected.Key, actual.Key); + } + + #endregion OptimizelyAttribute + + #region OptimizelyAudience + + private static void AreEquivalent(OptimizelyAudience[] expected, OptimizelyAudience[] actual) + { + Assert.AreEqual(expected.Count(), actual.Count()); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEqual(z.Expected, z.Actual); + }; + } + + private static void AreEqual(OptimizelyAudience expected, OptimizelyAudience actual) + { + Assert.AreEqual(expected.Id, actual.Id); + Assert.AreEqual(expected.Name, actual.Name); + //AreEqual(expected.Conditions, actual.Conditions); + } + + #endregion OptimizelyAudience + + #region OptimizelyConfig + + public static void AreEqual(OptimizelyConfig expected, OptimizelyConfig actual) + { + AreEquivalent(expected.Attributes, actual.Attributes); + AreEquivalent(expected.Audiences, actual.Audiences); + Assert.AreEqual(expected.EnvironmentKey, actual.EnvironmentKey); + AreEquivalent(expected.Events, actual.Events); + AreEquivalent(expected.ExperimentsMap, actual.ExperimentsMap); + AreEquivalent(expected.FeaturesMap, actual.FeaturesMap); + Assert.AreEqual(expected.Revision, actual.Revision); + Assert.AreEqual(expected.SDKKey, actual.SDKKey); + } + + #endregion OptimizelyConfig + + #region OptimizelyUserContext + + public static void AreEqual(OptimizelyUserContext expected, OptimizelyUserContext actual) + { + Assert.AreEqual(expected.GetUserId(), actual.GetUserId()); + AreEquivalent(expected.GetAttributes(), actual.GetAttributes()); + } + + private static void AreEquivalent(UserAttributes expected, UserAttributes actual) + { + Assert.AreEqual(expected.Count, actual.Count); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEquivalent(z.Expected, z.Actual); + }; + } + + #endregion + + #region OptimizelyEvent + + public static void AreEquivalent(OptimizelyEvent[] expected, OptimizelyEvent[] actual) + { + Assert.AreEqual(expected.Count(), actual.Count()); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEqual(z.Expected, z.Actual); + }; + } + + private static void AreEqual(OptimizelyEvent expected, OptimizelyEvent actual) + { + Assert.AreEqual(expected.Id, actual.Id); + Assert.AreEqual(expected.Key, actual.Key); + AreEquivalent(expected.ExperimentIds, actual.ExperimentIds); + } + + #endregion OptimizelyEvent + + #region OptimizelyDecision + + public static void AreEqual(OptimizelyDecision expected, OptimizelyDecision actual) + { + Assert.AreEqual(expected.Enabled, actual.Enabled); + Assert.AreEqual(expected.FlagKey, actual.FlagKey); + AreEquivalent(expected.Reasons, actual.Reasons); + Assert.AreEqual(expected.RuleKey, actual.RuleKey); + AreEqual(expected.UserContext, actual.UserContext); + Assert.AreEqual(expected.VariationKey, actual.VariationKey); + } + + public static void AreEquivalent(IDictionary expected, IDictionary actual) + { + Assert.AreEqual(expected.Count, actual.Count); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEquivalent(z.Expected, z.Actual); + }; + } + + public static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + { + Assert.AreEqual(expected.Key, actual.Key); + AreEqual(expected.Value, actual.Value); + } + + #endregion + + #region OptimizelyExperiement + + public static void AreEquivalent(List expected, List actual) + { + Assert.AreEqual(expected.Count, actual.Count); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEqual(z.Expected, z.Actual); + }; + } + + public static void AreEqual(OptimizelyExperiment expected, OptimizelyExperiment actual) + { + Assert.AreEqual(expected.Id, actual.Id); + Assert.AreEqual(expected.Key, actual.Key); + Assert.AreEqual(expected.Audiences, actual.Audiences); + } + + public static void AreEquivalent(IDictionary expected, IDictionary actual) + { + Assert.AreEqual(expected.Count, actual.Count); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEquivalent(z.Expected, z.Actual); + }; + } + + public static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + { + Assert.AreEqual(expected.Key, actual.Key); + AreEqual(expected.Value, actual.Value); + } + + #endregion OptimizelyExperiement + + #region OptimizelyFeature + + public static void AreEquivalent(IDictionary expected, IDictionary actual) + { + Assert.AreEqual(expected.Count, actual.Count); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEquivalent(z.Expected, z.Actual); + }; + } + + public static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + { + Assert.AreEqual(expected.Key, actual.Key); + AreEqual(expected.Value, actual.Value); + } + + public static void AreEqual(OptimizelyFeature expected, OptimizelyFeature actual) + { + AreEquivalent(expected.DeliveryRules, actual.DeliveryRules); + AreEquivalent(expected.ExperimentRules, actual.ExperimentRules); + AreEquivalent(expected.ExperimentsMap, actual.ExperimentsMap); + Assert.AreEqual(expected.Id, actual.Id); + Assert.AreEqual(expected.Key, actual.Key); + AreEquivalent(expected.VariablesMap, actual.VariablesMap); + } + + #endregion OptimizelyFeature + + #region OptimizelyVariable + + public static void AreEquivalent(IDictionary expected, IDictionary actual) + { + Assert.AreEqual(expected.Count, actual.Count); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEquivalent(z.Expected, z.Actual); + }; + } + + public static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + { + Assert.AreEqual(expected.Key, actual.Key); + AreEqual(expected.Value, actual.Value); + } + + public static void AreEqual(OptimizelyVariable expected, OptimizelyVariable actual) + { + Assert.AreEqual(expected.Id, actual.Id); + Assert.AreEqual(expected.Key, actual.Key); + Assert.AreEqual(expected.Type, actual.Type); + Assert.AreEqual(expected.Value, actual.Value); + } + + #endregion OptimizelyVariable + + #region Experiment + + public static void AreEqual(Experiment expected, Experiment actual) + { + Assert.AreEqual(expected.AudienceConditions, actual.AudienceConditions); + Assert.AreEqual(expected.AudienceConditionsList, actual.AudienceConditionsList); + Assert.AreEqual(expected.AudienceConditionsString, actual.AudienceConditionsString); + AreEquivalent(expected.AudienceIds, actual.AudienceIds); + Assert.AreEqual(expected.AudienceIdsList, actual.AudienceIdsList); + Assert.AreEqual(expected.AudienceIdsString, actual.AudienceIdsString); + AreEquivalent(expected.ForcedVariations, actual.ForcedVariations); + Assert.AreEqual(expected.GroupId, actual.GroupId); + Assert.AreEqual(expected.GroupPolicy, actual.GroupPolicy); + Assert.AreEqual(expected.Id, actual.Id); + Assert.AreEqual(expected.IsExperimentRunning, actual.IsExperimentRunning); + Assert.AreEqual(expected.IsInMutexGroup, actual.IsInMutexGroup); + Assert.AreEqual(expected.Key, actual.Key); + Assert.AreEqual(expected.LayerId, actual.LayerId); + Assert.AreEqual(expected.Status, actual.Status); + AreEquivalent(expected.TrafficAllocation, actual.TrafficAllocation); + AreEquivalent(expected.UserIdToKeyVariations, actual.UserIdToKeyVariations); + AreEquivalent(expected.VariationIdToVariationMap, actual.VariationIdToVariationMap); + AreEquivalent(expected.VariationKeyToVariationMap, actual.VariationKeyToVariationMap); + AreEquivalent(expected.Variations, actual.Variations); + } + + #endregion Experiment + + #region FeatureDecision + + public static void AreEqual(FeatureDecision expected, FeatureDecision actual) + { + AreEqual(expected.Experiment, actual.Experiment); + } + + #endregion FeatureDecision + + #region FeatureFlags + + public static void AreEquivalent(Dictionary expected, Dictionary actual) + { + Assert.AreEqual(expected.Count, actual.Count); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEqual(z.Expected, z.Actual); + } + } + + public static void AreEqual(KeyValuePair expected, KeyValuePair actual) + { + Assert.AreEqual(expected.Key, actual.Key); + AreEqual(expected.Value, actual.Value); + } + + public static void AreEqual(FeatureFlag expected, FeatureFlag actual) + { + Assert.AreEqual(expected.Id, actual.Id); + Assert.AreEqual(expected.Key, actual.Key); + Assert.AreEqual(expected.RolloutId, actual.RolloutId); + AreEquivalent(expected.VariableKeyToFeatureVariableMap, actual.VariableKeyToFeatureVariableMap); + } + + #endregion FeatureFlags + + #region FeatureVariable + + public static void AreEqual(FeatureVariable expected, FeatureVariable actual) + { + Assert.AreEqual(expected.Id, actual.Id); + Assert.AreEqual(expected.Key, actual.Key); + Assert.AreEqual(expected.DefaultValue, actual.DefaultValue); + Assert.AreEqual(expected.Status, actual.Status); + Assert.AreEqual(expected.SubType, actual.SubType); + Assert.AreEqual(expected.Type, actual.Type); + } + + public static void AreEquivalent(Dictionary expected, Dictionary actual) + { + Assert.AreEqual(expected.Count, actual.Count); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEquivalent(z.Expected, z.Actual); + } + } + + public static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + { + Assert.AreEqual(expected.Key, actual.Key); + AreEqual(expected.Value, actual.Value); + } + + #endregion FeatureVariable + + #region Variations + + public static void AreEqual(Variation expected, Variation actual) + { + Assert.AreEqual(expected.Id, actual.Id); + Assert.AreEqual(expected.Key, actual.Key); + Assert.AreEqual(expected.FeatureEnabled, actual.FeatureEnabled); + + AreEquivalent(expected.FeatureVariableUsageInstances, actual.FeatureVariableUsageInstances); + AreEquivalent(expected.VariableIdToVariableUsageInstanceMap, actual.VariableIdToVariableUsageInstanceMap); + } + + public static void AreEquivalent(Dictionary expected, Dictionary actual) + { + Assert.AreEqual(expected.Count, actual.Count); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEqual(z.Expected, z.Actual); + }; + } + + public static void AreEquivalent(List>> expected, List>> actual) + { + Assert.AreEqual(expected.Count, actual.Count); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEquivalent(z.Expected, z.Actual); + }; + } + + public static void AreEquivalent(IDictionary> expected, IDictionary> actual) + { + Assert.AreEqual(expected.Count, actual.Count); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + AreEqual(z.Expected, z.Actual); + }; + } + + public static void AreEqual(KeyValuePair expected, KeyValuePair actual) + { + Assert.AreEqual(expected.Key, actual.Key); + AreEqual(expected.Value, actual.Value); + } + + public static void AreEqual(KeyValuePair> expected, KeyValuePair> actual) + { + Assert.AreEqual(expected.Key, actual.Key); + AreEquivalent(expected.Value, actual.Value); + } + + public static void AreEquivalent(KeyValuePair> expected, KeyValuePair> actual) + { + Assert.AreEqual(expected.Key, actual.Key); + AreEquivalent(expected.Value, actual.Value); + } + + public static void AreEquivalent(IEnumerable expected, IEnumerable actual) + { + Assert.AreEqual(expected.Count(), actual.Count()); + expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList().ForEach((item) => + { + AreEqual(item.Expected, item.Actual); + }); + } + + #endregion Variations + + #region FeatureVariableUsage + + public static void AreEquivalent(IEnumerable expected, IEnumerable actual) + { + if (HasItems(expected, actual)) + { + expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList().ForEach((item) => + { + AreEqual(item.Expected, item.Actual); + }); + } + } + + public static void AreEquivalent(Dictionary expected, Dictionary actual) + { + if (expected == null && actual == null) + { + return; + } + + Assert.AreEqual(expected.Count(), actual.Count()); + expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList().ForEach((item) => + { + AreEquivalent(item.Expected, item.Actual); + }); + } + + public static void AreEquivalent(KeyValuePair expected, KeyValuePair actual) + { + Assert.AreEqual(expected.Key, actual.Key); + AreEqual(expected.Value, actual.Value); + } + + public static void AreEqual(FeatureVariableUsage expected, FeatureVariableUsage actual) + { + Assert.AreEqual(expected.Id, actual.Id); + Assert.AreEqual(expected.Value, actual.Value); + } + + #endregion FeatureVariableUsage + + #region TrafficAllocation + + public static void AreEquivalent(IEnumerable expected, IEnumerable actual) + { + Assert.AreEqual(expected.Count(), actual.Count()); + var zipped = expected.Zip(actual, (e, a) => + { + return new + { + Expected = e, + Actual = a + }; + }).ToList(); + + foreach (var z in zipped) + { + Assert.AreEqual(z.Expected, z.Actual); + }; + } + + public static void AreEqual(TrafficAllocation expected, TrafficAllocation actual) + { + Assert.AreEqual(expected.EndOfRange, actual.EndOfRange); + Assert.AreEqual(expected.EntityId, actual.EntityId); + } + + #endregion TrafficAllocation + + #region DecisionReasons + + public static void AreEqual(DecisionReasons expected, DecisionReasons actual) + { + AreEquivalent(expected.ToReport(), actual.ToReport()); + } + + #endregion DecisionReasons + + #region Result T + + public static void AreEqual(Result expected, Result actual) + { + AreEqual(expected.DecisionReasons, actual.DecisionReasons); + if (expected.ResultObject != null && actual.ResultObject != null) + { + AreEqual(expected.ResultObject, actual.ResultObject); + } + + #endregion Result T + } + } +} diff --git a/OptimizelySDK.Tests/DecisionServiceTest.cs b/OptimizelySDK.Tests/DecisionServiceTest.cs index 41353b7a..fcf9fb22 100644 --- a/OptimizelySDK.Tests/DecisionServiceTest.cs +++ b/OptimizelySDK.Tests/DecisionServiceTest.cs @@ -14,17 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -using System; -using System.Collections.Generic; + using Moq; -using OptimizelySDK.Logger; -using OptimizelySDK.ErrorHandler; -using OptimizelySDK.Entity; using NUnit.Framework; using OptimizelySDK.Bucketing; -using OptimizelySDK.Utils; using OptimizelySDK.Config; +using OptimizelySDK.Entity; +using OptimizelySDK.ErrorHandler; +using OptimizelySDK.Logger; using OptimizelySDK.OptimizelyDecisions; +using OptimizelySDK.Utils; +using System.Collections.Generic; namespace OptimizelySDK.Tests { @@ -38,6 +38,7 @@ public class DecisionServiceTest private Mock UserProfileServiceMock; private Mock BucketerMock; private Mock DecisionServiceMock; + private Mock OptimizelyUserContextMock; private ProjectConfig ProjectConfig; private Experiment WhitelistedExperiment; @@ -56,7 +57,6 @@ public void SetUp() ErrorHandlerMock = new Mock(); UserProfileServiceMock = new Mock(); BucketerMock = new Mock(LoggerMock.Object); - ProjectConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, ErrorHandlerMock.Object); WhitelistedExperiment = ProjectConfig.ExperimentIdMap["224"]; WhitelistedVariation = WhitelistedExperiment.VariationKeyToVariationMap["vtag5"]; @@ -78,16 +78,23 @@ public void TestGetVariationForcedVariationPrecedesAudienceEval() Experiment experiment = ProjectConfig.Experiments[8]; Variation expectedVariation = experiment.Variations[0]; + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(GenericUserId); // user excluded without audiences and whitelisting - Assert.IsNull(decisionService.GetVariation(experiment, GenericUserId, ProjectConfig, new UserAttributes()).ResultObject); + Assert.IsNull(decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig).ResultObject); - var actualVariation = decisionService.GetVariation(experiment, WhitelistedUserId, ProjectConfig, new UserAttributes()); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(WhitelistedUserId); + var actualVariation = decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig); LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format("User \"{0}\" is forced in variation \"vtag5\".", WhitelistedUserId)), Times.Once); + // no attributes provided for a experiment that has an audience - Assert.IsTrue(TestData.CompareObjects(actualVariation.ResultObject, expectedVariation)); + Assertions.AreEqual(expectedVariation, actualVariation.ResultObject); + BucketerMock.Verify(_ => _.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } + [Test] public void TestGetVariationLogsErrorWhenUserProfileMapItsNull() { @@ -101,15 +108,21 @@ public void TestGetVariationLogsErrorWhenUserProfileMapItsNull() DecisionService decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, UserProfileServiceMock.Object, LoggerMock.Object); var options = new OptimizelyDecideOption[] { OptimizelyDecideOption.INCLUDE_REASONS }; - var variationResult = decisionService.GetVariation(experiment, GenericUserId, ProjectConfig, new UserAttributes(), options); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(GenericUserId); + + var variationResult = decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig, options); Assert.AreEqual(variationResult.DecisionReasons.ToReport(true)[0], "We were unable to get a user profile map from the UserProfileService."); Assert.AreEqual(variationResult.DecisionReasons.ToReport(true)[1], "Audiences for experiment \"etag3\" collectively evaluated to FALSE"); Assert.AreEqual(variationResult.DecisionReasons.ToReport(true)[2], "User \"genericUserId\" does not meet conditions to be in experiment \"etag3\"."); } - + [Test] public void TestGetVariationEvaluatesUserProfileBeforeAudienceTargeting() { + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(GenericUserId); + Experiment experiment = ProjectConfig.Experiments[8]; Variation variation = experiment.Variations[0]; @@ -123,13 +136,15 @@ public void TestGetVariationEvaluatesUserProfileBeforeAudienceTargeting() DecisionService decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, UserProfileServiceMock.Object, LoggerMock.Object); - decisionService.GetVariation(experiment, GenericUserId, ProjectConfig, new UserAttributes()); + decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig); LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format("User \"{0}\" does not meet conditions to be in experiment \"{1}\".", GenericUserId, experiment.Key)), Times.Once); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); + // ensure that a user with a saved user profile, sees the same variation regardless of audience evaluation - decisionService.GetVariation(experiment, UserProfileId, ProjectConfig, new UserAttributes()); + decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig); BucketerMock.Verify(_ => _.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } @@ -138,10 +153,12 @@ public void TestGetVariationEvaluatesUserProfileBeforeAudienceTargeting() public void TestGetForcedVariationReturnsForcedVariation() { DecisionService decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); + var expectedVariation = decisionService.GetWhitelistedVariation(WhitelistedExperiment, WhitelistedUserId).ResultObject; + Assertions.AreEqual(WhitelistedVariation, expectedVariation); Assert.IsTrue(TestData.CompareObjects(WhitelistedVariation, decisionService.GetWhitelistedVariation(WhitelistedExperiment, WhitelistedUserId).ResultObject)); LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format("User \"{0}\" is forced in variation \"{1}\".", - WhitelistedUserId, WhitelistedVariation.Key)), Times.Once); + WhitelistedUserId, WhitelistedVariation.Key)), Times.Exactly(2)); BucketerMock.Verify(_ => _.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } @@ -168,7 +185,8 @@ public void TestGetForcedVariationWithInvalidVariation() {userId, invalidVariationKey } }; - var experiment = new Experiment { + var experiment = new Experiment + { Id = "1234", Key = "exp_key", Status = "Running", @@ -210,10 +228,16 @@ public void TestBucketReturnsVariationStoredInUserProfile() UserProfileServiceMock.Setup(_ => _.Lookup(UserProfileId)).Returns(userProfile.ToMap()); - DecisionService decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, UserProfileServiceMock.Object, LoggerMock.Object); - var actualVariation = decisionService.GetVariation(experiment, UserProfileId, ProjectConfig, new UserAttributes()); - Assert.IsTrue(TestData.CompareObjects(variation, actualVariation.ResultObject)); + + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); + + var actualVariation = decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig); + + Assertions.AreEqual(variation, actualVariation.ResultObject); + Assert.AreEqual(actualVariation.DecisionReasons.ToReport(true).Count, 1); Assert.AreEqual(actualVariation.DecisionReasons.ToReport(true)[0], "Returning previously activated variation \"vtag1\" of experiment \"etag1\" for user \"userProfileId\" from user profile."); @@ -221,7 +245,6 @@ public void TestBucketReturnsVariationStoredInUserProfile() variation.Key, experiment.Key, UserProfileId))); //BucketerMock.Verify(_ => _.Bucket(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } [Test] @@ -291,7 +314,9 @@ public void TestGetVariationSavesBucketedVariationIntoUserProfile() DecisionService decisionService = new DecisionService(mockBucketer.Object, ErrorHandlerMock.Object, UserProfileServiceMock.Object, LoggerMock.Object); - Assert.IsTrue(TestData.CompareObjects(variation.ResultObject, decisionService.GetVariation(experiment, UserProfileId, ProjectConfig, new UserAttributes()).ResultObject)); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); + + Assert.IsTrue(TestData.CompareObjects(variation.ResultObject, decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig).ResultObject)); LoggerMock.Verify(l => l.Log(LogLevel.INFO, string.Format("Saved variation \"{0}\" of experiment \"{1}\" for user \"{2}\".", variation.ResultObject.Id, experiment.Id, UserProfileId)), Times.Once); @@ -347,8 +372,12 @@ public void TestGetVariationSavesANewUserProfile() DecisionService decisionService = new DecisionService(mockBucketer.Object, ErrorHandlerMock.Object, UserProfileServiceMock.Object, LoggerMock.Object); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); + + var actualVariation = decisionService.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig); + + Assertions.AreEqual(variation.ResultObject, actualVariation.ResultObject); - Assert.IsTrue(TestData.CompareObjects(variation, decisionService.GetVariation(experiment, UserProfileId, ProjectConfig, new UserAttributes()))); UserProfileServiceMock.Verify(_ => _.Save(It.IsAny>()), Times.Once); } @@ -371,19 +400,20 @@ public void TestGetVariationUserWithSetForcedVariation() // confirm normal bucketing occurs before setting the forced variation var actualVariation = optlyObject.GetVariation(experimentKey, userId, userAttributes); - Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, actualVariation)); + Assertions.AreEqual(VariationWithKeyControl, actualVariation); // test valid experiment Assert.IsTrue(optlyObject.SetForcedVariation(experimentKey, userId, expectedForcedVariationKey), string.Format(@"Set variation to ""{0}"" failed.", expectedForcedVariationKey)); var actualForcedVariation = optlyObject.GetVariation(experimentKey, userId, userAttributes); - Assert.IsTrue(TestData.CompareObjects(VariationWithKeyVariation, actualForcedVariation)); + + Assertions.AreEqual(VariationWithKeyVariation, actualForcedVariation); // clear forced variation and confirm that normal bucketing occurs Assert.IsTrue(optlyObject.SetForcedVariation(experimentKey, userId, null)); actualVariation = optlyObject.GetVariation(experimentKey, userId, userAttributes); - Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, actualVariation)); + Assertions.AreEqual(VariationWithKeyControl, actualVariation); // check that a paused experiment returns null Assert.IsTrue(optlyObject.SetForcedVariation(pausedExperimentKey, userId, expectedForcedVariationKey), string.Format(@"Set variation to ""{0}"" failed.", expectedForcedVariationKey)); @@ -490,26 +520,31 @@ public void TestGetVariationWithBucketingId() public void TestGetVariationForFeatureExperimentGivenNullExperimentIds() { var featureFlag = ProjectConfig.GetFeatureFlagFromKey("empty_feature"); - var decision = DecisionService.GetVariationForFeatureExperiment(featureFlag, GenericUserId, new UserAttributes() { }, ProjectConfig, new OptimizelyDecideOption[]{}); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(GenericUserId); + + var decision = DecisionService.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, new UserAttributes() { }, ProjectConfig, new OptimizelyDecideOption[] { }); Assert.IsNull(decision.ResultObject); LoggerMock.Verify(l => l.Log(LogLevel.INFO, $"The feature flag \"{featureFlag.Key}\" is not used in any experiments.")); } - // Should return null and log when the experiment is not in the datafile + // Should return null and log when the experiment is not in the datafile [Test] public void TestGetVariationForFeatureExperimentGivenExperimentNotInDataFile() { var booleanFeature = ProjectConfig.GetFeatureFlagFromKey("boolean_feature"); - var featureFlag = new FeatureFlag { + var featureFlag = new FeatureFlag + { Id = booleanFeature.Id, Key = booleanFeature.Key, RolloutId = booleanFeature.RolloutId, ExperimentIds = new List { "29039203" } }; - var decision = DecisionService.GetVariationForFeatureExperiment(featureFlag, GenericUserId, new UserAttributes() { }, ProjectConfig, new OptimizelyDecideOption[] { }); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(GenericUserId); + + var decision = DecisionService.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, new UserAttributes() { }, ProjectConfig, new OptimizelyDecideOption[] { }); Assert.IsNull(decision.ResultObject); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Experiment ID \"29039203\" is not in datafile.")); @@ -521,10 +556,12 @@ public void TestGetVariationForFeatureExperimentGivenNonMutexGroupAndUserNotBuck { var multiVariateExp = ProjectConfig.GetExperimentFromKey("test_experiment_multivariate"); - DecisionServiceMock.Setup(ds => ds.GetVariation(multiVariateExp, "user1", ProjectConfig, null)).Returns(null); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns("user1"); + + DecisionServiceMock.Setup(ds => ds.GetVariation(multiVariateExp, OptimizelyUserContextMock.Object, ProjectConfig, null)).Returns(null); var featureFlag = ProjectConfig.GetFeatureFlagFromKey("multi_variate_feature"); - var decision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, "user1", new UserAttributes(), ProjectConfig, new OptimizelyDecideOption[] { }); + var decision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, new UserAttributes(), ProjectConfig, new OptimizelyDecideOption[] { }); Assert.IsNull(decision.ResultObject); LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"user1\" is not bucketed into any of the experiments on the feature \"multi_variate_feature\".")); @@ -539,11 +576,17 @@ public void TestGetVariationForFeatureExperimentGivenNonMutexGroupAndUserIsBucke var expectedDecision = new FeatureDecision(experiment, variation.ResultObject, FeatureDecision.DECISION_SOURCE_FEATURE_TEST); var userAttributes = new UserAttributes(); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + + OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); + + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns("user1"); + DecisionServiceMock.Setup(ds => ds.GetVariation(ProjectConfig.GetExperimentFromKey("test_experiment_multivariate"), - "user1", ProjectConfig, userAttributes, It.IsAny ())).Returns(variation); + OptimizelyUserContextMock.Object, ProjectConfig, It.IsAny())).Returns(variation); var featureFlag = ProjectConfig.GetFeatureFlagFromKey("multi_variate_feature"); - var decision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, "user1", userAttributes, ProjectConfig, new OptimizelyDecideOption[] { }); + var decision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, userAttributes, ProjectConfig, new OptimizelyDecideOption[] { }); Assert.IsTrue(TestData.CompareObjects(expectedDecision, decision.ResultObject)); @@ -559,35 +602,40 @@ public void TestGetVariationForFeatureExperimentGivenMutexGroupAndUserIsBucketed var userAttributes = new UserAttributes(); var expectedDecision = new FeatureDecision(mutexExperiment, variation.ResultObject, FeatureDecision.DECISION_SOURCE_FEATURE_TEST); - DecisionServiceMock.Setup(ds => ds.GetVariation(ProjectConfig.GetExperimentFromKey("group_experiment_1"), "user1", ProjectConfig, - userAttributes)).Returns(variation); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + + OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns("user1"); + + DecisionServiceMock.Setup(ds => ds.GetVariation(ProjectConfig.GetExperimentFromKey("group_experiment_1"), OptimizelyUserContextMock.Object, ProjectConfig)).Returns(variation); var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_feature"); - var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, "user1", userAttributes, ProjectConfig, new OptimizelyDecideOption[] { }); + var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, userAttributes, ProjectConfig, new OptimizelyDecideOption[] { }); - Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); + Assertions.AreEqual(expectedDecision, actualDecision.ResultObject); LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"user1\" is bucketed into experiment \"group_experiment_1\" of feature \"boolean_feature\".")); } - // Should return null and log a message when the user is not bucketed into any of the mutex experiments + // Should return null and log a message when the user is not bucketed into any of the mutex experiments [Test] public void TestGetVariationForFeatureExperimentGivenMutexGroupAndUserNotBucketed() { var mutexExperiment = ProjectConfig.GetExperimentFromKey("group_experiment_1"); - DecisionServiceMock.Setup(ds => ds.GetVariation(It.IsAny(), It.IsAny(), ProjectConfig, It.IsAny(), It.IsAny())) + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns("user1"); + + DecisionServiceMock.Setup(ds => ds.GetVariation(It.IsAny(), It.IsAny(), ProjectConfig, It.IsAny())) .Returns(Result.NullResult(null)); - var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_feature"); - var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, "user1", new UserAttributes(), ProjectConfig, new OptimizelyDecideOption[] { }); + var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, new UserAttributes(), ProjectConfig, new OptimizelyDecideOption[] { }); Assert.IsNull(actualDecision.ResultObject); LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"user1\" is not bucketed into any of the experiments on the feature \"boolean_feature\".")); } - #endregion // GetVariationForFeatureExperiment Tests + #endregion GetVariationForFeatureExperiment Tests #region GetVariationForFeatureRollout Tests @@ -602,10 +650,11 @@ public void TestGetVariationForFeatureRolloutWhenNoRuleInRollouts() var rollout = projectConfig.GetRolloutFromId(featureFlag.RolloutId); Assert.AreEqual(rollout.Experiments.Count, 0); - + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "userId1", null, ErrorHandlerMock.Object, LoggerMock.Object); var decisionService = new DecisionService(new Bucketer(new NoOpLogger()), new NoOpErrorHandler(), null, new NoOpLogger()); - var variation = decisionService.GetVariationForFeatureRollout(featureFlag, "userId1", null, projectConfig); + var variation = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, projectConfig); Assert.IsNull(variation.ResultObject); } @@ -613,6 +662,7 @@ public void TestGetVariationForFeatureRolloutWhenNoRuleInRollouts() [Test] public void TestGetVariationForFeatureRolloutWhenRolloutIsNotInDataFile() { + var featureFlag = ProjectConfig.GetFeatureFlagFromKey("boolean_feature"); var invalidRolloutFeature = new FeatureFlag { @@ -623,9 +673,10 @@ public void TestGetVariationForFeatureRolloutWhenRolloutIsNotInDataFile() Variables = featureFlag.Variables }; - DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment(It.IsAny(), It.IsAny(), It.IsAny(), ProjectConfig, new OptimizelyDecideOption[] { })).Returns(null); - - var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureRollout(featureFlag, "user1", new UserAttributes(), ProjectConfig); + DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment(It.IsAny(), It.IsAny(), It.IsAny(), ProjectConfig, new OptimizelyDecideOption[] { })).Returns(null); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user1", new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); Assert.IsNull(actualDecision.ResultObject); LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The feature flag \"boolean_feature\" is not used in a rollout.")); @@ -650,7 +701,10 @@ public void TestGetVariationForFeatureRolloutWhenUserIsBucketedInTheTargetingRul It.IsAny())).Returns(variation); var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); - var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, "user_1", userAttributes, ProjectConfig); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user_1", userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); + + var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); } @@ -675,11 +729,13 @@ public void TestGetVariationForFeatureRolloutWhenUserIsNotBucketedInTheTargeting BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), everyoneElseRule, It.IsAny(), It.IsAny())).Returns(variation); var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); - var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, "user_1", userAttributes, ProjectConfig); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user_1", userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); + var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); } - // Should log and return null when the user is not bucketed into the targeting rule + // Should log and return null when the user is not bucketed into the targeting rule // as well as "Everyone Else" rule. [Test] public void TestGetVariationForFeatureRolloutWhenUserIsNeitherBucketedInTheTargetingRuleNorToEveryoneElseRule() @@ -694,7 +750,9 @@ public void TestGetVariationForFeatureRolloutWhenUserIsNeitherBucketedInTheTarge BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Result.NullResult(null)); var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); - var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, "user_1", userAttributes, ProjectConfig); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user_1", userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); + var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); Assert.IsNull(actualDecision.ResultObject); } @@ -717,7 +775,9 @@ public void TestGetVariationForFeatureRolloutWhenUserDoesNotQualifyForAnyTargeti var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); // Provide null attributes so that user does not qualify for audience. - var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, "user_1", null, ProjectConfig); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, "user_1", null, ErrorHandlerMock.Object, LoggerMock.Object); + var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); @@ -743,21 +803,24 @@ public void TestGetVariationForFeatureRolloutAudienceAndTrafficeAllocationCheck( var decisionService = new DecisionService(mockBucketer.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); // Calling with audience iPhone users in San Francisco. - var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, GenericUserId, new UserAttributes + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, GenericUserId, new UserAttributes { { "device_type", "iPhone" }, { "location", "San Francisco" } - }, ProjectConfig); + }, ErrorHandlerMock.Object, LoggerMock.Object); + var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); // Returned variation id should be '177773' because of audience 'iPhone users in San Francisco'. var expectedDecision = new FeatureDecision(expWithAudienceiPhoneUsers, varWithAudienceiPhoneUsers, FeatureDecision.DECISION_SOURCE_ROLLOUT); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); // Calling with audience Chrome users. - actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, GenericUserId, new UserAttributes + var optimizelyUserContext2 = new OptimizelyUserContext(optlyObject, GenericUserId, new UserAttributes { { "browser_type", "chrome" } - }, ProjectConfig); + }, ErrorHandlerMock.Object, LoggerMock.Object); + actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext2, ProjectConfig); // Returned variation id should be '177771' because of audience 'Chrome users'. expectedDecision = new FeatureDecision(expWithAudienceChromeUsers, varWithAudienceChromeUsers, FeatureDecision.DECISION_SOURCE_ROLLOUT); @@ -765,7 +828,8 @@ public void TestGetVariationForFeatureRolloutAudienceAndTrafficeAllocationCheck( // Calling with no audience. mockBucketer.Setup(bm => bm.GenerateBucketValue(It.IsAny())).Returns(8000); - actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, GenericUserId, new UserAttributes(), ProjectConfig); + var optimizelyUserContext3 = new OptimizelyUserContext(optlyObject, GenericUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext3, ProjectConfig); // Returned variation id should be of everyone else rule because of no audience. expectedDecision = new FeatureDecision(expWithNoAudience, varWithNoAudience, FeatureDecision.DECISION_SOURCE_ROLLOUT); @@ -773,13 +837,14 @@ public void TestGetVariationForFeatureRolloutAudienceAndTrafficeAllocationCheck( // Calling with audience 'Chrome users' and traffice allocation '9500'. mockBucketer.Setup(bm => bm.GenerateBucketValue(It.IsAny())).Returns(9500); - actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, GenericUserId, new UserAttributes + var optimizelyUserContext4 = new OptimizelyUserContext(optlyObject, GenericUserId, new UserAttributes { { "browser_type", "chrome" } - }, ProjectConfig); + }, ErrorHandlerMock.Object, LoggerMock.Object); + actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext4, ProjectConfig); // Returned decision entity should be null because bucket value exceeds traffic allocation of everyone else rule. - Assert.Null(actualDecision.ResultObject); + Assert.Null(actualDecision.ResultObject?.Variation?.Key); } [Test] @@ -794,22 +859,25 @@ public void TestGetVariationForFeatureRolloutCheckAudienceInEveryoneElseRule() BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), everyoneElseRule, It.IsAny(), WhitelistedUserId)).Returns(variation); BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), everyoneElseRule, It.IsAny(), GenericUserId)).Returns(Result.NullResult(DecisionReasons)); - + var decisionService = new DecisionService(BucketerMock.Object, ErrorHandlerMock.Object, null, LoggerMock.Object); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); // Returned variation id should be of everyone else rule as it passes audience Id checking. - var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, WhitelistedUserId, null, ProjectConfig); + var optimizelyUserContext = new OptimizelyUserContext(optlyObject, WhitelistedUserId, null, ErrorHandlerMock.Object, LoggerMock.Object); + var actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext, ProjectConfig); Assert.True(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); // Returned variation id should be null. - actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, GenericUserId, null, ProjectConfig); + var optimizelyUserContext2 = new OptimizelyUserContext(optlyObject, GenericUserId, null, ErrorHandlerMock.Object, LoggerMock.Object); + actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext2, ProjectConfig); Assert.Null(actualDecision.ResultObject); // Returned variation id should be null as it fails audience Id checking. everyoneElseRule.AudienceIds = new string[] { ProjectConfig.Audiences[0].Id }; BucketerMock.Setup(bm => bm.Bucket(It.IsAny(), It.IsAny(), It.IsAny(), GenericUserId)).Returns(Result.NullResult(DecisionReasons)); - actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, GenericUserId, null, ProjectConfig) ?? Result.NullResult(DecisionReasons); + actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, optimizelyUserContext2, ProjectConfig) ?? Result.NullResult(DecisionReasons); Assert.Null(actualDecision.ResultObject); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUser1\" does not meet the conditions for targeting rule \"1\"."), Times.Once); @@ -819,7 +887,7 @@ public void TestGetVariationForFeatureRolloutCheckAudienceInEveryoneElseRule() LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"genericUserId\" does not meet the conditions for targeting rule \"3\"."), Times.Exactly(1)); } - #endregion // GetVariationForFeatureRollout Tests + #endregion GetVariationForFeatureRollout Tests #region GetVariationForFeature Tests @@ -833,14 +901,15 @@ public void TestGetVariationForFeatureWhenTheUserIsBucketedIntoFeatureExperiment var variation = expectedExperiment.Variations[0]; var expectedDecision = Result.NewResult(new FeatureDecision(expectedExperiment, variation, FeatureDecision.DECISION_SOURCE_FEATURE_TEST), DecisionReasons); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment(It.IsAny(), It.IsAny(), + DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment(It.IsAny(), It.IsAny(), It.IsAny(), ProjectConfig, It.IsAny())).Returns(expectedDecision); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns("user1"); - var actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, "user1", ProjectConfig, new UserAttributes()); + var actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, OptimizelyUserContextMock.Object, ProjectConfig); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision)); } - // Should return the bucketed variation when the user is not bucketed in the feature flag experiment, + // Should return the bucketed variation when the user is not bucketed in the feature flag experiment, // but is bucketed into a variation of the feature flag's rollout. [Test] public void TestGetVariationForFeatureWhenTheUserIsNotBucketedIntoFeatureExperimentAndBucketedToFeatureRollout() @@ -852,16 +921,20 @@ public void TestGetVariationForFeatureWhenTheUserIsNotBucketedIntoFeatureExperim var variation = expectedExperiment.Variations[0]; var expectedDecision = Result.NewResult(new FeatureDecision(expectedExperiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT), DecisionReasons); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment(It.IsAny(), It.IsAny(), + DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment(It.IsAny(), It.IsAny(), It.IsAny(), ProjectConfig, It.IsAny())).Returns(Result.NullResult(null)); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureRollout(It.IsAny(), It.IsAny(), - It.IsAny(), ProjectConfig)).Returns(expectedDecision); + DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureRollout(It.IsAny(), It.IsAny(), + ProjectConfig)).Returns(expectedDecision); + + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); - var actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, "user1", ProjectConfig, new UserAttributes()); + var actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, OptimizelyUserContextMock.Object, ProjectConfig); Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision)); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"user1\" is bucketed into a rollout for feature flag \"string_single_variable_feature\".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"userProfileId\" is bucketed into a rollout for feature flag \"string_single_variable_feature\".")); } // Should return null when the user neither gets bucketed into feature experiment nor in feature rollout. @@ -869,18 +942,21 @@ public void TestGetVariationForFeatureWhenTheUserIsNotBucketedIntoFeatureExperim public void TestGetVariationForFeatureWhenTheUserIsNeitherBucketedIntoFeatureExperimentNorToFeatureRollout() { var featureFlag = ProjectConfig.GetFeatureFlagFromKey("string_single_variable_feature"); - var expectedDecision = new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_ROLLOUT); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment(It.IsAny(), It.IsAny(), It.IsAny(), ProjectConfig, new OptimizelyDecideOption[] { })).Returns(Result.NullResult(null)); - DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureRollout(It.IsAny(), It.IsAny(), It.IsAny(), ProjectConfig)).Returns(Result.NullResult(null)); + DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureExperiment(It.IsAny(), It.IsAny(), It.IsAny(), ProjectConfig, new OptimizelyDecideOption[] { })).Returns(Result.NullResult(null)); + DecisionServiceMock.Setup(ds => ds.GetVariationForFeatureRollout(It.IsAny(), It.IsAny(), ProjectConfig)).Returns(Result.NullResult(null)); - var actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, "user1", ProjectConfig, new UserAttributes()); - Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, new UserAttributes(), ErrorHandlerMock.Object, LoggerMock.Object); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); + + var actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, OptimizelyUserContextMock.Object, ProjectConfig); + Assert.IsNull(actualDecision.ResultObject.Variation); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"user1\" is not bucketed into a rollout for feature flag \"string_single_variable_feature\".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"userProfileId\" is not bucketed into a rollout for feature flag \"string_single_variable_feature\".")); } - // Verify that the user is bucketed into the experiment's variation when the user satisfies bucketing and traffic allocation + // Verify that the user is bucketed into the experiment's variation when the user satisfies bucketing and traffic allocation // for feature flag experiment and feature flag rollout. [Test] public void TestGetVariationForFeatureWhenTheUserIsBuckedtedInBothExperimentAndRollout() @@ -893,8 +969,13 @@ public void TestGetVariationForFeatureWhenTheUserIsBuckedtedInBothExperimentAndR { "browser_type", "chrome" } }; - DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, "user1", ProjectConfig, userAttributes, It.IsAny())).Returns(variation); - var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, "user1", userAttributes, ProjectConfig, new OptimizelyDecideOption[] { }); + var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); + + OptimizelyUserContextMock = new Mock(optlyObject, WhitelistedUserId, userAttributes, ErrorHandlerMock.Object, LoggerMock.Object); + OptimizelyUserContextMock.Setup(ouc => ouc.GetUserId()).Returns(UserProfileId); + + DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, OptimizelyUserContextMock.Object, ProjectConfig, It.IsAny())).Returns(variation); + var actualDecision = DecisionServiceMock.Object.GetVariationForFeatureExperiment(featureFlag, OptimizelyUserContextMock.Object, userAttributes, ProjectConfig, new OptimizelyDecideOption[] { }); // The user is bucketed into feature experiment's variation. Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); @@ -905,22 +986,22 @@ public void TestGetVariationForFeatureWhenTheUserIsBuckedtedInBothExperimentAndR var expectedRolloutDecision = new FeatureDecision(rolloutExperiment, rolloutVariation.ResultObject, FeatureDecision.DECISION_SOURCE_ROLLOUT); BucketerMock.Setup(bm => bm.Bucket(ProjectConfig, rolloutExperiment, It.IsAny(), It.IsAny())).Returns(rolloutVariation); - var actualRolloutDecision = DecisionServiceMock.Object.GetVariationForFeatureRollout(featureFlag, "user1", userAttributes, ProjectConfig); + var actualRolloutDecision = DecisionServiceMock.Object.GetVariationForFeatureRollout(featureFlag, OptimizelyUserContextMock.Object, ProjectConfig); // The user is bucketed into feature rollout's variation. Assert.IsTrue(TestData.CompareObjects(expectedRolloutDecision, actualRolloutDecision.ResultObject)); - actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, "user1", ProjectConfig, userAttributes); + actualDecision = DecisionServiceMock.Object.GetVariationForFeature(featureFlag, OptimizelyUserContextMock.Object, ProjectConfig); // The user is bucketed into feature experiment's variation and not the rollout's variation. Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision.ResultObject)); - } - #endregion // GetVariationForFeature Tests + #endregion GetVariationForFeature Tests #region Forced variation Tests + [Test] public void TestSetGetForcedVariation() { @@ -1030,7 +1111,6 @@ public void TestGetForcedVariationLogs() [Test] public void TestSetForcedVariationMultipleSets() { - Assert.True(DecisionService.SetForcedVariation("test_experiment", "test_user_1", "variation", Config)); Assert.AreEqual(DecisionService.GetForcedVariation("test_experiment", "test_user_1", Config).ResultObject.Key, "variation"); @@ -1055,6 +1135,6 @@ public void TestSetForcedVariationMultipleSets() Assert.AreEqual(DecisionService.GetForcedVariation("group_experiment_1", "test_user_1", Config).ResultObject.Key, "group_exp_1_var_1"); } - #endregion // Forced variation Tests + #endregion Forced variation Tests } -} \ No newline at end of file +} diff --git a/OptimizelySDK.Tests/EventTests/EventFactoryTest.cs b/OptimizelySDK.Tests/EventTests/EventFactoryTest.cs index c39facb6..b17cff2c 100644 --- a/OptimizelySDK.Tests/EventTests/EventFactoryTest.cs +++ b/OptimizelySDK.Tests/EventTests/EventFactoryTest.cs @@ -511,13 +511,13 @@ public void TestCreateImpressionEventRemovesInvalidAttributesFromPayloadRollout( new Dictionary { {"campaign_id", null }, - {"experiment_id", null }, + {"experiment_id", string.Empty }, {"variation_id", null }, { "metadata", new Dictionary { { "rule_type", "rollout" }, - { "rule_key", "" }, + { "rule_key", string.Empty }, { "flag_key", "test_feature" }, - { "variation_key", "" }, + { "variation_key", string.Empty }, { "enabled", false } } } diff --git a/OptimizelySDK.Tests/ForcedDecisionsStoreTest.cs b/OptimizelySDK.Tests/ForcedDecisionsStoreTest.cs new file mode 100644 index 00000000..6cf2dfc8 --- /dev/null +++ b/OptimizelySDK.Tests/ForcedDecisionsStoreTest.cs @@ -0,0 +1,123 @@ +/** + * + * Copyright 2021, Optimizely and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using NUnit.Framework; + +namespace OptimizelySDK.Tests +{ + [TestFixture] + public class ForcedDecisionsStoreTest + { + [Test] + public void ForcedDecisionStoreGetSetForcedDecisionWithBothRuleAndFlagKey() + { + var expectedForcedDecision1 = new OptimizelyForcedDecision("sample_variation_key"); + var expectedForcedDecision2 = new OptimizelyForcedDecision("sample_variation_key_2"); + var context1 = new OptimizelyDecisionContext("flag_key", "rule_key"); + var context2 = new OptimizelyDecisionContext("flag_key", "rule_key1"); + var forcedDecisionStore = new ForcedDecisionsStore(); + forcedDecisionStore[context1] = expectedForcedDecision1; + forcedDecisionStore[context2] = expectedForcedDecision2; + + Assert.AreEqual(forcedDecisionStore.Count, 2); + Assert.AreEqual(forcedDecisionStore[context1].VariationKey, expectedForcedDecision1.VariationKey); + Assert.AreEqual(forcedDecisionStore[context2].VariationKey, expectedForcedDecision2.VariationKey); + } + + [Test] + public void ForcedDecisionStoreNullFlagKeyForcedDecisionContext() + { + var expectedForcedDecision = new OptimizelyForcedDecision("sample_variation_key"); + var context = new OptimizelyDecisionContext(null, "rule_key"); + var forcedDecisionStore = new ForcedDecisionsStore(); + forcedDecisionStore[context] = expectedForcedDecision; + + Assert.AreEqual(forcedDecisionStore.Count, 0); + } + + [Test] + public void ForcedDecisionStoreNullContextForcedDecisionContext() + { + var expectedForcedDecision = new OptimizelyForcedDecision("sample_variation_key"); + OptimizelyDecisionContext context = null; + var forcedDecisionStore = new ForcedDecisionsStore(); + forcedDecisionStore[context] = expectedForcedDecision; + + Assert.AreEqual(forcedDecisionStore.Count, 0); + } + + [Test] + public void ForcedDecisionStoreGetForcedDecisionWithBothRuleAndFlagKey() + { + var expectedForcedDecision1 = new OptimizelyForcedDecision("sample_variation_key"); + var context1 = new OptimizelyDecisionContext("flag_key", "rule_key"); + var NullFlagKeyContext = new OptimizelyDecisionContext(null, "rule_key"); + var forcedDecisionStore = new ForcedDecisionsStore(); + forcedDecisionStore[context1] = expectedForcedDecision1; + + Assert.AreEqual(forcedDecisionStore.Count, 1); + Assert.AreEqual(forcedDecisionStore[context1].VariationKey, expectedForcedDecision1.VariationKey); + Assert.IsNull(forcedDecisionStore[NullFlagKeyContext]); + } + + [Test] + public void ForcedDecisionStoreRemoveForcedDecisionTrue() + { + var expectedForcedDecision1 = new OptimizelyForcedDecision("sample_variation_key"); + var expectedForcedDecision2 = new OptimizelyForcedDecision("sample_variation_key_2"); + var context1 = new OptimizelyDecisionContext("flag_key", "rule_key"); + var context2 = new OptimizelyDecisionContext("flag_key", "rule_key1"); + var forcedDecisionStore = new ForcedDecisionsStore(); + forcedDecisionStore[context1] = expectedForcedDecision1; + forcedDecisionStore[context2] = expectedForcedDecision2; + + Assert.AreEqual(forcedDecisionStore.Count, 2); + Assert.IsTrue(forcedDecisionStore.Remove(context2)); + Assert.AreEqual(forcedDecisionStore.Count, 1); + Assert.AreEqual(forcedDecisionStore[context1].VariationKey, expectedForcedDecision1.VariationKey); + Assert.IsNull(forcedDecisionStore[context2]); + } + + [Test] + public void ForcedDecisionStoreRemoveForcedDecisionContextRuleKeyNotMatched() + { + var expectedForcedDecision = new OptimizelyForcedDecision("sample_variation_key"); + var contextNotMatched = new OptimizelyDecisionContext("flag_key", ""); + var context = new OptimizelyDecisionContext("flag_key", "rule_key"); + var forcedDecisionStore = new ForcedDecisionsStore(); + forcedDecisionStore[context] = expectedForcedDecision; + + Assert.AreEqual(forcedDecisionStore.Count, 1); + Assert.IsFalse(forcedDecisionStore.Remove(contextNotMatched)); + Assert.AreEqual(forcedDecisionStore.Count, 1); + } + + [Test] + public void ForcedDecisionStoreRemoveAllForcedDecisionContext() + { + var expectedForcedDecision = new OptimizelyForcedDecision("sample_variation_key"); + var context = new OptimizelyDecisionContext("flag_key", "rule_key"); + var forcedDecisionStore = new ForcedDecisionsStore(); + forcedDecisionStore[context] = expectedForcedDecision; + + Assert.AreEqual(forcedDecisionStore.Count, 1); + forcedDecisionStore.RemoveAll(); + Assert.AreEqual(forcedDecisionStore.Count, 0); + } + + } +} diff --git a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs index b03f6bef..007ee360 100644 --- a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs +++ b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2020-2021, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,23 +14,22 @@ * limitations under the License. */ -using System; using Moq; +using Newtonsoft.Json.Linq; using NUnit.Framework; using OptimizelySDK.Config; +using OptimizelySDK.Entity; using OptimizelySDK.Logger; using OptimizelySDK.OptlyConfig; -using System.Collections.Generic; -using System.Threading; using OptimizelySDK.Tests.UtilsTests; -using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; namespace OptimizelySDK.Tests.OptimizelyConfigTests { [TestFixture] public class OptimizelyConfigTest { - private Mock LoggerMock; [SetUp] @@ -42,7 +41,7 @@ public void Setup() #region Test OptimizelyConfigService - static Type[] ParameterTypes = { + private static Type[] ParameterTypes = { typeof(ProjectConfig), }; @@ -76,10 +75,9 @@ public void TestGetOptimizelyConfigServiceSerializedAudiences() new List() { "not", new JArray() { "and", "3468206642", "3988293898" } }, new List() { }, new List() { "or", "3468206642", "999999999" }, - }; - var expectedAudienceOutputs = new List + var expectedAudienceOutputs = new List { "\"exactString\" OR \"substringString\"", "\"exactString\" OR \"substringString\" OR \"exactNumber\"", @@ -147,7 +145,6 @@ public void TestGetOptimizelyConfigWithDuplicateExperimentKeys() var experimentMapFlag2 = optimizelyConfig.FeaturesMap["flag2"].ExperimentsMap; // 9300000007573 Assert.AreEqual(experimentMapFlag1["targeted_delivery"].Id, "9300000007569"); Assert.AreEqual(experimentMapFlag2["targeted_delivery"].Id, "9300000007573"); - } [Test] @@ -158,7 +155,7 @@ public void TestGetOptimizelyConfigWithDuplicateRuleKeys() var optimizelyConfig = optimizelyConfigService.GetOptimizelyConfig(); Assert.AreEqual(optimizelyConfig.ExperimentsMap.Count, 0); - var rolloutFlag1 = optimizelyConfig.FeaturesMap["flag_1"].DeliveryRules[0]; // 9300000004977, + var rolloutFlag1 = optimizelyConfig.FeaturesMap["flag_1"].DeliveryRules[0]; // 9300000004977, var rolloutFlag2 = optimizelyConfig.FeaturesMap["flag_2"].DeliveryRules[0]; // 9300000004979 var rolloutFlag3 = optimizelyConfig.FeaturesMap["flag_3"].DeliveryRules[0]; // 9300000004981 Assert.AreEqual(rolloutFlag1.Id, "9300000004977"); @@ -167,7 +164,6 @@ public void TestGetOptimizelyConfigWithDuplicateRuleKeys() Assert.AreEqual(rolloutFlag2.Key, "targeted_delivery"); Assert.AreEqual(rolloutFlag3.Id, "9300000004981"); Assert.AreEqual(rolloutFlag3.Key, "targeted_delivery"); - } [Test] @@ -275,7 +271,6 @@ public void TestGetOptimizelyConfigService() } }; - var featuresMap = new Dictionary { { @@ -542,7 +537,7 @@ public void TestGetOptimizelyConfigService() new OptimizelyAudience("3988293898", "substringString", "[\"and\",[\"or\",[\"or\",{\"name\":\"house\",\"type\":\"custom_attribute\",\"match\":\"substring\",\"value\":\"Slytherin\"}]]]"), }, events: new OptimizelyEvent[] - { + { new OptimizelyEvent() { Id = "594089", Key = "item_bought", ExperimentIds = new string[] { "11564051718", "1323241597" } @@ -555,12 +550,13 @@ public void TestGetOptimizelyConfigService() experimentsMap: experimentsMap, featuresMap: featuresMap, datafile: TestData.TypedAudienceDatafile); - Assert.IsTrue(TestData.CompareObjects(optimizelyConfig, expectedOptimizelyConfig)); + + Assertions.AreEqual(expectedOptimizelyConfig, optimizelyConfig); } - #endregion + #endregion Test OptimizelyConfigService - #region OptimizelyConfig entity tests + #region OptimizelyConfig entity tests [Test] public void TestOptimizelyConfigEntity() @@ -640,6 +636,6 @@ public void TestOptimizelyVariableEntity() Assert.AreEqual(expectedOptlyVariable.Value, "2"); } - #endregion + #endregion OptimizelyConfig entity tests } } diff --git a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj index d5231738..2a776bcb 100644 --- a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj +++ b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj @@ -70,6 +70,7 @@ + @@ -92,6 +93,7 @@ + @@ -134,6 +136,7 @@ OptimizelySDK + @@ -143,9 +146,7 @@ - - - +