Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 1 addition & 54 deletions OptimizelySDK.Tests/EntityTests/HoldoutTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,57 +53,6 @@ public void TestHoldoutDeserialization()
Assert.AreEqual(1, globalHoldout.Variations.Length);
Assert.IsNotNull(globalHoldout.TrafficAllocation);
Assert.AreEqual(1, globalHoldout.TrafficAllocation.Length);
Assert.IsNotNull(globalHoldout.IncludedFlags);
Assert.AreEqual(0, globalHoldout.IncludedFlags.Length);
Assert.IsNotNull(globalHoldout.ExcludedFlags);
Assert.AreEqual(0, globalHoldout.ExcludedFlags.Length);
}

[Test]
public void TestHoldoutWithIncludedFlags()
{
var includedHoldoutJson = testData["includedFlagsHoldout"].ToString();
var includedHoldout = JsonConvert.DeserializeObject<Holdout>(includedHoldoutJson);

Assert.IsNotNull(includedHoldout);
Assert.AreEqual("holdout_included_1", includedHoldout.Id);
Assert.AreEqual("included_holdout", includedHoldout.Key);
Assert.IsNotNull(includedHoldout.IncludedFlags);
Assert.AreEqual(2, includedHoldout.IncludedFlags.Length);
Assert.Contains("flag_1", includedHoldout.IncludedFlags);
Assert.Contains("flag_2", includedHoldout.IncludedFlags);
Assert.IsNotNull(includedHoldout.ExcludedFlags);
Assert.AreEqual(0, includedHoldout.ExcludedFlags.Length);
}

[Test]
public void TestHoldoutWithExcludedFlags()
{
var excludedHoldoutJson = testData["excludedFlagsHoldout"].ToString();
var excludedHoldout = JsonConvert.DeserializeObject<Holdout>(excludedHoldoutJson);

Assert.IsNotNull(excludedHoldout);
Assert.AreEqual("holdout_excluded_1", excludedHoldout.Id);
Assert.AreEqual("excluded_holdout", excludedHoldout.Key);
Assert.IsNotNull(excludedHoldout.IncludedFlags);
Assert.AreEqual(0, excludedHoldout.IncludedFlags.Length);
Assert.IsNotNull(excludedHoldout.ExcludedFlags);
Assert.AreEqual(2, excludedHoldout.ExcludedFlags.Length);
Assert.Contains("flag_3", excludedHoldout.ExcludedFlags);
Assert.Contains("flag_4", excludedHoldout.ExcludedFlags);
}

[Test]
public void TestHoldoutWithEmptyFlags()
{
var globalHoldoutJson = testData["globalHoldout"].ToString();
var globalHoldout = JsonConvert.DeserializeObject<Holdout>(globalHoldoutJson);

Assert.IsNotNull(globalHoldout);
Assert.IsNotNull(globalHoldout.IncludedFlags);
Assert.AreEqual(0, globalHoldout.IncludedFlags.Length);
Assert.IsNotNull(globalHoldout.ExcludedFlags);
Assert.AreEqual(0, globalHoldout.ExcludedFlags.Length);
}

[Test]
Expand Down Expand Up @@ -161,7 +110,7 @@ public void TestHoldoutTrafficAllocationDeserialization()
[Test]
public void TestHoldoutNullSafety()
{
// Test that holdout can handle null/missing includedFlags and excludedFlags
// Test that holdout can handle minimal JSON
var minimalHoldoutJson = @"{
""id"": ""test_holdout"",
""key"": ""test_key"",
Expand All @@ -177,8 +126,6 @@ public void TestHoldoutNullSafety()
Assert.IsNotNull(holdout);
Assert.AreEqual("test_holdout", holdout.Id);
Assert.AreEqual("test_key", holdout.Key);
Assert.IsNotNull(holdout.IncludedFlags);
Assert.IsNotNull(holdout.ExcludedFlags);
}
}
}
93 changes: 0 additions & 93 deletions OptimizelySDK.Tests/OptimizelyUserContextHoldoutTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,66 +95,6 @@ public void TestDecide_GlobalHoldout()
"Variation key should be valid or null");
}

[Test]
public void TestDecide_IncludedFlagsHoldout()
{
// Test holdout with includedFlags configuration
var featureFlag = Config.FeatureKeyMap["test_flag_1"];
Assert.IsNotNull(featureFlag, "Feature flag should exist");

// Check if there's a holdout that includes this flag
var includedHoldout = Config.Holdouts.FirstOrDefault(h =>
h.IncludedFlags != null && h.IncludedFlags.Contains(featureFlag.Id));

if (includedHoldout != null)
{
var userContext = OptimizelyInstance.CreateUserContext(TestUserId,
new UserAttributes { { "country", "us" } });

var decision = userContext.Decide("test_flag_1");

Assert.IsNotNull(decision, "Decision should not be null");
Assert.AreEqual("test_flag_1", decision.FlagKey, "Flag key should match");

// Verify decision is valid
Assert.IsTrue(decision.VariationKey != null || decision.VariationKey == null,
"Decision should have valid structure");
}
else
{
Assert.Inconclusive("No included holdout found for test_flag_1");
}
}

