Skip to content

Commit 006989c

Browse files
Holdout config file test case added
1 parent 3d953a3 commit 006989c

File tree

3 files changed

+275
-54
lines changed

3 files changed

+275
-54
lines changed

core-api/src/main/java/com/optimizely/ab/config/Holdout.java

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,8 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17-
1817
package com.optimizely.ab.config;
1918

20-
import com.fasterxml.jackson.annotation.JsonCreator;
21-
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
22-
import com.fasterxml.jackson.annotation.JsonProperty;
23-
import com.optimizely.ab.annotations.VisibleForTesting;
24-
import com.optimizely.ab.config.audience.AndCondition;
25-
import com.optimizely.ab.config.audience.AudienceIdCondition;
26-
import com.optimizely.ab.config.audience.Condition;
27-
import com.optimizely.ab.config.audience.EmptyCondition;
28-
import com.optimizely.ab.config.audience.NotCondition;
29-
import com.optimizely.ab.config.audience.OrCondition;
30-
3119
import java.util.Collections;
3220
import java.util.List;
3321
import java.util.Map;
@@ -36,6 +24,13 @@
3624
import javax.annotation.Nullable;
3725
import javax.annotation.concurrent.Immutable;
3826

27+
import com.fasterxml.jackson.annotation.JsonCreator;
28+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
29+
import com.fasterxml.jackson.annotation.JsonProperty;
30+
import com.optimizely.ab.annotations.VisibleForTesting;
31+
import com.optimizely.ab.config.audience.AudienceIdCondition;
32+
import com.optimizely.ab.config.audience.Condition;
33+
3934
@Immutable
4035
@JsonIgnoreProperties(ignoreUnknown = true)
4136
public class Holdout implements ExperimentCore {
@@ -57,7 +52,6 @@ public class Holdout implements ExperimentCore {
5752
// Not necessary for HO
5853
private final String layerId = "";
5954

60-
6155
public enum HoldoutStatus {
6256
RUNNING("Running"),
6357
DRAFT("Draft"),
@@ -82,27 +76,27 @@ public Holdout(String id, String key) {
8276

8377
@JsonCreator
8478
public Holdout(@JsonProperty("id") String id,
85-
@JsonProperty("key") String key,
86-
@JsonProperty("status") String status,
87-
@JsonProperty("audienceIds") List<String> audienceIds,
88-
@JsonProperty("audienceConditions") Condition audienceConditions,
89-
@JsonProperty("variations") List<Variation> variations,
90-
@JsonProperty("trafficAllocation") List<TrafficAllocation> trafficAllocation,
91-
@JsonProperty("includedFlags") List<String> includedFlags,
92-
@JsonProperty("excludedFlags") List<String> excludedFlags) {
79+
@JsonProperty("key") String key,
80+
@JsonProperty("status") String status,
81+
@JsonProperty("audienceIds") List<String> audienceIds,
82+
@JsonProperty("audienceConditions") Condition audienceConditions,
83+
@JsonProperty("variations") List<Variation> variations,
84+
@JsonProperty("trafficAllocation") List<TrafficAllocation> trafficAllocation,
85+
@JsonProperty("includedFlags") List<String> includedFlags,
86+
@JsonProperty("excludedFlags") List<String> excludedFlags) {
9387
this(id, key, status, audienceIds, audienceConditions, variations, trafficAllocation, includedFlags, excludedFlags, "");
9488
}
9589

9690
public Holdout(@Nonnull String id,
97-
@Nonnull String key,
98-
@Nullable String status,
99-
@Nonnull List<String> audienceIds,
100-
@Nullable Condition audienceConditions,
101-
@Nonnull List<Variation> variations,
102-
@Nonnull List<TrafficAllocation> trafficAllocation,
103-
@Nullable List<String> includedFlags,
104-
@Nullable List<String> excludedFlags,
105-
@Nonnull String groupId) {
91+
@Nonnull String key,
92+
@Nullable String status,
93+
@Nonnull List<String> audienceIds,
94+
@Nullable Condition audienceConditions,
95+
@Nonnull List<Variation> variations,
96+
@Nonnull List<TrafficAllocation> trafficAllocation,
97+
@Nullable List<String> includedFlags,
98+
@Nullable List<String> excludedFlags,
99+
@Nonnull String groupId) {
106100
this.id = id;
107101
this.key = key;
108102
this.status = status == null ? HoldoutStatus.RUNNING.toString() : status;
@@ -157,9 +151,13 @@ public List<TrafficAllocation> getTrafficAllocation() {
157151
return trafficAllocation;
158152
}
159153

160-
public List<String> getIncludedFlags() { return includedFlags; }
154+
public List<String> getIncludedFlags() {
155+
return includedFlags;
156+
}
161157

162-
public List<String> getExcludedFlags() { return excludedFlags; }
158+
public List<String> getExcludedFlags() {
159+
return excludedFlags;
160+
}
163161

164162
public String getGroupId() {
165163
return groupId;
@@ -175,16 +173,16 @@ public boolean isRunning() {
175173

176174
@Override
177175
public String toString() {
178-
return "Holdout {" +
179-
"id='" + id + '\'' +
180-
", key='" + key + '\'' +
181-
", groupId='" + groupId + '\'' +
182-
", status='" + status + '\'' +
183-
", audienceIds=" + audienceIds +
184-
", audienceConditions=" + audienceConditions +
185-
", variations=" + variations +
186-
", variationKeyToVariationMap=" + variationKeyToVariationMap +
187-
", trafficAllocation=" + trafficAllocation +
188-
'}';
176+
return "Holdout {"
177+
+ "id='" + id + '\''
178+
+ ", key='" + key + '\''
179+
+ ", groupId='" + groupId + '\''
180+
+ ", status='" + status + '\''
181+
+ ", audienceIds=" + audienceIds
182+
+ ", audienceConditions=" + audienceConditions
183+
+ ", variations=" + variations
184+
+ ", variationKeyToVariationMap=" + variationKeyToVariationMap
185+
+ ", trafficAllocation=" + trafficAllocation
186+
+ '}';
189187
}
190188
}

core-api/src/main/java/com/optimizely/ab/config/HoldoutConfig.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public HoldoutConfig(@Nonnull List<Holdout> allHoldouts) {
6565
* Updates internal mappings of holdouts including the id map, global list,
6666
* and per-flag inclusion/exclusion maps.
6767
*/
68-
public void updateHoldoutMapping() {
68+
private void updateHoldoutMapping() {
6969
holdoutIdMap.clear();
7070
for (Holdout holdout : allHoldouts) {
7171
holdoutIdMap.put(holdout.getId(), holdout);
@@ -159,14 +159,4 @@ public Holdout getHoldout(@Nonnull String id) {
159159
public List<Holdout> getAllHoldouts() {
160160
return Collections.unmodifiableList(allHoldouts);
161161
}
162-
163-
/**
164-
* Returns the global holdouts (those that apply to all flags by default).
165-
*
166-
* @return An unmodifiable list of global holdouts
167-
*/
168-
public List<Holdout> getGlobal() {
169-
return Collections.unmodifiableList(global);
170-
}
171-
172162
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/**
2+
*
3+
* Copyright 2016-2019, 2021, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.config;
18+
19+
import java.util.Arrays;
20+
import java.util.Collections;
21+
import java.util.List;
22+
23+
import static org.junit.Assert.assertEquals;
24+
import static org.junit.Assert.assertFalse;
25+
import static org.junit.Assert.assertNull;
26+
import static org.junit.Assert.assertSame;
27+
import static org.junit.Assert.assertTrue;
28+
import static org.junit.Assert.fail;
29+
import org.junit.Before;
30+
import org.junit.Test;
31+
32+
public class HoldoutConfigTest {
33+
34+
private Holdout globalHoldout;
35+
private Holdout includedHoldout;
36+
private Holdout excludedHoldout;
37+
private Holdout mixedHoldout;
38+
39+
@Before
40+
public void setUp() {
41+
// Global holdout (no included/excluded flags)
42+
globalHoldout = new Holdout("global1", "global_holdout");
43+
44+
// Holdout with included flags
45+
includedHoldout = new Holdout("included1", "included_holdout", null,
46+
Collections.emptyList(), null, Collections.emptyList(),
47+
Collections.emptyList(), Arrays.asList("flag1", "flag2"), null, "");
48+
49+
// Global holdout with excluded flags
50+
excludedHoldout = new Holdout("excluded1", "excluded_holdout", null,
51+
Collections.emptyList(), null, Collections.emptyList(),
52+
Collections.emptyList(), null, Arrays.asList("flag3"), "");
53+
54+
// Another global holdout for testing
55+
mixedHoldout = new Holdout("mixed1", "mixed_holdout");
56+
}
57+
58+
@Test
59+
public void testEmptyConstructor() {
60+
HoldoutConfig config = new HoldoutConfig();
61+
62+
assertTrue(config.getAllHoldouts().isEmpty());
63+
assertTrue(config.getHoldoutForFlag("any_flag").isEmpty());
64+
assertNull(config.getHoldout("any_id"));
65+
}
66+
67+
@Test
68+
public void testConstructorWithEmptyList() {
69+
HoldoutConfig config = new HoldoutConfig(Collections.emptyList());
70+
71+
assertTrue(config.getAllHoldouts().isEmpty());
72+
assertTrue(config.getHoldoutForFlag("any_flag").isEmpty());
73+
assertNull(config.getHoldout("any_id"));
74+
}
75+
76+
@Test
77+
public void testConstructorWithGlobalHoldouts() {
78+
List<Holdout> holdouts = Arrays.asList(globalHoldout, mixedHoldout);
79+
HoldoutConfig config = new HoldoutConfig(holdouts);
80+
81+
assertEquals(2, config.getAllHoldouts().size());
82+
assertTrue(config.getAllHoldouts().contains(globalHoldout));
83+
}
84+
85+
@Test
86+
public void testGetHoldout() {
87+
List<Holdout> holdouts = Arrays.asList(globalHoldout, includedHoldout);
88+
HoldoutConfig config = new HoldoutConfig(holdouts);
89+
90+
assertEquals(globalHoldout, config.getHoldout("global1"));
91+
assertEquals(includedHoldout, config.getHoldout("included1"));
92+
assertNull(config.getHoldout("nonexistent"));
93+
}
94+
95+
@Test
96+
public void testGetHoldoutForFlagWithGlobalHoldouts() {
97+
List<Holdout> holdouts = Arrays.asList(globalHoldout, mixedHoldout);
98+
HoldoutConfig config = new HoldoutConfig(holdouts);
99+
100+
List<Holdout> flagHoldouts = config.getHoldoutForFlag("any_flag");
101+
assertEquals(2, flagHoldouts.size());
102+
assertTrue(flagHoldouts.contains(globalHoldout));
103+
assertTrue(flagHoldouts.contains(mixedHoldout));
104+
}
105+
106+
@Test
107+
public void testGetHoldoutForFlagWithIncludedHoldouts() {
108+
List<Holdout> holdouts = Arrays.asList(globalHoldout, includedHoldout);
109+
HoldoutConfig config = new HoldoutConfig(holdouts);
110+
111+
// Flag included in holdout
112+
List<Holdout> flag1Holdouts = config.getHoldoutForFlag("flag1");
113+
assertEquals(2, flag1Holdouts.size());
114+
assertTrue(flag1Holdouts.contains(globalHoldout)); // Global first
115+
assertTrue(flag1Holdouts.contains(includedHoldout)); // Included second
116+
117+
List<Holdout> flag2Holdouts = config.getHoldoutForFlag("flag2");
118+
assertEquals(2, flag2Holdouts.size());
119+
assertTrue(flag2Holdouts.contains(globalHoldout));
120+
assertTrue(flag2Holdouts.contains(includedHoldout));
121+
122+
// Flag not included in holdout
123+
List<Holdout> flag3Holdouts = config.getHoldoutForFlag("flag3");
124+
assertEquals(1, flag3Holdouts.size());
125+
assertTrue(flag3Holdouts.contains(globalHoldout)); // Only global
126+
}
127+
128+
@Test
129+
public void testGetHoldoutForFlagWithExcludedHoldouts() {
130+
List<Holdout> holdouts = Arrays.asList(globalHoldout, excludedHoldout);
131+
HoldoutConfig config = new HoldoutConfig(holdouts);
132+
133+
// Flag excluded from holdout
134+
List<Holdout> flag3Holdouts = config.getHoldoutForFlag("flag3");
135+
assertEquals(1, flag3Holdouts.size());
136+
assertTrue(flag3Holdouts.contains(globalHoldout)); // excludedHoldout should be filtered out
137+
138+
// Flag not excluded
139+
List<Holdout> flag1Holdouts = config.getHoldoutForFlag("flag1");
140+
assertEquals(2, flag1Holdouts.size());
141+
assertTrue(flag1Holdouts.contains(globalHoldout));
142+
assertTrue(flag1Holdouts.contains(excludedHoldout));
143+
}
144+
145+
@Test
146+
public void testGetHoldoutForFlagWithMixedHoldouts() {
147+
List<Holdout> holdouts = Arrays.asList(globalHoldout, includedHoldout, excludedHoldout);
148+
HoldoutConfig config = new HoldoutConfig(holdouts);
149+
150+
// flag1 is included in includedHoldout
151+
List<Holdout> flag1Holdouts = config.getHoldoutForFlag("flag1");
152+
assertEquals(3, flag1Holdouts.size());
153+
assertTrue(flag1Holdouts.contains(globalHoldout));
154+
assertTrue(flag1Holdouts.contains(excludedHoldout));
155+
assertTrue(flag1Holdouts.contains(includedHoldout));
156+
157+
// flag3 is excluded from excludedHoldout
158+
List<Holdout> flag3Holdouts = config.getHoldoutForFlag("flag3");
159+
assertEquals(1, flag3Holdouts.size());
160+
assertTrue(flag3Holdouts.contains(globalHoldout)); // Only global, excludedHoldout filtered out
161+
162+
// flag4 has no specific inclusion/exclusion
163+
List<Holdout> flag4Holdouts = config.getHoldoutForFlag("flag4");
164+
assertEquals(2, flag4Holdouts.size());
165+
assertTrue(flag4Holdouts.contains(globalHoldout));
166+
assertTrue(flag4Holdouts.contains(excludedHoldout));
167+
}
168+
169+
@Test
170+
public void testCachingBehavior() {
171+
List<Holdout> holdouts = Arrays.asList(globalHoldout, includedHoldout);
172+
HoldoutConfig config = new HoldoutConfig(holdouts);
173+
174+
// First call
175+
List<Holdout> firstCall = config.getHoldoutForFlag("flag1");
176+
// Second call should return cached result (same object reference)
177+
List<Holdout> secondCall = config.getHoldoutForFlag("flag1");
178+
179+
assertSame(firstCall, secondCall);
180+
assertEquals(2, firstCall.size());
181+
}
182+
183+
@Test
184+
public void testGetAllHoldoutsIsUnmodifiable() {
185+
List<Holdout> holdouts = Arrays.asList(globalHoldout, includedHoldout);
186+
HoldoutConfig config = new HoldoutConfig(holdouts);
187+
188+
List<Holdout> allHoldouts = config.getAllHoldouts();
189+
190+
try {
191+
allHoldouts.add(mixedHoldout);
192+
fail("Should throw UnsupportedOperationException");
193+
} catch (UnsupportedOperationException e) {
194+
// Expected
195+
}
196+
}
197+
198+
@Test
199+
public void testEmptyFlagHoldouts() {
200+
HoldoutConfig config = new HoldoutConfig();
201+
202+
List<Holdout> flagHoldouts = config.getHoldoutForFlag("any_flag");
203+
assertTrue(flagHoldouts.isEmpty());
204+
205+
// Should return same empty list for subsequent calls (caching)
206+
List<Holdout> secondCall = config.getHoldoutForFlag("any_flag");
207+
assertSame(flagHoldouts, secondCall);
208+
}
209+
210+
@Test
211+
public void testHoldoutWithBothIncludedAndExcluded() {
212+
// Create a holdout with both included and excluded flags (included takes precedence)
213+
Holdout bothHoldout = new Holdout("both1", "both_holdout", null,
214+
Collections.emptyList(), null, Collections.emptyList(),
215+
Collections.emptyList(), Arrays.asList("flag1"), Arrays.asList("flag2"), "");
216+
217+
List<Holdout> holdouts = Arrays.asList(globalHoldout, bothHoldout);
218+
HoldoutConfig config = new HoldoutConfig(holdouts);
219+
220+
// flag1 should include bothHoldout (included takes precedence)
221+
List<Holdout> flag1Holdouts = config.getHoldoutForFlag("flag1");
222+
assertEquals(2, flag1Holdouts.size());
223+
assertTrue(flag1Holdouts.contains(globalHoldout));
224+
assertTrue(flag1Holdouts.contains(bothHoldout));
225+
226+
// flag2 should not include bothHoldout (not in included list)
227+
List<Holdout> flag2Holdouts = config.getHoldoutForFlag("flag2");
228+
assertEquals(1, flag2Holdouts.size());
229+
assertTrue(flag2Holdouts.contains(globalHoldout));
230+
assertFalse(flag2Holdouts.contains(bothHoldout));
231+
}
232+
233+
}

0 commit comments

Comments
 (0)