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