[Test]
public void TestDecide_ExcludedFlagsHoldout()
{
// Test holdout with excludedFlags configuration
// Based on test data, flag_3 and flag_4 are excluded by holdout_excluded_1
var userContext = OptimizelyInstance.CreateUserContext(TestUserId,
new UserAttributes { { "country", "us" } });

// Test with an excluded flag (test_flag_3 maps to flag_3)
var excludedDecision = userContext.Decide("test_flag_3");

Assert.IsNotNull(excludedDecision, "Decision should not be null for excluded flag");
Assert.AreEqual("test_flag_3", excludedDecision.FlagKey, "Flag key should match");

// For excluded flags, the decision should not come from the excluded holdout
// The excluded holdout has key "excluded_holdout"
Assert.AreNotEqual("excluded_holdout", excludedDecision.RuleKey,
"Decision should not come from excluded holdout for flag_3");

// Also test with a non-excluded flag (test_flag_1 maps to flag_1)
var nonExcludedDecision = userContext.Decide("test_flag_1");

Assert.IsNotNull(nonExcludedDecision, "Decision should not be null for non-excluded flag");
Assert.AreEqual("test_flag_1", nonExcludedDecision.FlagKey, "Flag key should match");

// For non-excluded flags, they can potentially be affected by holdouts
// (depending on other holdout configurations like global or included holdouts)
}

[Test]
public void TestDecideAll_MultipleHoldouts()
{
Expand Down Expand Up @@ -327,39 +267,6 @@ public void TestDecide_WithDecisionReasons()
Assert.IsTrue(decision.Reasons.Length >= 0, "Decision reasons should be present");
}

[Test]
public void TestDecide_HoldoutPriority()
{
// Test holdout evaluation priority (global vs included vs excluded)
var featureFlag = Config.FeatureKeyMap["test_flag_1"];
Assert.IsNotNull(featureFlag, "Feature flag should exist");

// Check if we have multiple holdouts
var globalHoldouts = Config.Holdouts.Where(h =>
h.IncludedFlags == null || h.IncludedFlags.Length == 0).ToList();
var includedHoldouts = Config.Holdouts.Where(h =>
h.IncludedFlags != null && h.IncludedFlags.Contains(featureFlag.Id)).ToList();

if (globalHoldouts.Count > 0 || includedHoldouts.Count > 0)
{
var userContext = OptimizelyInstance.CreateUserContext(TestUserId,
new UserAttributes { { "country", "us" } });

var decision = userContext.Decide("test_flag_1");

Assert.IsNotNull(decision, "Decision should not be null");
Assert.AreEqual("test_flag_1", decision.FlagKey, "Flag key should match");

// Decision should be valid regardless of which holdout is selected
Assert.IsTrue(decision.VariationKey != null || decision.VariationKey == null,
"Decision should have valid structure");
}
else
{
Assert.Inconclusive("No holdouts found to test priority");
}
}

#endregion

#region Holdout Decision Reasons Tests
Expand Down
31 changes: 0 additions & 31 deletions OptimizelySDK.Tests/ProjectConfigTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1371,33 +1371,6 @@ public void TestHoldoutDeserialization_FromDatafile()
Assert.AreEqual(4, datafileProjectConfig.Holdouts.Length);
}

[Test]
public void TestGetHoldoutsForFlag_Integration()
{
var testDataPath = Path.Combine(TestContext.CurrentContext.TestDirectory,
"TestData", "HoldoutTestData.json");
var jsonContent = File.ReadAllText(testDataPath);
var testData = JObject.Parse(jsonContent);

var datafileJson = testData["datafileWithHoldouts"].ToString();

var datafileProjectConfig = DatafileProjectConfig.Create(datafileJson,
new NoOpLogger(), new NoOpErrorHandler()) as DatafileProjectConfig;

// Test GetHoldoutsForFlag method
var holdoutsForFlag1 = datafileProjectConfig.GetHoldoutsForFlag("flag_1");
Assert.IsNotNull(holdoutsForFlag1);
Assert.AreEqual(4, holdoutsForFlag1.Length); // Global + excluded holdout (applies to all except flag_3/flag_4) + included holdout + empty holdout

var holdoutsForFlag3 = datafileProjectConfig.GetHoldoutsForFlag("flag_3");
Assert.IsNotNull(holdoutsForFlag3);
Assert.AreEqual(2, holdoutsForFlag3.Length); // Global + empty holdout (excluded holdout excludes flag_3, included holdout doesn't include flag_3)

var holdoutsForUnknownFlag = datafileProjectConfig.GetHoldoutsForFlag("unknown_flag");
Assert.IsNotNull(holdoutsForUnknownFlag);
Assert.AreEqual(3, holdoutsForUnknownFlag.Length); // Global + excluded holdout (unknown_flag not in excluded list) + empty holdout
}

