Skip to content

Commit b3b3a6e

Browse files
[FSSDK-11544] test coverage
1 parent 6f7d2c8 commit b3b3a6e

File tree

7 files changed

+897
-18
lines changed

7 files changed

+897
-18
lines changed
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* Copyright 2025, Optimizely
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using System;
18+
using System.IO;
19+
using Newtonsoft.Json;
20+
using Newtonsoft.Json.Linq;
21+
using NUnit.Framework;
22+
using OptimizelySDK.Entity;
23+
24+
namespace OptimizelySDK.Tests
25+
{
26+
[TestFixture]
27+
public class HoldoutTests
28+
{
29+
private JObject testData;
30+
31+
[SetUp]
32+
public void Setup()
33+
{
34+
// Load test data
35+
var testDataPath = Path.Combine(TestContext.CurrentContext.TestDirectory,
36+
"TestData", "HoldoutTestData.json");
37+
var jsonContent = File.ReadAllText(testDataPath);
38+
testData = JObject.Parse(jsonContent);
39+
}
40+
41+
[Test]
42+
public void TestHoldoutDeserialization()
43+
{
44+
// Test global holdout deserialization
45+
var globalHoldoutJson = testData["globalHoldout"].ToString();
46+
var globalHoldout = JsonConvert.DeserializeObject<Holdout>(globalHoldoutJson);
47+
48+
Assert.IsNotNull(globalHoldout);
49+
Assert.AreEqual("holdout_global_1", globalHoldout.Id);
50+
Assert.AreEqual("global_holdout", globalHoldout.Key);
51+
Assert.AreEqual("Running", globalHoldout.Status);
52+
Assert.AreEqual("layer_1", globalHoldout.LayerId);
53+
Assert.IsNotNull(globalHoldout.Variations);
54+
Assert.AreEqual(1, globalHoldout.Variations.Length);
55+
Assert.IsNotNull(globalHoldout.TrafficAllocation);
56+
Assert.AreEqual(1, globalHoldout.TrafficAllocation.Length);
57+
Assert.IsNotNull(globalHoldout.IncludedFlags);
58+
Assert.AreEqual(0, globalHoldout.IncludedFlags.Length);
59+
Assert.IsNotNull(globalHoldout.ExcludedFlags);
60+
Assert.AreEqual(0, globalHoldout.ExcludedFlags.Length);
61+
}
62+
63+
[Test]
64+
public void TestHoldoutWithIncludedFlags()
65+
{
66+
var includedHoldoutJson = testData["includedFlagsHoldout"].ToString();
67+
var includedHoldout = JsonConvert.DeserializeObject<Holdout>(includedHoldoutJson);
68+
69+
Assert.IsNotNull(includedHoldout);
70+
Assert.AreEqual("holdout_included_1", includedHoldout.Id);
71+
Assert.AreEqual("included_holdout", includedHoldout.Key);
72+
Assert.IsNotNull(includedHoldout.IncludedFlags);
73+
Assert.AreEqual(2, includedHoldout.IncludedFlags.Length);
74+
Assert.Contains("flag_1", includedHoldout.IncludedFlags);
75+
Assert.Contains("flag_2", includedHoldout.IncludedFlags);
76+
Assert.IsNotNull(includedHoldout.ExcludedFlags);
77+
Assert.AreEqual(0, includedHoldout.ExcludedFlags.Length);
78+
}
79+
80+
[Test]
81+
public void TestHoldoutWithExcludedFlags()
82+
{
83+
var excludedHoldoutJson = testData["excludedFlagsHoldout"].ToString();
84+
var excludedHoldout = JsonConvert.DeserializeObject<Holdout>(excludedHoldoutJson);
85+
86+
Assert.IsNotNull(excludedHoldout);
87+
Assert.AreEqual("holdout_excluded_1", excludedHoldout.Id);
88+
Assert.AreEqual("excluded_holdout", excludedHoldout.Key);
89+
Assert.IsNotNull(excludedHoldout.IncludedFlags);
90+
Assert.AreEqual(0, excludedHoldout.IncludedFlags.Length);
91+
Assert.IsNotNull(excludedHoldout.ExcludedFlags);
92+
Assert.AreEqual(2, excludedHoldout.ExcludedFlags.Length);
93+
Assert.Contains("flag_3", excludedHoldout.ExcludedFlags);
94+
Assert.Contains("flag_4", excludedHoldout.ExcludedFlags);
95+
}
96+
97+
[Test]
98+
public void TestHoldoutWithEmptyFlags()
99+
{
100+
var globalHoldoutJson = testData["globalHoldout"].ToString();
101+
var globalHoldout = JsonConvert.DeserializeObject<Holdout>(globalHoldoutJson);
102+
103+
Assert.IsNotNull(globalHoldout);
104+
Assert.IsNotNull(globalHoldout.IncludedFlags);
105+
Assert.AreEqual(0, globalHoldout.IncludedFlags.Length);
106+
Assert.IsNotNull(globalHoldout.ExcludedFlags);
107+
Assert.AreEqual(0, globalHoldout.ExcludedFlags.Length);
108+
}
109+
110+
[Test]
111+
public void TestHoldoutEquality()
112+
{
113+
var holdoutJson = testData["globalHoldout"].ToString();
114+
var holdout1 = JsonConvert.DeserializeObject<Holdout>(holdoutJson);
115+
var holdout2 = JsonConvert.DeserializeObject<Holdout>(holdoutJson);
116+
117+
Assert.IsNotNull(holdout1);
118+
Assert.IsNotNull(holdout2);
119+
// Note: This test depends on how Holdout implements equality
120+
// If Holdout doesn't override Equals, this will test reference equality
121+
// You may need to implement custom equality logic for Holdout
122+
}
123+
124+
[Test]
125+
public void TestHoldoutStatusParsing()
126+
{
127+
var globalHoldoutJson = testData["globalHoldout"].ToString();
128+
var globalHoldout = JsonConvert.DeserializeObject<Holdout>(globalHoldoutJson);
129+
130+
Assert.IsNotNull(globalHoldout);
131+
Assert.AreEqual("Running", globalHoldout.Status);
132+
133+
// Test that the holdout is considered activated when status is "Running"
134+
// This assumes there's an IsActivated property or similar logic
135+
// Adjust based on actual Holdout implementation
136+
}
137+
138+
[Test]
139+
public void TestHoldoutVariationsDeserialization()
140+
{
141+
var holdoutJson = testData["includedFlagsHoldout"].ToString();
142+
var holdout = JsonConvert.DeserializeObject<Holdout>(holdoutJson);
143+
144+
Assert.IsNotNull(holdout);
145+
Assert.IsNotNull(holdout.Variations);
146+
Assert.AreEqual(1, holdout.Variations.Length);
147+
148+
var variation = holdout.Variations[0];
149+
Assert.AreEqual("var_2", variation.Id);
150+
Assert.AreEqual("treatment", variation.Key);
151+
Assert.AreEqual(true, variation.FeatureEnabled);
152+
}
153+
154+
[Test]
155+
public void TestHoldoutTrafficAllocationDeserialization()
156+
{
157+
var holdoutJson = testData["excludedFlagsHoldout"].ToString();
158+
var holdout = JsonConvert.DeserializeObject<Holdout>(holdoutJson);
159+
160+
Assert.IsNotNull(holdout);
161+
Assert.IsNotNull(holdout.TrafficAllocation);
162+
Assert.AreEqual(1, holdout.TrafficAllocation.Length);
163+
164+
var trafficAllocation = holdout.TrafficAllocation[0];
165+
Assert.AreEqual("var_3", trafficAllocation.EntityId);
166+
Assert.AreEqual(10000, trafficAllocation.EndOfRange);
167+
}
168+
169+
[Test]
170+
public void TestHoldoutNullSafety()
171+
{
172+
// Test that holdout can handle null/missing includedFlags and excludedFlags
173+
var minimalHoldoutJson = @"{
174+
""id"": ""test_holdout"",
175+
""key"": ""test_key"",
176+
""status"": ""Running"",
177+
""layerId"": ""test_layer"",
178+
""variations"": [],
179+
""trafficAllocation"": [],
180+
""audienceIds"": [],
181+
""audienceConditions"": []
182+
}";
183+
184+
var holdout = JsonConvert.DeserializeObject<Holdout>(minimalHoldoutJson);
185+
186+
Assert.IsNotNull(holdout);
187+
Assert.AreEqual("test_holdout", holdout.Id);
188+
Assert.AreEqual("test_key", holdout.Key);
189+
190+
// Verify that missing includedFlags and excludedFlags are handled properly
191+
// This depends on how the Holdout entity handles missing properties
192+
Assert.IsNotNull(holdout.IncludedFlags);
193+
Assert.IsNotNull(holdout.ExcludedFlags);
194+
}
195+
}
196+
}

