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