[Test]
public void TestGetHoldout_Integration()
{
Expand Down Expand Up @@ -1446,10 +1419,6 @@ public void TestMissingHoldoutsField_BackwardCompatibility()
Assert.AreEqual(0, datafileProjectConfig.Holdouts.Length);

// Methods should still work with empty holdouts
var holdouts = datafileProjectConfig.GetHoldoutsForFlag("any_flag");
Assert.IsNotNull(holdouts);
Assert.AreEqual(0, holdouts.Length);

var holdout = datafileProjectConfig.GetHoldout("any_id");
Assert.IsNull(holdout);
}
Expand Down
143 changes: 143 additions & 0 deletions OptimizelySDK.Tests/UtilsTests/HoldoutConfigBasicTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Copyright 2025, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using NUnit.Framework;
using OptimizelySDK.Entity;
using OptimizelySDK.Utils;

namespace OptimizelySDK.Tests
{
[TestFixture]
public class HoldoutConfigBasicTests
{
[Test]
public void TestEmptyHoldouts_ShouldHaveEmptyMaps()
{
var config = new HoldoutConfig(new Holdout[0]);

Assert.IsNotNull(config.HoldoutIdMap);
Assert.AreEqual(0, config.HoldoutIdMap.Count);
Assert.AreEqual(0, config.HoldoutCount);
}

[Test]
public void TestHoldoutIdMapping()
{
var holdout1 = CreateTestHoldout("holdout_1", "h1");
var holdout2 = CreateTestHoldout("holdout_2", "h2");
var allHoldouts = new[] { holdout1, holdout2 };
var config = new HoldoutConfig(allHoldouts);

Assert.IsNotNull(config.HoldoutIdMap);
Assert.AreEqual(2, config.HoldoutIdMap.Count);
Assert.AreEqual(2, config.HoldoutCount);

Assert.IsTrue(config.HoldoutIdMap.ContainsKey("holdout_1"));
Assert.IsTrue(config.HoldoutIdMap.ContainsKey("holdout_2"));

Assert.AreEqual(holdout1.Id, config.HoldoutIdMap["holdout_1"].Id);
Assert.AreEqual(holdout2.Id, config.HoldoutIdMap["holdout_2"].Id);
}

[Test]
public void TestGetHoldoutById()
{
var holdout = CreateTestHoldout("holdout_1", "h1");
var config = new HoldoutConfig(new[] { holdout });

var retrieved = config.GetHoldout("holdout_1");

Assert.IsNotNull(retrieved);
Assert.AreEqual("holdout_1", retrieved.Id);
Assert.AreEqual("h1", retrieved.Key);
}

[Test]
public void TestGetHoldoutById_InvalidId()
{
var holdout = CreateTestHoldout("holdout_1", "h1");
var config = new HoldoutConfig(new[] { holdout });

var result = config.GetHoldout("invalid_id");
Assert.IsNull(result);
}

[Test]
public void TestGetHoldoutById_NullId()
{
var holdout = CreateTestHoldout("holdout_1", "h1");
var config = new HoldoutConfig(new[] { holdout });

var result = config.GetHoldout(null);
Assert.IsNull(result);
}

[Test]
public void TestGetHoldoutById_EmptyId()
{
var holdout = CreateTestHoldout("holdout_1", "h1");
var config = new HoldoutConfig(new[] { holdout });

var result = config.GetHoldout("");
Assert.IsNull(result);
}

[Test]
public void TestUpdateHoldoutMapping()
{
var holdout1 = CreateTestHoldout("holdout_1", "h1");
var config = new HoldoutConfig(new[] { holdout1 });

// Initial state
Assert.AreEqual(1, config.HoldoutIdMap.Count);
Assert.AreEqual(1, config.HoldoutCount);

// Update with new holdouts
var holdout2 = CreateTestHoldout("holdout_2", "h2");
config.UpdateHoldoutMapping(new[] { holdout1, holdout2 });

Assert.AreEqual(2, config.HoldoutIdMap.Count);
Assert.AreEqual(2, config.HoldoutCount);
Assert.IsTrue(config.HoldoutIdMap.ContainsKey("holdout_1"));
Assert.IsTrue(config.HoldoutIdMap.ContainsKey("holdout_2"));
}

[Test]
public void TestNullHoldouts()
{
var config = new HoldoutConfig(null);

Assert.IsNotNull(config.HoldoutIdMap);
Assert.AreEqual(0, config.HoldoutIdMap.Count);
Assert.AreEqual(0, config.HoldoutCount);
}

// Helper method to create test holdouts
private Holdout CreateTestHoldout(string id, string key)
{
return new Holdout
{
Id = id,
Key = key,
Status = "Running",
Variations = new Variation[0],
TrafficAllocation = new TrafficAllocation[0],
AudienceIds = new string[0],
AudienceConditions = null
};
}
}
}
Loading
Loading