OptimizelySDK.Tests/OptimizelySDK.Tests.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,19 +119,24 @@
119119
<Compile Include="ValidEventDispatcher.cs"/>
120120
<Compile Include="ConfigTest\TestPollingProjectConfigManager.cs"/>
121121
<Compile Include="EntityTests\FeatureVariableTest.cs"/>
122+
<Compile Include="EntityTests\HoldoutTests.cs"/>
122123
<Compile Include="EventTests\EventEntitiesTest.cs"/>
123124
<Compile Include="EventTests\UserEventFactoryTest.cs"/>
124125
<Compile Include="EventTests\EventFactoryTest.cs"/>
125126
<Compile Include="EventTests\CanonicalEvent.cs"/>
126127
<Compile Include="OptimizelyFactoryTest.cs"/>
127128
<Compile Include="Utils\TestData.cs"/>
128129
<Compile Include="Utils\Reflection.cs"/>
130+
<Compile Include="UtilsTests\HoldoutConfigTests.cs"/>
129131
<Compile Include="ConfigTest\ProjectConfigProps.cs"/>
130132
<Compile Include="Utils\TestHttpProjectConfigManagerUtil.cs"/>
131133
</ItemGroup>
132134
<ItemGroup>
133135
<Content Include="App.config"/>
134136
<EmbeddedResource Include="OdpSegmentsDatafile.json"/>
137+
<Content Include="TestData\HoldoutTestData.json">
138+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
139+
</Content>
135140
</ItemGroup>
136141
<ItemGroup>
137142
<EmbeddedResource Include="IntegrationEmptyDatafile.json"/>

