diff --git a/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs b/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs index 2e90011b..f185038f 100644 --- a/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs +++ b/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs @@ -1,5 +1,5 @@ /* - * Copyright 2019, Optimizely + * Copyright 2019-2020, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,7 +82,7 @@ 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); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, $@"Audience condition ""{condition}"" has an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK"), Times.Once); + 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); } [Test] @@ -91,7 +91,7 @@ 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); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, $@"Audience condition ""{condition}"" has an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK"), Times.Once); + 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); } [Test] @@ -100,7 +100,7 @@ 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); - 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); + 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); } [Test] @@ -111,10 +111,10 @@ public void TestEvaluateLogsWarningAndReturnNullWhenAttributeIsNotProvidedAndCon Assert.That(LTCondition.Evaluate(null, new UserAttributes { }, Logger), Is.Null); Assert.That(GTCondition.Evaluate(null, new UserAttributes { }, 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); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because no value was passed for user attribute ""distance_lt"""), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because no value was passed for user attribute ""distance_gt"""), Times.Once); + 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); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because no value was passed for user attribute ""distance_lt""."), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because no value was passed for user attribute ""distance_gt""."), Times.Once); } [Test] @@ -125,10 +125,10 @@ public void TestEvaluateLogsAndReturnNullWhenAttributeValueIsNullAndConditionTyp 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); - 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); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because a null value was passed for user attribute ""distance_lt"""), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because a null value was passed for user attribute ""distance_gt"""), Times.Once); + 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); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""lt"",""name"":""distance_lt"",""value"":10} evaluated to UNKNOWN because a null value was passed for user attribute ""distance_lt""."), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""gt"",""name"":""distance_gt"",""value"":10} evaluated to UNKNOWN because a null value was passed for user attribute ""distance_gt""."), Times.Once); } [Test] @@ -146,10 +146,10 @@ public void TestEvaluateLogsWarningAndReturnNullWhenAttributeTypeIsInvalid() 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); - 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); - 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); - 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 ""Boolean"" was passed for user attribute ""distance_gt"""), 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 ""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); + 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); + 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 ""Boolean"" was passed for user attribute ""distance_gt""."), Times.Once); } [Test] @@ -157,19 +157,19 @@ 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); - 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); + 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); - 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); + 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); - 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); + 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); - 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); + 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); } #endregion // Evaluate Tests @@ -193,10 +193,10 @@ public void TestExactMatcherReturnsNullWhenTypeMismatch() 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); - 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); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""pi_value"",""value"":3.14} evaluated to UNKNOWN because a value of type ""Boolean"" was passed for user attribute ""pi_value"""), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""lasers_count"",""value"":9000} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""lasers_count"""), Times.Once); + 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); + LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""pi_value"",""value"":3.14} evaluated to UNKNOWN because a value of type ""Boolean"" was passed for user attribute ""pi_value""."), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""lasers_count"",""value"":9000} evaluated to UNKNOWN because a value of type ""String"" was passed for user attribute ""lasers_count""."), Times.Once); } [Test] @@ -206,9 +206,9 @@ public void TestExactMatcherReturnsNullForOutOfBoundNumericValues() 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); - 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); - LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""max_num_value"",""value"":9223372036854775807} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK"), Times.Once); + 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); + LoggerMock.Verify(l => l.Log(LogLevel.WARN, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""max_num_value"",""value"":9223372036854775807} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."), Times.Once); } [Test] @@ -259,7 +259,7 @@ public void TestSubstringMatcherReturnsFalseWhenAttributeValueIsNotASubstring() public void TestSubstringMatcherReturnsNullWhenAttributeValueIsNotAString() { Assert.That(SubstrCondition.Evaluate(null, new UserAttributes { { "location", 10.5 } }, 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); + 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] @@ -284,7 +284,7 @@ public void TestGTMatcherReturnsFalseWhenAttributeValueIsLessThanOrEqualToCondit public void TestGTMatcherReturnsNullWhenAttributeValueIsNotANumericValue() { Assert.That(GTCondition.Evaluate(null, new UserAttributes { { "distance_gt", "invalid_type" } }, 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); + 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] @@ -292,7 +292,7 @@ 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); - 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)); + 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] @@ -316,7 +316,7 @@ public void TestLTMatcherReturnsFalseWhenAttributeValueIsGreaterThanOrEqualToCon public void TestLTMatcherReturnsNullWhenAttributeValueIsNotANumericValue() { Assert.That(LTCondition.Evaluate(null, new UserAttributes { { "distance_lt", "invalid_type" } }, 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); + 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] @@ -324,7 +324,7 @@ 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); - 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)); + 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] diff --git a/OptimizelySDK.Tests/DecisionServiceTest.cs b/OptimizelySDK.Tests/DecisionServiceTest.cs index f90e8507..0936a857 100644 --- a/OptimizelySDK.Tests/DecisionServiceTest.cs +++ b/OptimizelySDK.Tests/DecisionServiceTest.cs @@ -1,6 +1,6 @@ /** * - * Copyright 2017-2019, Optimizely and contributors + * Copyright 2017-2020, 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. @@ -697,8 +697,8 @@ public void TestGetVariationForFeatureRolloutWhenUserDoesNotQualifyForAnyTargeti Assert.IsTrue(TestData.CompareObjects(expectedDecision, actualDecision)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"User \"user_1\" does not meet the conditions to be in rollout rule for audience \"{ProjectConfig.AudienceIdMap[experiment0.AudienceIds[0]].Name}\".")); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"User \"user_1\" does not meet the conditions to be in rollout rule for audience \"{ProjectConfig.AudienceIdMap[experiment1.AudienceIds[0]].Name}\".")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"User \"user_1\" does not meet the conditions for targeting rule \"1\".")); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $"User \"user_1\" does not meet the conditions for targeting rule \"2\".")); } [Test] @@ -785,10 +785,11 @@ public void TestGetVariationForFeatureRolloutCheckAudienceInEveryoneElseRule() actualDecision = decisionService.GetVariationForFeatureRollout(featureFlag, GenericUserId, null, ProjectConfig); Assert.Null(actualDecision); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUser1\" does not meet the conditions to be in rollout rule for audience \"Chrome users\"."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUser1\" does not meet the conditions to be in rollout rule for audience \"iPhone users in San Francisco\"."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"genericUserId\" does not meet the conditions to be in rollout rule for audience \"Chrome users\"."), Times.Exactly(2)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"genericUserId\" does not meet the conditions to be in rollout rule for audience \"iPhone users in San Francisco\"."), Times.Exactly(3)); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUser1\" does not meet the conditions for targeting rule \"1\"."), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUser1\" does not meet the conditions for targeting rule \"2\"."), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"genericUserId\" does not meet the conditions for targeting rule \"1\"."), Times.Exactly(2)); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"genericUserId\" does not meet the conditions for targeting rule \"2\"."), Times.Exactly(2)); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"genericUserId\" does not meet the conditions for targeting rule \"3\"."), Times.Exactly(1)); } #endregion // GetVariationForFeatureRollout Tests diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index d4ae9081..af398f9e 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -1440,7 +1440,7 @@ public void TestGetFeatureVariableDoubleReturnsRightValueWhenUserBuckedIntoFeatu var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Returning variable value ""{variableValue}"" for variation ""{variation.Key}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } [Test] @@ -1469,7 +1469,7 @@ public void TestGetFeatureVariableIntegerReturnsRightValueWhenUserBuckedIntoFeat var variableValue = (int)optly.Invoke("GetFeatureVariableInteger", featureKey, variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Returning variable value ""{variableValue}"" for variation ""{variation.Key}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } [Test] @@ -1492,7 +1492,7 @@ public void TestGetFeatureVariableDoubleReturnsDefaultValueWhenUserBuckedIntoFea var variableValue = (double)optly.Invoke("GetFeatureVariableDouble", featureKey, variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning default value for variable ""{variableKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""{variableValue}"".")); } [Test] @@ -1521,7 +1521,7 @@ public void TestGetFeatureVariableIntegerReturnsDefaultValueWhenUserBuckedIntoFe var variableValue = (int)optly.Invoke("GetFeatureVariableInteger", featureKey, variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning default value for variable ""{variableKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""{variableValue}"".")); } [Test] @@ -1543,7 +1543,7 @@ public void TestGetFeatureVariableBooleanReturnsRightValueWhenUserBuckedIntoRoll var variableValue = (bool)optly.Invoke("GetFeatureVariableBoolean", featureKey, variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Returning variable value ""true"" for variation ""{variation.Key}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""true"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } [Test] @@ -1575,7 +1575,7 @@ public void TestGetFeatureVariableJSONReturnsRightValueWhenUserBucketIntoRollout Assert.AreEqual(expectedIntValue, variableValue.GetValue("int_var")); Assert.AreEqual(expectedStringValue, variableValue.GetValue("string_var")); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Returning variable value ""{variableValue}"" for variation ""{variation.Key}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } [Test] @@ -1607,7 +1607,7 @@ public void TestGetFeatureVariableJSONReturnsRightValueWhenUserBucketIntoRollout Assert.AreEqual(expectedIntValue, variableValue.GetValue("int_var")); Assert.AreEqual(expectedStringValue, variableValue.GetValue("string_var")); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Returning variable value ""{variableValue}"" for variation ""{variation.Key}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } [Test] @@ -1637,7 +1637,7 @@ public void TestGetFeatureVariableStringReturnsRightValueWhenUserBuckedIntoRollo var variableValue = (string)optly.Invoke("GetFeatureVariableString", featureKey, variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Returning variable value ""{variableValue}"" for variation ""{variation.Key}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } [Test] @@ -1658,8 +1658,7 @@ public void TestGetFeatureVariableBooleanReturnsDefaultValueWhenUserBuckedIntoRo optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); var variableValue = (bool)optly.Invoke("GetFeatureVariableBoolean", featureKey, variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); - - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning default value for variable ""{variableKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""true"".")); } [Test] @@ -1688,7 +1687,7 @@ public void TestGetFeatureVariableStringReturnsDefaultValueWhenUserBuckedIntoRol var variableValue = (string)optly.Invoke("GetFeatureVariableString", featureKey, variableKey, TestUserId, userAttributes); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning default value for variable ""{variableKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""{variableValue}"".")); } [Test] @@ -1820,7 +1819,7 @@ public void TestGetFeatureVariableValueForTypeGivenFeatureFlagIsNotEnabledForUse Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning default value for variable ""{variableKey}"".")); + $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""{variableValue}"".")); } // Should return default value and log message when feature is enabled for the user @@ -1876,10 +1875,9 @@ public void TestGetFeatureVariableValueForTypeGivenFeatureFlagIsEnabledForUserAn var variableValue = (double?)optly.InvokeGeneric("GetFeatureVariableValueForType", new Type[] { typeof(double?) }, featureKey, variableKey, TestUserId, null, variableType); Assert.AreEqual(expectedValue, variableValue); - LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Returning variable value ""{variableValue}"" for variation ""{variation.Key}"" of feature flag ""{featureKey}"".")); + LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } - + // Verify that GetFeatureVariableValueForType returns correct variable value for rollout rule. [Test] public void TestGetFeatureVariableValueForTypeWithRolloutRule() @@ -2082,8 +2080,8 @@ public void TestIsFeatureEnabledGivenVariationNotFoundInFeatureExperimentButInRo Assert.True(Optimizely.IsFeatureEnabled(featureKey, TestUserId, userAttributes)); LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The feature flag \"boolean_single_variable_feature\" is not used in any experiments."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUserId\" does not meet the conditions to be in rollout rule for audience \"Chrome users\"."), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUserId\" does not meet the conditions to be in rollout rule for audience \"iPhone users in San Francisco\"."), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUserId\" does not meet the conditions for targeting rule \"1\"."), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "User \"testUserId\" does not meet the conditions for targeting rule \"2\"."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [8408] to user [testUserId] with bucketing ID [testUserId]."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"testUserId\" is bucketed into a rollout for feature flag \"boolean_single_variable_feature\"."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, "The user \"testUserId\" is not being experimented on feature \"boolean_single_variable_feature\"."), Times.Once); @@ -3318,6 +3316,7 @@ public void TestGetAllFeatureVariablesRollout() var result = (OptimizelyJSON)optly.Invoke("GetAllFeatureVariables", featureKey, TestUserId, userAttributes); Assert.NotNull(result); + LoggerMock.Verify(log => log.Log(LogLevel.INFO, "Feature \"" + featureKey + "\" is not enabled for user \"" + TestUserId + "\""), Times.Once); LoggerMock.Verify(log => log.Log(LogLevel.INFO, "User \"" + TestUserId + "\" was not bucketed into any variation for feature flag \"" + featureKey + "\". " + "The default values are being returned."), Times.Once); @@ -3335,6 +3334,7 @@ public void TestGetAllFeatureVariablesSourceFeatureTest() var variableValues = optimizely.GetAllFeatureVariables(featureKey, TestUserId, null); Assert.IsTrue(TestData.CompareObjects(variableValues.ToDictionary(), expectedValue)); + LoggerMock.Verify(log => log.Log(LogLevel.INFO, "Feature \"" + featureKey + "\" is enabled for user \"" + TestUserId + "\""), Times.Once); LoggerMock.Verify(log => log.Log(LogLevel.INFO, "User \"" + TestUserId + "\" was not bucketed into any variation for feature flag \"" + featureKey + "\". " + "The default values are being returned."), Times.Never); diff --git a/OptimizelySDK.Tests/UtilsTests/ExperimentUtilsTest.cs b/OptimizelySDK.Tests/UtilsTests/ExperimentUtilsTest.cs index 157ec28c..6706204e 100644 --- a/OptimizelySDK.Tests/UtilsTests/ExperimentUtilsTest.cs +++ b/OptimizelySDK.Tests/UtilsTests/ExperimentUtilsTest.cs @@ -38,38 +38,38 @@ public void Setup() LoggerMock.Setup(l => l.Log(It.IsAny(), It.IsAny())); Logger = LoggerMock.Object; } - - #region IsUserInExperiment Tests + + #region DoesUserMeetAudienceConditions Tests [Test] - public void TestIsUserInExperimentReturnsTrueWithNoAudience() + public void TestDoesUserMeetAudienceConditionsReturnsTrueWithNoAudience() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); experiment.AudienceIds = new string[] { }; experiment.AudienceConditions = null; - Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, null, Logger)); + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, null, "experiment", experiment.Key, Logger)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": []"), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": []."), Times.Once); } [Test] - public void TestIsUserInExperimentReturnsTrueWhenAudienceUsedInExperimentNoAttributesProvided() + public void TestDoesUserMeetAudienceConditionsReturnsTrueWhenAudienceUsedInExperimentNoAttributesProvided() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); experiment.AudienceIds = new string[] { "3468206648" }; - Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, null, Logger)); - Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, new UserAttributes { }, Logger)); + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, null , "experiment", experiment.Key, Logger));; + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, new UserAttributes { }, "experiment", experiment.Key, Logger)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206648""]"), Times.Exactly(2)); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206648""]."), Times.Exactly(2)); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206648"" with conditions: [""not"",{""name"":""input_value"",""type"":""custom_attribute"",""match"":""exists""}]"), Times.Exactly(2)); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $@"Audience ""3468206648"" evaluated to TRUE"), Times.Exactly(2)); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to TRUE"), Times.Exactly(2)); } [Test] - public void TestIsUserInExperimentReturnsFalseIfNoAudienceInORConditionPass() + public void TestDoesUserMeetAudienceConditionsReturnsFalseIfNoAudienceInORConditionPass() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); var userAttributes = new UserAttributes @@ -79,9 +79,9 @@ public void TestIsUserInExperimentReturnsFalseIfNoAudienceInORConditionPass() { "should_do_it", false } }; - Assert.False(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger)); + Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206642"",""3988293898"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]"), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206642"",""3988293898"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206642"" evaluated to FALSE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3988293899"" with conditions: [""and"",[""or"",[""or"",{""name"":""favorite_ice_cream"",""type"":""custom_attribute"",""match"":""exists""}]]]"), Times.Once); @@ -98,7 +98,7 @@ public void TestIsUserInExperimentReturnsFalseIfNoAudienceInORConditionPass() } [Test] - public void TestIsUserInExperimentReturnsTrueIfAnyAudienceInORConditionPass() + public void TestDoesUserMeetAudienceConditionsReturnsTrueIfAnyAudienceInORConditionPass() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); var userAttributes = new UserAttributes @@ -108,16 +108,16 @@ public void TestIsUserInExperimentReturnsTrueIfAnyAudienceInORConditionPass() { "should_do_it", false } }; - Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger)); + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206642"",""3988293898"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]"), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206642"",""3988293898"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206642"" evaluated to TRUE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""feat_with_var_test"" collectively evaluated to TRUE"), Times.Once); } [Test] - public void TestIsUserInExperimentReturnsTrueIfAllAudiencesInANDConditionPass() + public void TestDoesUserMeetAudienceConditionsReturnsTrueIfAllAudiencesInANDConditionPass() { var experiment = Config.GetExperimentFromKey("audience_combinations_experiment"); var userAttributes = new UserAttributes @@ -126,11 +126,11 @@ public void TestIsUserInExperimentReturnsTrueIfAllAudiencesInANDConditionPass() { "favorite_ice_cream", "walls" } }; - Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger)); + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger)); } [Test] - public void TestIsUserInExperimentReturnsFalseIfAnyAudienceInANDConditionDoesNotPass() + public void TestDoesUserMeetAudienceConditionsReturnsFalseIfAnyAudienceInANDConditionDoesNotPass() { var experiment = Config.GetExperimentFromKey("audience_combinations_experiment"); var userAttributes = new UserAttributes @@ -139,9 +139,9 @@ public void TestIsUserInExperimentReturnsFalseIfAnyAudienceInANDConditionDoesNot { "lasers", 50 } }; - Assert.False(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger)); + Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger)); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""audience_combinations_experiment"": [""and"",[""or"",""3468206642"",""3988293898""],[""or"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]]"), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""audience_combinations_experiment"": [""and"",[""or"",""3468206642"",""3988293898""],[""or"",""3988293899"",""3468206646"",""3468206647"",""3468206644"",""3468206643""]]."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206642"" with conditions: [""and"", [""or"", [""or"", {""name"": ""house"", ""type"": ""custom_attribute"", ""value"": ""Gryffindor""}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206642"" evaluated to TRUE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3988293899"" with conditions: [""and"",[""or"",[""or"",{""name"":""favorite_ice_cream"",""type"":""custom_attribute"",""match"":""exists""}]]]"), Times.Once); @@ -153,13 +153,13 @@ public void TestIsUserInExperimentReturnsFalseIfAnyAudienceInANDConditionDoesNot LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206644"" with conditions: [""and"",[""or"",[""or"",{""name"":""lasers"",""type"":""custom_attribute"",""match"":""lt"",""value"":1.0}]]]"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206644"" evaluated to FALSE"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206643"" with conditions: [""and"",[""or"",[""or"",{""name"":""should_do_it"",""type"":""custom_attribute"",""match"":""exact"",""value"":true}]]]"), Times.Once); - LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""should_do_it"",""value"":true} evaluated to UNKNOWN because no value was passed for user attribute ""should_do_it"""), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience condition {""type"":""custom_attribute"",""match"":""exact"",""name"":""should_do_it"",""value"":true} evaluated to UNKNOWN because no value was passed for user attribute ""should_do_it""."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Audience ""3468206643"" evaluated to UNKNOWN"), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, @"Audiences for experiment ""audience_combinations_experiment"" collectively evaluated to FALSE"), Times.Once); } [Test] - public void TestIsUserInExperimentReturnsTrueIfAudienceInNOTConditionDoesNotPass() + public void TestDoesUserMeetAudienceConditionsReturnsTrueIfAudienceInNOTConditionDoesNotPass() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); experiment.AudienceIds = new string[] { "3468206645" }; @@ -168,11 +168,11 @@ public void TestIsUserInExperimentReturnsTrueIfAudienceInNOTConditionDoesNotPass { "browser_type", "Safari" } }; - Assert.True(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger)); + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger)); } [Test] - public void TestIsUserInExperimentReturnsFalseIfAudienceInNOTConditionGetsPassed() + public void TestDoesUserMeetAudienceConditionsReturnsFalseIfAudienceInNOTConditionGetsPassed() { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); experiment.AudienceIds = new string[] { "3468206645" }; @@ -181,9 +181,9 @@ public void TestIsUserInExperimentReturnsFalseIfAudienceInNOTConditionGetsPassed { "browser_type", "Chrome" } }; - Assert.False(ExperimentUtils.IsUserInExperiment(Config, experiment, userAttributes, Logger)); + Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger)); } - #endregion // IsUserInExperiment Tests + #endregion // DoesUserMeetAudienceConditions Tests } } diff --git a/OptimizelySDK.Tests/UtilsTests/ValidatorTest.cs b/OptimizelySDK.Tests/UtilsTests/ValidatorTest.cs index 17a87eb8..a3092ed6 100644 --- a/OptimizelySDK.Tests/UtilsTests/ValidatorTest.cs +++ b/OptimizelySDK.Tests/UtilsTests/ValidatorTest.cs @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019, Optimizely + * Copyright 2017-2020, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,17 +79,17 @@ public void TestValidateJsonSchemaNoJsonContent() [Test] - public void TestIsUserInExperimentNoAudienceUsedInExperiment() + public void TestDoesUserMeetAudienceConditionsNoAudienceUsedInExperiment() { - Assert.IsTrue(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("paused_experiment"), new UserAttributes(), Logger)); + Assert.IsTrue(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("paused_experiment"), new UserAttributes(), "experiment", "paused_experiment", Logger)); } [Test] - public void TestIsUserInExperimentAudienceUsedInExperimentNoAttributesProvided() + public void TestDoesUserMeetAudienceConditionsAudienceUsedInExperimentNoAttributesProvided() { - Assert.IsFalse(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("test_experiment"), new UserAttributes(), Logger)); + Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), new UserAttributes(), "experiment", "test_experiment", Logger)); - Assert.IsFalse(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("test_experiment"), null, Logger)); + Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), null, "experiment", "test_experiment", Logger)); } [Test] @@ -100,11 +100,11 @@ public void TestUserInExperimentAudienceMatch() {"device_type", "iPhone" }, {"location", "San Francisco" } }; - Assert.IsTrue(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("test_experiment"), userAttributes, Logger)); + Assert.IsTrue(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), userAttributes, "experiment", "test_experiment", Logger)); } [Test] - public void TestIsUserInExperimentAudienceNoMatch() + public void TestDoesUserMeetAudienceConditionsAudienceNoMatch() { var userAttributes = new UserAttributes { @@ -112,7 +112,7 @@ public void TestIsUserInExperimentAudienceNoMatch() {"location", "San Francisco" } }; - Assert.IsFalse(ExperimentUtils.IsUserInExperiment(Config, Config.GetExperimentFromKey("test_experiment"), null, Logger)); + Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), null, "experiment", "test_experiment", Logger)); } [Test] diff --git a/OptimizelySDK/AudienceConditions/BaseCondition.cs b/OptimizelySDK/AudienceConditions/BaseCondition.cs index 7aac97b4..882f4d7c 100644 --- a/OptimizelySDK/AudienceConditions/BaseCondition.cs +++ b/OptimizelySDK/AudienceConditions/BaseCondition.cs @@ -1,5 +1,5 @@ /* - * Copyright 2019, Optimizely + * Copyright 2019-2020, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,21 +48,21 @@ public class BaseCondition : ICondition { if (Type == null || Type != CUSTOM_ATTRIBUTE_CONDITION_TYPE) { - logger.Log(LogLevel.WARN, $@"Audience condition ""{this}"" has an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK"); + logger.Log(LogLevel.WARN, $@"Audience condition ""{this}"" uses an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK."); return null; } object attributeValue = null; if (userAttributes.TryGetValue(Name, out attributeValue) == false && Match != AttributeMatchTypes.EXIST) { - logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because no value was passed for user attribute ""{Name}"""); + logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because no value was passed for user attribute ""{Name}""."); return null; } var evaluator = GetEvaluator(); if (evaluator == null) { - logger.Log(LogLevel.WARN, $@"Audience condition ""{this}"" uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK"); + logger.Log(LogLevel.WARN, $@"Audience condition ""{this}"" uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK."); return null; } @@ -94,25 +94,25 @@ public class BaseCondition : ICondition { if (!IsValueTypeValidForExactConditions(Value) || (Validator.IsNumericType(Value) && !Validator.IsValidNumericValue(Value))) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK"); + logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."); return null; } if (attributeValue == null) { - logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because a null value was passed for user attribute ""{Name}"""); + logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because a null value was passed for user attribute ""{Name}""."); return null; } if (!IsValueTypeValidForExactConditions(attributeValue) || !AreValuesSameType(Value, attributeValue)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}"""); + logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}""."); return null; } if (Validator.IsNumericType(attributeValue) && !Validator.IsValidNumericValue(attributeValue)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because the number value for user attribute ""{Name}"" is not in the range [-2^53, +2^53]"); + logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because the number value for user attribute ""{Name}"" is not in the range [-2^53, +2^53]."); return null; } @@ -131,25 +131,25 @@ public class BaseCondition : ICondition { if (!Validator.IsValidNumericValue(Value)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK"); + logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."); return null; } if (attributeValue == null) { - logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because a null value was passed for user attribute ""{Name}"""); + logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because a null value was passed for user attribute ""{Name}""."); return null; } if (!Validator.IsNumericType(attributeValue)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}"""); + logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}""."); return null; } if (!Validator.IsValidNumericValue(attributeValue)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because the number value for user attribute ""{Name}"" is not in the range [-2^53, +2^53]"); + logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because the number value for user attribute ""{Name}"" is not in the range [-2^53, +2^53]."); return null; } @@ -160,25 +160,25 @@ public class BaseCondition : ICondition { if (!Validator.IsValidNumericValue(Value)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK"); + logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."); return null; } if (attributeValue == null) { - logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because a null value was passed for user attribute ""{Name}"""); + logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because a null value was passed for user attribute ""{Name}""."); return null; } if (!Validator.IsNumericType(attributeValue)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}"""); + logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}""."); return null; } if (!Validator.IsValidNumericValue(attributeValue)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because the number value for user attribute ""{Name}"" is not in the range [-2^53, +2^53]"); + logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because the number value for user attribute ""{Name}"" is not in the range [-2^53, +2^53]."); return null; } @@ -189,19 +189,19 @@ public class BaseCondition : ICondition { if (!(Value is string)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK"); + logger.Log(LogLevel.WARN, $@"Audience condition {this} has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK."); return null; } if (attributeValue == null) { - logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because a null value was passed for user attribute ""{Name}"""); + logger.Log(LogLevel.DEBUG, $@"Audience condition {this} evaluated to UNKNOWN because a null value was passed for user attribute ""{Name}""."); return null; } if (!(attributeValue is string)) { - logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}"""); + logger.Log(LogLevel.WARN, $@"Audience condition {this} evaluated to UNKNOWN because a value of type ""{attributeValue.GetType().Name}"" was passed for user attribute ""{Name}""."); return null; } diff --git a/OptimizelySDK/Bucketing/DecisionService.cs b/OptimizelySDK/Bucketing/DecisionService.cs index 0abb7341..3959dbbd 100644 --- a/OptimizelySDK/Bucketing/DecisionService.cs +++ b/OptimizelySDK/Bucketing/DecisionService.cs @@ -1,5 +1,5 @@ /* -* Copyright 2017-2019, Optimizely +* Copyright 2017-2020, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,9 @@ namespace OptimizelySDK.Bucketing /// public class DecisionService { + public const string LOGGING_KEY_TYPE_EXPERIMENT = "experiment"; + public const string LOGGING_KEY_TYPE_RULE = "rule"; + private Bucketer Bucketer; private IErrorHandler ErrorHandler; private UserProfileService UserProfileService; @@ -120,7 +123,7 @@ public virtual Variation GetVariation(Experiment experiment, string userId, Proj } } - if (ExperimentUtils.IsUserInExperiment(config, experiment, filteredAttributes, Logger)) + if (ExperimentUtils.DoesUserMeetAudienceConditions(config, experiment, filteredAttributes, LOGGING_KEY_TYPE_EXPERIMENT, experiment.Key, Logger)) { // Get Bucketing ID from user attributes. string bucketingId = GetBucketingId(userId, filteredAttributes); @@ -402,8 +405,9 @@ public virtual FeatureDecision GetVariationForFeatureRollout(FeatureFlag feature // For all rules before the everyone else rule for (int i = 0; i < rolloutRulesLength - 1; i++) { + string loggingKey = (i + 1).ToString(); var rolloutRule = rollout.Experiments[i]; - if (ExperimentUtils.IsUserInExperiment(config, rolloutRule, filteredAttributes, Logger)) + if (ExperimentUtils.DoesUserMeetAudienceConditions(config, rolloutRule, filteredAttributes, LOGGING_KEY_TYPE_RULE, loggingKey, Logger)) { variation = Bucketer.Bucket(config, rolloutRule, bucketingId, userId); if (variation == null || string.IsNullOrEmpty(variation.Id)) @@ -414,17 +418,20 @@ public virtual FeatureDecision GetVariationForFeatureRollout(FeatureFlag feature else { var audience = config.GetAudience(rolloutRule.AudienceIds[0]); - Logger.Log(LogLevel.DEBUG, $"User \"{userId}\" does not meet the conditions to be in rollout rule for audience \"{audience.Name}\"."); + Logger.Log(LogLevel.DEBUG, $"User \"{userId}\" does not meet the conditions for targeting rule \"{loggingKey}\"."); } } // Get the last rule which is everyone else rule. var everyoneElseRolloutRule = rollout.Experiments[rolloutRulesLength - 1]; - if (ExperimentUtils.IsUserInExperiment(config, everyoneElseRolloutRule, filteredAttributes, Logger)) + if (ExperimentUtils.DoesUserMeetAudienceConditions(config, everyoneElseRolloutRule, filteredAttributes, LOGGING_KEY_TYPE_RULE, "Everyone Else", Logger)) { variation = Bucketer.Bucket(config, everyoneElseRolloutRule, bucketingId, userId); if (variation != null && !string.IsNullOrEmpty(variation.Id)) + { + Logger.Log(LogLevel.DEBUG, $"User \"{userId}\" meets conditions for targeting rule \"Everyone Else\"."); return new FeatureDecision(everyoneElseRolloutRule, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT); + } } else { diff --git a/OptimizelySDK/Optimizely.cs b/OptimizelySDK/Optimizely.cs index 35f1bc35..e9353384 100644 --- a/OptimizelySDK/Optimizely.cs +++ b/OptimizelySDK/Optimizely.cs @@ -200,7 +200,7 @@ private bool ValidatePreconditions(Experiment experiment, string userId, Project return true; } - if (!ExperimentUtils.IsUserInExperiment(config, experiment, userAttributes, Logger)) + if (!ExperimentUtils.DoesUserMeetAudienceConditions(config, experiment, userAttributes, "experiment", experiment.Key, Logger)) { Logger.Log(LogLevel.INFO, string.Format("User \"{0}\" does not meet conditions to be in experiment \"{1}\".", userId, experiment.Key)); return false; @@ -569,11 +569,11 @@ public virtual T GetFeatureVariableValueForType(string featureKey, string var if (variation.FeatureEnabled == true) { variableValue = featureVariableUsageInstance.Value; - Logger.Log(LogLevel.INFO, $@"Returning variable value ""{variableValue}"" for variation ""{variation.Key}"" of feature flag ""{featureKey}""."); + Logger.Log(LogLevel.INFO, $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}""."); } else { - Logger.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {userId}. Returning default value for variable ""{variableKey}""."); + Logger.Log(LogLevel.INFO, $@"Feature ""{featureKey}"" is not enabled for user {userId}. Returning the default variable value ""{variableValue}""."); } } else @@ -791,6 +791,14 @@ public OptimizelyJSON GetAllFeatureVariables(string featureKey, string userId, "The default values are being returned."); } + if (featureEnabled) + { + Logger.Log(LogLevel.INFO, "Feature \"" + featureKey + "\" is enabled for user \"" + userId + "\""); + } + else + { + Logger.Log(LogLevel.INFO, "Feature \"" + featureKey + "\" is not enabled for user \"" + userId + "\""); + } var valuesMap = new Dictionary(); foreach (var featureVariable in featureFlag.Variables) { diff --git a/OptimizelySDK/Utils/ExperimentUtils.cs b/OptimizelySDK/Utils/ExperimentUtils.cs index 5f452677..470b014c 100644 --- a/OptimizelySDK/Utils/ExperimentUtils.cs +++ b/OptimizelySDK/Utils/ExperimentUtils.cs @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019, Optimizely + * Copyright 2017-2020, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,8 +42,15 @@ public static bool IsExperimentActive(Experiment experiment, ILogger logger) /// ProjectConfig Configuration for the project /// Experiment Entity representing the experiment /// Attributes of the user. Defaults to empty attributes array if not provided + /// It can be either experiment or rule. + /// In case loggingKeyType is experiment it will be experiment key or else it will be rule number. /// true if the user meets audience conditions to be in experiment, false otherwise. - public static bool IsUserInExperiment(ProjectConfig config, Experiment experiment, UserAttributes userAttributes, ILogger logger) + public static bool DoesUserMeetAudienceConditions(ProjectConfig config, + Experiment experiment, + UserAttributes userAttributes, + string loggingKeyType, + string loggingKey, + ILogger logger) { if (userAttributes == null) userAttributes = new UserAttributes(); @@ -52,12 +59,12 @@ public static bool IsUserInExperiment(ProjectConfig config, Experiment experimen if (experiment.AudienceConditionsList != null) { expConditions = experiment.AudienceConditionsList; - logger.Log(LogLevel.DEBUG, $@"Evaluating audiences for experiment ""{experiment.Key}"": {experiment.AudienceConditionsString}"); + logger.Log(LogLevel.DEBUG, $@"Evaluating audiences for {loggingKeyType} ""{loggingKey}"": {experiment.AudienceConditionsString}."); } else { expConditions = experiment.AudienceIdsList; - logger.Log(LogLevel.DEBUG, $@"Evaluating audiences for experiment ""{experiment.Key}"": {experiment.AudienceIdsString}"); + logger.Log(LogLevel.DEBUG, $@"Evaluating audiences for {loggingKeyType} ""{loggingKey}"": {experiment.AudienceIdsString}."); } // If there are no audiences, return true because that means ALL users are included in the experiment. @@ -66,7 +73,7 @@ public static bool IsUserInExperiment(ProjectConfig config, Experiment experimen var result = expConditions.Evaluate(config, userAttributes, logger).GetValueOrDefault(); var resultText = result.ToString().ToUpper(); - logger.Log(LogLevel.INFO, $@"Audiences for experiment ""{experiment.Key}"" collectively evaluated to {resultText}"); + logger.Log(LogLevel.INFO, $@"Audiences for {loggingKeyType} ""{loggingKey}"" collectively evaluated to {resultText}"); return result; } }