From 3d20b51147bf589f5cc9806b19ecc29734bcd3f0 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Fri, 15 Jul 2022 09:17:09 -0400 Subject: [PATCH 01/41] Updated user context --- OptimizelySDK/OptimizelyUserContext.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/OptimizelySDK/OptimizelyUserContext.cs b/OptimizelySDK/OptimizelyUserContext.cs index 103acdf8..4aa24c5f 100644 --- a/OptimizelySDK/OptimizelyUserContext.cs +++ b/OptimizelySDK/OptimizelyUserContext.cs @@ -37,6 +37,8 @@ public class OptimizelyUserContext // user attributes for Optimizely user context. private UserAttributes Attributes; + + private List QualifiedSegments; // Optimizely object to be used. private Optimizely Optimizely; @@ -56,9 +58,19 @@ public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttribute Attributes = userAttributes ?? new UserAttributes(); ForcedDecisionsStore = forcedDecisionsStore ?? new ForcedDecisionsStore(); UserId = userId; + QualifiedSegments = new List(); } private OptimizelyUserContext Copy() => new OptimizelyUserContext(Optimizely, UserId, GetAttributes(), GetForcedDecisionsStore(), ErrorHandler, Logger); + + /// + /// Returns true if the user is qualified for the given segment name + /// + /// A String segment key which will be check in qualified segments list that if it exist then user is qualified. + /// Is user qualified for a segment. + public bool IsQualifiedFor(string segment) { + return QualifiedSegments.Contains(segment); + } /// /// Returns Optimizely instance associated with the UserContext. From 0b4d1b991d8957a7884480fc0d6230247523fb0c Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Fri, 15 Jul 2022 13:23:43 -0400 Subject: [PATCH 02/41] Added ref to SDK from DemoApp so solution builds. --- OptimizelySDK.DemoApp/OptimizelySDK.DemoApp.csproj | 6 ++++++ 1 file changed, 6 insertions(+) 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) From b80966d58217f07a1b4004f105c029ede7f29b48 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Mon, 18 Jul 2022 17:09:11 -0400 Subject: [PATCH 03/41] (Datafile)ProjectConfig & Integration added --- .../OptimizelySDK.Net35.csproj | 3 +++ .../OptimizelySDK.Net40.csproj | 3 +++ .../OptimizelySDK.NetStandard16.csproj | 2 +- .../OptimizelySDK.NetStandard20.csproj | 3 +++ OptimizelySDK/Config/DatafileProjectConfig.cs | 27 +++++++++++++++++++ OptimizelySDK/Config/Integration.cs | 25 +++++++++++++++++ OptimizelySDK/OptimizelySDK.csproj | 1 + OptimizelySDK/ProjectConfig.cs | 12 ++++++++- 8 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 OptimizelySDK/Config/Integration.cs diff --git a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj index 9d3d441d..41e026f2 100644 --- a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj +++ b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj @@ -233,6 +233,9 @@ Config\DatafileProjectConfig + + Config\Integration + Config\ProjectConfigManager diff --git a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj index 3dfdfbc9..4517ae1b 100644 --- a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj +++ b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj @@ -232,6 +232,9 @@ Config\DatafileProjectConfig + + Config\Integration + Config\ProjectConfigManager diff --git a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj index 740e5d4d..a76514d0 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..4e49e21d 100644 --- a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj +++ b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj @@ -64,6 +64,9 @@ Config\DatafileProjectConfig.cs + + Config\Integration + Config\FallbackProjectConfigManager.cs diff --git a/OptimizelySDK/Config/DatafileProjectConfig.cs b/OptimizelySDK/Config/DatafileProjectConfig.cs index 93a0aceb..df0f47bc 100644 --- a/OptimizelySDK/Config/DatafileProjectConfig.cs +++ b/OptimizelySDK/Config/DatafileProjectConfig.cs @@ -20,6 +20,7 @@ using OptimizelySDK.Exceptions; using OptimizelySDK.Logger; using OptimizelySDK.Utils; +using System; using System.Collections.Generic; using System.Linq; using Attribute = OptimizelySDK.Entity.Attribute; @@ -98,6 +99,16 @@ public enum OPTLYSDKVersion /// public string Datafile { get; set; } + /// + /// Configured host name for the Optimizely Data Platform. + /// + public string HostForOdp { get; set; } + + /// + /// Configured public key from the Optimizely Data Platform. + /// + public string PublicKeyForOdp { get; set; } + /// /// Supported datafile versions list. /// @@ -263,6 +274,11 @@ private Dictionary> _VariationIdMap /// Associative list of Rollouts. /// public Rollout[] Rollouts { get; set; } + + /// + /// Associative list of Integrations. + /// + private Integration[] Integrations { get; set; } //========================= Initialization =========================== @@ -280,6 +296,7 @@ private void Initialize() TypedAudiences = TypedAudiences ?? new Audience[0]; FeatureFlags = FeatureFlags ?? new FeatureFlag[0]; Rollouts = Rollouts ?? new Rollout[0]; + Integrations = Integrations ?? new Integration[0]; _ExperimentKeyMap = new Dictionary(); _GroupIdMap = ConfigParser.GenerateMap(entities: Groups, getKey: g => g.Id.ToString(), clone: true); @@ -354,6 +371,16 @@ private void Initialize() } } + foreach (var integration in Integrations) + { + if (integration.Key?.ToLower() == "odp") + { + HostForOdp = integration.Host; + PublicKeyForOdp = integration.PublicKey; + break; + } + } + var flagToVariationsMap = new Dictionary>(); // Adding experiments in experiment-feature map and flag variation map to use. foreach (var feature in FeatureFlags) diff --git a/OptimizelySDK/Config/Integration.cs b/OptimizelySDK/Config/Integration.cs new file mode 100644 index 00000000..ec199efd --- /dev/null +++ b/OptimizelySDK/Config/Integration.cs @@ -0,0 +1,25 @@ +namespace OptimizelySDK.Config +{ + public class Integration + { + public string Key { get; private set; } + public string Host { get; private set; } + public string PublicKey { get; private set; } + + public Integration( + string key, + string host, + string publicKey + ) + { + Key = key; + Host = host; + PublicKey = publicKey; + } + + public override string ToString() + { + return $"Integration{{key='{Key}', host='{Host}', publicKey='{PublicKey}'}}"; + } + } +} \ No newline at end of file diff --git a/OptimizelySDK/OptimizelySDK.csproj b/OptimizelySDK/OptimizelySDK.csproj index c8791237..fcce93e7 100644 --- a/OptimizelySDK/OptimizelySDK.csproj +++ b/OptimizelySDK/OptimizelySDK.csproj @@ -72,6 +72,7 @@ + diff --git a/OptimizelySDK/ProjectConfig.cs b/OptimizelySDK/ProjectConfig.cs index 9a6f0701..1d604270 100644 --- a/OptimizelySDK/ProjectConfig.cs +++ b/OptimizelySDK/ProjectConfig.cs @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021, 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. @@ -66,6 +66,16 @@ public interface ProjectConfig /// bool? BotFiltering { get; set; } + /// + /// Configured host name for the Optimizely Data Platform. + /// + string HostForOdp { get; set; } + + /// + /// Configured public key from the Optimizely Data Platform. + /// + string PublicKeyForOdp { get; set; } + //========================= Mappings =========================== /// From bac1f1ddf347889997ee6db429439febbcb867e0 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Tue, 19 Jul 2022 09:15:52 -0400 Subject: [PATCH 04/41] Update Condition interface & implementing classes --- OptimizelySDK/AudienceConditions/AndCondition.cs | 8 ++++---- OptimizelySDK/AudienceConditions/AudienceIdCondition.cs | 4 ++-- OptimizelySDK/AudienceConditions/EmptyCondition.cs | 6 +++--- OptimizelySDK/AudienceConditions/ICondition.cs | 6 +++--- OptimizelySDK/AudienceConditions/NotCondition.cs | 8 ++++---- OptimizelySDK/AudienceConditions/OrCondition.cs | 8 ++++---- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/OptimizelySDK/AudienceConditions/AndCondition.cs b/OptimizelySDK/AudienceConditions/AndCondition.cs index 55bf75fd..e5e2b91f 100644 --- a/OptimizelySDK/AudienceConditions/AndCondition.cs +++ b/OptimizelySDK/AudienceConditions/AndCondition.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, @@ -26,7 +26,7 @@ public class AndCondition : ICondition { public ICondition[] Conditions { get; set; } - public bool? Evaluate(ProjectConfig config, UserAttributes attributes, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger) { // According to the matrix: // false and true is false @@ -38,7 +38,7 @@ public class AndCondition : ICondition var foundNull = false; foreach (var condition in Conditions) { - var result = condition.Evaluate(config, attributes, logger); + var result = condition.Evaluate(config, user, logger); if (result == null) foundNull = true; else if (result == false) diff --git a/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs b/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs index 028ec5bd..cbd3da9e 100644 --- a/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs +++ b/OptimizelySDK/AudienceConditions/AudienceIdCondition.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, diff --git a/OptimizelySDK/AudienceConditions/EmptyCondition.cs b/OptimizelySDK/AudienceConditions/EmptyCondition.cs index 0c82309a..90801176 100644 --- a/OptimizelySDK/AudienceConditions/EmptyCondition.cs +++ b/OptimizelySDK/AudienceConditions/EmptyCondition.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, @@ -24,7 +24,7 @@ namespace OptimizelySDK.AudienceConditions /// public class EmptyCondition : ICondition { - public bool? Evaluate(ProjectConfig config, UserAttributes attributes, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger) { return true; } diff --git a/OptimizelySDK/AudienceConditions/ICondition.cs b/OptimizelySDK/AudienceConditions/ICondition.cs index acefd4e8..52893aed 100644 --- a/OptimizelySDK/AudienceConditions/ICondition.cs +++ b/OptimizelySDK/AudienceConditions/ICondition.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, @@ -24,6 +24,6 @@ namespace OptimizelySDK.AudienceConditions /// public interface ICondition { - bool? Evaluate(ProjectConfig config, UserAttributes attributes, ILogger logger); + bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger); } } diff --git a/OptimizelySDK/AudienceConditions/NotCondition.cs b/OptimizelySDK/AudienceConditions/NotCondition.cs index 85032497..ff6cd1a2 100644 --- a/OptimizelySDK/AudienceConditions/NotCondition.cs +++ b/OptimizelySDK/AudienceConditions/NotCondition.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, @@ -26,9 +26,9 @@ public class NotCondition : ICondition { public ICondition Condition { get; set; } - public bool? Evaluate(ProjectConfig config, UserAttributes attributes, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger) { - var result = Condition?.Evaluate(config, attributes, logger); + var result = Condition?.Evaluate(config, user, logger); return result == null ? null : !result; } } diff --git a/OptimizelySDK/AudienceConditions/OrCondition.cs b/OptimizelySDK/AudienceConditions/OrCondition.cs index ee42ceb4..f471a0d8 100644 --- a/OptimizelySDK/AudienceConditions/OrCondition.cs +++ b/OptimizelySDK/AudienceConditions/OrCondition.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, @@ -26,7 +26,7 @@ public class OrCondition : ICondition { public ICondition[] Conditions { get; set; } - public bool? Evaluate(ProjectConfig config, UserAttributes attributes, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger) { // According to the matrix: // true returns true @@ -36,7 +36,7 @@ public class OrCondition : ICondition var foundNull = false; foreach (var condition in Conditions) { - var result = condition.Evaluate(config, attributes, logger); + var result = condition.Evaluate(config, user, logger); if (result == null) foundNull = true; else if (result == true) From b8022ae265c8a4c43f3135b1c4627df21971472f Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Tue, 19 Jul 2022 09:35:52 -0400 Subject: [PATCH 05/41] Add copyright notice --- OptimizelySDK/Config/Integration.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/OptimizelySDK/Config/Integration.cs b/OptimizelySDK/Config/Integration.cs index ec199efd..c7f4acef 100644 --- a/OptimizelySDK/Config/Integration.cs +++ b/OptimizelySDK/Config/Integration.cs @@ -1,4 +1,20 @@ -namespace OptimizelySDK.Config +/* + * 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. + */ + +namespace OptimizelySDK.Config { public class Integration { From 15f5b6341463c6bd102205dcea7e05ad850aa462 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Tue, 19 Jul 2022 09:51:36 -0400 Subject: [PATCH 06/41] Add AttributeType --- .../AudienceConditions/AttributeType.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 OptimizelySDK/AudienceConditions/AttributeType.cs diff --git a/OptimizelySDK/AudienceConditions/AttributeType.cs b/OptimizelySDK/AudienceConditions/AttributeType.cs new file mode 100644 index 00000000..dd3d8478 --- /dev/null +++ b/OptimizelySDK/AudienceConditions/AttributeType.cs @@ -0,0 +1,35 @@ +/* + * 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. + */ + +namespace OptimizelySDK.AudienceConditions +{ + public static class AttributeType + { + public static string CUSTOM_ATTRIBUTE = "custom_attribute"; + public static string THIRD_PARTY_DIMENSION = "third_party_dimension"; + + public string key { get; private set; } + + public AttributeType(string key) + { + key = key; + } + + public string ToString() => { + key; + } + } +} \ No newline at end of file From 7915232d7d0b3a6fc83f50f879638701979ffe72 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Tue, 19 Jul 2022 16:28:58 -0400 Subject: [PATCH 07/41] Implement changes from DoesUserMeetAudienceConditions --- .../ConditionEvaluationTest.cs | 305 +++++++++--------- .../AudienceConditionsTests/ConditionsTest.cs | 12 +- .../OptimizelySDK.Tests.csproj | 1 + OptimizelySDK.Tests/OptimizelyTest.cs | 64 +--- .../Utils/TestConversionExtensions.cs | 43 +++ .../UtilsTests/ExperimentUtilsTest.cs | 32 +- .../UtilsTests/ValidatorTest.cs | 19 +- .../AudienceConditions/AudienceIdCondition.cs | 9 +- .../AudienceConditions/BaseCondition.cs | 11 +- OptimizelySDK/Bucketing/DecisionService.cs | 13 +- OptimizelySDK/Optimizely.cs | 32 +- OptimizelySDK/Utils/ExperimentUtils.cs | 17 +- 12 files changed, 254 insertions(+), 304 deletions(-) create mode 100644 OptimizelySDK.Tests/Utils/TestConversionExtensions.cs diff --git a/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs b/OptimizelySDK.Tests/AudienceConditionsTests/ConditionEvaluationTest.cs index dc5d66b2..d212e3fb 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,6 +19,7 @@ using OptimizelySDK.AudienceConditions; using OptimizelySDK.Entity; using OptimizelySDK.Logger; +using OptimizelySDK.Tests.Utils; using System; namespace OptimizelySDK.Tests.AudienceConditionsTests @@ -68,26 +69,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 +97,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 +106,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 +114,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 +128,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 +142,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 +164,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 +187,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 +210,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 +222,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 +235,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 +260,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 +284,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 +336,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 +368,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 +413,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 +444,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 +473,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 +502,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 +551,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 +598,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,7 +650,7 @@ 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}"); } } 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/OptimizelySDK.Tests.csproj b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj index 2a776bcb..b6fc4876 100644 --- a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj +++ b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj @@ -106,6 +106,7 @@ + diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index 05e50bc9..25414234 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -1,11 +1,11 @@ /* - * Copyright 2017-2021, Optimizely + * Copyright 2017-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, @@ -510,66 +510,6 @@ public void TestErrorHandlingWithUnsupportedConfigVersion() ErrorHandlerMock.Verify(e => e.HandleError(It.Is(ex => ex.Message == $"This version of the C# SDK does not support the given datafile version: 5")), Times.Once); } - [Test] - public void TestValidatePreconditionsExperimentNotRunning() - { - var optly = Helper.CreatePrivateOptimizely(); - - optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager); - - bool result = (bool)optly.Invoke("ValidatePreconditions", - Config.GetExperimentFromKey("paused_experiment"), - TestUserId, ConfigManager.GetConfig(), new UserAttributes { }); - Assert.IsFalse(result); - } - - [Test] - public void TestValidatePreconditionsExperimentRunning() - { - var optly = Helper.CreatePrivateOptimizely(); - bool result = (bool)optly.Invoke("ValidatePreconditions", - Config.GetExperimentFromKey("test_experiment"), - TestUserId, - ConfigManager.GetConfig(), - new UserAttributes - { - { "device_type", "iPhone" }, - { "location", "San Francisco" } - } - ); - Assert.IsTrue(result); - } - - [Test] - public void TestValidatePreconditionsUserInForcedVariationNotInExperiment() - { - var optly = Helper.CreatePrivateOptimizely(); - bool result = (bool)optly.Invoke("ValidatePreconditions", - Config.GetExperimentFromKey("test_experiment"), - "user1", ConfigManager.GetConfig(), new UserAttributes { }); - Assert.IsTrue(result); - } - - [Test] - public void TestValidatePreconditionsUserInForcedVariationInExperiment() - { - var optly = Helper.CreatePrivateOptimizely(); - bool result = (bool)optly.Invoke("ValidatePreconditions", - Config.GetExperimentFromKey("test_experiment"), - "user1", ConfigManager.GetConfig(), new UserAttributes { }); - Assert.IsTrue(result); - } - - [Test] - public void TestValidatePreconditionsUserNotInForcedVariationNotInExperiment() - { - var optly = Helper.CreatePrivateOptimizely(); - bool result = (bool)optly.Invoke("ValidatePreconditions", - Config.GetExperimentFromKey("test_experiment"), - TestUserId, ConfigManager.GetConfig(), new UserAttributes { }); - Assert.IsFalse(result); - } - [Test] public void TestValidatePreconditionsUserNotInForcedVariationInExperiment() { diff --git a/OptimizelySDK.Tests/Utils/TestConversionExtensions.cs b/OptimizelySDK.Tests/Utils/TestConversionExtensions.cs new file mode 100644 index 00000000..e44dfb1f --- /dev/null +++ b/OptimizelySDK.Tests/Utils/TestConversionExtensions.cs @@ -0,0 +1,43 @@ +/* + * Copyright 2017-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 Moq; +using OptimizelySDK.Config; +using OptimizelySDK.Entity; +using OptimizelySDK.ErrorHandler; +using OptimizelySDK.Logger; + +namespace OptimizelySDK.Tests.Utils +{ + public static class TestConversionExtensions + { + public static OptimizelyUserContext ToUserContext(this UserAttributes attributes) + { + var mockLogger = new Mock(); + mockLogger.Setup(i => i.Log(It.IsAny(), It.IsAny())); + + var errorHandler = new NoOpErrorHandler(); + var config = DatafileProjectConfig.Create( + content: TestData.Datafile, + logger: mockLogger.Object, + errorHandler: errorHandler); + var configManager = new FallbackProjectConfigManager(config); + var optimizely = new Optimizely(configManager); + + return new OptimizelyUserContext(optimizely, "any-user", attributes, errorHandler, + mockLogger.Object); + } + } +} \ No newline at end of file diff --git a/OptimizelySDK.Tests/UtilsTests/ExperimentUtilsTest.cs b/OptimizelySDK.Tests/UtilsTests/ExperimentUtilsTest.cs index 805409e7..2b27f2e0 100644 --- a/OptimizelySDK.Tests/UtilsTests/ExperimentUtilsTest.cs +++ b/OptimizelySDK.Tests/UtilsTests/ExperimentUtilsTest.cs @@ -1,11 +1,11 @@ /* - * Copyright 2019-2021, 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,7 @@ using OptimizelySDK.Config; using OptimizelySDK.Entity; using OptimizelySDK.Logger; -using OptimizelySDK.OptimizelyDecisions; +using OptimizelySDK.Tests.Utils; using OptimizelySDK.Utils; namespace OptimizelySDK.Tests.UtilsTests @@ -49,7 +49,7 @@ public void TestDoesUserMeetAudienceConditionsReturnsTrueWithNoAudience() experiment.AudienceIds = new string[] { }; experiment.AudienceConditions = null; - Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, null, "experiment", experiment.Key, Logger).ResultObject); + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, new UserAttributes().ToUserContext(), "experiment", experiment.Key, Logger).ResultObject); LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": []."), Times.Once); } @@ -59,15 +59,15 @@ public void TestDoesUserMeetAudienceConditionsReturnsTrueWhenAudienceUsedInExper { var experiment = Config.GetExperimentFromKey("feat_with_var_test"); experiment.AudienceIds = new string[] { "3468206648" }; - Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, null , "experiment", experiment.Key, Logger).ResultObject);; - var boolResult = ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, new UserAttributes { }, "experiment", experiment.Key, Logger); + + var boolResult = ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, new UserAttributes { }.ToUserContext(), "experiment", experiment.Key, Logger); Assert.True(boolResult.ResultObject); Assert.AreEqual(boolResult.DecisionReasons.ToReport(true)[0], "Audiences for experiment \"feat_with_var_test\" collectively evaluated to TRUE"); - 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)); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Evaluating audiences for experiment ""feat_with_var_test"": [""3468206648""]."), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, @"Starting to evaluate audience ""3468206648"" with conditions: [""not"",{""name"":""input_value"",""type"":""custom_attribute"",""match"":""exists""}]"), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, $@"Audience ""3468206648"" 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] @@ -81,7 +81,7 @@ public void TestDoesUserMeetAudienceConditionsReturnsFalseIfNoAudienceInORCondit { "should_do_it", false } }; - Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger).ResultObject); + Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes.ToUserContext(), "experiment", experiment.Key, Logger).ResultObject); 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); @@ -110,7 +110,7 @@ public void TestDoesUserMeetAudienceConditionsReturnsTrueIfAnyAudienceInORCondit { "should_do_it", false } }; - Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger).ResultObject); + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes.ToUserContext(), "experiment", experiment.Key, Logger).ResultObject); 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); @@ -128,7 +128,7 @@ public void TestDoesUserMeetAudienceConditionsReturnsTrueIfAllAudiencesInANDCond { "favorite_ice_cream", "walls" } }; - Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger).ResultObject); + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes.ToUserContext(), "experiment", experiment.Key, Logger).ResultObject); } [Test] @@ -141,7 +141,7 @@ public void TestDoesUserMeetAudienceConditionsReturnsFalseIfAnyAudienceInANDCond { "lasers", 50 } }; - Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger).ResultObject); + Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes.ToUserContext(), "experiment", experiment.Key, Logger).ResultObject); 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); @@ -170,7 +170,7 @@ public void TestDoesUserMeetAudienceConditionsReturnsTrueIfAudienceInNOTConditio { "browser_type", "Safari" } }; - Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger).ResultObject); + Assert.True(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes.ToUserContext(), "experiment", experiment.Key, Logger).ResultObject); } [Test] @@ -183,7 +183,7 @@ public void TestDoesUserMeetAudienceConditionsReturnsFalseIfAudienceInNOTConditi { "browser_type", "Chrome" } }; - Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes, "experiment", experiment.Key, Logger).ResultObject); + Assert.False(ExperimentUtils.DoesUserMeetAudienceConditions(Config, experiment, userAttributes.ToUserContext(), "experiment", experiment.Key, Logger).ResultObject); } #endregion // DoesUserMeetAudienceConditions Tests diff --git a/OptimizelySDK.Tests/UtilsTests/ValidatorTest.cs b/OptimizelySDK.Tests/UtilsTests/ValidatorTest.cs index 33305648..e37b6ddf 100644 --- a/OptimizelySDK.Tests/UtilsTests/ValidatorTest.cs +++ b/OptimizelySDK.Tests/UtilsTests/ValidatorTest.cs @@ -1,11 +1,11 @@ /* - * Copyright 2017-2021, Optimizely + * Copyright 2017-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,6 +19,7 @@ using OptimizelySDK.Entity; using NUnit.Framework; using OptimizelySDK.Config; +using OptimizelySDK.Tests.Utils; namespace OptimizelySDK.Tests.UtilsTests { @@ -81,13 +82,13 @@ public void TestValidateJsonSchemaNoJsonContent() [Test] public void TestDoesUserMeetAudienceConditionsNoAudienceUsedInExperiment() { - Assert.IsTrue(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("paused_experiment"), new UserAttributes(), "experiment", "paused_experiment", Logger).ResultObject); + Assert.IsTrue(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("paused_experiment"), new UserAttributes().ToUserContext(), "experiment", "paused_experiment", Logger).ResultObject); } [Test] public void TestDoesUserMeetAudienceConditionsAudienceUsedInExperimentNoAttributesProvided() { - Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), new UserAttributes(), "experiment", "test_experiment", Logger).ResultObject); + Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), new UserAttributes().ToUserContext(), "experiment", "test_experiment", Logger).ResultObject); Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), null, "experiment", "test_experiment", Logger).ResultObject); } @@ -100,19 +101,13 @@ public void TestUserInExperimentAudienceMatch() {"device_type", "iPhone" }, {"location", "San Francisco" } }; - Assert.IsTrue(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), userAttributes, "experiment", "test_experiment", Logger).ResultObject); + Assert.IsTrue(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), userAttributes.ToUserContext(), "experiment", "test_experiment", Logger).ResultObject); } [Test] public void TestDoesUserMeetAudienceConditionsAudienceNoMatch() { - var userAttributes = new UserAttributes - { - {"device_type", "Android" }, - {"location", "San Francisco" } - }; - - Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), null, "experiment", "test_experiment", Logger).ResultObject); + Assert.IsFalse(ExperimentUtils.DoesUserMeetAudienceConditions(Config, Config.GetExperimentFromKey("test_experiment"), new UserAttributes().ToUserContext(), "experiment", "test_experiment", Logger).ResultObject); } [Test] diff --git a/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs b/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs index cbd3da9e..6dbcccaf 100644 --- a/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs +++ b/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs @@ -14,28 +14,25 @@ * limitations under the License. */ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using OptimizelySDK.Entity; using OptimizelySDK.Logger; namespace OptimizelySDK.AudienceConditions { /// - /// Represents Audiece Id condition for audience evaluation. + /// Represents Audience Id condition for audience evaluation. /// public class AudienceIdCondition : ICondition { public string AudienceId { get; set; } - public bool? Evaluate(ProjectConfig config, UserAttributes attributes, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger) { var audience = config?.GetAudience(AudienceId); if (audience == null || string.IsNullOrEmpty(audience.Id)) return null; logger.Log(LogLevel.DEBUG, $@"Starting to evaluate audience ""{AudienceId}"" with conditions: {audience.ConditionsString}"); - var result = audience.ConditionList.Evaluate(config, attributes, logger); + var result = audience.ConditionList.Evaluate(config, user, logger); var resultText = result?.ToString().ToUpper() ?? "UNKNOWN"; logger.Log(LogLevel.DEBUG, $@"Audience ""{AudienceId}"" evaluated to {resultText}"); diff --git a/OptimizelySDK/AudienceConditions/BaseCondition.cs b/OptimizelySDK/AudienceConditions/BaseCondition.cs index bc58413d..377f5d0b 100644 --- a/OptimizelySDK/AudienceConditions/BaseCondition.cs +++ b/OptimizelySDK/AudienceConditions/BaseCondition.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, @@ -15,7 +15,6 @@ */ using Newtonsoft.Json; -using OptimizelySDK.Entity; using OptimizelySDK.Logger; using OptimizelySDK.Utils; using System; @@ -28,7 +27,7 @@ namespace OptimizelySDK.AudienceConditions public class BaseCondition : ICondition { /// - /// String constant representing custome attribute condition type. + /// String constant representing custom attribute condition type. /// public const string CUSTOM_ATTRIBUTE_CONDITION_TYPE = "custom_attribute"; @@ -44,13 +43,15 @@ public class BaseCondition : ICondition [JsonProperty("value")] public object Value { get; set; } - public bool? Evaluate(ProjectConfig config, UserAttributes userAttributes, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger) { if (Type == null || Type != CUSTOM_ATTRIBUTE_CONDITION_TYPE) { 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; } + + var userAttributes = user.GetAttributes(); object attributeValue = null; if (userAttributes.TryGetValue(Name, out attributeValue) == false && Match != AttributeMatchTypes.EXIST) diff --git a/OptimizelySDK/Bucketing/DecisionService.cs b/OptimizelySDK/Bucketing/DecisionService.cs index bf01bf60..97fd958d 100644 --- a/OptimizelySDK/Bucketing/DecisionService.cs +++ b/OptimizelySDK/Bucketing/DecisionService.cs @@ -1,11 +1,11 @@ /* -* Copyright 2017-2021, Optimizely +* Copyright 2017-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, @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; -using System.Linq; using OptimizelySDK.Entity; using OptimizelySDK.ErrorHandler; using OptimizelySDK.Logger; @@ -160,7 +159,7 @@ public virtual Result GetVariation(Experiment experiment, } } var filteredAttributes = user.GetAttributes(); - var doesUserMeetAudienceConditionsResult = ExperimentUtils.DoesUserMeetAudienceConditions(config, experiment, filteredAttributes, LOGGING_KEY_TYPE_EXPERIMENT, experiment.Key, Logger); + var doesUserMeetAudienceConditionsResult = ExperimentUtils.DoesUserMeetAudienceConditions(config, experiment, user, LOGGING_KEY_TYPE_EXPERIMENT, experiment.Key, Logger); reasons += doesUserMeetAudienceConditionsResult.DecisionReasons; if (doesUserMeetAudienceConditionsResult.ResultObject) { @@ -411,9 +410,7 @@ public void SaveVariation(Experiment experiment, Variation variation, UserProfil /// Fall back onto the everyone else rule if the user is ever excluded from a rule due to traffic allocation. /// /// The feature flag the user wants to access. - /// User Identifier - /// The user's attributes. This should be filtered to just attributes in the Datafile. - /// Decision log messages. + /// The user context. /// null if the user is not bucketed into the rollout or if the feature flag was not attached to a rollout. /// otherwise the FeatureDecision entity public virtual Result GetVariationForFeatureRollout(FeatureFlag featureFlag, @@ -480,7 +477,7 @@ public virtual Result GetVariationForFeatureRollout(FeatureFlag var loggingKey = everyoneElse ? "Everyone Else" : string.Format("{0}", index + 1); // Evaluate if user meets the audience condition of this rollout rule - var doesUserMeetAudienceConditionsResult = ExperimentUtils.DoesUserMeetAudienceConditions(config, rule, attributes, LOGGING_KEY_TYPE_RULE, rule.Key, Logger); + var doesUserMeetAudienceConditionsResult = ExperimentUtils.DoesUserMeetAudienceConditions(config, rule, user, LOGGING_KEY_TYPE_RULE, rule.Key, Logger); reasons += doesUserMeetAudienceConditionsResult.DecisionReasons; if (doesUserMeetAudienceConditionsResult.ResultObject) { diff --git a/OptimizelySDK/Optimizely.cs b/OptimizelySDK/Optimizely.cs index cef5a68c..7a9f53dd 100644 --- a/OptimizelySDK/Optimizely.cs +++ b/OptimizelySDK/Optimizely.cs @@ -1,11 +1,11 @@ /* - * Copyright 2017-2021, Optimizely + * Copyright 2017-2022, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use 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, @@ -193,34 +193,6 @@ private void InitializeComponents(IEventDispatcher eventDispatcher = null, DefaultDecideOptions = defaultDecideOptions ?? new OptimizelyDecideOption[] { }; } - /// - /// Helper function to validate all required conditions before performing activate or track. - /// - /// Experiment Object representing experiment - /// string ID for user - /// associative array of Attributes for the user - private bool ValidatePreconditions(Experiment experiment, string userId, ProjectConfig config, UserAttributes userAttributes = null) - { - if (!experiment.IsExperimentRunning) - { - Logger.Log(LogLevel.INFO, string.Format("Experiment {0} is not running.", experiment.Key)); - return false; - } - - if (experiment.IsUserInForcedVariation(userId)) - { - return true; - } - - if (!ExperimentUtils.DoesUserMeetAudienceConditions(config, experiment, userAttributes, "experiment", experiment.Key, Logger).ResultObject) - { - Logger.Log(LogLevel.INFO, string.Format("User \"{0}\" does not meet conditions to be in experiment \"{1}\".", userId, experiment.Key)); - return false; - } - - return true; - } - /// /// Buckets visitor and sends impression event to Optimizely. /// diff --git a/OptimizelySDK/Utils/ExperimentUtils.cs b/OptimizelySDK/Utils/ExperimentUtils.cs index d36ee36a..cffd0721 100644 --- a/OptimizelySDK/Utils/ExperimentUtils.cs +++ b/OptimizelySDK/Utils/ExperimentUtils.cs @@ -1,11 +1,11 @@ /* - * Copyright 2017-2021, Optimizely + * Copyright 2017-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,20 +41,23 @@ 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 + /// OptimizelyUserContext in use /// It can be either experiment or rule. /// In case loggingKeyType is experiment it will be experiment key or else it will be rule number. + /// Custom logger implementation to record log outputs /// true if the user meets audience conditions to be in experiment, false otherwise. public static Result DoesUserMeetAudienceConditions(ProjectConfig config, Experiment experiment, - UserAttributes userAttributes, + OptimizelyUserContext user, string loggingKeyType, string loggingKey, ILogger logger) { var reasons = new DecisionReasons(); - if (userAttributes == null) - userAttributes = new UserAttributes(); + if (user == null) + { + return Result.NewResult(false, reasons); + } ICondition expConditions = null; if (experiment.AudienceConditionsList != null) @@ -72,7 +75,7 @@ public static Result DoesUserMeetAudienceConditions(ProjectConfig config, if (expConditions == null) return Result.NewResult(true, reasons); - var result = expConditions.Evaluate(config, userAttributes, logger).GetValueOrDefault(); + var result = expConditions.Evaluate(config, user, logger).GetValueOrDefault(); var resultText = result.ToString().ToUpper(); logger.Log(LogLevel.INFO, reasons.AddInfo($@"Audiences for {loggingKeyType} ""{loggingKey}"" collectively evaluated to {resultText}")); return Result.NewResult(result, reasons); From af97fcce93b1cd8908b7a857ecf795fbcc45f3bb Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Tue, 19 Jul 2022 17:01:15 -0400 Subject: [PATCH 08/41] Correct accessibility & missing interface item --- OptimizelySDK/Config/DatafileProjectConfig.cs | 8 ++++---- OptimizelySDK/OptimizelyUserContext.cs | 6 +++--- OptimizelySDK/ProjectConfig.cs | 12 +++++++++--- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/OptimizelySDK/Config/DatafileProjectConfig.cs b/OptimizelySDK/Config/DatafileProjectConfig.cs index df0f47bc..8c0c8ea5 100644 --- a/OptimizelySDK/Config/DatafileProjectConfig.cs +++ b/OptimizelySDK/Config/DatafileProjectConfig.cs @@ -5,7 +5,7 @@ * 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, @@ -102,12 +102,12 @@ public enum OPTLYSDKVersion /// /// Configured host name for the Optimizely Data Platform. /// - public string HostForOdp { get; set; } + public string HostForOdp { get; private set; } /// /// Configured public key from the Optimizely Data Platform. /// - public string PublicKeyForOdp { get; set; } + public string PublicKeyForOdp { get; private set; } /// /// Supported datafile versions list. @@ -278,7 +278,7 @@ private Dictionary> _VariationIdMap /// /// Associative list of Integrations. /// - private Integration[] Integrations { get; set; } + public Integration[] Integrations { get; private set; } //========================= Initialization =========================== diff --git a/OptimizelySDK/OptimizelyUserContext.cs b/OptimizelySDK/OptimizelyUserContext.cs index 4aa24c5f..a17fa992 100644 --- a/OptimizelySDK/OptimizelyUserContext.cs +++ b/OptimizelySDK/OptimizelyUserContext.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, @@ -38,7 +38,7 @@ public class OptimizelyUserContext // user attributes for Optimizely user context. private UserAttributes Attributes; - private List QualifiedSegments; + private readonly List QualifiedSegments; // Optimizely object to be used. private Optimizely Optimizely; diff --git a/OptimizelySDK/ProjectConfig.cs b/OptimizelySDK/ProjectConfig.cs index 1d604270..4a5368b3 100644 --- a/OptimizelySDK/ProjectConfig.cs +++ b/OptimizelySDK/ProjectConfig.cs @@ -5,7 +5,7 @@ * 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, @@ -15,6 +15,7 @@ */ using OptimizelySDK.Entity; +using OptimizelySDK.Config; using System.Collections.Generic; namespace OptimizelySDK @@ -69,12 +70,12 @@ public interface ProjectConfig /// /// Configured host name for the Optimizely Data Platform. /// - string HostForOdp { get; set; } + string HostForOdp { get; } /// /// Configured public key from the Optimizely Data Platform. /// - string PublicKeyForOdp { get; set; } + string PublicKeyForOdp { get; } //========================= Mappings =========================== @@ -175,6 +176,11 @@ public interface ProjectConfig /// Rollout[] Rollouts { get; set; } + /// + /// Associative list of Integrations. + /// + Integration[] Integrations { get; } + //========================= Getters =========================== /// From cdc6c29b40f2835662bc91ab5b2cec74545a82ba Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Wed, 20 Jul 2022 09:45:10 -0400 Subject: [PATCH 09/41] Bring AttributeType into BaseCondition --- .../AudienceConditions/AttributeType.cs | 35 ------------------- .../AudienceConditions/BaseCondition.cs | 31 ++++++++++++++-- .../AudienceConditions/ICondition.cs | 1 - 3 files changed, 29 insertions(+), 38 deletions(-) delete mode 100644 OptimizelySDK/AudienceConditions/AttributeType.cs diff --git a/OptimizelySDK/AudienceConditions/AttributeType.cs b/OptimizelySDK/AudienceConditions/AttributeType.cs deleted file mode 100644 index dd3d8478..00000000 --- a/OptimizelySDK/AudienceConditions/AttributeType.cs +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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. - */ - -namespace OptimizelySDK.AudienceConditions -{ - public static class AttributeType - { - public static string CUSTOM_ATTRIBUTE = "custom_attribute"; - public static string THIRD_PARTY_DIMENSION = "third_party_dimension"; - - public string key { get; private set; } - - public AttributeType(string key) - { - key = key; - } - - public string ToString() => { - key; - } - } -} \ No newline at end of file diff --git a/OptimizelySDK/AudienceConditions/BaseCondition.cs b/OptimizelySDK/AudienceConditions/BaseCondition.cs index 377f5d0b..bc4449db 100644 --- a/OptimizelySDK/AudienceConditions/BaseCondition.cs +++ b/OptimizelySDK/AudienceConditions/BaseCondition.cs @@ -18,6 +18,7 @@ using OptimizelySDK.Logger; using OptimizelySDK.Utils; using System; +using System.Linq; namespace OptimizelySDK.AudienceConditions { @@ -29,7 +30,17 @@ public class BaseCondition : ICondition /// /// String constant representing custom attribute condition type. /// - public const string CUSTOM_ATTRIBUTE_CONDITION_TYPE = "custom_attribute"; + private const string CUSTOM_ATTRIBUTE = "custom_attribute"; + + /// + /// String constant representing a third-party condition type. + /// + private const string THIRD_PARTY_DIMENSION = "third_party_dimension"; + + /// + /// String constant to match status of qualified segments. + /// + private const string QUALIFIED = "qualified"; [JsonProperty("type")] public string Type { get; set; } @@ -45,7 +56,7 @@ public class BaseCondition : ICondition public bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger) { - if (Type == null || Type != CUSTOM_ATTRIBUTE_CONDITION_TYPE) + if (!IsValidType()) { 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; @@ -60,6 +71,17 @@ public class BaseCondition : ICondition return null; } + if (Match == QUALIFIED) + { + if (Value is string) + { + return user.IsQualifiedFor(Value.ToString()); + } + + logger.Log(LogLevel.WARN, $@"Audience condition ""{this}"" has a qualified match but invalid value."); + return null; + } + var evaluator = GetEvaluator(); if (evaluator == null) { @@ -69,6 +91,11 @@ public class BaseCondition : ICondition return evaluator(attributeValue, logger); } + + private bool IsValidType() + { + return new[] {CUSTOM_ATTRIBUTE, THIRD_PARTY_DIMENSION}.Contains(Type); + } public Func GetEvaluator() { diff --git a/OptimizelySDK/AudienceConditions/ICondition.cs b/OptimizelySDK/AudienceConditions/ICondition.cs index 52893aed..58c057f2 100644 --- a/OptimizelySDK/AudienceConditions/ICondition.cs +++ b/OptimizelySDK/AudienceConditions/ICondition.cs @@ -14,7 +14,6 @@ * limitations under the License. */ -using OptimizelySDK.Entity; using OptimizelySDK.Logger; namespace OptimizelySDK.AudienceConditions From 4c415b35dc5d68c249d9351068e5869f321b928e Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Wed, 20 Jul 2022 10:46:27 -0400 Subject: [PATCH 10/41] Add missing reference --- OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj | 3 +++ OptimizelySDK/Config/DatafileProjectConfig.cs | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj index 4e49e21d..1828f0bb 100644 --- a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj +++ b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj @@ -337,4 +337,7 @@ OptimizelySDK.Utils.schema.json + + + diff --git a/OptimizelySDK/Config/DatafileProjectConfig.cs b/OptimizelySDK/Config/DatafileProjectConfig.cs index 8c0c8ea5..0f4b3f38 100644 --- a/OptimizelySDK/Config/DatafileProjectConfig.cs +++ b/OptimizelySDK/Config/DatafileProjectConfig.cs @@ -20,7 +20,6 @@ using OptimizelySDK.Exceptions; using OptimizelySDK.Logger; using OptimizelySDK.Utils; -using System; using System.Collections.Generic; using System.Linq; using Attribute = OptimizelySDK.Entity.Attribute; From 5cd1eeb69d453ad44d02ff159aa7a8a00cb431a2 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Wed, 20 Jul 2022 13:56:05 -0400 Subject: [PATCH 11/41] Fix parsing --- OptimizelySDK/Config/DatafileProjectConfig.cs | 14 ++++--------- .../{Config => Entity}/Integration.cs | 21 +++++-------------- 2 files changed, 9 insertions(+), 26 deletions(-) rename OptimizelySDK/{Config => Entity}/Integration.cs (63%) diff --git a/OptimizelySDK/Config/DatafileProjectConfig.cs b/OptimizelySDK/Config/DatafileProjectConfig.cs index 0f4b3f38..5b50177c 100644 --- a/OptimizelySDK/Config/DatafileProjectConfig.cs +++ b/OptimizelySDK/Config/DatafileProjectConfig.cs @@ -277,7 +277,7 @@ private Dictionary> _VariationIdMap /// /// Associative list of Integrations. /// - public Integration[] Integrations { get; private set; } + public Integration[] Integrations { get; set; } //========================= Initialization =========================== @@ -370,15 +370,9 @@ private void Initialize() } } - foreach (var integration in Integrations) - { - if (integration.Key?.ToLower() == "odp") - { - HostForOdp = integration.Host; - PublicKeyForOdp = integration.PublicKey; - break; - } - } + var integration = Integrations.FirstOrDefault(i => i.Key.ToLower() == "odp"); + HostForOdp = integration?.Host; + PublicKeyForOdp = integration?.PublicKey; var flagToVariationsMap = new Dictionary>(); // Adding experiments in experiment-feature map and flag variation map to use. diff --git a/OptimizelySDK/Config/Integration.cs b/OptimizelySDK/Entity/Integration.cs similarity index 63% rename from OptimizelySDK/Config/Integration.cs rename to OptimizelySDK/Entity/Integration.cs index c7f4acef..3c65c926 100644 --- a/OptimizelySDK/Config/Integration.cs +++ b/OptimizelySDK/Entity/Integration.cs @@ -14,24 +14,13 @@ * limitations under the License. */ -namespace OptimizelySDK.Config +namespace OptimizelySDK.Entity { - public class Integration + public class Integration : IdKeyEntity { - public string Key { get; private set; } - public string Host { get; private set; } - public string PublicKey { get; private set; } - - public Integration( - string key, - string host, - string publicKey - ) - { - Key = key; - Host = host; - PublicKey = publicKey; - } + public string Host { get; set; } + + public string PublicKey { get; set; } public override string ToString() { From d998b572a3f3b997eb3c5855ad1fdb1ff28e7e0e Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Wed, 20 Jul 2022 13:56:23 -0400 Subject: [PATCH 12/41] Add test for Integration parsing --- .../OptimizelyConfigTests/OptimizelyConfigTest.cs | 9 +++++++++ OptimizelySDK.Tests/typed_audience_datafile.json | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs index 007ee360..878bbae2 100644 --- a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs +++ b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs @@ -177,6 +177,15 @@ public void TestGetOptimizelyConfigSDKAndEnvironmentKeyDefault() Assert.AreEqual(optimizelyConfig.EnvironmentKey, ""); } + [Test] + public void TestGetOptimizelyConfigWithOdpIntegration() + { + var datafileProjectConfig = DatafileProjectConfig.Create(TestData.TypedAudienceDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); + + Assert.AreEqual("https://api.zaius.com", datafileProjectConfig.HostForOdp); + Assert.AreEqual("W4WzcEs-ABgXorzY7h1LCQ", datafileProjectConfig.PublicKeyForOdp); + } + [Test] public void TestGetOptimizelyConfigService() { diff --git a/OptimizelySDK.Tests/typed_audience_datafile.json b/OptimizelySDK.Tests/typed_audience_datafile.json index 7c6fee4b..3f1b4cfd 100644 --- a/OptimizelySDK.Tests/typed_audience_datafile.json +++ b/OptimizelySDK.Tests/typed_audience_datafile.json @@ -337,6 +337,13 @@ } ], "groups": [], + "integrations": [ + { + "key": "odp", + "host": "https://api.zaius.com", + "publicKey": "W4WzcEs-ABgXorzY7h1LCQ" + } + ], "attributes": [{ "key": "house", "id": "594015" From 7fedccb0539feddef2a67f6b75925a18831a2ec0 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Wed, 20 Jul 2022 14:09:49 -0400 Subject: [PATCH 13/41] Move reference to Integration --- OptimizelySDK.Net35/OptimizelySDK.Net35.csproj | 4 ++-- OptimizelySDK.Net40/OptimizelySDK.Net40.csproj | 4 ++-- .../OptimizelySDK.NetStandard16.csproj | 2 +- .../OptimizelySDK.NetStandard20.csproj | 6 +++--- OptimizelySDK/OptimizelySDK.csproj | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj index 41e026f2..11265206 100644 --- a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj +++ b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj @@ -233,8 +233,8 @@ Config\DatafileProjectConfig - - Config\Integration + + Entity\Integration Config\ProjectConfigManager diff --git a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj index 4517ae1b..0c7092ea 100644 --- a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj +++ b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj @@ -232,8 +232,8 @@ Config\DatafileProjectConfig - - Config\Integration + + Entity\Integration Config\ProjectConfigManager diff --git a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj index a76514d0..94828d2b 100644 --- a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj +++ b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj @@ -76,7 +76,7 @@ - + diff --git a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj index 1828f0bb..3aa192db 100644 --- a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj +++ b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj @@ -64,9 +64,6 @@ Config\DatafileProjectConfig.cs - - Config\Integration - Config\FallbackProjectConfigManager.cs @@ -118,6 +115,9 @@ Entity\Group.cs + + Entity\Integration.cs + Entity\IdKeyEntity.cs diff --git a/OptimizelySDK/OptimizelySDK.csproj b/OptimizelySDK/OptimizelySDK.csproj index fcce93e7..2db6cd33 100644 --- a/OptimizelySDK/OptimizelySDK.csproj +++ b/OptimizelySDK/OptimizelySDK.csproj @@ -72,7 +72,6 @@ - @@ -87,6 +86,7 @@ + From a9295d202dc1178ddcc4061ba54dafe639c26a95 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Wed, 20 Jul 2022 14:27:21 -0400 Subject: [PATCH 14/41] Correction to NetStandard 2.0 project reference --- OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj index 3aa192db..3b2962e0 100644 --- a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj +++ b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj @@ -337,7 +337,4 @@ OptimizelySDK.Utils.schema.json - - - From dd62705abd5d02aec191b71581f54d084365f408 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Wed, 20 Jul 2022 14:31:19 -0400 Subject: [PATCH 15/41] Add empty Integration test --- OptimizelySDK.Tests/EmptyRolloutRule.json | 1 + .../OptimizelyConfigTests/OptimizelyConfigTest.cs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/OptimizelySDK.Tests/EmptyRolloutRule.json b/OptimizelySDK.Tests/EmptyRolloutRule.json index 8aa8e559..c30491ce 100644 --- a/OptimizelySDK.Tests/EmptyRolloutRule.json +++ b/OptimizelySDK.Tests/EmptyRolloutRule.json @@ -50,6 +50,7 @@ } ], "groups": [], + "integrations": [], "attributes": [], "botFiltering": false, "accountId": "8272261422", diff --git a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs index 878bbae2..49fff8a9 100644 --- a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs +++ b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs @@ -186,6 +186,15 @@ public void TestGetOptimizelyConfigWithOdpIntegration() Assert.AreEqual("W4WzcEs-ABgXorzY7h1LCQ", datafileProjectConfig.PublicKeyForOdp); } + [Test] + public void TestGetOptimizelyConfigWithEmptyOdpIntegration() + { + var datafileProjectConfig = DatafileProjectConfig.Create(TestData.EmptyRolloutDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); + + Assert.IsNull(datafileProjectConfig.HostForOdp); + Assert.IsNull(datafileProjectConfig.PublicKeyForOdp); + } + [Test] public void TestGetOptimizelyConfigService() { From 5420bf65bcab43d0e4785dc6079884c3aed42222 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Wed, 20 Jul 2022 14:35:33 -0400 Subject: [PATCH 16/41] Test for empty integration and non "odp" integration --- OptimizelySDK.Tests/EmptyRolloutRule.json | 8 +++++++- .../OptimizelyConfigTests/OptimizelyConfigTest.cs | 11 ++++++++++- OptimizelySDK.Tests/emptydatafile.json | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/OptimizelySDK.Tests/EmptyRolloutRule.json b/OptimizelySDK.Tests/EmptyRolloutRule.json index c30491ce..cfdb0819 100644 --- a/OptimizelySDK.Tests/EmptyRolloutRule.json +++ b/OptimizelySDK.Tests/EmptyRolloutRule.json @@ -50,7 +50,13 @@ } ], "groups": [], - "integrations": [], + "integrations": [ + { + "key": "not-odp", + "host": "https://example.com", + "publicKey": "CleAr1y-!a-R3a7-pUbl1c-k3y" + } + ], "attributes": [], "botFiltering": false, "accountId": "8272261422", diff --git a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs index 49fff8a9..ef9d634c 100644 --- a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs +++ b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs @@ -187,7 +187,16 @@ public void TestGetOptimizelyConfigWithOdpIntegration() } [Test] - public void TestGetOptimizelyConfigWithEmptyOdpIntegration() + public void TestGetOptimizelyConfigWithEmptyIntegrationCollection() + { + var datafileProjectConfig = DatafileProjectConfig.Create(TestData.EmptyDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); + + Assert.IsNull(datafileProjectConfig.HostForOdp); + Assert.IsNull(datafileProjectConfig.PublicKeyForOdp); + } + + [Test] + public void TestGetOptimizelyConfigWithOtherIntegrationsInCollection() { var datafileProjectConfig = DatafileProjectConfig.Create(TestData.EmptyRolloutDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); diff --git a/OptimizelySDK.Tests/emptydatafile.json b/OptimizelySDK.Tests/emptydatafile.json index b77e660a..54a5c630 100644 --- a/OptimizelySDK.Tests/emptydatafile.json +++ b/OptimizelySDK.Tests/emptydatafile.json @@ -8,6 +8,7 @@ "experiments": [], "audiences": [], "groups": [], + "integrations": [], "attributes": [], "accountId": "10367498574", "events": [], From e29a61a6e568bb41f234ba98ded763e22a2b0c34 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Wed, 20 Jul 2022 15:28:21 -0400 Subject: [PATCH 17/41] Fix missed Copyright update --- .../OptimizelyConfigTests/OptimizelyConfigTest.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs index ef9d634c..51438bcb 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; From ecbd7ec2555de009b292cd5ec4ee77de3eabf4c2 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Thu, 21 Jul 2022 13:51:45 -0400 Subject: [PATCH 18/41] Fix copyright notice --- OptimizelySDK.Tests/Utils/TestConversionExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OptimizelySDK.Tests/Utils/TestConversionExtensions.cs b/OptimizelySDK.Tests/Utils/TestConversionExtensions.cs index e44dfb1f..f1e47920 100644 --- a/OptimizelySDK.Tests/Utils/TestConversionExtensions.cs +++ b/OptimizelySDK.Tests/Utils/TestConversionExtensions.cs @@ -1,5 +1,5 @@ /* - * Copyright 2017-2022, Optimizely + * 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + using Moq; using OptimizelySDK.Config; using OptimizelySDK.Entity; From c0bcf384a33574a0980429a171d8da667d4d8192 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Mon, 25 Jul 2022 09:27:23 -0400 Subject: [PATCH 19/41] Add additional line to eof --- OptimizelySDK.Tests/Utils/TestConversionExtensions.cs | 2 +- OptimizelySDK/Entity/Integration.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OptimizelySDK.Tests/Utils/TestConversionExtensions.cs b/OptimizelySDK.Tests/Utils/TestConversionExtensions.cs index f1e47920..336e207c 100644 --- a/OptimizelySDK.Tests/Utils/TestConversionExtensions.cs +++ b/OptimizelySDK.Tests/Utils/TestConversionExtensions.cs @@ -41,4 +41,4 @@ public static OptimizelyUserContext ToUserContext(this UserAttributes attributes mockLogger.Object); } } -} \ No newline at end of file +} diff --git a/OptimizelySDK/Entity/Integration.cs b/OptimizelySDK/Entity/Integration.cs index 3c65c926..5bbde6e4 100644 --- a/OptimizelySDK/Entity/Integration.cs +++ b/OptimizelySDK/Entity/Integration.cs @@ -27,4 +27,4 @@ public override string ToString() return $"Integration{{key='{Key}', host='{Host}', publicKey='{PublicKey}'}}"; } } -} \ No newline at end of file +} From a7e0b19dd8495bdc36742bbe27117ea23675651c Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Mon, 25 Jul 2022 09:29:43 -0400 Subject: [PATCH 20/41] Refactor `user` to `context` for OptimizelyUserContext param --- OptimizelySDK/AudienceConditions/AndCondition.cs | 4 ++-- OptimizelySDK/AudienceConditions/AudienceIdCondition.cs | 4 ++-- OptimizelySDK/AudienceConditions/BaseCondition.cs | 6 +++--- OptimizelySDK/AudienceConditions/EmptyCondition.cs | 2 +- OptimizelySDK/AudienceConditions/ICondition.cs | 2 +- OptimizelySDK/AudienceConditions/NotCondition.cs | 4 ++-- OptimizelySDK/AudienceConditions/OrCondition.cs | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/OptimizelySDK/AudienceConditions/AndCondition.cs b/OptimizelySDK/AudienceConditions/AndCondition.cs index e5e2b91f..606d5f6a 100644 --- a/OptimizelySDK/AudienceConditions/AndCondition.cs +++ b/OptimizelySDK/AudienceConditions/AndCondition.cs @@ -26,7 +26,7 @@ public class AndCondition : ICondition { public ICondition[] Conditions { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger) { // According to the matrix: // false and true is false @@ -38,7 +38,7 @@ public class AndCondition : ICondition var foundNull = false; foreach (var condition in Conditions) { - var result = condition.Evaluate(config, user, logger); + var result = condition.Evaluate(config, context, logger); if (result == null) foundNull = true; else if (result == false) diff --git a/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs b/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs index 6dbcccaf..35c6bb58 100644 --- a/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs +++ b/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs @@ -25,14 +25,14 @@ public class AudienceIdCondition : ICondition { public string AudienceId { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger) { var audience = config?.GetAudience(AudienceId); if (audience == null || string.IsNullOrEmpty(audience.Id)) return null; logger.Log(LogLevel.DEBUG, $@"Starting to evaluate audience ""{AudienceId}"" with conditions: {audience.ConditionsString}"); - var result = audience.ConditionList.Evaluate(config, user, logger); + var result = audience.ConditionList.Evaluate(config, context, logger); var resultText = result?.ToString().ToUpper() ?? "UNKNOWN"; logger.Log(LogLevel.DEBUG, $@"Audience ""{AudienceId}"" evaluated to {resultText}"); diff --git a/OptimizelySDK/AudienceConditions/BaseCondition.cs b/OptimizelySDK/AudienceConditions/BaseCondition.cs index bc4449db..57a24bc9 100644 --- a/OptimizelySDK/AudienceConditions/BaseCondition.cs +++ b/OptimizelySDK/AudienceConditions/BaseCondition.cs @@ -54,7 +54,7 @@ public class BaseCondition : ICondition [JsonProperty("value")] public object Value { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger) { if (!IsValidType()) { @@ -62,7 +62,7 @@ public class BaseCondition : ICondition return null; } - var userAttributes = user.GetAttributes(); + var userAttributes = context.GetAttributes(); object attributeValue = null; if (userAttributes.TryGetValue(Name, out attributeValue) == false && Match != AttributeMatchTypes.EXIST) @@ -75,7 +75,7 @@ public class BaseCondition : ICondition { if (Value is string) { - return user.IsQualifiedFor(Value.ToString()); + return context.IsQualifiedFor(Value.ToString()); } logger.Log(LogLevel.WARN, $@"Audience condition ""{this}"" has a qualified match but invalid value."); diff --git a/OptimizelySDK/AudienceConditions/EmptyCondition.cs b/OptimizelySDK/AudienceConditions/EmptyCondition.cs index 90801176..c848601b 100644 --- a/OptimizelySDK/AudienceConditions/EmptyCondition.cs +++ b/OptimizelySDK/AudienceConditions/EmptyCondition.cs @@ -24,7 +24,7 @@ namespace OptimizelySDK.AudienceConditions /// public class EmptyCondition : ICondition { - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger) { return true; } diff --git a/OptimizelySDK/AudienceConditions/ICondition.cs b/OptimizelySDK/AudienceConditions/ICondition.cs index 58c057f2..221c885a 100644 --- a/OptimizelySDK/AudienceConditions/ICondition.cs +++ b/OptimizelySDK/AudienceConditions/ICondition.cs @@ -23,6 +23,6 @@ namespace OptimizelySDK.AudienceConditions /// public interface ICondition { - bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger); + bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger); } } diff --git a/OptimizelySDK/AudienceConditions/NotCondition.cs b/OptimizelySDK/AudienceConditions/NotCondition.cs index ff6cd1a2..5f307722 100644 --- a/OptimizelySDK/AudienceConditions/NotCondition.cs +++ b/OptimizelySDK/AudienceConditions/NotCondition.cs @@ -26,9 +26,9 @@ public class NotCondition : ICondition { public ICondition Condition { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger) { - var result = Condition?.Evaluate(config, user, logger); + var result = Condition?.Evaluate(config, context, logger); return result == null ? null : !result; } } diff --git a/OptimizelySDK/AudienceConditions/OrCondition.cs b/OptimizelySDK/AudienceConditions/OrCondition.cs index f471a0d8..9e603e02 100644 --- a/OptimizelySDK/AudienceConditions/OrCondition.cs +++ b/OptimizelySDK/AudienceConditions/OrCondition.cs @@ -26,7 +26,7 @@ public class OrCondition : ICondition { public ICondition[] Conditions { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext user, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger) { // According to the matrix: // true returns true @@ -36,7 +36,7 @@ public class OrCondition : ICondition var foundNull = false; foreach (var condition in Conditions) { - var result = condition.Evaluate(config, user, logger); + var result = condition.Evaluate(config, context, logger); if (result == null) foundNull = true; else if (result == true) From 67d28cfb4dd09f960b64111859069f12658d2d87 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Mon, 25 Jul 2022 09:43:39 -0400 Subject: [PATCH 21/41] Refactor: use static array of valid types --- OptimizelySDK/AudienceConditions/BaseCondition.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/OptimizelySDK/AudienceConditions/BaseCondition.cs b/OptimizelySDK/AudienceConditions/BaseCondition.cs index 57a24bc9..1930023a 100644 --- a/OptimizelySDK/AudienceConditions/BaseCondition.cs +++ b/OptimizelySDK/AudienceConditions/BaseCondition.cs @@ -42,6 +42,13 @@ public class BaseCondition : ICondition /// private const string QUALIFIED = "qualified"; + /// + /// Valid types allowed for validation + /// + private static readonly string[] validTypes = { + CUSTOM_ATTRIBUTE, THIRD_PARTY_DIMENSION, + }; + [JsonProperty("type")] public string Type { get; set; } @@ -56,7 +63,7 @@ public class BaseCondition : ICondition public bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger) { - if (!IsValidType()) + if (!validTypes.Contains(Type)) { 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; @@ -91,11 +98,6 @@ public class BaseCondition : ICondition return evaluator(attributeValue, logger); } - - private bool IsValidType() - { - return new[] {CUSTOM_ATTRIBUTE, THIRD_PARTY_DIMENSION}.Contains(Type); - } public Func GetEvaluator() { From 2cf6fb41fe7b48401c68947128d00f4d709066bf Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Mon, 25 Jul 2022 09:56:31 -0400 Subject: [PATCH 22/41] Oops use `userContext` instead of `context` --- OptimizelySDK/AudienceConditions/AndCondition.cs | 4 ++-- OptimizelySDK/AudienceConditions/AudienceIdCondition.cs | 4 ++-- OptimizelySDK/AudienceConditions/BaseCondition.cs | 6 +++--- OptimizelySDK/AudienceConditions/EmptyCondition.cs | 2 +- OptimizelySDK/AudienceConditions/ICondition.cs | 2 +- OptimizelySDK/AudienceConditions/NotCondition.cs | 4 ++-- OptimizelySDK/AudienceConditions/OrCondition.cs | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/OptimizelySDK/AudienceConditions/AndCondition.cs b/OptimizelySDK/AudienceConditions/AndCondition.cs index 606d5f6a..3835fc8c 100644 --- a/OptimizelySDK/AudienceConditions/AndCondition.cs +++ b/OptimizelySDK/AudienceConditions/AndCondition.cs @@ -26,7 +26,7 @@ public class AndCondition : ICondition { public ICondition[] Conditions { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, ILogger logger) { // According to the matrix: // false and true is false @@ -38,7 +38,7 @@ public class AndCondition : ICondition var foundNull = false; foreach (var condition in Conditions) { - var result = condition.Evaluate(config, context, logger); + var result = condition.Evaluate(config, userContext, logger); if (result == null) foundNull = true; else if (result == false) diff --git a/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs b/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs index 35c6bb58..06d34f9f 100644 --- a/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs +++ b/OptimizelySDK/AudienceConditions/AudienceIdCondition.cs @@ -25,14 +25,14 @@ public class AudienceIdCondition : ICondition { public string AudienceId { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, ILogger logger) { var audience = config?.GetAudience(AudienceId); if (audience == null || string.IsNullOrEmpty(audience.Id)) return null; logger.Log(LogLevel.DEBUG, $@"Starting to evaluate audience ""{AudienceId}"" with conditions: {audience.ConditionsString}"); - var result = audience.ConditionList.Evaluate(config, context, logger); + var result = audience.ConditionList.Evaluate(config, userContext, logger); var resultText = result?.ToString().ToUpper() ?? "UNKNOWN"; logger.Log(LogLevel.DEBUG, $@"Audience ""{AudienceId}"" evaluated to {resultText}"); diff --git a/OptimizelySDK/AudienceConditions/BaseCondition.cs b/OptimizelySDK/AudienceConditions/BaseCondition.cs index 1930023a..61252217 100644 --- a/OptimizelySDK/AudienceConditions/BaseCondition.cs +++ b/OptimizelySDK/AudienceConditions/BaseCondition.cs @@ -61,7 +61,7 @@ public class BaseCondition : ICondition [JsonProperty("value")] public object Value { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, ILogger logger) { if (!validTypes.Contains(Type)) { @@ -69,7 +69,7 @@ public class BaseCondition : ICondition return null; } - var userAttributes = context.GetAttributes(); + var userAttributes = userContext.GetAttributes(); object attributeValue = null; if (userAttributes.TryGetValue(Name, out attributeValue) == false && Match != AttributeMatchTypes.EXIST) @@ -82,7 +82,7 @@ public class BaseCondition : ICondition { if (Value is string) { - return context.IsQualifiedFor(Value.ToString()); + return userContext.IsQualifiedFor(Value.ToString()); } logger.Log(LogLevel.WARN, $@"Audience condition ""{this}"" has a qualified match but invalid value."); diff --git a/OptimizelySDK/AudienceConditions/EmptyCondition.cs b/OptimizelySDK/AudienceConditions/EmptyCondition.cs index c848601b..5f37c203 100644 --- a/OptimizelySDK/AudienceConditions/EmptyCondition.cs +++ b/OptimizelySDK/AudienceConditions/EmptyCondition.cs @@ -24,7 +24,7 @@ namespace OptimizelySDK.AudienceConditions /// public class EmptyCondition : ICondition { - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, ILogger logger) { return true; } diff --git a/OptimizelySDK/AudienceConditions/ICondition.cs b/OptimizelySDK/AudienceConditions/ICondition.cs index 221c885a..0e3db022 100644 --- a/OptimizelySDK/AudienceConditions/ICondition.cs +++ b/OptimizelySDK/AudienceConditions/ICondition.cs @@ -23,6 +23,6 @@ namespace OptimizelySDK.AudienceConditions /// public interface ICondition { - bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger); + bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, ILogger logger); } } diff --git a/OptimizelySDK/AudienceConditions/NotCondition.cs b/OptimizelySDK/AudienceConditions/NotCondition.cs index 5f307722..028996de 100644 --- a/OptimizelySDK/AudienceConditions/NotCondition.cs +++ b/OptimizelySDK/AudienceConditions/NotCondition.cs @@ -26,9 +26,9 @@ public class NotCondition : ICondition { public ICondition Condition { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, ILogger logger) { - var result = Condition?.Evaluate(config, context, logger); + var result = Condition?.Evaluate(config, userContext, logger); return result == null ? null : !result; } } diff --git a/OptimizelySDK/AudienceConditions/OrCondition.cs b/OptimizelySDK/AudienceConditions/OrCondition.cs index 9e603e02..ad04787c 100644 --- a/OptimizelySDK/AudienceConditions/OrCondition.cs +++ b/OptimizelySDK/AudienceConditions/OrCondition.cs @@ -26,7 +26,7 @@ public class OrCondition : ICondition { public ICondition[] Conditions { get; set; } - public bool? Evaluate(ProjectConfig config, OptimizelyUserContext context, ILogger logger) + public bool? Evaluate(ProjectConfig config, OptimizelyUserContext userContext, ILogger logger) { // According to the matrix: // true returns true @@ -36,7 +36,7 @@ public class OrCondition : ICondition var foundNull = false; foreach (var condition in Conditions) { - var result = condition.Evaluate(config, context, logger); + var result = condition.Evaluate(config, userContext, logger); if (result == null) foundNull = true; else if (result == true) From 2d033ebe265541ab7141f99c934b71b19233f674 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Wed, 27 Jul 2022 09:15:12 -0400 Subject: [PATCH 23/41] Update OptimizelySDK/AudienceConditions/ICondition.cs Co-authored-by: Jae Kim <45045038+jaeopt@users.noreply.github.com> --- OptimizelySDK/AudienceConditions/ICondition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OptimizelySDK/AudienceConditions/ICondition.cs b/OptimizelySDK/AudienceConditions/ICondition.cs index 0e3db022..74cc609f 100644 --- a/OptimizelySDK/AudienceConditions/ICondition.cs +++ b/OptimizelySDK/AudienceConditions/ICondition.cs @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022, 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. From 3ab49f8d458221b4a8ee55f3522745f08393ae24 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Wed, 27 Jul 2022 09:15:21 -0400 Subject: [PATCH 24/41] Update OptimizelySDK/AudienceConditions/EmptyCondition.cs Co-authored-by: Jae Kim <45045038+jaeopt@users.noreply.github.com> --- OptimizelySDK/AudienceConditions/EmptyCondition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OptimizelySDK/AudienceConditions/EmptyCondition.cs b/OptimizelySDK/AudienceConditions/EmptyCondition.cs index 5f37c203..d2ee9d81 100644 --- a/OptimizelySDK/AudienceConditions/EmptyCondition.cs +++ b/OptimizelySDK/AudienceConditions/EmptyCondition.cs @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022, 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. From ee72d487ff72ed9dc88f94be227bb2af0f5d4881 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Wed, 27 Jul 2022 11:52:48 -0400 Subject: [PATCH 25/41] Support Copy() including QualifiedSegments --- OptimizelySDK/OptimizelyUserContext.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OptimizelySDK/OptimizelyUserContext.cs b/OptimizelySDK/OptimizelyUserContext.cs index a17fa992..b0084c9e 100644 --- a/OptimizelySDK/OptimizelyUserContext.cs +++ b/OptimizelySDK/OptimizelyUserContext.cs @@ -50,7 +50,7 @@ public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttribute { } - public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttributes userAttributes, ForcedDecisionsStore forcedDecisionsStore, IErrorHandler errorHandler, ILogger logger) + public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttributes userAttributes, ForcedDecisionsStore forcedDecisionsStore, IErrorHandler errorHandler, ILogger logger, List qualifiedSegments = null) { ErrorHandler = errorHandler; Logger = logger; @@ -58,10 +58,10 @@ public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttribute Attributes = userAttributes ?? new UserAttributes(); ForcedDecisionsStore = forcedDecisionsStore ?? new ForcedDecisionsStore(); UserId = userId; - QualifiedSegments = new List(); + QualifiedSegments = qualifiedSegments ?? new List(); } - private OptimizelyUserContext Copy() => new OptimizelyUserContext(Optimizely, UserId, GetAttributes(), GetForcedDecisionsStore(), ErrorHandler, Logger); + private OptimizelyUserContext Copy() => new OptimizelyUserContext(Optimizely, UserId, GetAttributes(), GetForcedDecisionsStore(), ErrorHandler, Logger, QualifiedSegments); /// /// Returns true if the user is qualified for the given segment name From d91c00b2b7842b4ef22c7c7832ec96553a34803e Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Wed, 27 Jul 2022 11:57:14 -0400 Subject: [PATCH 26/41] Move datafile Integration tests --- .../OptimizelyConfigTest.cs | 27 ------------------- OptimizelySDK.Tests/ProjectConfigTest.cs | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs index 51438bcb..0689e6a2 100644 --- a/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs +++ b/OptimizelySDK.Tests/OptimizelyConfigTests/OptimizelyConfigTest.cs @@ -176,33 +176,6 @@ public void TestGetOptimizelyConfigSDKAndEnvironmentKeyDefault() Assert.AreEqual(optimizelyConfig.EnvironmentKey, ""); } - [Test] - public void TestGetOptimizelyConfigWithOdpIntegration() - { - var datafileProjectConfig = DatafileProjectConfig.Create(TestData.TypedAudienceDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); - - Assert.AreEqual("https://api.zaius.com", datafileProjectConfig.HostForOdp); - Assert.AreEqual("W4WzcEs-ABgXorzY7h1LCQ", datafileProjectConfig.PublicKeyForOdp); - } - - [Test] - public void TestGetOptimizelyConfigWithEmptyIntegrationCollection() - { - var datafileProjectConfig = DatafileProjectConfig.Create(TestData.EmptyDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); - - Assert.IsNull(datafileProjectConfig.HostForOdp); - Assert.IsNull(datafileProjectConfig.PublicKeyForOdp); - } - - [Test] - public void TestGetOptimizelyConfigWithOtherIntegrationsInCollection() - { - var datafileProjectConfig = DatafileProjectConfig.Create(TestData.EmptyRolloutDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); - - Assert.IsNull(datafileProjectConfig.HostForOdp); - Assert.IsNull(datafileProjectConfig.PublicKeyForOdp); - } - [Test] public void TestGetOptimizelyConfigService() { diff --git a/OptimizelySDK.Tests/ProjectConfigTest.cs b/OptimizelySDK.Tests/ProjectConfigTest.cs index 7cf69c54..71d16677 100644 --- a/OptimizelySDK.Tests/ProjectConfigTest.cs +++ b/OptimizelySDK.Tests/ProjectConfigTest.cs @@ -868,5 +868,32 @@ public void TestRolloutWithConsistingOfANullRolloutId() Assert.IsNull(rollout.Experiments); Assert.IsNull(rollout.Id); } + + [Test] + public void TestGetOptimizelyConfigWithOdpIntegration() + { + var datafileProjectConfig = DatafileProjectConfig.Create(TestData.TypedAudienceDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); + + Assert.AreEqual("https://api.zaius.com", datafileProjectConfig.HostForOdp); + Assert.AreEqual("W4WzcEs-ABgXorzY7h1LCQ", datafileProjectConfig.PublicKeyForOdp); + } + + [Test] + public void TestGetOptimizelyConfigWithEmptyIntegrationCollection() + { + var datafileProjectConfig = DatafileProjectConfig.Create(TestData.EmptyDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); + + Assert.IsNull(datafileProjectConfig.HostForOdp); + Assert.IsNull(datafileProjectConfig.PublicKeyForOdp); + } + + [Test] + public void TestGetOptimizelyConfigWithOtherIntegrationsInCollection() + { + var datafileProjectConfig = DatafileProjectConfig.Create(TestData.EmptyRolloutDatafile, new NoOpLogger(), new ErrorHandler.NoOpErrorHandler()); + + Assert.IsNull(datafileProjectConfig.HostForOdp); + Assert.IsNull(datafileProjectConfig.PublicKeyForOdp); + } } } From aeff581d18c0d58d6791c3011e8115550a7ed59e Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Mon, 1 Aug 2022 10:32:53 -0400 Subject: [PATCH 27/41] Integration ToString made generic + tests --- .../EntityTests/IntegrationTest.cs | 81 +++++++++++++++++++ .../OptimizelySDK.Tests.csproj | 1 + OptimizelySDK/Entity/Integration.cs | 19 ++++- 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 OptimizelySDK.Tests/EntityTests/IntegrationTest.cs diff --git a/OptimizelySDK.Tests/EntityTests/IntegrationTest.cs b/OptimizelySDK.Tests/EntityTests/IntegrationTest.cs new file mode 100644 index 00000000..5bb2b6ad --- /dev/null +++ b/OptimizelySDK.Tests/EntityTests/IntegrationTest.cs @@ -0,0 +1,81 @@ +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)); + } + } +} \ No newline at end of file diff --git a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj index b6fc4876..0d9c178a 100644 --- a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj +++ b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj @@ -78,6 +78,7 @@ + diff --git a/OptimizelySDK/Entity/Integration.cs b/OptimizelySDK/Entity/Integration.cs index 5bbde6e4..e056f1f5 100644 --- a/OptimizelySDK/Entity/Integration.cs +++ b/OptimizelySDK/Entity/Integration.cs @@ -14,6 +14,8 @@ * limitations under the License. */ +using System.Text; + namespace OptimizelySDK.Entity { public class Integration : IdKeyEntity @@ -24,7 +26,22 @@ public class Integration : IdKeyEntity public override string ToString() { - return $"Integration{{key='{Key}', host='{Host}', publicKey='{PublicKey}'}}"; + var sb = new StringBuilder(); + sb.AppendFormat("Integration{{key='{0}'", Key); + + if (!string.IsNullOrEmpty(Host)) + { + sb.AppendFormat(", host='{0}'", Host); + } + + if (!string.IsNullOrEmpty(PublicKey)) + { + sb.AppendFormat(", publicKey='{0}'", PublicKey); + } + + sb.Append("}"); + + return sb.ToString(); } } } From f08359ea0a069dc53cb3164558b0e33169a5a043 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Mon, 1 Aug 2022 10:46:06 -0400 Subject: [PATCH 28/41] Minor refactor and doc to userconext --- OptimizelySDK/OptimizelyUserContext.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OptimizelySDK/OptimizelyUserContext.cs b/OptimizelySDK/OptimizelyUserContext.cs index b0084c9e..3c41dd1b 100644 --- a/OptimizelySDK/OptimizelyUserContext.cs +++ b/OptimizelySDK/OptimizelyUserContext.cs @@ -38,6 +38,7 @@ public class OptimizelyUserContext // user attributes for Optimizely user context. private UserAttributes Attributes; + // set of qualified segments private readonly List QualifiedSegments; // Optimizely object to be used. @@ -68,7 +69,8 @@ public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttribute /// /// A String segment key which will be check in qualified segments list that if it exist then user is qualified. /// Is user qualified for a segment. - public bool IsQualifiedFor(string segment) { + public bool IsQualifiedFor(string segment) + { return QualifiedSegments.Contains(segment); } From 92f38f854e9e77f34481f2130c6e7d449001fbc8 Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Mon, 1 Aug 2022 10:55:13 -0400 Subject: [PATCH 29/41] Lock UserContext before determining IsQualifiedFor --- OptimizelySDK/OptimizelyUserContext.cs | 68 +++++++++++++++++--------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/OptimizelySDK/OptimizelyUserContext.cs b/OptimizelySDK/OptimizelyUserContext.cs index 3c41dd1b..4188d873 100644 --- a/OptimizelySDK/OptimizelyUserContext.cs +++ b/OptimizelySDK/OptimizelyUserContext.cs @@ -37,21 +37,24 @@ public class OptimizelyUserContext // user attributes for Optimizely user context. private UserAttributes Attributes; - + // set of qualified segments - private readonly List QualifiedSegments; + public List QualifiedSegments { get; } // Optimizely object to be used. private Optimizely Optimizely; private ForcedDecisionsStore ForcedDecisionsStore { get; set; } - public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttributes userAttributes, IErrorHandler errorHandler, ILogger logger) : - this(optimizely, userId, userAttributes, null, errorHandler, logger) - { - } + public OptimizelyUserContext(Optimizely optimizely, string userId, + UserAttributes userAttributes, IErrorHandler errorHandler, ILogger logger + ) : + this(optimizely, userId, userAttributes, null, errorHandler, logger) { } - public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttributes userAttributes, ForcedDecisionsStore forcedDecisionsStore, IErrorHandler errorHandler, ILogger logger, List qualifiedSegments = null) + public OptimizelyUserContext(Optimizely optimizely, string userId, + UserAttributes userAttributes, ForcedDecisionsStore forcedDecisionsStore, + IErrorHandler errorHandler, ILogger logger, List qualifiedSegments = null + ) { ErrorHandler = errorHandler; Logger = logger; @@ -62,16 +65,21 @@ public OptimizelyUserContext(Optimizely optimizely, string userId, UserAttribute QualifiedSegments = qualifiedSegments ?? new List(); } - private OptimizelyUserContext Copy() => new OptimizelyUserContext(Optimizely, UserId, GetAttributes(), GetForcedDecisionsStore(), ErrorHandler, Logger, QualifiedSegments); - + private OptimizelyUserContext Copy() => + new OptimizelyUserContext(Optimizely, UserId, GetAttributes(), + GetForcedDecisionsStore(), ErrorHandler, Logger, QualifiedSegments); + /// /// Returns true if the user is qualified for the given segment name /// /// A String segment key which will be check in qualified segments list that if it exist then user is qualified. /// Is user qualified for a segment. - public bool IsQualifiedFor(string segment) - { - return QualifiedSegments.Contains(segment); + public bool IsQualifiedFor(string segment) + { + lock (mutex) + { + return QualifiedSegments.Contains(segment); + } } /// @@ -119,7 +127,8 @@ public ForcedDecisionsStore GetForcedDecisionsStore() if (ForcedDecisionsStore.Count == 0) { copiedForcedDecisionsStore = ForcedDecisionsStore.NullForcedDecision(); - } else + } + else { copiedForcedDecisionsStore = new ForcedDecisionsStore(ForcedDecisionsStore); } @@ -158,7 +167,8 @@ public void SetAttribute(string key, object value) /// A decision result. public virtual OptimizelyDecision Decide(string key) { - return Decide(key, new OptimizelyDecideOption[] { }); + return Decide(key, new OptimizelyDecideOption[] + { }); } /// @@ -171,7 +181,8 @@ public virtual OptimizelyDecision Decide(string key) /// A list of options for decision-making. /// A decision result. public virtual OptimizelyDecision Decide(string key, - OptimizelyDecideOption[] options) + OptimizelyDecideOption[] options + ) { var optimizelyUserContext = Copy(); return Optimizely.Decide(optimizelyUserContext, key, options); @@ -182,7 +193,9 @@ public virtual OptimizelyDecision Decide(string key, /// /// list of flag keys for which a decision will be made. /// A dictionary of all decision results, mapped by flag keys. - public virtual Dictionary DecideForKeys(string[] keys, OptimizelyDecideOption[] options) + public virtual Dictionary DecideForKeys(string[] keys, + OptimizelyDecideOption[] options + ) { var optimizelyUserContext = Copy(); return Optimizely.DecideForKeys(optimizelyUserContext, keys, options); @@ -195,7 +208,8 @@ public virtual Dictionary DecideForKeys(string[] key /// A dictionary of all decision results, mapped by flag keys. public virtual Dictionary DecideForKeys(string[] keys) { - return DecideForKeys(keys, new OptimizelyDecideOption[] { }); + return DecideForKeys(keys, new OptimizelyDecideOption[] + { }); } /// @@ -204,7 +218,8 @@ public virtual Dictionary DecideForKeys(string[] key /// A dictionary of all decision results, mapped by flag keys. public virtual Dictionary DecideAll() { - return DecideAll(new OptimizelyDecideOption[] { }); + return DecideAll(new OptimizelyDecideOption[] + { }); } /// @@ -212,7 +227,9 @@ public virtual Dictionary DecideAll() /// /// A list of options for decision-making. /// All decision results mapped by flag keys. - public virtual Dictionary DecideAll(OptimizelyDecideOption[] options) + public virtual Dictionary DecideAll( + OptimizelyDecideOption[] options + ) { var optimizelyUserContext = Copy(); return Optimizely.DecideAll(optimizelyUserContext, options); @@ -233,7 +250,8 @@ public virtual void TrackEvent(string eventName) /// The event name. /// A map of event tag names to event tag values. public virtual void TrackEvent(string eventName, - EventTags eventTags) + EventTags eventTags + ) { Optimizely.Track(eventName, UserId, Attributes, eventTags); } @@ -244,7 +262,9 @@ public virtual void TrackEvent(string eventName, /// The context object containing flag and rule key. /// OptimizelyForcedDecision object containing variation key. /// - public bool SetForcedDecision(OptimizelyDecisionContext context, OptimizelyForcedDecision decision) + public bool SetForcedDecision(OptimizelyDecisionContext context, + OptimizelyForcedDecision decision + ) { lock (mutex) { @@ -278,6 +298,7 @@ public OptimizelyForcedDecision GetForcedDecision(OptimizelyDecisionContext cont { decision = ForcedDecisionsStore[context]; } + return decision; } @@ -293,7 +314,7 @@ public bool RemoveForcedDecision(OptimizelyDecisionContext context) Logger.Log(LogLevel.WARN, "FlagKey cannot be null"); return false; } - + lock (mutex) { return ForcedDecisionsStore.Remove(context); @@ -310,7 +331,8 @@ public bool RemoveAllForcedDecisions() { ForcedDecisionsStore.RemoveAll(); } + return true; } } -} +} \ No newline at end of file From 845cc30606d6ef6454fd56471a22767ea45bae2c Mon Sep 17 00:00:00 2001 From: Mike Chu Date: Mon, 1 Aug 2022 11:19:03 -0400 Subject: [PATCH 30/41] Separate integration test datafile content --- OptimizelySDK.Tests/EmptyRolloutRule.json | 7 - .../IntegrationEmptyDatafile.json | 18 + .../IntegrationNonOdpDatafile.json | 65 ++++ .../IntegrationOdpDatafile.json | 318 ++++++++++++++++++ .../OptimizelySDK.Tests.csproj | 5 +- OptimizelySDK.Tests/ProjectConfigTest.cs | 6 +- OptimizelySDK.Tests/Utils/TestData.cs | 22 ++ OptimizelySDK.Tests/emptydatafile.json | 1 - .../typed_audience_datafile.json | 7 - 9 files changed, 429 insertions(+), 20 deletions(-) create mode 100644 OptimizelySDK.Tests/IntegrationEmptyDatafile.json create mode 100644 OptimizelySDK.Tests/IntegrationNonOdpDatafile.json create mode 100644 OptimizelySDK.Tests/IntegrationOdpDatafile.json diff --git a/OptimizelySDK.Tests/EmptyRolloutRule.json b/OptimizelySDK.Tests/EmptyRolloutRule.json index cfdb0819..8aa8e559 100644 --- a/OptimizelySDK.Tests/EmptyRolloutRule.json +++ b/OptimizelySDK.Tests/EmptyRolloutRule.json @@ -50,13 +50,6 @@ } ], "groups": [], - "integrations": [ - { - "key": "not-odp", - "host": "https://example.com", - "publicKey": "CleAr1y-!a-R3a7-pUbl1c-k3y" - } - ], "attributes": [], "botFiltering": false, "accountId": "8272261422", 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/OptimizelySDK.Tests.csproj b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj index 0d9c178a..d88b00d0 100644 --- a/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj +++ b/OptimizelySDK.Tests/OptimizelySDK.Tests.csproj @@ -125,6 +125,9 @@ + + + @@ -138,7 +141,6 @@ OptimizelySDK - @@ -148,7 +150,6 @@ -