diff --git a/OptimizelySDK.DemoApp/OptimizelySDK.DemoApp.csproj b/OptimizelySDK.DemoApp/OptimizelySDK.DemoApp.csproj index 7da4aa12..adbf4c47 100644 --- a/OptimizelySDK.DemoApp/OptimizelySDK.DemoApp.csproj +++ b/OptimizelySDK.DemoApp/OptimizelySDK.DemoApp.csproj @@ -241,6 +241,12 @@ + + + {4dde7faa-110d-441c-ab3b-3f31b593e8bf} + OptimizelySDK + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj index 9d3d441d..11265206 100644 --- a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj +++ b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj @@ -233,6 +233,9 @@ Config\DatafileProjectConfig + + Entity\Integration + Config\ProjectConfigManager diff --git a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj index 3dfdfbc9..0c7092ea 100644 --- a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj +++ b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj @@ -232,6 +232,9 @@ Config\DatafileProjectConfig + + Entity\Integration + Config\ProjectConfigManager diff --git a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj index 740e5d4d..94828d2b 100644 --- a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj +++ b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj @@ -76,6 +76,7 @@ + @@ -159,5 +160,4 @@ - diff --git a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj index 0202fe84..3b2962e0 100644 --- a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj +++ b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj @@ -115,6 +115,9 @@ Entity\Group.cs + + Entity\Integration.cs + Entity\IdKeyEntity.cs diff --git a/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs b/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs index dc5d66b2..9d84e4b4 100644 --- a/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs +++ b/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs @@ -1,11 +1,11 @@ /* - * Copyright 2019-2020, Optimizely + * Copyright 2019-2022, 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 + * https://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, @@ -19,7 +19,9 @@ using OptimizelySDK.AudienceConditions; using OptimizelySDK.Entity; using OptimizelySDK.Logger; +using OptimizelySDK.Tests.Utils; using System; +using System.Collections.Generic; namespace OptimizelySDK.Tests.AudienceConditionsTests { @@ -68,26 +70,26 @@ public void TestEvaluateWithDifferentTypedAttributes() {"pi_value", 3.14 }, }; - Assert.That(ExactStrCondition.Evaluate(null, userAttributes, Logger), Is.True); - Assert.That(ExactBoolCondition.Evaluate(null, userAttributes, Logger), Is.True); - Assert.That(GTCondition.Evaluate(null, userAttributes, Logger), Is.True); - Assert.That(ExactDecimalCondition.Evaluate(null, userAttributes, Logger), Is.True); + Assert.That(ExactStrCondition.Evaluate(null, userAttributes.ToUserContext(), Logger), Is.True); + Assert.That(ExactBoolCondition.Evaluate(null, userAttributes.ToUserContext(), Logger), Is.True); + Assert.That(GTCondition.Evaluate(null, userAttributes.ToUserContext(), Logger), Is.True); + Assert.That(ExactDecimalCondition.Evaluate(null, userAttributes.ToUserContext(), Logger), Is.True); } [Test] public void TestEvaluateWithNoMatchType() { - Assert.That(LegacyCondition.Evaluate(null, new UserAttributes { { "device_type", "iPhone" } }, Logger), Is.True); + Assert.That(LegacyCondition.Evaluate(null, new UserAttributes { { "device_type", "iPhone" } }.ToUserContext(), Logger), Is.True); // Assumes exact evaluator if no match type is provided. - Assert.That(LegacyCondition.Evaluate(null, new UserAttributes { { "device_type", "IPhone" } }, Logger), Is.False); + Assert.That(LegacyCondition.Evaluate(null, new UserAttributes { { "device_type", "IPhone" } }.ToUserContext(), Logger), Is.False); } [Test] public void TestEvaluateWithInvalidTypeProperty() { BaseCondition condition = new BaseCondition { Name = "input_value", Value = "Android", Match = "exists", Type = "invalid_type" }; - Assert.That(condition.Evaluate(null, new UserAttributes { { "device_type", "iPhone" } }, Logger), Is.Null); + Assert.That(condition.Evaluate(null, new UserAttributes { { "device_type", "iPhone" } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, $@"Audience condition ""{condition}"" uses an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); } @@ -96,7 +98,7 @@ public void TestEvaluateWithInvalidTypeProperty() public void TestEvaluateWithMissingTypeProperty() { var condition = new BaseCondition { Name = "input_value", Value = "Android", Match = "exists" }; - Assert.That(condition.Evaluate(null, new UserAttributes { { "device_type", "iPhone" } }, Logger), Is.Null); + Assert.That(condition.Evaluate(null, new UserAttributes { { "device_type", "iPhone" } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, $@"Audience condition ""{condition}"" uses an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); } @@ -105,7 +107,7 @@ public void TestEvaluateWithMissingTypeProperty() public void TestEvaluateWithInvalidMatchProperty() { BaseCondition condition = new BaseCondition { Name = "device_type", Value = "Android", Match = "invalid_match", Type = "custom_attribute" }; - Assert.That(condition.Evaluate(null, new UserAttributes { { "device_type", "Android" } }, Logger), Is.Null); + Assert.That(condition.Evaluate(null, new UserAttributes { { "device_type", "Android" } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, $@"Audience condition ""{condition}"" uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); } @@ -113,10 +115,10 @@ public void TestEvaluateWithInvalidMatchProperty() [Test] public void TestEvaluateLogsWarningAndReturnNullWhenAttributeIsNotProvidedAndConditionTypeIsNotExists() { - Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { }, Logger), Is.Null); - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { }, Logger), Is.Null); - Assert.That(LTCondition.Evaluate(null, new UserAttributes { }, Logger), Is.Null); - Assert.That(GTCondition.Evaluate(null, new UserAttributes { }, Logger), Is.Null); + Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), Is.Null); + Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), Is.Null); + Assert.That(LTCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), Is.Null); + Assert.That(GTCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":false} evaluated to UNKNOWN because no value was passed for user attribute ""is_registered_user""."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":""USA""} evaluated to UNKNOWN because no value was passed for user attribute ""location""."), Times.Once); @@ -127,10 +129,10 @@ public void TestEvaluateLogsWarningAndReturnNullWhenAttributeIsNotProvidedAndCon [Test] public void TestEvaluateLogsAndReturnNullWhenAttributeValueIsNullAndConditionTypeIsNotExists() { - Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", null } }, Logger), Is.Null); - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", null } }, Logger), Is.Null); - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", null } }, Logger), Is.Null); - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", null } }, Logger), Is.Null); + Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", null } }.ToUserContext(), Logger), Is.Null); + Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", null } }.ToUserContext(), Logger), Is.Null); + Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", null } }.ToUserContext(), Logger), Is.Null); + Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", null } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":false} evaluated to UNKNOWN because a null value was passed for user attribute ""is_registered_user""."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":""USA""} evaluated to UNKNOWN because a null value was passed for user attribute ""location""."), Times.Once); @@ -141,17 +143,17 @@ public void TestEvaluateLogsAndReturnNullWhenAttributeValueIsNullAndConditionTyp [Test] public void TestEvaluateReturnsFalseAndDoesNotLogForExistsConditionWhenAttributeIsNotProvided() { - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes(), Logger), Is.False); + Assert.That(ExistsCondition.Evaluate(null, new UserAttributes().ToUserContext(), Logger), Is.False); LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Never); } [Test] public void TestEvaluateLogsWarningAndReturnNullWhenAttributeTypeIsInvalid() { - Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", 5 } }, Logger), Is.Null); - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", false } }, Logger), Is.Null); - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", "invalid" } }, Logger), Is.Null); - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", true } }, Logger), Is.Null); + Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", 5 } }.ToUserContext(), Logger), Is.Null); + Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", false } }.ToUserContext(), Logger), Is.Null); + Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", "invalid" } }.ToUserContext(), Logger), Is.Null); + Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", true } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":false} evaluated to UNKNOWN because a value of type ""Int32"" was passed for user attribute ""is_registered_user""."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":""USA""} evaluated to UNKNOWN because a value of type ""Boolean"" was passed for user attribute ""location""."), Times.Once); @@ -163,19 +165,19 @@ public void TestEvaluateLogsWarningAndReturnNullWhenAttributeTypeIsInvalid() public void TestEvaluateLogsWarningAndReturnNullWhenConditionTypeIsInvalid() { var invalidCondition = new BaseCondition { Name = "is_registered_user", Value = new string[] { }, Match = "exact", Type = "custom_attribute" }; - Assert.That(invalidCondition.Evaluate(null, new UserAttributes { { "is_registered_user", true } }, Logger), Is.Null); + Assert.That(invalidCondition.Evaluate(null, new UserAttributes { { "is_registered_user", true } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":[]} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); invalidCondition = new BaseCondition { Name = "location", Value = 25, Match = "substring", Type = "custom_attribute" }; - Assert.That(invalidCondition.Evaluate(null, new UserAttributes { { "location", "USA" } }, Logger), Is.Null); + Assert.That(invalidCondition.Evaluate(null, new UserAttributes { { "location", "USA" } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":25} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); invalidCondition = new BaseCondition { Name = "distance_lt", Value = "invalid", Match = "lt", Type = "custom_attribute" }; - Assert.That(invalidCondition.Evaluate(null, new UserAttributes { { "distance_lt", 5 } }, Logger), Is.Null); + Assert.That(invalidCondition.Evaluate(null, new UserAttributes { { "distance_lt", 5 } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":""invalid""} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); invalidCondition = new BaseCondition { Name = "distance_gt", Value = "invalid", Match = "gt", Type = "custom_attribute" }; - Assert.That(invalidCondition.Evaluate(null, new UserAttributes { { "distance_gt", "invalid" } }, Logger), Is.Null); + Assert.That(invalidCondition.Evaluate(null, new UserAttributes { { "distance_gt", "invalid" } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":""invalid""} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); } @@ -186,19 +188,19 @@ public void TestEvaluateLogsWarningAndReturnNullWhenConditionTypeIsInvalid() [Test] public void TestExactMatcherReturnsFalseWhenAttributeValueDoesNotMatch() { - Assert.That(ExactStrCondition.Evaluate(null, new UserAttributes { { "browser_type", "chrome" } }, Logger), Is.False); - Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", true } }, Logger), Is.False); - Assert.That(ExactDecimalCondition.Evaluate(null, new UserAttributes { { "pi_value", 2.5 } }, Logger), Is.False); - Assert.That(ExactIntCondition.Evaluate(null, new UserAttributes { { "lasers_count", 55 } }, Logger), Is.False); + Assert.That(ExactStrCondition.Evaluate(null, new UserAttributes { { "browser_type", "chrome" } }.ToUserContext(), Logger), Is.False); + Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", true } }.ToUserContext(), Logger), Is.False); + Assert.That(ExactDecimalCondition.Evaluate(null, new UserAttributes { { "pi_value", 2.5 } }.ToUserContext(), Logger), Is.False); + Assert.That(ExactIntCondition.Evaluate(null, new UserAttributes { { "lasers_count", 55 } }.ToUserContext(), Logger), Is.False); } [Test] public void TestExactMatcherReturnsNullWhenTypeMismatch() { - Assert.That(ExactStrCondition.Evaluate(null, new UserAttributes { { "browser_type", true } }, Logger), Is.Null); - Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", "abcd" } }, Logger), Is.Null); - Assert.That(ExactDecimalCondition.Evaluate(null, new UserAttributes { { "pi_value", false } }, Logger), Is.Null); - Assert.That(ExactIntCondition.Evaluate(null, new UserAttributes { { "lasers_count", "infinity" } }, Logger), Is.Null); + Assert.That(ExactStrCondition.Evaluate(null, new UserAttributes { { "browser_type", true } }.ToUserContext(), Logger), Is.Null); + Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", "abcd" } }.ToUserContext(), Logger), Is.Null); + Assert.That(ExactDecimalCondition.Evaluate(null, new UserAttributes { { "pi_value", false } }.ToUserContext(), Logger), Is.Null); + Assert.That(ExactIntCondition.Evaluate(null, new UserAttributes { { "lasers_count", "infinity" } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""browser_type"",""value"":""firefox""} evaluated to UNKNOWN because a value of type ""Boolean"" was passed for user attribute ""browser_type""."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""is_registered_user"",""value"":false} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""is_registered_user""."), Times.Once); @@ -209,9 +211,9 @@ public void TestExactMatcherReturnsNullWhenTypeMismatch() [Test] public void TestExactMatcherReturnsNullForOutOfBoundNumericValues() { - Assert.That(ExactIntCondition.Evaluate(null, new UserAttributes { { "lasers_count", double.NegativeInfinity } }, Logger), Is.Null); - Assert.That(ExactDecimalCondition.Evaluate(null, new UserAttributes { { "pi_value", Math.Pow(2, 53) + 2 } }, Logger), Is.Null); - Assert.That(InfinityIntCondition.Evaluate(null, new UserAttributes { { "max_num_value", 15 } }, Logger), Is.Null); + Assert.That(ExactIntCondition.Evaluate(null, new UserAttributes { { "lasers_count", double.NegativeInfinity } }.ToUserContext(), Logger), Is.Null); + Assert.That(ExactDecimalCondition.Evaluate(null, new UserAttributes { { "pi_value", Math.Pow(2, 53) + 2 } }.ToUserContext(), Logger), Is.Null); + Assert.That(InfinityIntCondition.Evaluate(null, new UserAttributes { { "max_num_value", 15 } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""lasers_count"",""value"":9000} evaluated to UNKNOWN because the number value for user attribute ""lasers_count"" is not in the range [-2^53, +2^53]."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""pi_value"",""value"":3.14} evaluated to UNKNOWN because the number value for user attribute ""pi_value"" is not in the range [-2^53, +2^53]."), Times.Once); @@ -221,10 +223,10 @@ public void TestExactMatcherReturnsNullForOutOfBoundNumericValues() [Test] public void TestExactMatcherReturnsTrueWhenAttributeValueMatches() { - Assert.That(ExactStrCondition.Evaluate(null, new UserAttributes { { "browser_type", "firefox" } }, Logger), Is.True); - Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", false } }, Logger), Is.True); - Assert.That(ExactDecimalCondition.Evaluate(null, new UserAttributes { { "pi_value", 3.14 } }, Logger), Is.True); - Assert.That(ExactIntCondition.Evaluate(null, new UserAttributes { { "lasers_count", 9000 } }, Logger), Is.True); + Assert.That(ExactStrCondition.Evaluate(null, new UserAttributes { { "browser_type", "firefox" } }.ToUserContext(), Logger), Is.True); + Assert.That(ExactBoolCondition.Evaluate(null, new UserAttributes { { "is_registered_user", false } }.ToUserContext(), Logger), Is.True); + Assert.That(ExactDecimalCondition.Evaluate(null, new UserAttributes { { "pi_value", 3.14 } }.ToUserContext(), Logger), Is.True); + Assert.That(ExactIntCondition.Evaluate(null, new UserAttributes { { "lasers_count", 9000 } }.ToUserContext(), Logger), Is.True); } #endregion // ExactMatcher Tests @@ -234,22 +236,22 @@ public void TestExactMatcherReturnsTrueWhenAttributeValueMatches() [Test] public void TestExistsMatcherReturnsFalseWhenAttributeIsNotProvided() { - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { }, Logger), Is.False); + Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { }.ToUserContext(), Logger), Is.False); } [Test] public void TestExistsMatcherReturnsFalseWhenAttributeIsNull() { - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", null } }, Logger), Is.False); + Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", null } }.ToUserContext(), Logger), Is.False); } [Test] public void TestExistsMatcherReturnsTrueWhenAttributeValueIsProvided() { - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", "" } }, Logger), Is.True); - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", "iPhone" } }, Logger), Is.True); - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", 10 } }, Logger), Is.True); - Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", false } }, Logger), Is.True); + Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", "" } }.ToUserContext(), Logger), Is.True); + Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", "iPhone" } }.ToUserContext(), Logger), Is.True); + Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", 10 } }.ToUserContext(), Logger), Is.True); + Assert.That(ExistsCondition.Evaluate(null, new UserAttributes { { "input_value", false } }.ToUserContext(), Logger), Is.True); } #endregion // ExistsMatcher Tests @@ -259,21 +261,21 @@ public void TestExistsMatcherReturnsTrueWhenAttributeValueIsProvided() [Test] public void TestSubstringMatcherReturnsFalseWhenAttributeValueIsNotASubstring() { - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", "Los Angeles" } }, Logger), Is.False); + Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", "Los Angeles" } }.ToUserContext(), Logger), Is.False); } [Test] public void TestSubstringMatcherReturnsNullWhenAttributeValueIsNotAString() { - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", 10.5 } }, Logger), Is.Null); + Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", 10.5 } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""substring"",""name"":""location"",""value"":""USA""} evaluated to UNKNOWN because a value of type ""Double"" was passed for user attribute ""location""."), Times.Once); } [Test] public void TestSubstringMatcherReturnsTrueWhenAttributeValueIsASubstring() { - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", "USA" } }, Logger), Is.True); - Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", "San Francisco, USA" } }, Logger), Is.True); + Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", "USA" } }.ToUserContext(), Logger), Is.True); + Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", "San Francisco, USA" } }.ToUserContext(), Logger), Is.True); } #endregion // SubstringMatcher Tests @@ -283,50 +285,50 @@ public void TestSubstringMatcherReturnsTrueWhenAttributeValueIsASubstring() [Test] public void TestGTMatcherReturnsFalseWhenAttributeValueIsLessThanOrEqualToConditionValue() { - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", 5 } }, Logger), Is.False); - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", 10 } }, Logger), Is.False); + Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", 5 } }.ToUserContext(), Logger), Is.False); + Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", 10 } }.ToUserContext(), Logger), Is.False); } [Test] public void TestGTMatcherReturnsNullWhenAttributeValueIsNotANumericValue() { - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", "invalid_type" } }, Logger), Is.Null); + Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", "invalid_type" } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_gt""."), Times.Once); } [Test] public void TestGTMatcherReturnsNullWhenAttributeValueIsOutOfBounds() { - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", double.PositiveInfinity } }, Logger), Is.Null); - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", Math.Pow(2, 53) + 2 } }, Logger), Is.Null); + Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", double.PositiveInfinity } }.ToUserContext(), Logger), Is.Null); + Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", Math.Pow(2, 53) + 2 } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because the number value for user attribute ""distance_gt"" is not in the range [-2^53, +2^53]."), Times.Exactly(2)); } [Test] public void TestGTMatcherReturnsTrueWhenAttributeValueIsGreaterThanConditionValue() { - Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", 15 } }, Logger), Is.True); + Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", 15 } }.ToUserContext(), Logger), Is.True); } [Test] public void TestSemVerGTTargetBetaComplex() { var semverGTCondition = new BaseCondition { Name = "semversion_gt", Value = "2.1.3-beta+1", Match = "semver_gt", Type = "custom_attribute" }; - Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "2.1.3-beta+1.2.3" } }, Logger) ?? false); + Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "2.1.3-beta+1.2.3" } }.ToUserContext(), Logger) ?? false); } [Test] public void TestSemVerGTCompareAgainstPreReleaseToPreRelease() { var semverGTCondition = new BaseCondition { Name = "semversion_gt", Value = "3.7.1-prerelease+build", Match = "semver_gt", Type = "custom_attribute" }; - Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.1-prerelease+rc" } }, Logger) ?? false); + Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.1-prerelease+rc" } }.ToUserContext(), Logger) ?? false); } [Test] public void TestSemVerGTComparePrereleaseSmallerThanBuild() { var semverGTCondition = new BaseCondition { Name = "semversion_gt", Value = "3.7.1-prerelease", Match = "semver_gt", Type = "custom_attribute" }; - Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.1+build" } }, Logger) ?? false); + Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.1+build" } }.ToUserContext(), Logger) ?? false); } #endregion // GTMatcher Tests @@ -335,29 +337,29 @@ public void TestSemVerGTComparePrereleaseSmallerThanBuild() [Test] public void TestGEMatcherReturnsFalseWhenAttributeValueIsLessButTrueForEqualToConditionValue() { - Assert.IsFalse(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", 5 } }, Logger)?? true); - Assert.IsTrue(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", 10 } }, Logger)?? false); + Assert.IsFalse(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", 5 } }.ToUserContext(), Logger)?? true); + Assert.IsTrue(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", 10 } }.ToUserContext(), Logger)?? false); } [Test] public void TestGEMatcherReturnsNullWhenAttributeValueIsNotANumericValue() { - Assert.IsNull(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", "invalid_type" } }, Logger)); + Assert.IsNull(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", "invalid_type" } }.ToUserContext(), Logger)); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""ge"",""name"":""distance_ge"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_ge""."), Times.Once); } [Test] public void TestGEMatcherReturnsNullWhenAttributeValueIsOutOfBounds() { - Assert.IsNull(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", double.PositiveInfinity } }, Logger)); - Assert.IsNull(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", Math.Pow(2, 53) + 2 } }, Logger)); + Assert.IsNull(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", double.PositiveInfinity } }.ToUserContext(), Logger)); + Assert.IsNull(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", Math.Pow(2, 53) + 2 } }.ToUserContext(), Logger)); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""ge"",""name"":""distance_ge"",""value"":10} evaluated to UNKNOWN because the number value for user attribute ""distance_ge"" is not in the range [-2^53, +2^53]."), Times.Exactly(2)); } [Test] public void TestGEMatcherReturnsTrueWhenAttributeValueIsGreaterThanConditionValue() { - Assert.IsTrue(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", 15 } }, Logger)?? false); + Assert.IsTrue(GECondition.Evaluate(null, new UserAttributes { { "distance_ge", 15 } }.ToUserContext(), Logger)?? false); } #endregion // GEMatcher Tests @@ -367,43 +369,43 @@ public void TestGEMatcherReturnsTrueWhenAttributeValueIsGreaterThanConditionValu [Test] public void TestLTMatcherReturnsFalseWhenAttributeValueIsGreaterThanOrEqualToConditionValue() { - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", 15 } }, Logger), Is.False); - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", 10 } }, Logger), Is.False); + Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", 15 } }.ToUserContext(), Logger), Is.False); + Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", 10 } }.ToUserContext(), Logger), Is.False); } [Test] public void TestLTMatcherReturnsNullWhenAttributeValueIsNotANumericValue() { - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", "invalid_type" } }, Logger), Is.Null); + Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", "invalid_type" } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_lt""."), Times.Once); } [Test] public void TestLTMatcherReturnsNullWhenAttributeValueIsOutOfBounds() { - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", double.NegativeInfinity } }, Logger), Is.Null); - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", -Math.Pow(2, 53) - 2 } }, Logger), Is.Null); + Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", double.NegativeInfinity } }.ToUserContext(), Logger), Is.Null); + Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", -Math.Pow(2, 53) - 2 } }.ToUserContext(), Logger), Is.Null); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because the number value for user attribute ""distance_lt"" is not in the range [-2^53, +2^53]."), Times.Exactly(2)); } [Test] public void TestLTMatcherReturnsTrueWhenAttributeValueIsLessThanConditionValue() { - Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", 5 } }, Logger), Is.True); + Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", 5 } }.ToUserContext(), Logger), Is.True); } [Test] public void TestSemVerLTTargetBuildComplex() { var semverLTCondition = new BaseCondition { Name = "semversion_lt", Value = "2.1.3-beta+1.2.3", Match = "semver_lt", Type = "custom_attribute" }; - Assert.IsTrue(semverLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "2.1.3-beta+1" } }, Logger) ?? false); + Assert.IsTrue(semverLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "2.1.3-beta+1" } }.ToUserContext(), Logger) ?? false); } [Test] public void TestSemVerLTCompareMultipleDash() { var semverLTCondition = new BaseCondition { Name = "semversion_lt", Value = "2.1.3-beta-1.2.3", Match = "semver_lt", Type = "custom_attribute" }; - Assert.IsTrue(semverLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "2.1.3-beta-1" } }, Logger) ?? false); + Assert.IsTrue(semverLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "2.1.3-beta-1" } }.ToUserContext(), Logger) ?? false); } #endregion // LTMatcher Tests @@ -412,29 +414,29 @@ public void TestSemVerLTCompareMultipleDash() [Test] public void TestLEMatcherReturnsFalseWhenAttributeValueIsGreaterAndTrueIfEqualToConditionValue() { - Assert.IsFalse(LECondition.Evaluate(null, new UserAttributes { { "distance_le", 15 } }, Logger) ?? true); - Assert.IsTrue(LECondition.Evaluate(null, new UserAttributes { { "distance_le", 10 } }, Logger) ?? false); + Assert.IsFalse(LECondition.Evaluate(null, new UserAttributes { { "distance_le", 15 } }.ToUserContext(), Logger) ?? true); + Assert.IsTrue(LECondition.Evaluate(null, new UserAttributes { { "distance_le", 10 } }.ToUserContext(), Logger) ?? false); } [Test] public void TestLEMatcherReturnsNullWhenAttributeValueIsNotANumericValue() { - Assert.IsNull(LECondition.Evaluate(null, new UserAttributes { { "distance_le", "invalid_type" } }, Logger)); + Assert.IsNull(LECondition.Evaluate(null, new UserAttributes { { "distance_le", "invalid_type" } }.ToUserContext(), Logger)); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""le"",""name"":""distance_le"",""value"":10} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""distance_le""."), Times.Once); } [Test] public void TestLEMatcherReturnsNullWhenAttributeValueIsOutOfBounds() { - Assert.IsNull(LECondition.Evaluate(null, new UserAttributes { { "distance_le", double.NegativeInfinity } }, Logger)); - Assert.IsNull(LECondition.Evaluate(null, new UserAttributes { { "distance_le", -Math.Pow(2, 53) - 2 } }, Logger)); + Assert.IsNull(LECondition.Evaluate(null, new UserAttributes { { "distance_le", double.NegativeInfinity } }.ToUserContext(), Logger)); + Assert.IsNull(LECondition.Evaluate(null, new UserAttributes { { "distance_le", -Math.Pow(2, 53) - 2 } }.ToUserContext(), Logger)); LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""le"",""name"":""distance_le"",""value"":10} evaluated to UNKNOWN because the number value for user attribute ""distance_le"" is not in the range [-2^53, +2^53]."), Times.Exactly(2)); } [Test] public void TestLEMatcherReturnsTrueWhenAttributeValueIsLessThanConditionValue() { - Assert.IsTrue(LECondition.Evaluate(null, new UserAttributes { { "distance_le", 5 } }, Logger) ?? false); + Assert.IsTrue(LECondition.Evaluate(null, new UserAttributes { { "distance_le", 5 } }.ToUserContext(), Logger) ?? false); } #endregion // LEMatcher Tests @@ -443,28 +445,28 @@ public void TestLEMatcherReturnsTrueWhenAttributeValueIsLessThanConditionValue() [Test] public void TestSemVerLTMatcherReturnsFalseWhenAttributeValueIsGreaterThanOrEqualToConditionValue() { - Assert.IsFalse(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.2" } }, Logger) ?? true); - Assert.IsFalse(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.1" } }, Logger) ?? true); - Assert.IsFalse(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.8" } }, Logger) ?? true); - Assert.IsFalse(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "4" } }, Logger) ?? true); + Assert.IsFalse(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.2" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.1" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.8" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "4" } }.ToUserContext(), Logger) ?? true); } [Test] public void TestSemVerLTMatcherReturnsTrueWhenAttributeValueIsLessThanConditionValue() { - Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.0" } }, Logger) ?? false); - Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.1-beta" } }, Logger) ?? false); - Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "2.7.1" } }, Logger) ?? false); - Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7" } }, Logger) ?? false); - Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3" } }, Logger) ?? false); + Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.0" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.1-beta" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "2.7.1" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(SemVerLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3" } }.ToUserContext(), Logger) ?? false); } [Test] public void TestSemVerLTMatcherReturnsTrueWhenAttributeValueIsLessThanConditionValueBeta() { var semverLTCondition = new BaseCondition { Name = "semversion_lt", Value = "3.7.0-beta.2.3", Match = "semver_lt", Type = "custom_attribute" }; - Assert.IsTrue(semverLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.0-beta.2.1" } }, Logger) ?? false); - Assert.IsTrue(semverLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.0-beta" } }, Logger) ?? false); + Assert.IsTrue(semverLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.0-beta.2.1" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(semverLTCondition.Evaluate(null, new UserAttributes { { "semversion_lt", "3.7.0-beta" } }.ToUserContext(), Logger) ?? false); } #endregion // SemVerLTMatcher Tests @@ -472,28 +474,28 @@ public void TestSemVerLTMatcherReturnsTrueWhenAttributeValueIsLessThanConditionV [Test] public void TestSemVerGTMatcherReturnsFalseWhenAttributeValueIsLessThanOrEqualToConditionValue() { - Assert.IsFalse(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.0" } }, Logger)?? true); - Assert.IsFalse(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.1" } }, Logger)?? true); - Assert.IsFalse(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.6" } }, Logger)?? true); - Assert.IsFalse(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "2" } }, Logger)?? true); + Assert.IsFalse(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.0" } }.ToUserContext(), Logger)?? true); + Assert.IsFalse(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.1" } }.ToUserContext(), Logger)?? true); + Assert.IsFalse(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.6" } }.ToUserContext(), Logger)?? true); + Assert.IsFalse(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "2" } }.ToUserContext(), Logger)?? true); } [Test] public void TestSemVerGTMatcherReturnsTrueWhenAttributeValueIsGreaterThanConditionValue() { - Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.2" } }, Logger)?? false); - Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.2-beta" } }, Logger)?? false); - Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "4.7.1" } }, Logger)?? false); - Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.8" } }, Logger)?? false); - Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "4" } }, Logger)?? false); + Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.2" } }.ToUserContext(), Logger)?? false); + Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.2-beta" } }.ToUserContext(), Logger)?? false); + Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "4.7.1" } }.ToUserContext(), Logger)?? false); + Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.8" } }.ToUserContext(), Logger)?? false); + Assert.IsTrue(SemVerGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "4" } }.ToUserContext(), Logger)?? false); } [Test] public void TestSemVerGTMatcherReturnsTrueWhenAttributeValueIsGreaterThanConditionValueBeta() { var semverGTCondition = new BaseCondition { Name = "semversion_gt", Value = "3.7.0-beta.2.3", Match = "semver_gt", Type = "custom_attribute" }; - Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.0-beta.2.4" } }, Logger)?? false); - Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.0" } }, Logger)?? false); + Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.0-beta.2.4" } }.ToUserContext(), Logger)?? false); + Assert.IsTrue(semverGTCondition.Evaluate(null, new UserAttributes { { "semversion_gt", "3.7.0" } }.ToUserContext(), Logger)?? false); } #endregion // SemVerGTMatcher Tests @@ -501,48 +503,48 @@ public void TestSemVerGTMatcherReturnsTrueWhenAttributeValueIsGreaterThanConditi [Test] public void TestSemVerEQMatcherReturnsFalseWhenAttributeValueIsNotEqualToConditionValue() { - Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.7.0" } }, Logger) ?? true); - Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.7.2" } }, Logger) ?? true); - Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.6" } }, Logger)?? true); - Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "2" } }, Logger)?? true); - Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "4" } }, Logger)?? true); - Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3" } }, Logger)?? true); + Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.7.0" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.7.2" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.6" } }.ToUserContext(), Logger)?? true); + Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "2" } }.ToUserContext(), Logger)?? true); + Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "4" } }.ToUserContext(), Logger)?? true); + Assert.IsFalse(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3" } }.ToUserContext(), Logger)?? true); } [Test] public void TestSemVerEQMatcherReturnsTrueWhenAttributeValueIsEqualToConditionValue() { - Assert.IsTrue(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.7.1" } }, Logger) ?? false); + Assert.IsTrue(SemVerEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.7.1" } }.ToUserContext(), Logger) ?? false); } [Test] public void TestSemVerEQMatcherReturnsTrueWhenAttributeValueIsEqualToConditionValueMajorOnly() { var semverEQCondition = new BaseCondition { Name = "semversion_eq", Value = "3", Match = "semver_eq", Type = "custom_attribute" }; - Assert.IsTrue(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.0.0" } }, Logger) ?? false); - Assert.IsTrue(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.1" } }, Logger) ?? false); + Assert.IsTrue(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.0.0" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.1" } }.ToUserContext(), Logger) ?? false); } [Test] public void TestSemVerEQMatcherReturnsFalseOrFalseWhenAttributeValueIsNotEqualToConditionValueMajorOnly() { var semverEQCondition = new BaseCondition { Name = "semversion_eq", Value = "3", Match = "semver_eq", Type = "custom_attribute" }; - Assert.IsFalse(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "4.0" } }, Logger) ?? true); - Assert.IsFalse(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "2" } }, Logger) ?? true); + Assert.IsFalse(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "4.0" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "2" } }.ToUserContext(), Logger) ?? true); } [Test] public void TestSemVerEQMatcherReturnsTrueWhenAttributeValueIsEqualToConditionValueBeta() { var semverEQCondition = new BaseCondition { Name = "semversion_eq", Value = "3.7.0-beta.2.3", Match = "semver_eq", Type = "custom_attribute" }; - Assert.IsTrue(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.7.0-beta.2.3" } }, Logger) ?? false); + Assert.IsTrue(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "3.7.0-beta.2.3" } }.ToUserContext(), Logger) ?? false); } [Test] public void TestSemVerEQTargetBuildIgnores() { var semverEQCondition = new BaseCondition { Name = "semversion_eq", Value = "2.1.3", Match = "semver_eq", Type = "custom_attribute" }; - Assert.IsTrue(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "2.1.3+build" } }, Logger) ?? false); + Assert.IsTrue(semverEQCondition.Evaluate(null, new UserAttributes { { "semversion_eq", "2.1.3+build" } }.ToUserContext(), Logger) ?? false); } #endregion // SemVerEQMatcher Tests @@ -550,46 +552,46 @@ public void TestSemVerEQTargetBuildIgnores() [Test] public void TestSemVerGEMatcherReturnsFalseWhenAttributeValueIsNotGreaterOrEqualToConditionValue() { - Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0" } }, Logger) ?? true); - Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.1-beta" } }, Logger) ?? true); - Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.6" } }, Logger) ?? true); - Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "2" } }, Logger) ?? true); - Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3" } }, Logger) ?? true); + Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.1-beta" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.6" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "2" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3" } }.ToUserContext(), Logger) ?? true); } [Test] public void TestSemVerGEMatcherReturnsTrueWhenAttributeValueIsGreaterOrEqualToConditionValue() { - Assert.IsTrue(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.1" } }, Logger) ?? false); - Assert.IsTrue(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.2" } }, Logger) ?? false); - Assert.IsTrue(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.8.1" } }, Logger) ?? false); ; - Assert.IsTrue(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "4.7.1" } }, Logger) ?? false); + Assert.IsTrue(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.1" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.2" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.8.1" } }.ToUserContext(), Logger) ?? false); ; + Assert.IsTrue(SemVerGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "4.7.1" } }.ToUserContext(), Logger) ?? false); } [Test] public void TestSemVerGEMatcherReturnsTrueWhenAttributeValueIsGreaterOrEqualToConditionValueMajorOnly() { var semverGECondition = new BaseCondition { Name = "semversion_ge", Value = "3", Match = "semver_ge", Type = "custom_attribute" }; - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0" } }, Logger) ?? false); - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.0.0" } }, Logger) ?? false); - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "4.0" } }, Logger) ?? false); + Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.0.0" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "4.0" } }.ToUserContext(), Logger) ?? false); } [Test] public void TestSemVerGEMatcherReturnsFalseWhenAttributeValueIsNotGreaterOrEqualToConditionValueMajorOnly() { var semverGECondition = new BaseCondition { Name = "semversion_ge", Value = "3", Match = "semver_ge", Type = "custom_attribute" }; - Assert.IsFalse(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "2" } }, Logger) ?? true); + Assert.IsFalse(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "2" } }.ToUserContext(), Logger) ?? true); } [Test] public void TestSemVerGEMatcherReturnsTrueWhenAttributeValueIsGreaterOrEqualToConditionValueBeta() { var semverGECondition = new BaseCondition { Name = "semversion_ge", Value = "3.7.0-beta.2.3", Match = "semver_ge", Type = "custom_attribute" }; - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0-beta.2.3" } }, Logger) ?? false); - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0-beta.2.4" } }, Logger) ?? false); - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0-beta.2.3+1.2.3" } }, Logger) ?? false); - Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.1-beta.2.3" } }, Logger) ?? false); + Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0-beta.2.3" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0-beta.2.4" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.0-beta.2.3+1.2.3" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(semverGECondition.Evaluate(null, new UserAttributes { { "semversion_ge", "3.7.1-beta.2.3" } }.ToUserContext(), Logger) ?? false); } #endregion // SemVerGEMatcher Tests @@ -597,46 +599,46 @@ public void TestSemVerGEMatcherReturnsTrueWhenAttributeValueIsGreaterOrEqualToCo [Test] public void TestSemVerLEMatcherReturnsFalseWhenAttributeValueIsNotLessOrEqualToConditionValue() { - Assert.IsFalse(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.2" } }, Logger) ?? true); - Assert.IsFalse(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.8" } }, Logger) ?? true); - Assert.IsFalse(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "4" } }, Logger) ?? true); + Assert.IsFalse(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.2" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.8" } }.ToUserContext(), Logger) ?? true); + Assert.IsFalse(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "4" } }.ToUserContext(), Logger) ?? true); } [Test] public void TestSemVerLEMatcherReturnsTrueWhenAttributeValueIsLessOrEqualToConditionValue() { - Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.1" } }, Logger) ?? false); - Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0" } }, Logger) ?? false); - Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.6.1" } }, Logger) ?? false); - Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "2.7.1" } }, Logger) ?? false); - Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.1-beta" } }, Logger) ?? false); + Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.1" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.6.1" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "2.7.1" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(SemVerLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.1-beta" } }.ToUserContext(), Logger) ?? false); } [Test] public void TestSemVerLEMatcherReturnsTrueWhenAttributeValueIsLessOrEqualToConditionValueMajorOnly() { var semverLECondition = new BaseCondition { Name = "semversion_le", Value = "3", Match = "semver_le", Type = "custom_attribute" }; - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0-beta.2.4" } }, Logger) ?? false); - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.1-beta" } }, Logger) ?? false); - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.0.0" } }, Logger) ?? false); - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "2.0" } }, Logger) ?? false); + Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0-beta.2.4" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.1-beta" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.0.0" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "2.0" } }.ToUserContext(), Logger) ?? false); } [Test] public void TestSemVerLEMatcherReturnsFalseWhenAttributeValueIsNotLessOrEqualToConditionValueMajorOnly() { var semverLECondition = new BaseCondition { Name = "semversion_le", Value = "3", Match = "semver_le", Type = "custom_attribute" }; - Assert.IsFalse(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "4" } }, Logger) ?? true); + Assert.IsFalse(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "4" } }.ToUserContext(), Logger) ?? true); } [Test] public void TestSemVerLEMatcherReturnsTrueWhenAttributeValueIsLessOrEqualToConditionValueBeta() { var semverLECondition = new BaseCondition { Name = "semversion_le", Value = "3.7.0-beta.2.3", Match = "semver_le", Type = "custom_attribute" }; - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0-beta.2.2" } }, Logger) ?? false); - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0-beta.2.3" } }, Logger) ?? false); - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0-beta.2.2+1.2.3" } }, Logger) ?? false); - Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.6.1-beta.2.3+1.2" } }, Logger) ?? false); + Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0-beta.2.2" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0-beta.2.3" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.7.0-beta.2.2+1.2.3" } }.ToUserContext(), Logger) ?? false); + Assert.IsTrue(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", "3.6.1-beta.2.3+1.2" } }.ToUserContext(), Logger) ?? false); } #endregion // SemVerLEMatcher Tests @@ -649,10 +651,69 @@ public void TestInvalidSemVersions() ".2.2", "3.7.2.2", "3.x", ",", "+build-prerelese"}; var semverLECondition = new BaseCondition { Name = "semversion_le", Value = "3", Match = "semver_le", Type = "custom_attribute" }; foreach(var invalidValue in invalidValues) { - Assert.IsNull(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", invalidValue } }, Logger), $"returned for {invalidValue}"); + Assert.IsNull(semverLECondition.Evaluate(null, new UserAttributes { { "semversion_le", invalidValue } }.ToUserContext(), Logger), $"returned for {invalidValue}"); } } #endregion + + #region Qualified Tests + + [Test] + public void QualifiedConditionWithNonStringValueShouldFail() + { + var condition = new BaseCondition() + { + Type = BaseCondition.THIRD_PARTY_DIMENSION, + Match = BaseCondition.QUALIFIED, + Value = 100, + }; + + var result = condition.Evaluate(null, null, Logger); + + Assert.That(result, Is.Null); + LoggerMock.Verify(l => l.Log(LogLevel.WARN, $@"Audience condition ""{condition}"" has a qualified match but invalid value."), Times.Once); + } + + + private const string QUALIFIED_VALUE = "part-of-the-rebellion"; + private readonly List _qualifiedSegments = new List() + { + QUALIFIED_VALUE, + }; + + [Test] + public void QualifiedConditionWithMatchingValueShouldBeTrue() + { + var condition = new BaseCondition() + { + Type = BaseCondition.THIRD_PARTY_DIMENSION, + Match = BaseCondition.QUALIFIED, + Value = QUALIFIED_VALUE, + }; + + var result = condition.Evaluate(null, _qualifiedSegments.ToUserContext(), Logger); + + Assert.True(result.HasValue && result.Value); + LoggerMock.Verify(l => l.Log(It.IsAny(), It.IsAny()), Times.Never); + } + + [Test] + public void QualifiedConditionWithNonMatchingValueShouldBeFalse() + { + var condition = new BaseCondition() + { + Type = BaseCondition.THIRD_PARTY_DIMENSION, + Match = BaseCondition.QUALIFIED, + Value = "empire-star-system", + }; + + var result = condition.Evaluate(null, _qualifiedSegments.ToUserContext(), Logger); + + Assert.True(result.HasValue && result.Value == false); + LoggerMock.Verify(l => l.Log( It.IsAny(), It.IsAny()), Times.Never); + } + + #endregion } } diff --git a/OptimizelySDK.Tests/AudienceConditionsTests/ConditionsTest.cs b/OptimizelySDK.Tests/AudienceConditionsTests/ConditionsTest.cs index 00daf52f..af311b8e 100644 --- a/OptimizelySDK.Tests/AudienceConditionsTests/ConditionsTest.cs +++ b/OptimizelySDK.Tests/AudienceConditionsTests/ConditionsTest.cs @@ -1,11 +1,11 @@ /* - * Copyright 2019, Optimizely + * Copyright 2019-2022, 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 + * https://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, @@ -41,11 +41,11 @@ public class ConditionsTest public void Initialize() { TrueConditionMock = new Mock(); - TrueConditionMock.Setup(condition => condition.Evaluate(It.IsAny(), It.IsAny(), It.IsAny())).Returns(true); + TrueConditionMock.Setup(condition => condition.Evaluate(It.IsAny(), It.IsAny(), It.IsAny())).Returns(true); FalseConditionMock = new Mock(); - FalseConditionMock.Setup(condition => condition.Evaluate(It.IsAny(), It.IsAny(), It.IsAny())).Returns(false); + FalseConditionMock.Setup(condition => condition.Evaluate(It.IsAny(), It.IsAny(), It.IsAny())).Returns(false); NullConditionMock = new Mock(); - NullConditionMock.Setup(condition => condition.Evaluate(It.IsAny(), It.IsAny(), It.IsAny())).Returns((bool?)null); + NullConditionMock.Setup(condition => condition.Evaluate(It.IsAny(), It.IsAny(), It.IsAny())).Returns((bool?)null); TrueCondition = TrueConditionMock.Object; FalseCondition = FalseConditionMock.Object; @@ -76,7 +76,7 @@ public void TestAndEvaluatorReturnsFalseWhenAnyOperandEvaluatesToFalse() Assert.That(andCondition.Evaluate(null, null, Logger), Is.False); // Should not be called due to short circuiting. - TrueConditionMock.Verify(condition => condition.Evaluate(It.IsAny(), It.IsAny(), Logger), Times.Never); + TrueConditionMock.Verify(condition => condition.Evaluate(It.IsAny(), It.IsAny(), Logger), Times.Never); } [Test] diff --git a/OptimizelySDK.Tests/EntityTests/IntegrationTest.cs b/OptimizelySDK.Tests/EntityTests/IntegrationTest.cs new file mode 100644 index 00000000..6daea7a8 --- /dev/null +++ b/OptimizelySDK.Tests/EntityTests/IntegrationTest.cs @@ -0,0 +1,97 @@ +/* + * Copyright 2022, 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 + * + * https://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; + +namespace OptimizelySDK.Tests.EntityTests +{ + [TestFixture] + public class IntegrationTest + { + private const string KEY = "test-key"; + + private const string HOST = "api.example.com"; + private const string PUBLIC_KEY = "FAk3-pUblic-K3y"; + + [Test] + public void ToStringWithNoHostShouldSucceed() + { + var integration = new Integration() + { + Key = KEY, + PublicKey = PUBLIC_KEY, + }; + + var stringValue = integration.ToString(); + + Assert.True(stringValue.Contains($@"key='{KEY}'")); + Assert.True(stringValue.Contains($@"publicKey='{PUBLIC_KEY}'")); + Assert.False(stringValue.Contains("host")); + Assert.False(stringValue.Contains(HOST)); + } + + [Test] + public void ToStringWithNoPublicKeyShouldSucceed() + { + var integration = new Integration() + { + Key = KEY, + Host = HOST, + }; + + var stringValue = integration.ToString(); + + Assert.True(stringValue.Contains($@"key='{KEY}'")); + Assert.True(stringValue.Contains($@"host='{HOST}'")); + Assert.False(stringValue.Contains("publicKey")); + Assert.False(stringValue.Contains(PUBLIC_KEY)); + } + + [Test] + public void ToStringWithAllPropertiesShouldSucceed() + { + var integration = new Integration() + { + Key = KEY, + Host = HOST, + PublicKey = PUBLIC_KEY, + }; + + var stringValue = integration.ToString(); + + Assert.True( + stringValue.Contains($@"key='{KEY}', host='{HOST}', publicKey='{PUBLIC_KEY}'")); + } + + [Test] + public void ToStringWithOnlyKeyShouldSucceed() + { + var integration = new Integration() + { + Key = KEY, + }; + + var stringValue = integration.ToString(); + + Assert.True(stringValue.Contains($@"key='{KEY}'")); + Assert.False(stringValue.Contains("host")); + Assert.False(stringValue.Contains(HOST)); + Assert.False(stringValue.Contains("publicKey")); + Assert.False(stringValue.Contains(PUBLIC_KEY)); + } + } +} diff --git a/OptimizelySDK.Tests/IntegrationEmptyDatafile.json b/OptimizelySDK.Tests/IntegrationEmptyDatafile.json new file mode 100644 index 00000000..1212bc81 --- /dev/null +++ b/OptimizelySDK.Tests/IntegrationEmptyDatafile.json @@ -0,0 +1,18 @@ +{ + "version": "4", + "rollouts": [], + "anonymizeIP": true, + "projectId": "20441150641", + "variables": [], + "featureFlags": [], + "experiments": [], + "audiences": [], + "groups": [], + "integrations": [], + "attributes": [], + "accountId": "11467598500", + "events": [], + "revision": "241", + "sdkKey": "EmptyIntegrationDatafile", + "environmentKey": "Production" +} diff --git a/OptimizelySDK.Tests/IntegrationNonOdpDatafile.json b/OptimizelySDK.Tests/IntegrationNonOdpDatafile.json new file mode 100644 index 00000000..cfdb0819 --- /dev/null +++ b/OptimizelySDK.Tests/IntegrationNonOdpDatafile.json @@ -0,0 +1,65 @@ +{ + "version": "4", + "rollouts": [ + { + "experiments": [ + ], + "id": "18309384009" + } + ], + "typedAudiences": [], + "anonymizeIP": true, + "projectId": "18326250003", + "variables": [], + "featureFlags": [ + { + "experimentIds": [], + "rolloutId": "18309384009", + "variables": [ + { + "defaultValue": "", + "type": "string", + "id": "18323951833", + "key": "var_1" + } + ], + "id": "18244658520", + "key": "empty_rollout" + }, + { + "experimentIds": [], + "rolloutId": "", + "variables": [ + { + "defaultValue": "", + "type": "string", + "id": "2832355113", + "key": "var_2" + } + ], + "id": "24246538512", + "key": "empty_rollout_id" + } + ], + "experiments": [], + "audiences": [ + { + "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + "id": "$opt_dummy_audience", + "name": "Optimizely-Generated Audience for Backwards Compatibility" + } + ], + "groups": [], + "integrations": [ + { + "key": "not-odp", + "host": "https://example.com", + "publicKey": "CleAr1y-!a-R3a7-pUbl1c-k3y" + } + ], + "attributes": [], + "botFiltering": false, + "accountId": "8272261422", + "events": [], + "revision": "2" +} diff --git a/OptimizelySDK.Tests/IntegrationOdpDatafile.json b/OptimizelySDK.Tests/IntegrationOdpDatafile.json new file mode 100644 index 00000000..219718e0 --- /dev/null +++ b/OptimizelySDK.Tests/IntegrationOdpDatafile.json @@ -0,0 +1,318 @@ +{ + "version": "4", + "rollouts": [{ + "experiments": [{ + "status": "Running", + "key": "feat_no_vars_rule", + "layerId": "11551226731", + "trafficAllocation": [{ + "entityId": "11557362669", + "endOfRange": 10000 + }], + "audienceIds": ["3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"], + "variations": [{ + "variables": [], + "id": "11557362669", + "key": "11557362669", + "featureEnabled": true + }], + "forcedVariations": {}, + "id": "11488548027" + }], + "id": "11551226731" + }, + { + "experiments": [{ + "status": "Paused", + "key": "feat_with_var_rule", + "layerId": "11638870867", + "trafficAllocation": [{ + "entityId": "11475708558", + "endOfRange": 0 + }], + "audienceIds": [], + "variations": [{ + "variables": [], + "id": "11475708558", + "key": "11475708558", + "featureEnabled": false + }], + "forcedVariations": {}, + "id": "11630490911" + }], + "id": "11638870867" + }, + { + "experiments": [{ + "status": "Running", + "key": "11488548028", + "layerId": "11551226732", + "trafficAllocation": [{ + "entityId": "11557362670", + "endOfRange": 10000 + }], + "audienceIds": ["0"], + "audienceConditions": ["and", ["or", "3468206642", "3988293898"], + ["or", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"] + ], + "variations": [{ + "variables": [], + "id": "11557362670", + "key": "11557362670", + "featureEnabled": true + }], + "forcedVariations": {}, + "id": "11488548028" + }], + "id": "11551226732" + }, + { + "experiments": [{ + "status": "Paused", + "key": "11630490912", + "layerId": "11638870868", + "trafficAllocation": [{ + "entityId": "11475708559", + "endOfRange": 0 + }], + "audienceIds": [], + "variations": [{ + "variables": [], + "id": "11475708559", + "key": "11475708559", + "featureEnabled": false + }], + "forcedVariations": {}, + "id": "11630490912" + }], + "id": "11638870868" + } + ], + "anonymizeIP": false, + "projectId": "11624721371", + "variables": [], + "featureFlags": [{ + "experimentIds": [], + "rolloutId": "11551226731", + "variables": [], + "id": "11477755619", + "key": "feat_no_vars" + }, + { + "experimentIds": [ + "11564051718" + ], + "rolloutId": "11638870867", + "variables": [{ + "defaultValue": "x", + "type": "string", + "id": "11535264366", + "key": "x" + }], + "id": "11567102051", + "key": "feat_with_var" + }, + { + "experimentIds": [], + "rolloutId": "11551226732", + "variables": [], + "id": "11567102052", + "key": "feat2" + }, + { + "experimentIds": ["1323241599"], + "rolloutId": "11638870868", + "variables": [{ + "defaultValue": "10", + "type": "integer", + "id": "11535264367", + "key": "z" + }], + "id": "11567102053", + "key": "feat2_with_var" + } + ], + "experiments": [{ + "status": "Running", + "key": "feat_with_var_test", + "layerId": "11504144555", + "trafficAllocation": [{ + "entityId": "11617170975", + "endOfRange": 10000 + }], + "audienceIds": ["3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"], + "variations": [{ + "variables": [{ + "id": "11535264366", + "value": "xyz" + }], + "id": "11617170975", + "key": "variation_2", + "featureEnabled": true + }], + "forcedVariations": {}, + "id": "11564051718" + }, + { + "id": "1323241597", + "key": "typed_audience_experiment", + "layerId": "1630555627", + "status": "Running", + "variations": [{ + "id": "1423767503", + "key": "A", + "variables": [] + }], + "trafficAllocation": [{ + "entityId": "1423767503", + "endOfRange": 10000 + }], + "audienceIds": ["3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"], + "forcedVariations": {} + }, + { + "id": "1323241598", + "key": "audience_combinations_experiment", + "layerId": "1323241598", + "status": "Running", + "variations": [{ + "id": "1423767504", + "key": "A", + "variables": [] + }], + "trafficAllocation": [{ + "entityId": "1423767504", + "endOfRange": 10000 + }], + "audienceIds": ["0"], + "audienceConditions": ["and", ["or", "3468206642", "3988293898"], + ["or", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"] + ], + "forcedVariations": {} + }, + { + "id": "1323241599", + "key": "feat2_with_var_test", + "layerId": "1323241600", + "status": "Running", + "variations": [{ + "variables": [{ + "id": "11535264367", + "value": "150" + }], + "id": "1423767505", + "key": "variation_2", + "featureEnabled": true + }], + "trafficAllocation": [{ + "entityId": "1423767505", + "endOfRange": 10000 + }], + "audienceIds": ["0"], + "audienceConditions": ["and", ["or", "3468206642", "3988293898"], + ["or", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"] + ], + "forcedVariations": {} + } + ], + "audiences": [ + { + "id": "3468206642", + "name": "exactString", + "conditions": "[\"and\", [\"or\", [\"or\", {\"name\": \"house\", \"type\": \"custom_attribute\", \"value\": \"Gryffindor\"}]]]" + }, + { + "id": "3468206645", + "name": "notChrome", + "conditions": "[\"and\", [\"or\", [\"not\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_attribute\", \"value\":\"Chrome\"}]]]]" + }, + { + "id": "3988293898", + "name": "$$dummySubstringString", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3988293899", + "name": "$$dummyExists", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206646", + "name": "$$dummyExactNumber", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206647", + "name": "$$dummyGtNumber", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206644", + "name": "$$dummyLtNumber", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206643", + "name": "$$dummyExactBoolean", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "0", + "name": "$$dummy", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "$opt_dummy_audience", + "name": "dummy_audience", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + } + ], + "typedAudiences": [], + "groups": [], + "integrations": [ + { + "key": "odp", + "host": "https://api.zaius.com", + "publicKey": "W4WzcEs-ABgXorzY7h1LCQ" + } + ], + "attributes": [{ + "key": "house", + "id": "594015" + }, + { + "key": "lasers", + "id": "594016" + }, + { + "key": "should_do_it", + "id": "594017" + }, + { + "key": "favorite_ice_cream", + "id": "594018" + } + ], + "botFiltering": false, + "accountId": "4879520872", + "events": [{ + "key": "item_bought", + "id": "594089", + "experimentIds": [ + "11564051718", + "1323241597" + ] + }, + { + "key": "user_signed_up", + "id": "594090", + "experimentIds": [ + "1323241598", + "1323241599" + ] + } + ], + "revision": "3", + "sdkKey": "typedAudienceDatafile", + "environmentKey": "Production" +} diff --git a/OptimizelySDK.Tests/IntegrationOdpWithOtherFieldsDatafile.json b/OptimizelySDK.Tests/IntegrationOdpWithOtherFieldsDatafile.json new file mode 100644 index 00000000..4f6dd8ea --- /dev/null +++ b/OptimizelySDK.Tests/IntegrationOdpWithOtherFieldsDatafile.json @@ -0,0 +1,321 @@ +{ + "version": "4", + "rollouts": [{ + "experiments": [{ + "status": "Running", + "key": "feat_no_vars_rule", + "layerId": "11551226731", + "trafficAllocation": [{ + "entityId": "11557362669", + "endOfRange": 10000 + }], + "audienceIds": ["3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"], + "variations": [{ + "variables": [], + "id": "11557362669", + "key": "11557362669", + "featureEnabled": true + }], + "forcedVariations": {}, + "id": "11488548027" + }], + "id": "11551226731" + }, + { + "experiments": [{ + "status": "Paused", + "key": "feat_with_var_rule", + "layerId": "11638870867", + "trafficAllocation": [{ + "entityId": "11475708558", + "endOfRange": 0 + }], + "audienceIds": [], + "variations": [{ + "variables": [], + "id": "11475708558", + "key": "11475708558", + "featureEnabled": false + }], + "forcedVariations": {}, + "id": "11630490911" + }], + "id": "11638870867" + }, + { + "experiments": [{ + "status": "Running", + "key": "11488548028", + "layerId": "11551226732", + "trafficAllocation": [{ + "entityId": "11557362670", + "endOfRange": 10000 + }], + "audienceIds": ["0"], + "audienceConditions": ["and", ["or", "3468206642", "3988293898"], + ["or", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"] + ], + "variations": [{ + "variables": [], + "id": "11557362670", + "key": "11557362670", + "featureEnabled": true + }], + "forcedVariations": {}, + "id": "11488548028" + }], + "id": "11551226732" + }, + { + "experiments": [{ + "status": "Paused", + "key": "11630490912", + "layerId": "11638870868", + "trafficAllocation": [{ + "entityId": "11475708559", + "endOfRange": 0 + }], + "audienceIds": [], + "variations": [{ + "variables": [], + "id": "11475708559", + "key": "11475708559", + "featureEnabled": false + }], + "forcedVariations": {}, + "id": "11630490912" + }], + "id": "11638870868" + } + ], + "anonymizeIP": false, + "projectId": "11624721371", + "variables": [], + "featureFlags": [{ + "experimentIds": [], + "rolloutId": "11551226731", + "variables": [], + "id": "11477755619", + "key": "feat_no_vars" + }, + { + "experimentIds": [ + "11564051718" + ], + "rolloutId": "11638870867", + "variables": [{ + "defaultValue": "x", + "type": "string", + "id": "11535264366", + "key": "x" + }], + "id": "11567102051", + "key": "feat_with_var" + }, + { + "experimentIds": [], + "rolloutId": "11551226732", + "variables": [], + "id": "11567102052", + "key": "feat2" + }, + { + "experimentIds": ["1323241599"], + "rolloutId": "11638870868", + "variables": [{ + "defaultValue": "10", + "type": "integer", + "id": "11535264367", + "key": "z" + }], + "id": "11567102053", + "key": "feat2_with_var" + } + ], + "experiments": [{ + "status": "Running", + "key": "feat_with_var_test", + "layerId": "11504144555", + "trafficAllocation": [{ + "entityId": "11617170975", + "endOfRange": 10000 + }], + "audienceIds": ["3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"], + "variations": [{ + "variables": [{ + "id": "11535264366", + "value": "xyz" + }], + "id": "11617170975", + "key": "variation_2", + "featureEnabled": true + }], + "forcedVariations": {}, + "id": "11564051718" + }, + { + "id": "1323241597", + "key": "typed_audience_experiment", + "layerId": "1630555627", + "status": "Running", + "variations": [{ + "id": "1423767503", + "key": "A", + "variables": [] + }], + "trafficAllocation": [{ + "entityId": "1423767503", + "endOfRange": 10000 + }], + "audienceIds": ["3468206642", "3988293898", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"], + "forcedVariations": {} + }, + { + "id": "1323241598", + "key": "audience_combinations_experiment", + "layerId": "1323241598", + "status": "Running", + "variations": [{ + "id": "1423767504", + "key": "A", + "variables": [] + }], + "trafficAllocation": [{ + "entityId": "1423767504", + "endOfRange": 10000 + }], + "audienceIds": ["0"], + "audienceConditions": ["and", ["or", "3468206642", "3988293898"], + ["or", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"] + ], + "forcedVariations": {} + }, + { + "id": "1323241599", + "key": "feat2_with_var_test", + "layerId": "1323241600", + "status": "Running", + "variations": [{ + "variables": [{ + "id": "11535264367", + "value": "150" + }], + "id": "1423767505", + "key": "variation_2", + "featureEnabled": true + }], + "trafficAllocation": [{ + "entityId": "1423767505", + "endOfRange": 10000 + }], + "audienceIds": ["0"], + "audienceConditions": ["and", ["or", "3468206642", "3988293898"], + ["or", "3988293899", "3468206646", "3468206647", "3468206644", "3468206643"] + ], + "forcedVariations": {} + } + ], + "audiences": [ + { + "id": "3468206642", + "name": "exactString", + "conditions": "[\"and\", [\"or\", [\"or\", {\"name\": \"house\", \"type\": \"custom_attribute\", \"value\": \"Gryffindor\"}]]]" + }, + { + "id": "3468206645", + "name": "notChrome", + "conditions": "[\"and\", [\"or\", [\"not\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_attribute\", \"value\":\"Chrome\"}]]]]" + }, + { + "id": "3988293898", + "name": "$$dummySubstringString", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3988293899", + "name": "$$dummyExists", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206646", + "name": "$$dummyExactNumber", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206647", + "name": "$$dummyGtNumber", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206644", + "name": "$$dummyLtNumber", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "3468206643", + "name": "$$dummyExactBoolean", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "0", + "name": "$$dummy", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + }, + { + "id": "$opt_dummy_audience", + "name": "dummy_audience", + "conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\"}" + } + ], + "typedAudiences": [], + "groups": [], + "integrations": [ + { + "key": "odp", + "host": "https://api.zaius.com", + "publicKey": "W4WzcEs-ABgXorzY7h1LCQ", + "k1": 10, + "k2": true, + "k3": "any-value" + } + ], + "attributes": [{ + "key": "house", + "id": "594015" + }, + { + "key": "lasers", + "id": "594016" + }, + { + "key": "should_do_it", + "id": "594017" + }, + { + "key": "favorite_ice_cream", + "id": "594018" + } + ], + "botFiltering": false, + "accountId": "4879520872", + "events": [{ + "key": "item_bought", + "id": "594089", + "experimentIds": [ + "11564051718", + "1323241597" + ] + }, + { + "key": "user_signed_up", + "id": "594090", + "experimentIds": [ + "1323241598", + "1323241599" + ] + } + ], + "revision": "3", + "sdkKey": "typedAudienceDatafile", + "environmentKey": "Production" +} diff --git a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs index 007ee360..0689e6a2 100644 --- a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs +++ b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs @@ -1,11 +1,11 @@ /* - * Copyright 2020-2021, Optimizely + * Copyright 2020-2022, 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 + * https://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, @@ -18,7 +18,6 @@ using Newtonsoft.Json.Linq; using NUnit.Framework; using OptimizelySDK.Config; -using OptimizelySDK.Entity; using OptimizelySDK.Logger; using OptimizelySDK.OptlyConfig; using OptimizelySDK.Tests.UtilsTests; diff --git a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj index 2a776bcb..d0a2d2ae 100644 --- a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj +++ b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj @@ -78,6 +78,7 @@ + @@ -106,6 +107,7 @@ + @@ -123,6 +125,10 @@ + + + + @@ -136,7 +142,6 @@ OptimizelySDK - @@ -146,7 +151,6 @@ -