diff --git a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj
index 3b2962e05..66204977c 100644
--- a/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj
+++ b/OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj
@@ -10,6 +10,84 @@
IOptimizely.cs
+
+ Notifications\NotificationCenterRegistry.cs
+
+
+ Odp\Constants.cs
+
+
+ Odp\Entity\Audience.cs
+
+
+ Odp\Entity\Customer.cs
+
+
+ Odp\Entity\Data.cs
+
+
+ Odp\Entity\Edge.cs
+
+
+ Odp\Entity\Error.cs
+
+
+ Odp\Entity\Extension.cs
+
+
+ Odp\Entity\Location.cs
+
+
+ Odp\Entity\Node.cs
+
+
+ Odp\Entity\OdpEvent.cs
+
+
+ Odp\Entity\Response.cs
+
+
+ Odp\Enums.cs
+
+
+ Odp\ICache.cs
+
+
+ Odp\IOdpEventApiManager.cs
+
+
+ Odp\IOdpEventManager.cs
+
+
+ Odp\IOdpManager.cs
+
+
+ Odp\IOdpSegmentApiManager.cs
+
+
+ Odp\IOdpSegmentManager.cs
+
+
+ Odp\LruCache.cs
+
+
+ Odp\OdpConfig.cs
+
+
+ Odp\OdpEventApiManager.cs
+
+
+ Odp\OdpEventManager.cs
+
+
+ Odp\OdpManager.cs
+
+
+ Odp\OdpSegmentApiManager.cs
+
+
+ Odp\OdpSegmentManager.cs
+
Optimizely.cs
@@ -223,6 +301,9 @@
Utils\AttributeMatchTypes.cs
+
+ Utils\CollectionExtensions.cs
+
Utils\ConditionParser.cs
diff --git a/OptimizelySDK.Tests/AudienceConditionsTests/SegmentsTests.cs b/OptimizelySDK.Tests/AudienceConditionsTests/SegmentsTests.cs
new file mode 100644
index 000000000..42eccab23
--- /dev/null
+++ b/OptimizelySDK.Tests/AudienceConditionsTests/SegmentsTests.cs
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2022-2023 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 NUnit.Framework;
+using OptimizelySDK.AudienceConditions;
+using OptimizelySDK.Entity;
+using OptimizelySDK.Logger;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace OptimizelySDK.Tests.AudienceConditionsTests
+{
+ [TestFixture]
+ public class SegmentsTests
+ {
+ private BaseCondition _firstThirdPartyOdpQualifiedMatchCondition;
+ private BaseCondition _secondThirdPartyOdpQualifiedMatchCondition;
+ private ICondition _customExactMatchCondition;
+ private Mock _mockLogger;
+
+ private const string FIRST_CONDITION_VALUE = "first_condition_value";
+ private const string SECOND_CONDITION_VALUE = "second_condition_value";
+
+ [TestFixtureSetUp]
+ public void Setup()
+ {
+ _firstThirdPartyOdpQualifiedMatchCondition = new BaseCondition
+ {
+ Value = FIRST_CONDITION_VALUE,
+ Type = "third_party_dimension",
+ Name = "odp.audiences",
+ Match = "qualified",
+ };
+
+ _secondThirdPartyOdpQualifiedMatchCondition = new BaseCondition
+ {
+ Value = SECOND_CONDITION_VALUE,
+ Type = "third_party_dimension",
+ Name = "odp.audiences",
+ Match = "qualified",
+ };
+
+ _customExactMatchCondition = new BaseCondition()
+ {
+ Value = "test_custom_value",
+ Type = "custom_attribute",
+ Name = "test_custom_name",
+ Match = "exact",
+ };
+
+ _mockLogger = new Mock();
+ _mockLogger.Setup(l => l.Log(It.IsAny(), It.IsAny()));
+ }
+
+ [Test]
+ public void ShouldGetSegmentsFromDatafileTypedAudiences()
+ {
+ var expectedSegments = new SortedSet
+ {
+ "ats_bug_bash_segment_gender",
+ "ats_bug_bash_segment_has_purchased",
+ "has_email_opted_out",
+ "ats_bug_bash_segment_dob",
+ };
+ var optimizelyClient = new Optimizely(TestData.OdpSegmentsDatafile,
+ new ValidEventDispatcher(), _mockLogger.Object);
+
+ var allSegments = optimizelyClient.ProjectConfigManager.GetConfig().Segments;
+
+ var orderedDistinctSegments = new SortedSet(allSegments);
+ // check for no duplicates
+ Assert.AreEqual(allSegments.Length, orderedDistinctSegments.Count);
+ Assert.AreEqual(expectedSegments, orderedDistinctSegments);
+ }
+
+ [Test]
+ public void ShouldFindOdpSegmentFromAndCondition()
+ {
+ var conditions = new AndCondition
+ {
+ Conditions = new[]
+ {
+ _firstThirdPartyOdpQualifiedMatchCondition, _customExactMatchCondition,
+ },
+ };
+
+ var allSegments = Audience.GetSegments(conditions);
+
+ Assert.AreEqual(_firstThirdPartyOdpQualifiedMatchCondition.Value.ToString(),
+ allSegments.FirstOrDefault());
+ }
+
+ [Test]
+ public void ShouldFindOdpSegmentFromOrCondition()
+ {
+ var conditions = new OrCondition
+ {
+ Conditions = new[]
+ {
+ _customExactMatchCondition, _firstThirdPartyOdpQualifiedMatchCondition,
+ },
+ };
+
+ var allSegments = Audience.GetSegments(conditions);
+
+ Assert.AreEqual(_firstThirdPartyOdpQualifiedMatchCondition.Value.ToString(),
+ allSegments.FirstOrDefault());
+ }
+
+ [Test]
+ public void ShouldNotFindOdpSegmentsFromConditions()
+ {
+ var conditions = new AndCondition
+ {
+ Conditions = new[]
+ {
+ _customExactMatchCondition, _customExactMatchCondition, _customExactMatchCondition,
+ },
+ };
+
+ var allSegments = Audience.GetSegments(conditions);
+
+ Assert.IsEmpty(allSegments);
+ }
+
+ [Test]
+ public void ShouldFindAndDedupeNestedOdpSegments()
+ {
+ var qualifiedAndExact = new AndCondition
+ {
+ Conditions = new ICondition[]
+ {
+ _firstThirdPartyOdpQualifiedMatchCondition, _customExactMatchCondition,
+ },
+ };
+ var twoQualified = new AndCondition
+ {
+ Conditions = new ICondition[]
+ {
+ _secondThirdPartyOdpQualifiedMatchCondition, _firstThirdPartyOdpQualifiedMatchCondition,
+ },
+ };
+ var orConditions = new OrCondition
+ {
+ Conditions = new ICondition[]
+ {
+ qualifiedAndExact, twoQualified,
+ },
+ };
+ var notCondition = new NotCondition
+ {
+ Condition = orConditions,
+ };
+
+ var allSegments = Audience.GetSegments(notCondition).ToList();
+
+ Assert.AreEqual(2, allSegments.Count);
+ Assert.Contains(FIRST_CONDITION_VALUE, allSegments);
+ Assert.Contains(SECOND_CONDITION_VALUE, allSegments);
+ }
+ }
+}
diff --git a/OptimizelySDK.Tests/ConfigTest/PollingProjectConfigManagerTest.cs b/OptimizelySDK.Tests/ConfigTest/PollingProjectConfigManagerTest.cs
index e7877ca80..96603d6b8 100644
--- a/OptimizelySDK.Tests/ConfigTest/PollingProjectConfigManagerTest.cs
+++ b/OptimizelySDK.Tests/ConfigTest/PollingProjectConfigManagerTest.cs
@@ -1,11 +1,11 @@
/*
- * Copyright 2019, Optimizely
+ * Copyright 2019, 2023 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,
@@ -22,7 +22,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Threading;
namespace OptimizelySDK.Tests.DatafileManagement_Tests
{
@@ -34,17 +33,20 @@ public class PollingProjectConfigManagerTest
[SetUp]
public void Setup()
- {
+ {
LoggerMock = new Mock();
LoggerMock.Setup(l => l.Log(It.IsAny(), It.IsAny()));
- ProjectConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, null);
+ ProjectConfig =
+ DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, null);
}
-
+
[Test]
public void TestPollingConfigManagerDoesNotBlockWhenProjectConfigIsAlreadyProvided()
{
var stopwatch = new Stopwatch();
- var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3), true, LoggerMock.Object, new int[] { });
+ var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(2),
+ TimeSpan.FromSeconds(3), true, LoggerMock.Object, new int[]
+ { });
configManager.SetConfig(ProjectConfig);
stopwatch.Start();
@@ -59,14 +61,19 @@ public void TestPollingConfigManagerDoesNotBlockWhenProjectConfigIsAlreadyProvid
[Test]
public void TestPollingConfigManagerBlocksWhenProjectConfigIsNotProvided()
{
+ const int POLL_EVERY_MILLISECONDS = 500;
var stopwatch = new Stopwatch();
- var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2), true, LoggerMock.Object, new int[] {500 });
+ var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(2),
+ TimeSpan.FromSeconds(2), true, LoggerMock.Object, new[]
+ {
+ POLL_EVERY_MILLISECONDS,
+ });
stopwatch.Start();
- var config = configManager.GetConfig();
+ configManager.GetConfig();
stopwatch.Stop();
- Assert.True(stopwatch.Elapsed.TotalMilliseconds >= 500);
+ Assert.GreaterOrEqual(stopwatch.Elapsed.TotalMilliseconds, POLL_EVERY_MILLISECONDS);
configManager.Dispose();
}
@@ -75,7 +82,11 @@ public void TestImmediatelyCalledScheduledRequestIfPreviousRequestDelayedInRespo
{
// period to call is one second
// Giving response in 1200 milliseconds
- var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(1500), true, LoggerMock.Object, new int[] { 1200, 500, 500 });
+ var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(1),
+ TimeSpan.FromMilliseconds(1500), true, LoggerMock.Object, new int[]
+ {
+ 1200, 500, 500
+ });
configManager.Start();
System.Threading.Tasks.Task.Delay(50).Wait();
@@ -89,7 +100,6 @@ public void TestImmediatelyCalledScheduledRequestIfPreviousRequestDelayedInRespo
//Thread.Sleep(200);
Assert.AreEqual(2, configManager.Counter);
configManager.Dispose();
-
}
[Test]
@@ -97,11 +107,16 @@ public void TestTimedoutIfTakingMorethanBlockingTimeout()
{
// period to call is one second
// Giving response in 1200 milliseconds
- var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[] { 1300, 500, 500 });
+ var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3),
+ TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[]
+ {
+ 1300, 500, 500
+ });
configManager.Start();
var config = configManager.GetConfig();
- LoggerMock.Verify(l => l.Log(LogLevel.WARN, "Timeout exceeded waiting for ProjectConfig to be set, returning null."));
+ LoggerMock.Verify(l => l.Log(LogLevel.WARN,
+ "Timeout exceeded waiting for ProjectConfig to be set, returning null."));
configManager.Dispose();
}
@@ -110,7 +125,11 @@ public void TestTimedoutOnlyIfSchedulerStarted()
{
// period to call is 3 second
// Giving response in 1200 milliseconds and timedout should be in 1000 miliseconds
- var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[] { 1300, 500, 500 });
+ var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3),
+ TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[]
+ {
+ 1300, 500, 500
+ });
Stopwatch sw = new Stopwatch();
sw.Start();
var config = configManager.GetConfig();
@@ -124,7 +143,11 @@ public void TestDontTimedoutIfSchedulerNotStarted()
{
// period to call is 3 second
// Giving response in 1200 milliseconds and timedout should be in 1000 miliseconds
- var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[] { 1300, 500, 500 });
+ var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3),
+ TimeSpan.FromMilliseconds(1000), true, LoggerMock.Object, new int[]
+ {
+ 1300, 500, 500
+ });
Stopwatch sw = new Stopwatch();
sw.Start();
var config = configManager.GetConfig();
@@ -136,13 +159,26 @@ public void TestDontTimedoutIfSchedulerNotStarted()
[Test]
public void TestReturnDatafileImmediatelyOnceGetValidDatafileRemotely()
{
- var projConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, null);
- var data = new List() {
- new TestPollingData { PollingTime = 500, ChangeVersion = false, ConfigDatafile = projConfig},
- new TestPollingData { PollingTime = 500, ChangeVersion = false, ConfigDatafile = projConfig}
+ var projConfig =
+ DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, null);
+ var data = new List()
+ {
+ new TestPollingData
+ {
+ PollingTime = 500,
+ ChangeVersion = false,
+ ConfigDatafile = projConfig
+ },
+ new TestPollingData
+ {
+ PollingTime = 500,
+ ChangeVersion = false,
+ ConfigDatafile = projConfig
+ }
};
- var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(5000), true, LoggerMock.Object, data.ToArray());
+ var configManager = new TestPollingProjectConfigManager(TimeSpan.FromSeconds(3),
+ TimeSpan.FromMilliseconds(5000), true, LoggerMock.Object, data.ToArray());
var config = configManager.GetConfig();
Assert.NotNull(config);
@@ -160,15 +196,33 @@ public void TestWaitUntilValidDatfileIsNotGiven()
// then send the right datafile
// see it should release blocking.
// blocking timeout must be inifinity.
- var projConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, null);
- var data = new List() {
- new TestPollingData { PollingTime = 50, ChangeVersion = false, ConfigDatafile = null},
- new TestPollingData { PollingTime = 50, ChangeVersion = false, ConfigDatafile = null},
- new TestPollingData { PollingTime = 50, ChangeVersion = false, ConfigDatafile = projConfig}
+ var projConfig =
+ DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, null);
+ var data = new List()
+ {
+ new TestPollingData
+ {
+ PollingTime = 50,
+ ChangeVersion = false,
+ ConfigDatafile = null
+ },
+ new TestPollingData
+ {
+ PollingTime = 50,
+ ChangeVersion = false,
+ ConfigDatafile = null
+ },
+ new TestPollingData
+ {
+ PollingTime = 50,
+ ChangeVersion = false,
+ ConfigDatafile = projConfig
+ }
};
- var configManager = new TestPollingProjectConfigManager(TimeSpan.FromMilliseconds(500), TimeSpan.FromMilliseconds(10000), true, LoggerMock.Object, data.ToArray());
+ var configManager = new TestPollingProjectConfigManager(TimeSpan.FromMilliseconds(500),
+ TimeSpan.FromMilliseconds(10000), true, LoggerMock.Object, data.ToArray());
configManager.Start();
// after 3rd attempt should get
var config = configManager.GetConfig();
@@ -181,13 +235,30 @@ public void TestWaitUntilValidDatfileIsNotGiven()
[Test]
public void TestWaitUntilValidDatafileIsNotGivenOrTimedout()
{
- var data = new List() {
- new TestPollingData { PollingTime = 50, ChangeVersion = false, ConfigDatafile = null},
- new TestPollingData { PollingTime = 50, ChangeVersion = false, ConfigDatafile = null},
- new TestPollingData { PollingTime = 50, ChangeVersion = false, ConfigDatafile = null}
+ var data = new List()
+ {
+ new TestPollingData
+ {
+ PollingTime = 50,
+ ChangeVersion = false,
+ ConfigDatafile = null
+ },
+ new TestPollingData
+ {
+ PollingTime = 50,
+ ChangeVersion = false,
+ ConfigDatafile = null
+ },
+ new TestPollingData
+ {
+ PollingTime = 50,
+ ChangeVersion = false,
+ ConfigDatafile = null
+ }
};
- var configManager = new TestPollingProjectConfigManager(TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(2500), true, LoggerMock.Object, data.ToArray());
+ var configManager = new TestPollingProjectConfigManager(TimeSpan.FromMilliseconds(1000),
+ TimeSpan.FromMilliseconds(2500), true, LoggerMock.Object, data.ToArray());
configManager.Start();
// after 3rd attempt should be released with null.
var config = configManager.GetConfig();
diff --git a/OptimizelySDK.Tests/IntegrationOdpDatafile.json b/OptimizelySDK.Tests/IntegrationOdpDatafile.json
index 219718e0d..d5c94dc0c 100644
--- a/OptimizelySDK.Tests/IntegrationOdpDatafile.json
+++ b/OptimizelySDK.Tests/IntegrationOdpDatafile.json
@@ -273,7 +273,7 @@
{
"key": "odp",
"host": "https://api.zaius.com",
- "publicKey": "W4WzcEs-ABgXorzY7h1LCQ"
+ "publicKey": "fake-public-key"
}
],
"attributes": [{
diff --git a/OptimizelySDK.Tests/IntegrationOdpWithOtherFieldsDatafile.json b/OptimizelySDK.Tests/IntegrationOdpWithOtherFieldsDatafile.json
index 4f6dd8ea6..3fea39e72 100644
--- a/OptimizelySDK.Tests/IntegrationOdpWithOtherFieldsDatafile.json
+++ b/OptimizelySDK.Tests/IntegrationOdpWithOtherFieldsDatafile.json
@@ -273,7 +273,7 @@
{
"key": "odp",
"host": "https://api.zaius.com",
- "publicKey": "W4WzcEs-ABgXorzY7h1LCQ",
+ "publicKey": "fake-public-key",
"k1": 10,
"k2": true,
"k3": "any-value"
diff --git a/OptimizelySDK.Tests/OdpSegmentsDatafile.json b/OptimizelySDK.Tests/OdpSegmentsDatafile.json
new file mode 100644
index 000000000..074ecb48f
--- /dev/null
+++ b/OptimizelySDK.Tests/OdpSegmentsDatafile.json
@@ -0,0 +1,327 @@
+{
+ "groups": [
+ ],
+ "environmentKey": "production",
+ "rollouts": [
+ {
+ "experiments": [
+ {
+ "status": "Running",
+ "audienceConditions": [
+ ],
+ "audienceIds": [
+ ],
+ "variations": [
+ {
+ "variables": [
+ ],
+ "id": "109682",
+ "key": "off",
+ "featureEnabled": false
+ }
+ ],
+ "forcedVariations": {
+ },
+ "key": "default-rollout-36645-22702840437",
+ "layerId": "rollout-36645-22702840437",
+ "trafficAllocation": [
+ {
+ "entityId": "109682",
+ "endOfRange": 10000
+ }
+ ],
+ "id": "default-rollout-36645-22702840437"
+ }
+ ],
+ "id": "rollout-36645-22702840437"
+ }
+ ],
+ "typedAudiences": [
+ {
+ "id": "22727912729",
+ "conditions": [
+ "and",
+ [
+ "or",
+ [
+ "or",
+ {
+ "value": "ats_bug_bash_segment_gender",
+ "type": "third_party_dimension",
+ "name": "odp.audiences",
+ "match": "qualified"
+ }
+ ]
+ ]
+ ],
+ "name": "ODP Gender Audience"
+ },
+ {
+ "id": "22642853106",
+ "conditions": [
+ "and",
+ [
+ "or",
+ [
+ "or",
+ {
+ "value": "has_email_opted_out",
+ "type": "third_party_dimension",
+ "name": "odp.audiences",
+ "match": "qualified"
+ }
+ ]
+ ]
+ ],
+ "name": "Email Opt-Out Audience"
+ },
+ {
+ "id": "22722060443",
+ "conditions": [
+ "and",
+ [
+ "or",
+ [
+ "or",
+ {
+ "value": "kwanzaa",
+ "type": "custom_attribute",
+ "name": "current_holiday",
+ "match": "exact"
+ }
+ ],
+ [
+ "or",
+ {
+ "value": "ats_bug_bash_segment_has_purchased",
+ "type": "third_party_dimension",
+ "name": "odp.audiences",
+ "match": "qualified"
+ }
+ ],
+ [
+ "or",
+ {
+ "value": "ats_bug_bash_segment_gender",
+ "type": "third_party_dimension",
+ "name": "odp.audiences",
+ "match": "qualified"
+ }
+ ]
+ ]
+ ],
+ "name": "Current Holiday Purchases"
+ },
+ {
+ "id": "22713562125",
+ "conditions": [
+ "and",
+ [
+ "or",
+ [
+ "or",
+ {
+ "value": "ats_bug_bash_segment_dob",
+ "type": "third_party_dimension",
+ "name": "odp.audiences",
+ "match": "qualified"
+ }
+ ]
+ ]
+ ],
+ "name": "ODP Birthdate Audience"
+ },
+ {
+ "id": "22734824597",
+ "conditions": [
+ "and",
+ [
+ "or",
+ [
+ "not",
+ [
+ "or",
+ {
+ "value": true,
+ "type": "custom_attribute",
+ "name": "likes_presents",
+ "match": "exact"
+ }
+ ]
+ ]
+ ]
+ ],
+ "name": "Hates Presents"
+ }
+ ],
+ "projectId": "22720961022",
+ "variables": [
+ ],
+ "featureFlags": [
+ {
+ "experimentIds": [
+ "9300000149777"
+ ],
+ "rolloutId": "rollout-36645-22702840437",
+ "variables": [
+ ],
+ "id": "36645",
+ "key": "favorite_holiday"
+ }
+ ],
+ "integrations": [
+ {
+ "publicKey": "ax6Bz223fD-jpOo9u0BMg",
+ "host": "https://example.com",
+ "key": "odp"
+ }
+ ],
+ "experiments": [
+ {
+ "status": "Running",
+ "audienceConditions": [
+ "or",
+ "22722060443",
+ "22713562125",
+ "22727912729",
+ "22642853106",
+ "22734824597"
+ ],
+ "audienceIds": [
+ "22722060443",
+ "22713562125",
+ "22727912729",
+ "22642853106",
+ "22734824597"
+ ],
+ "variations": [
+ {
+ "variables": [
+ ],
+ "id": "109684",
+ "key": "christmas",
+ "featureEnabled": true
+ },
+ {
+ "variables": [
+ ],
+ "id": "109685",
+ "key": "hanukkah",
+ "featureEnabled": true
+ },
+ {
+ "variables": [
+ ],
+ "id": "109686",
+ "key": "kwanzaa",
+ "featureEnabled": true
+ }
+ ],
+ "forcedVariations": {
+ },
+ "key": "holiday_banner",
+ "layerId": "9300000114602",
+ "trafficAllocation": [
+ {
+ "entityId": "109684",
+ "endOfRange": 1966
+ },
+ {
+ "entityId": "",
+ "endOfRange": 2400
+ },
+ {
+ "entityId": "109685",
+ "endOfRange": 4366
+ },
+ {
+ "entityId": "",
+ "endOfRange": 4800
+ },
+ {
+ "entityId": "109686",
+ "endOfRange": 6767
+ },
+ {
+ "entityId": "",
+ "endOfRange": 7200
+ },
+ {
+ "entityId": "109684",
+ "endOfRange": 7500
+ },
+ {
+ "entityId": "109685",
+ "endOfRange": 7800
+ },
+ {
+ "entityId": "109686",
+ "endOfRange": 8100
+ },
+ {
+ "entityId": "109684",
+ "endOfRange": 8234
+ },
+ {
+ "entityId": "109685",
+ "endOfRange": 8368
+ },
+ {
+ "entityId": "109686",
+ "endOfRange": 8501
+ }
+ ],
+ "id": "9300000149777"
+ }
+ ],
+ "version": "4",
+ "audiences": [
+ {
+ "id": "22727912729",
+ "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]",
+ "name": "ODP Gender Audience"
+ },
+ {
+ "id": "22642853106",
+ "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]",
+ "name": "Email Opt-Out Audience"
+ },
+ {
+ "id": "22722060443",
+ "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]",
+ "name": "Current Holiday Purchases"
+ },
+ {
+ "id": "22713562125",
+ "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]",
+ "name": "ODP Birthdate Audience"
+ },
+ {
+ "id": "22734824597",
+ "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]",
+ "name": "Hates Presents"
+ },
+ {
+ "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"
+ }
+ ],
+ "anonymizeIP": true,
+ "sdkKey": "VZEZsi7xS2Tv3nESLvFui",
+ "attributes": [
+ {
+ "id": "22696496486",
+ "key": "likes_presents"
+ },
+ {
+ "id": "22700670748",
+ "key": "current_holiday"
+ }
+ ],
+ "botFiltering": false,
+ "accountId": "21468570738",
+ "events": [
+ ],
+ "revision": "29"
+}
diff --git a/OptimizelySDK.Tests/OdpTests/LruCacheTest.cs b/OptimizelySDK.Tests/OdpTests/LruCacheTest.cs
index dd5183ab1..27f8ee169 100644
--- a/OptimizelySDK.Tests/OdpTests/LruCacheTest.cs
+++ b/OptimizelySDK.Tests/OdpTests/LruCacheTest.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2022, Optimizely
+ * Copyright 2022-2023, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -67,7 +67,7 @@ public void ShouldSaveAndLookupMultipleItems()
cache.Save("user2", _segments3And4);
cache.Save("user3", _segments5And6);
- var cacheKeys = cache._readCurrentCacheKeys();
+ var cacheKeys = cache.CurrentCacheKeysForTesting();
Assert.AreEqual("user3", cacheKeys[0]);
Assert.AreEqual("user2", cacheKeys[1]);
@@ -76,7 +76,7 @@ public void ShouldSaveAndLookupMultipleItems()
// Lookup should move user1 to top of the list and push down others.
Assert.AreEqual(_segments1And2, cache.Lookup("user1"));
- cacheKeys = cache._readCurrentCacheKeys();
+ cacheKeys = cache.CurrentCacheKeysForTesting();
Assert.AreEqual("user1", cacheKeys[0]);
Assert.AreEqual("user3", cacheKeys[1]);
Assert.AreEqual("user2", cacheKeys[2]);
@@ -84,7 +84,7 @@ public void ShouldSaveAndLookupMultipleItems()
// Lookup should move user2 to the beginning of the list and push others.
Assert.AreEqual(_segments3And4, cache.Lookup("user2"));
- cacheKeys = cache._readCurrentCacheKeys();
+ cacheKeys = cache.CurrentCacheKeysForTesting();
Assert.AreEqual("user2", cacheKeys[0]);
Assert.AreEqual("user1", cacheKeys[1]);
Assert.AreEqual("user3", cacheKeys[2]);
@@ -92,7 +92,7 @@ public void ShouldSaveAndLookupMultipleItems()
// Lookup moves user3 to top and pushes others down.
Assert.AreEqual(_segments5And6, cache.Lookup("user3"));
- cacheKeys = cache._readCurrentCacheKeys();
+ cacheKeys = cache.CurrentCacheKeysForTesting();
Assert.AreEqual("user3", cacheKeys[0]);
Assert.AreEqual("user2", cacheKeys[1]);
Assert.AreEqual("user1", cacheKeys[2]);
@@ -107,36 +107,36 @@ public void ShouldReorderListOnSave()
cache.Save("user2", _segments3And4);
cache.Save("user3", _segments5And6);
- var cacheKeys = cache._readCurrentCacheKeys();
-
+ var cacheKeys = cache.CurrentCacheKeysForTesting();
+
// last added should be at the top of the list
- Assert.AreEqual("user3",cacheKeys[0]);
- Assert.AreEqual("user2",cacheKeys[1]);
- Assert.AreEqual("user1" ,cacheKeys[2]);
+ Assert.AreEqual("user3", cacheKeys[0]);
+ Assert.AreEqual("user2", cacheKeys[1]);
+ Assert.AreEqual("user1", cacheKeys[2]);
// save should move user1 to the top of the list and push down others.
cache.Save("user1", _segments1And2);
-
- cacheKeys = cache._readCurrentCacheKeys();
- Assert.AreEqual("user1",cacheKeys[0]);
- Assert.AreEqual("user3",cacheKeys[1]);
- Assert.AreEqual("user2",cacheKeys[2]);
+
+ cacheKeys = cache.CurrentCacheKeysForTesting();
+ Assert.AreEqual("user1", cacheKeys[0]);
+ Assert.AreEqual("user3", cacheKeys[1]);
+ Assert.AreEqual("user2", cacheKeys[2]);
// save user2 should bring it to the top and push down others.
cache.Save("user2", _segments3And4);
-
- cacheKeys = cache._readCurrentCacheKeys();
- Assert.AreEqual("user2",cacheKeys[0]);
- Assert.AreEqual("user1",cacheKeys[1]);
- Assert.AreEqual("user3",cacheKeys[2]);
+
+ cacheKeys = cache.CurrentCacheKeysForTesting();
+ Assert.AreEqual("user2", cacheKeys[0]);
+ Assert.AreEqual("user1", cacheKeys[1]);
+ Assert.AreEqual("user3", cacheKeys[2]);
// saving user3 again should return to the original insertion order.
cache.Save("user3", _segments5And6);
-
- cacheKeys = cache._readCurrentCacheKeys();
- Assert.AreEqual("user3",cacheKeys[0]);
- Assert.AreEqual("user2",cacheKeys[1]);
- Assert.AreEqual("user1" ,cacheKeys[2]);
+
+ cacheKeys = cache.CurrentCacheKeysForTesting();
+ Assert.AreEqual("user3", cacheKeys[0]);
+ Assert.AreEqual("user2", cacheKeys[1]);
+ Assert.AreEqual("user1", cacheKeys[2]);
}
[Test]
@@ -161,12 +161,12 @@ public void ShouldHandleWhenItemsExpire()
cache.Save("user1", _segments1And2);
Assert.AreEqual(_segments1And2, cache.Lookup("user1"));
- Assert.AreEqual(1, cache._readCurrentCacheKeys().Length);
+ Assert.AreEqual(1, cache.CurrentCacheKeysForTesting().Length);
Thread.Sleep(1200);
Assert.IsNull(cache.Lookup("user1"));
- Assert.AreEqual(0, cache._readCurrentCacheKeys().Length);
+ Assert.AreEqual(0, cache.CurrentCacheKeysForTesting().Length);
}
[Test]
@@ -178,7 +178,7 @@ public void ShouldHandleWhenCacheReachesMaxSize()
cache.Save("user2", _segments3And4);
cache.Save("user3", _segments5And6);
- Assert.AreEqual(2, cache._readCurrentCacheKeys().Length);
+ Assert.AreEqual(2, cache.CurrentCacheKeysForTesting().Length);
Assert.AreEqual(_segments5And6, cache.Lookup("user3"));
Assert.AreEqual(_segments3And4, cache.Lookup("user2"));
@@ -198,7 +198,7 @@ public void ShouldHandleWhenCacheIsReset()
Assert.AreEqual(_segments3And4, cache.Lookup("user2"));
Assert.AreEqual(_segments5And6, cache.Lookup("user3"));
- Assert.AreEqual(3, cache._readCurrentCacheKeys().Length);
+ Assert.AreEqual(3, cache.CurrentCacheKeysForTesting().Length);
cache.Reset();
@@ -206,7 +206,7 @@ public void ShouldHandleWhenCacheIsReset()
Assert.IsNull(cache.Lookup("user2"));
Assert.IsNull(cache.Lookup("user3"));
- Assert.AreEqual(0, cache._readCurrentCacheKeys().Length);
+ Assert.AreEqual(0, cache.CurrentCacheKeysForTesting().Length);
}
}
}
diff --git a/OptimizelySDK.Tests/OdpTests/OdpEventManagerTests.cs b/OptimizelySDK.Tests/OdpTests/OdpEventManagerTests.cs
index 0fea8a829..87ce608da 100644
--- a/OptimizelySDK.Tests/OdpTests/OdpEventManagerTests.cs
+++ b/OptimizelySDK.Tests/OdpTests/OdpEventManagerTests.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2022, Optimizely
+ * Copyright 2022-2023, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -155,12 +155,14 @@ public void Setup()
[Test]
public void ShouldLogAndDiscardEventsWhenEventManagerNotRunning()
{
- var eventManager = new OdpEventManager.Builder().WithOdpConfig(_odpConfig).
+ var eventManager = new OdpEventManager.Builder().
WithOdpEventApiManager(_mockApiManager.Object).
WithLogger(_mockLogger.Object).
- Build(startImmediately: false);
+ WithAutoStart(false).
+ Build();
+ eventManager.UpdateSettings(_odpConfig);
- // since we've not called start() then...
+ // since we've not called Start() then...
eventManager.SendEvent(_testEvents[0]);
// ...we should get a notice after trying to send an event
@@ -173,11 +175,13 @@ public void ShouldLogAndDiscardEventsWhenEventManagerNotRunning()
public void ShouldLogAndDiscardEventsWhenEventManagerConfigNotReady()
{
var mockOdpConfig = new Mock(API_KEY, API_HOST, _emptySegmentsToCheck);
- mockOdpConfig.Setup(o => o.IsReady()).Returns(false);
- var eventManager = new OdpEventManager.Builder().WithOdpConfig(mockOdpConfig.Object).
+ mockOdpConfig.Setup(o => o.IsReady()).Returns(false); // stay not ready
+ var eventManager = new OdpEventManager.Builder().
WithOdpEventApiManager(_mockApiManager.Object).
WithLogger(_mockLogger.Object).
- Build(startImmediately: false); // doing it manually in Act next
+ WithAutoStart(false). // start manually in Act
+ Build();
+ eventManager.UpdateSettings(mockOdpConfig.Object);
eventManager.Start(); // Log when Start() called
eventManager.SendEvent(_testEvents[0]); // Log when enqueue attempted
@@ -191,30 +195,34 @@ public void ShouldLogAndDiscardEventsWhenEventManagerConfigNotReady()
public void ShouldLogWhenOdpNotIntegratedAndIdentifyUserCalled()
{
var mockOdpConfig = new Mock(API_KEY, API_HOST, _emptySegmentsToCheck);
- mockOdpConfig.Setup(o => o.IsReady()).Returns(false);
- var eventManager = new OdpEventManager.Builder().WithOdpConfig(mockOdpConfig.Object).
+ mockOdpConfig.Setup(o => o.IsReady()).Returns(false); // never ready
+ var eventManager = new OdpEventManager.Builder().
WithOdpEventApiManager(_mockApiManager.Object).
WithLogger(_mockLogger.Object).
- Build();
+ Build(); // assumed AutoStart true; Logs 1x here
+ eventManager.UpdateSettings(mockOdpConfig.
+ Object); // auto-start after update; Logs 1x here
- eventManager.IdentifyUser(FS_USER_ID);
+ eventManager.IdentifyUser(FS_USER_ID); // Logs 1x here too
_mockLogger.Verify(
l => l.Log(LogLevel.WARN, Constants.ODP_NOT_INTEGRATED_MESSAGE),
- Times.Exactly(2)); // during Start() and SendEvent()
+ Times.Exactly(3)); // during Start() and SendEvent()
}
[Test]
public void ShouldLogWhenOdpNotIntegratedAndStartCalled()
{
var mockOdpConfig = new Mock(API_KEY, API_HOST, _emptySegmentsToCheck);
- mockOdpConfig.Setup(o => o.IsReady()).Returns(false);
- var eventManager = new OdpEventManager.Builder().WithOdpConfig(mockOdpConfig.Object).
+ mockOdpConfig.Setup(o => o.IsReady()).Returns(false); // since never ready
+ var eventManager = new OdpEventManager.Builder().
WithOdpEventApiManager(_mockApiManager.Object).
WithLogger(_mockLogger.Object).
- Build(startImmediately: false); // doing it manually in Act next
+ WithAutoStart(false). // doing it manually in Act next
+ Build();
+ eventManager.UpdateSettings(mockOdpConfig.Object);
- eventManager.Start();
+ eventManager.Start(); // Log 1x here too
_mockLogger.Verify(l => l.Log(LogLevel.WARN, Constants.ODP_NOT_INTEGRATED_MESSAGE),
Times.Once);
@@ -250,10 +258,11 @@ public void ShouldDiscardEventsWithInvalidData()
"key-3", new DateTime()
},
});
- var eventManager = new OdpEventManager.Builder().WithOdpConfig(_odpConfig).
+ var eventManager = new OdpEventManager.Builder().
WithOdpEventApiManager(_mockApiManager.Object).
WithLogger(_mockLogger.Object).
Build();
+ eventManager.UpdateSettings(_odpConfig);
eventManager.SendEvent(eventWithAnArray);
eventManager.SendEvent(eventWithADate);
@@ -271,16 +280,16 @@ public void ShouldAddAdditionalInformationToEachEvent()
_mockApiManager.Setup(api => api.SendEvents(It.IsAny(), It.IsAny(),
Capture.In(eventsCollector))).
Callback(() => cde.Signal());
- var eventManager = new OdpEventManager.Builder().WithOdpConfig(_odpConfig).
+ var eventManager = new OdpEventManager.Builder().
WithOdpEventApiManager(_mockApiManager.Object).
WithLogger(_mockLogger.Object).
WithEventQueue(new BlockingCollection