OptimizelySDK.Tests/ProjectConfigTest.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
using System;
1818
using System.Collections.Generic;
19+
using System.IO;
1920
using System.Linq;
2021
using Moq;
2122
using Newtonsoft.Json;
@@ -1351,5 +1352,121 @@ public void TestProjectConfigWithOtherIntegrationsInCollection()
13511352
Assert.IsNull(datafileProjectConfig.HostForOdp);
13521353
Assert.IsNull(datafileProjectConfig.PublicKeyForOdp);
13531354
}
1355+
1356+
#region Holdout Integration Tests
1357+
1358+
[Test]
1359+
public void TestHoldoutDeserialization_FromDatafile()
1360+
{
1361+
// Test that holdouts can be deserialized from a datafile with holdouts
1362+
var testDataPath = Path.Combine(TestContext.CurrentContext.TestDirectory,
1363+
"TestData", "HoldoutTestData.json");
1364+
var jsonContent = File.ReadAllText(testDataPath);
1365+
var testData = JObject.Parse(jsonContent);
1366+
1367+
var datafileJson = testData["datafileWithHoldouts"].ToString();
1368+
1369+
var datafileProjectConfig = DatafileProjectConfig.Create(datafileJson,
1370+
new NoOpLogger(), new NoOpErrorHandler()) as DatafileProjectConfig;
1371+
1372+
Assert.IsNotNull(datafileProjectConfig.Holdouts);
1373+
Assert.AreEqual(3, datafileProjectConfig.Holdouts.Length);
1374+
Assert.IsNotNull(datafileProjectConfig.HoldoutIdMap);
1375+
Assert.AreEqual(3, datafileProjectConfig.HoldoutIdMap.Count);
1376+
1377+
// Verify specific holdouts are present
1378+
Assert.IsTrue(datafileProjectConfig.HoldoutIdMap.ContainsKey("holdout_global_1"));
1379+
Assert.IsTrue(datafileProjectConfig.HoldoutIdMap.ContainsKey("holdout_included_1"));
1380+
Assert.IsTrue(datafileProjectConfig.HoldoutIdMap.ContainsKey("holdout_excluded_1"));
1381+
}
1382+
1383+
[Test]
1384+
public void TestGetHoldoutsForFlag_Integration()
1385+
{
1386+
var testDataPath = Path.Combine(TestContext.CurrentContext.TestDirectory,
1387+
"TestData", "HoldoutTestData.json");
1388+
var jsonContent = File.ReadAllText(testDataPath);
1389+
var testData = JObject.Parse(jsonContent);
1390+
1391+
var datafileJson = testData["datafileWithHoldouts"].ToString();
1392+
1393+
var datafileProjectConfig = DatafileProjectConfig.Create(datafileJson,
1394+
new NoOpLogger(), new NoOpErrorHandler()) as DatafileProjectConfig;
1395+
1396+
// Test GetHoldoutsForFlag method
1397+
var holdoutsForFlag1 = datafileProjectConfig.GetHoldoutsForFlag("flag_1");
1398+
Assert.IsNotNull(holdoutsForFlag1);
1399+
Assert.AreEqual(3, holdoutsForFlag1.Length); // Global + excluded holdout (applies to all except flag_3/flag_4) + included holdout
1400+
1401+
var holdoutsForFlag3 = datafileProjectConfig.GetHoldoutsForFlag("flag_3");
1402+
Assert.IsNotNull(holdoutsForFlag3);
1403+
Assert.AreEqual(1, holdoutsForFlag3.Length); // Only true global (excluded holdout excludes flag_3)
1404+
1405+
var holdoutsForUnknownFlag = datafileProjectConfig.GetHoldoutsForFlag("unknown_flag");
1406+
Assert.IsNotNull(holdoutsForUnknownFlag);
1407+
Assert.AreEqual(2, holdoutsForUnknownFlag.Length); // Global + excluded holdout (unknown_flag not in excluded list)
1408+
}
1409+
1410+
[Test]
1411+
public void TestGetHoldout_Integration()
1412+
{
1413+
var testDataPath = Path.Combine(TestContext.CurrentContext.TestDirectory,
1414+
"TestData", "HoldoutTestData.json");
1415+
var jsonContent = File.ReadAllText(testDataPath);
1416+
var testData = JObject.Parse(jsonContent);
1417+
1418+
var datafileJson = testData["datafileWithHoldouts"].ToString();
1419+
1420+
var datafileProjectConfig = DatafileProjectConfig.Create(datafileJson,
1421+
new NoOpLogger(), new NoOpErrorHandler()) as DatafileProjectConfig;
1422+
1423+
// Test GetHoldout method
1424+
var globalHoldout = datafileProjectConfig.GetHoldout("holdout_global_1");
1425+
Assert.IsNotNull(globalHoldout);
1426+
Assert.AreEqual("holdout_global_1", globalHoldout.Id);
1427+
Assert.AreEqual("global_holdout", globalHoldout.Key);
1428+
1429+
var invalidHoldout = datafileProjectConfig.GetHoldout("invalid_id");
1430+
Assert.IsNotNull(invalidHoldout);
1431+
Assert.AreEqual("", invalidHoldout.Id); // Dummy holdout has empty ID
1432+
}
1433+
1434+
[Test]
1435+
public void TestMissingHoldoutsField_BackwardCompatibility()
1436+
{
1437+
// Test that a datafile without holdouts field still works
1438+
var datafileWithoutHoldouts = @"{
1439+
""version"": ""4"",
1440+
""rollouts"": [],
1441+
""projectId"": ""test_project"",
1442+
""experiments"": [],
1443+
""groups"": [],
1444+
""attributes"": [],
1445+
""audiences"": [],
1446+
""layers"": [],
1447+
""events"": [],
1448+
""revision"": ""1"",
1449+
""featureFlags"": []
1450+
}";
1451+
1452+
var datafileProjectConfig = DatafileProjectConfig.Create(datafileWithoutHoldouts,
1453+
new NoOpLogger(), new NoOpErrorHandler()) as DatafileProjectConfig;
1454+
1455+
Assert.IsNotNull(datafileProjectConfig.Holdouts);
1456+
Assert.AreEqual(0, datafileProjectConfig.Holdouts.Length);
1457+
Assert.IsNotNull(datafileProjectConfig.HoldoutIdMap);
1458+
Assert.AreEqual(0, datafileProjectConfig.HoldoutIdMap.Count);
1459+
1460+
// Methods should still work with empty holdouts
1461+
var holdouts = datafileProjectConfig.GetHoldoutsForFlag("any_flag");
1462+
Assert.IsNotNull(holdouts);
1463+
Assert.AreEqual(0, holdouts.Length);
1464+
1465+
var holdout = datafileProjectConfig.GetHoldout("any_id");
1466+
Assert.IsNotNull(holdout);
1467+
Assert.AreEqual("", holdout.Id); // Dummy holdout has empty ID
1468+
}
1469+
1470+
#endregion
13541471
}
13551472
}

0 commit comments

Comments
 (0)