55import pytest
66import copy
77
8- from splitio .models .splits import Split , Status
8+ from splitio .models .splits import Split , Status , from_raw , Prerequisites
99from splitio .models import segments
1010from splitio .models .grammar .condition import Condition , ConditionType
1111from splitio .models .impressions import Label
@@ -127,6 +127,7 @@ def test_evaluate_treatment_killed_split(self, mocker):
127127 mocked_split .killed = True
128128 mocked_split .change_number = 123
129129 mocked_split .get_configurations_for .return_value = '{"some_property": 123}'
130+ mocked_split .prerequisites = []
130131
131132 ctx = EvaluationContext (flags = {'some' : mocked_split }, segment_memberships = set (), rbs_segments = {})
132133 result = e .eval_with_context ('some_key' , 'some_bucketing_key' , 'some' , {}, ctx )
@@ -146,6 +147,8 @@ def test_evaluate_treatment_ok(self, mocker):
146147 mocked_split .killed = False
147148 mocked_split .change_number = 123
148149 mocked_split .get_configurations_for .return_value = '{"some_property": 123}'
150+ mocked_split .prerequisites = []
151+
149152 ctx = EvaluationContext (flags = {'some' : mocked_split }, segment_memberships = set (), rbs_segments = {})
150153 result = e .eval_with_context ('some_key' , 'some_bucketing_key' , 'some' , {}, ctx )
151154 assert result ['treatment' ] == 'on'
@@ -165,6 +168,8 @@ def test_evaluate_treatment_ok_no_config(self, mocker):
165168 mocked_split .killed = False
166169 mocked_split .change_number = 123
167170 mocked_split .get_configurations_for .return_value = None
171+ mocked_split .prerequisites = []
172+
168173 ctx = EvaluationContext (flags = {'some' : mocked_split }, segment_memberships = set (), rbs_segments = {})
169174 result = e .eval_with_context ('some_key' , 'some_bucketing_key' , 'some' , {}, ctx )
170175 assert result ['treatment' ] == 'on'
@@ -184,13 +189,15 @@ def test_evaluate_treatments(self, mocker):
184189 mocked_split .killed = False
185190 mocked_split .change_number = 123
186191 mocked_split .get_configurations_for .return_value = '{"some_property": 123}'
192+ mocked_split .prerequisites = []
187193
188194 mocked_split2 = mocker .Mock (spec = Split )
189195 mocked_split2 .name = 'feature4'
190196 mocked_split2 .default_treatment = 'on'
191197 mocked_split2 .killed = False
192198 mocked_split2 .change_number = 123
193199 mocked_split2 .get_configurations_for .return_value = None
200+ mocked_split2 .prerequisites = []
194201
195202 ctx = EvaluationContext (flags = {'feature2' : mocked_split , 'feature4' : mocked_split2 }, segment_memberships = set (), rbs_segments = {})
196203 results = e .eval_many_with_context ('some_key' , 'some_bucketing_key' , ['feature2' , 'feature4' ], {}, ctx )
@@ -215,6 +222,8 @@ def test_get_gtreatment_for_split_no_condition_matches(self, mocker):
215222 mocked_split .change_number = '123'
216223 mocked_split .conditions = []
217224 mocked_split .get_configurations_for = None
225+ mocked_split .prerequisites = []
226+
218227 ctx = EvaluationContext (flags = {'some' : mocked_split }, segment_memberships = set (), rbs_segments = {})
219228 assert e ._treatment_for_flag (mocked_split , 'some_key' , 'some_bucketing' , {}, ctx ) == (
220229 'off' ,
@@ -232,6 +241,8 @@ def test_get_gtreatment_for_split_non_rollout(self, mocker):
232241 mocked_split = mocker .Mock (spec = Split )
233242 mocked_split .killed = False
234243 mocked_split .conditions = [mocked_condition_1 ]
244+ mocked_split .prerequisites = []
245+
235246 treatment , label = e ._treatment_for_flag (mocked_split , 'some_key' , 'some_bucketing' , {}, EvaluationContext (None , None , None ))
236247 assert treatment == 'on'
237248 assert label == 'some_label'
@@ -240,7 +251,7 @@ def test_evaluate_treatment_with_rule_based_segment(self, mocker):
240251 """Test that a non-killed split returns the appropriate treatment."""
241252 e = evaluator .Evaluator (splitters .Splitter ())
242253
243- mocked_split = Split ('some' , 12345 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False )
254+ mocked_split = Split ('some' , 12345 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False , [] )
244255
245256 ctx = EvaluationContext (flags = {'some' : mocked_split }, segment_memberships = set (), rbs_segments = {'sample_rule_based_segment' : rule_based_segments .from_raw (rbs_raw )})
246257 result = e .eval_with_context ('bilal@split.io' , 'bilal@split.io' , 'some' , {'email' : 'bilal@split.io' }, ctx )
@@ -257,7 +268,7 @@ def test_evaluate_treatment_with_rbs_in_condition(self):
257268 with open (rbs_segments , 'r' ) as flo :
258269 data = json .loads (flo .read ())
259270
260- mocked_split = Split ('some' , 12345 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False )
271+ mocked_split = Split ('some' , 12345 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False , [] )
261272 rbs = rule_based_segments .from_raw (data ["rbs" ]["d" ][0 ])
262273 rbs2 = rule_based_segments .from_raw (data ["rbs" ]["d" ][1 ])
263274 rbs_storage .update ([rbs , rbs2 ], [], 12 )
@@ -279,7 +290,7 @@ def test_using_segment_in_excluded(self):
279290 segment_storage = InMemorySegmentStorage ()
280291 evaluation_facctory = EvaluationDataFactory (splits_storage , segment_storage , rbs_storage )
281292
282- mocked_split = Split ('some' , 12345 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False )
293+ mocked_split = Split ('some' , 12345 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False , [] )
283294 rbs = rule_based_segments .from_raw (data ["rbs" ]["d" ][0 ])
284295 rbs_storage .update ([rbs ], [], 12 )
285296 splits_storage .update ([mocked_split ], [], 12 )
@@ -303,7 +314,7 @@ def test_using_rbs_in_excluded(self):
303314 segment_storage = InMemorySegmentStorage ()
304315 evaluation_facctory = EvaluationDataFactory (splits_storage , segment_storage , rbs_storage )
305316
306- mocked_split = Split ('some' , 12345 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False )
317+ mocked_split = Split ('some' , 12345 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False , [] )
307318 rbs = rule_based_segments .from_raw (data ["rbs" ]["d" ][0 ])
308319 rbs2 = rule_based_segments .from_raw (data ["rbs" ]["d" ][1 ])
309320 rbs_storage .update ([rbs , rbs2 ], [], 12 )
@@ -315,7 +326,52 @@ def test_using_rbs_in_excluded(self):
315326 assert e .eval_with_context ('bilal' , 'bilal' , 'some' , {'email' : 'bilal' }, ctx )['treatment' ] == "on"
316327 ctx = evaluation_facctory .context_for ('bilal2@split.io' , ['some' ])
317328 assert e .eval_with_context ('bilal2@split.io' , 'bilal2@split.io' , 'some' , {'email' : 'bilal2@split.io' }, ctx )['treatment' ] == "off"
318-
329+
330+ def test_prerequisites (self ):
331+ splits_load = os .path .join (os .path .dirname (__file__ ), '../models/grammar/files' , 'splits_prereq.json' )
332+ with open (splits_load , 'r' ) as flo :
333+ data = json .loads (flo .read ())
334+ e = evaluator .Evaluator (splitters .Splitter ())
335+ splits_storage = InMemorySplitStorage ()
336+ rbs_storage = InMemoryRuleBasedSegmentStorage ()
337+ segment_storage = InMemorySegmentStorage ()
338+ evaluation_facctory = EvaluationDataFactory (splits_storage , segment_storage , rbs_storage )
339+
340+ rbs = rule_based_segments .from_raw (data ["rbs" ]["d" ][0 ])
341+ split1 = from_raw (data ["ff" ]["d" ][0 ])
342+ split2 = from_raw (data ["ff" ]["d" ][1 ])
343+ split3 = from_raw (data ["ff" ]["d" ][2 ])
344+ split4 = from_raw (data ["ff" ]["d" ][3 ])
345+ rbs_storage .update ([rbs ], [], 12 )
346+ splits_storage .update ([split1 , split2 , split3 , split4 ], [], 12 )
347+ segment = segments .from_raw ({'name' : 'segment-test' , 'added' : ['pato@split.io' ], 'removed' : [], 'till' : 123 })
348+ segment_storage .put (segment )
349+
350+ ctx = evaluation_facctory .context_for ('bilal@split.io' , ['test_prereq' ])
351+ assert e .eval_with_context ('bilal@split.io' , 'bilal@split.io' , 'test_prereq' , {'email' : 'bilal@split.io' }, ctx )['treatment' ] == "on"
352+ assert e .eval_with_context ('bilal@split.io' , 'bilal@split.io' , 'test_prereq' , {}, ctx )['treatment' ] == "def_treatment"
353+
354+ ctx = evaluation_facctory .context_for ('mauro@split.io' , ['test_prereq' ])
355+ assert e .eval_with_context ('mauro@split.io' , 'mauro@split.io' , 'test_prereq' , {'email' : 'mauro@split.io' }, ctx )['treatment' ] == "def_treatment"
356+
357+ ctx = evaluation_facctory .context_for ('pato@split.io' , ['test_prereq' ])
358+ assert e .eval_with_context ('pato@split.io' , 'pato@split.io' , 'test_prereq' , {'email' : 'pato@split.io' }, ctx )['treatment' ] == "def_treatment"
359+
360+ ctx = evaluation_facctory .context_for ('nico@split.io' , ['test_prereq' ])
361+ assert e .eval_with_context ('nico@split.io' , 'nico@split.io' , 'test_prereq' , {'email' : 'nico@split.io' }, ctx )['treatment' ] == "on"
362+
363+ ctx = evaluation_facctory .context_for ('bilal@split.io' , ['prereq_chain' ])
364+ assert e .eval_with_context ('bilal@split.io' , 'bilal@split.io' , 'prereq_chain' , {'email' : 'bilal@split.io' }, ctx )['treatment' ] == "on_whitelist"
365+
366+ ctx = evaluation_facctory .context_for ('nico@split.io' , ['prereq_chain' ])
367+ assert e .eval_with_context ('nico@split.io' , 'nico@split.io' , 'test_prereq' , {'email' : 'nico@split.io' }, ctx )['treatment' ] == "on"
368+
369+ ctx = evaluation_facctory .context_for ('pato@split.io' , ['prereq_chain' ])
370+ assert e .eval_with_context ('pato@split.io' , 'pato@split.io' , 'prereq_chain' , {'email' : 'pato@split.io' }, ctx )['treatment' ] == "on_default"
371+
372+ ctx = evaluation_facctory .context_for ('mauro@split.io' , ['prereq_chain' ])
373+ assert e .eval_with_context ('mauro@split.io' , 'mauro@split.io' , 'prereq_chain' , {'email' : 'mauro@split.io' }, ctx )['treatment' ] == "on_default"
374+
319375 @pytest .mark .asyncio
320376 async def test_evaluate_treatment_with_rbs_in_condition_async (self ):
321377 e = evaluator .Evaluator (splitters .Splitter ())
@@ -388,16 +444,63 @@ async def test_using_rbs_in_excluded_async(self):
388444 ctx = await evaluation_facctory .context_for ('bilal2@split.io' , ['some' ])
389445 assert e .eval_with_context ('bilal2@split.io' , 'bilal2@split.io' , 'some' , {'email' : 'bilal2@split.io' }, ctx )['treatment' ] == "off"
390446
447+ @pytest .mark .asyncio
448+ async def test_prerequisites (self ):
449+ splits_load = os .path .join (os .path .dirname (__file__ ), '../models/grammar/files' , 'splits_prereq.json' )
450+ with open (splits_load , 'r' ) as flo :
451+ data = json .loads (flo .read ())
452+ e = evaluator .Evaluator (splitters .Splitter ())
453+ splits_storage = InMemorySplitStorageAsync ()
454+ rbs_storage = InMemoryRuleBasedSegmentStorageAsync ()
455+ segment_storage = InMemorySegmentStorageAsync ()
456+ evaluation_facctory = AsyncEvaluationDataFactory (splits_storage , segment_storage , rbs_storage )
457+
458+ rbs = rule_based_segments .from_raw (data ["rbs" ]["d" ][0 ])
459+ split1 = from_raw (data ["ff" ]["d" ][0 ])
460+ split2 = from_raw (data ["ff" ]["d" ][1 ])
461+ split3 = from_raw (data ["ff" ]["d" ][2 ])
462+ split4 = from_raw (data ["ff" ]["d" ][3 ])
463+ await rbs_storage .update ([rbs ], [], 12 )
464+ await splits_storage .update ([split1 , split2 , split3 , split4 ], [], 12 )
465+ segment = segments .from_raw ({'name' : 'segment-test' , 'added' : ['pato@split.io' ], 'removed' : [], 'till' : 123 })
466+ await segment_storage .put (segment )
467+
468+ ctx = await evaluation_facctory .context_for ('bilal@split.io' , ['test_prereq' ])
469+ assert e .eval_with_context ('bilal@split.io' , 'bilal@split.io' , 'test_prereq' , {'email' : 'bilal@split.io' }, ctx )['treatment' ] == "on"
470+ assert e .eval_with_context ('bilal@split.io' , 'bilal@split.io' , 'test_prereq' , {}, ctx )['treatment' ] == "def_treatment"
471+
472+ ctx = await evaluation_facctory .context_for ('mauro@split.io' , ['test_prereq' ])
473+ assert e .eval_with_context ('mauro@split.io' , 'mauro@split.io' , 'test_prereq' , {'email' : 'mauro@split.io' }, ctx )['treatment' ] == "def_treatment"
474+
475+ ctx = await evaluation_facctory .context_for ('pato@split.io' , ['test_prereq' ])
476+ assert e .eval_with_context ('pato@split.io' , 'pato@split.io' , 'test_prereq' , {'email' : 'pato@split.io' }, ctx )['treatment' ] == "def_treatment"
477+
478+ ctx = await evaluation_facctory .context_for ('nico@split.io' , ['test_prereq' ])
479+ assert e .eval_with_context ('nico@split.io' , 'nico@split.io' , 'test_prereq' , {'email' : 'nico@split.io' }, ctx )['treatment' ] == "on"
480+
481+ ctx = await evaluation_facctory .context_for ('bilal@split.io' , ['prereq_chain' ])
482+ assert e .eval_with_context ('bilal@split.io' , 'bilal@split.io' , 'prereq_chain' , {'email' : 'bilal@split.io' }, ctx )['treatment' ] == "on_whitelist"
483+
484+ ctx = await evaluation_facctory .context_for ('nico@split.io' , ['prereq_chain' ])
485+ assert e .eval_with_context ('nico@split.io' , 'nico@split.io' , 'test_prereq' , {'email' : 'nico@split.io' }, ctx )['treatment' ] == "on"
486+
487+ ctx = await evaluation_facctory .context_for ('pato@split.io' , ['prereq_chain' ])
488+ assert e .eval_with_context ('pato@split.io' , 'pato@split.io' , 'prereq_chain' , {'email' : 'pato@split.io' }, ctx )['treatment' ] == "on_default"
489+
490+ ctx = await evaluation_facctory .context_for ('mauro@split.io' , ['prereq_chain' ])
491+ assert e .eval_with_context ('mauro@split.io' , 'mauro@split.io' , 'prereq_chain' , {'email' : 'mauro@split.io' }, ctx )['treatment' ] == "on_default"
492+
391493class EvaluationDataFactoryTests (object ):
392494 """Test evaluation factory class."""
393495
394496 def test_get_context (self ):
395497 """Test context."""
396- mocked_split = Split ('some' , 12345 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False )
498+ mocked_split = Split ('some' , 12345 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False , [Prerequisites ('split2' , ['on' ])])
499+ split2 = Split ('split2' , 12345 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False , [])
397500 flag_storage = InMemorySplitStorage ([])
398501 segment_storage = InMemorySegmentStorage ()
399502 rbs_segment_storage = InMemoryRuleBasedSegmentStorage ()
400- flag_storage .update ([mocked_split ], [], - 1 )
503+ flag_storage .update ([mocked_split , split2 ], [], - 1 )
401504 rbs = copy .deepcopy (rbs_raw )
402505 rbs ['conditions' ].append (
403506 {"matcherGroup" : {
@@ -421,6 +524,7 @@ def test_get_context(self):
421524 ec = eval_factory .context_for ('bilal@split.io' , ['some' ])
422525 assert ec .rbs_segments == {'sample_rule_based_segment' : rbs }
423526 assert ec .segment_memberships == {"employees" : False }
527+ assert ec .flags .get ("split2" ).name == "split2"
424528
425529 segment_storage .update ("employees" , {"mauro@split.io" }, {}, 1234 )
426530 ec = eval_factory .context_for ('mauro@split.io' , ['some' ])
@@ -433,11 +537,12 @@ class EvaluationDataFactoryAsyncTests(object):
433537 @pytest .mark .asyncio
434538 async def test_get_context (self ):
435539 """Test context."""
436- mocked_split = Split ('some' , 123 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False )
540+ mocked_split = Split ('some' , 12345 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False , [Prerequisites ('split2' , ['on' ])])
541+ split2 = Split ('split2' , 12345 , False , 'off' , 'user' , Status .ACTIVE , 12 , split_conditions , 1.2 , 100 , 1234 , {}, None , False , [])
437542 flag_storage = InMemorySplitStorageAsync ([])
438543 segment_storage = InMemorySegmentStorageAsync ()
439544 rbs_segment_storage = InMemoryRuleBasedSegmentStorageAsync ()
440- await flag_storage .update ([mocked_split ], [], - 1 )
545+ await flag_storage .update ([mocked_split , split2 ], [], - 1 )
441546 rbs = copy .deepcopy (rbs_raw )
442547 rbs ['conditions' ].append (
443548 {"matcherGroup" : {
@@ -461,6 +566,7 @@ async def test_get_context(self):
461566 ec = await eval_factory .context_for ('bilal@split.io' , ['some' ])
462567 assert ec .rbs_segments == {'sample_rule_based_segment' : rbs }
463568 assert ec .segment_memberships == {"employees" : False }
569+ assert ec .flags .get ("split2" ).name == "split2"
464570
465571 await segment_storage .update ("employees" , {"mauro@split.io" }, {}, 1234 )
466572 ec = await eval_factory .context_for ('mauro@split.io' , ['some' ])
0 commit comments