99from .types import LocalFlagsConfig , ExperimentationFlag , RuleSet , Variant , Rollout , FlagTestUsers , ExperimentationFlags , VariantOverride
1010from .local_feature_flags import LocalFeatureFlagsProvider
1111
12+
1213def create_test_flag (
1314 flag_key : str = "test_flag" ,
1415 context : str = "distinct_id" ,
1516 variants : Optional [list [Variant ]] = None ,
1617 variant_override : Optional [VariantOverride ] = None ,
1718 rollout_percentage : float = 100.0 ,
1819 runtime_evaluation : Optional [Dict ] = None ,
19- test_users : Optional [Dict [str , str ]] = None ) -> ExperimentationFlag :
20+ test_users : Optional [Dict [str , str ]] = None ,
21+ experiment_id : Optional [str ] = None ,
22+ is_experiment_active : Optional [bool ] = None ,
23+ variant_splits : Optional [Dict [str , float ]] = None ) -> ExperimentationFlag :
2024
2125 if variants is None :
2226 variants = [
@@ -27,7 +31,8 @@ def create_test_flag(
2731 rollouts = [Rollout (
2832 rollout_percentage = rollout_percentage ,
2933 runtime_evaluation_definition = runtime_evaluation ,
30- variant_override = variant_override
34+ variant_override = variant_override ,
35+ variant_splits = variant_splits
3136 )]
3237
3338 test_config = None
@@ -47,7 +52,9 @@ def create_test_flag(
4752 status = "active" ,
4853 project_id = 123 ,
4954 ruleset = ruleset ,
50- context = context
55+ context = context ,
56+ experiment_id = experiment_id ,
57+ is_experiment_active = is_experiment_active
5158 )
5259
5360
@@ -216,6 +223,32 @@ async def test_get_variant_value_picks_correct_variant_with_hundred_percent_spli
216223 result = self ._flags .get_variant_value ("test_flag" , "fallback" , {"distinct_id" : "user123" })
217224 assert result == "variant_a"
218225
226+ @respx .mock
227+ async def test_get_variant_value_picks_correct_variant_with_half_migrated_group_splits (self ):
228+ variants = [
229+ Variant (key = "A" , value = "variant_a" , is_control = False , split = 100.0 ),
230+ Variant (key = "B" , value = "variant_b" , is_control = False , split = 0.0 ),
231+ Variant (key = "C" , value = "variant_c" , is_control = False , split = 0.0 )
232+ ]
233+ variant_splits = {"A" : 0.0 , "B" : 100.0 , "C" : 0.0 }
234+ flag = create_test_flag (variants = variants , rollout_percentage = 100.0 , variant_splits = variant_splits )
235+ await self .setup_flags ([flag ])
236+ result = self ._flags .get_variant_value ("test_flag" , "fallback" , {"distinct_id" : "user123" })
237+ assert result == "variant_b"
238+
239+ @respx .mock
240+ async def test_get_variant_value_picks_correct_variant_with_full_migrated_group_splits (self ):
241+ variants = [
242+ Variant (key = "A" , value = "variant_a" , is_control = False ),
243+ Variant (key = "B" , value = "variant_b" , is_control = False ),
244+ Variant (key = "C" , value = "variant_c" , is_control = False ),
245+ ]
246+ variant_splits = {"A" : 0.0 , "B" : 0.0 , "C" : 100.0 }
247+ flag = create_test_flag (variants = variants , rollout_percentage = 100.0 , variant_splits = variant_splits )
248+ await self .setup_flags ([flag ])
249+ result = self ._flags .get_variant_value ("test_flag" , "fallback" , {"distinct_id" : "user123" })
250+ assert result == "variant_c"
251+
219252 @respx .mock
220253 async def test_get_variant_value_picks_overriden_variant (self ):
221254 variants = [
@@ -236,6 +269,43 @@ async def test_get_variant_value_tracks_exposure_when_variant_selected(self):
236269 _ = self ._flags .get_variant_value ("test_flag" , "fallback" , {"distinct_id" : "user123" })
237270 self ._mock_tracker .assert_called_once ()
238271
272+ @respx .mock
273+ @pytest .mark .parametrize ("experiment_id,is_experiment_active,use_qa_user" , [
274+ ("exp-123" , True , True ), # QA tester with active experiment
275+ ("exp-456" , False , True ), # QA tester with inactive experiment
276+ ("exp-789" , True , False ), # Regular user with active experiment
277+ ("exp-000" , False , False ), # Regular user with inactive experiment
278+ (None , None , True ), # QA tester with no experiment
279+ (None , None , False ), # Regular user with no experiment
280+ ])
281+ async def test_get_variant_value_tracks_exposure_with_correct_properties (self , experiment_id , is_experiment_active , use_qa_user ):
282+ flag = create_test_flag (
283+ experiment_id = experiment_id ,
284+ is_experiment_active = is_experiment_active ,
285+ test_users = {"qa_user" : "treatment" }
286+ )
287+
288+ await self .setup_flags ([flag ])
289+
290+ distinct_id = "qa_user" if use_qa_user else "regular_user"
291+
292+ with patch ('mixpanel.flags.utils.normalized_hash' ) as mock_hash :
293+ mock_hash .return_value = 0.5
294+ _ = self ._flags .get_variant_value ("test_flag" , "fallback" , {"distinct_id" : distinct_id })
295+
296+ self ._mock_tracker .assert_called_once ()
297+
298+ call_args = self ._mock_tracker .call_args
299+ properties = call_args [0 ][2 ]
300+
301+ assert properties ["$experiment_id" ] == experiment_id
302+ assert properties ["$is_experiment_active" ] == is_experiment_active
303+
304+ if use_qa_user :
305+ assert properties ["$is_qa_tester" ] == True
306+ else :
307+ assert properties .get ("$is_qa_tester" ) is None
308+
239309 @respx .mock
240310 async def test_get_variant_value_does_not_track_exposure_on_fallback (self ):
241311 await self .setup_flags ([])
0 commit comments