Skip to content

Commit f1f198c

Browse files
committed
integration e2e tests
1 parent 3419e0e commit f1f198c

File tree

1 file changed

+329
-1
lines changed

1 file changed

+329
-1
lines changed

tests/integration/test_client_e2e.py

Lines changed: 329 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
InMemorySegmentStorage, InMemorySplitStorage, InMemoryTelemetryStorage
1616
from splitio.storage.redis import RedisEventsStorage, RedisImpressionsStorage, \
1717
RedisSplitStorage, RedisSegmentStorage, RedisTelemetryStorage
18+
from splitio.storage.pluggable import PluggableEventsStorage, PluggableImpressionsStorage, PluggableSegmentStorage, \
19+
PluggableTelemetryStorage, PluggableSplitStorage
1820
from splitio.storage.adapters.redis import build, RedisAdapter
1921
from splitio.models import splits, segments
2022
from splitio.engine.impressions.impressions import Manager as ImpressionsManager, ImpressionsMode
@@ -25,8 +27,13 @@
2527
from splitio.recorder.recorder import StandardRecorder, PipelinedRecorder
2628
from splitio.client.config import DEFAULT_CONFIG
2729
from splitio.sync.synchronizer import SplitTasks, SplitSynchronizers, Synchronizer
28-
from splitio.sync.manager import Manager
30+
from splitio.sync.manager import Manager, RedisManager
31+
from splitio.sync.synchronizer import PluggableSynchronizer
32+
2933
from tests.integration import splits_json
34+
from tests.storage.test_pluggable import StorageMockAdapter
35+
36+
3037
class InMemoryIntegrationTests(object):
3138
"""Inmemory storage-based integration tests."""
3239

@@ -1124,3 +1131,324 @@ def test_localhost_e2e(self):
11241131
event = threading.Event()
11251132
factory.destroy(event)
11261133
event.wait()
1134+
1135+
1136+
class PluggableIntegrationTests(object):
1137+
"""Pluggable storage-based integration tests."""
1138+
1139+
def setup_method(self):
1140+
"""Prepare storages with test data."""
1141+
metadata = SdkMetadata('python-1.2.3', 'some_ip', 'some_name')
1142+
self.pluggable_storage_adapter = StorageMockAdapter()
1143+
split_storage = PluggableSplitStorage(self.pluggable_storage_adapter, 'myprefix')
1144+
segment_storage = PluggableSegmentStorage(self.pluggable_storage_adapter, 'myprefix')
1145+
1146+
telemetry_pluggable_storage = PluggableTelemetryStorage(self.pluggable_storage_adapter, metadata, 'myprefix')
1147+
telemetry_producer = TelemetryStorageProducer(telemetry_pluggable_storage)
1148+
telemetry_consumer = TelemetryStorageConsumer(telemetry_pluggable_storage)
1149+
telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer()
1150+
1151+
storages = {
1152+
'splits': split_storage,
1153+
'segments': segment_storage,
1154+
'impressions': PluggableImpressionsStorage(self.pluggable_storage_adapter, metadata),
1155+
'events': PluggableEventsStorage(self.pluggable_storage_adapter, metadata),
1156+
'telemetry': telemetry_pluggable_storage
1157+
}
1158+
1159+
impmanager = ImpressionsManager(StrategyDebugMode(), telemetry_runtime_producer) # no listener
1160+
recorder = StandardRecorder(impmanager, storages['events'],
1161+
storages['impressions'], storages['telemetry'])
1162+
1163+
self.factory = SplitFactory('some_api_key',
1164+
storages,
1165+
True,
1166+
recorder,
1167+
RedisManager(PluggableSynchronizer()),
1168+
sdk_ready_flag=None,
1169+
telemetry_producer=telemetry_producer,
1170+
telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(),
1171+
) # pylint:disable=attribute-defined-outside-init
1172+
1173+
# Adding data to storage
1174+
split_fn = os.path.join(os.path.dirname(__file__), 'files', 'splitChanges.json')
1175+
with open(split_fn, 'r') as flo:
1176+
data = json.loads(flo.read())
1177+
for split in data['splits']:
1178+
self.pluggable_storage_adapter.set(split_storage._prefix.format(split_name=split['name']), split)
1179+
self.pluggable_storage_adapter.set(split_storage._split_till_prefix, data['till'])
1180+
1181+
segment_fn = os.path.join(os.path.dirname(__file__), 'files', 'segmentEmployeesChanges.json')
1182+
with open(segment_fn, 'r') as flo:
1183+
data = json.loads(flo.read())
1184+
self.pluggable_storage_adapter.set(segment_storage._prefix.format(segment_name=data['name']), set(data['added']))
1185+
self.pluggable_storage_adapter.set(segment_storage._segment_till_prefix.format(segment_name=data['name']), data['till'])
1186+
1187+
segment_fn = os.path.join(os.path.dirname(__file__), 'files', 'segmentHumanBeignsChanges.json')
1188+
with open(segment_fn, 'r') as flo:
1189+
data = json.loads(flo.read())
1190+
self.pluggable_storage_adapter.set(segment_storage._prefix.format(segment_name=data['name']), set(data['added']))
1191+
self.pluggable_storage_adapter.set(segment_storage._segment_till_prefix.format(segment_name=data['name']), data['till'])
1192+
1193+
1194+
def _validate_last_events(self, client, *to_validate):
1195+
"""Validate the last N impressions are present disregarding the order."""
1196+
event_storage = client._factory._get_storage('events')
1197+
events_raw = []
1198+
if self.pluggable_storage_adapter.get(event_storage._events_queue_key) is not None:
1199+
events_raw = [json.loads(im) for im in self.pluggable_storage_adapter.get(event_storage._events_queue_key)]
1200+
1201+
as_tup_set = set(
1202+
(i['e']['key'], i['e']['trafficTypeName'], i['e']['eventTypeId'], i['e']['value'], str(i['e']['properties']))
1203+
for i in events_raw
1204+
)
1205+
assert as_tup_set == set(to_validate)
1206+
1207+
def _validate_last_impressions(self, client, *to_validate):
1208+
"""Validate the last N impressions are present disregarding the order."""
1209+
imp_storage = client._factory._get_storage('impressions')
1210+
impressions_raw = []
1211+
if self.pluggable_storage_adapter.get(imp_storage._impressions_queue_key) is not None:
1212+
impressions_raw = [json.loads(im) for im in self.pluggable_storage_adapter.get(imp_storage._impressions_queue_key)]
1213+
as_tup_set = set(
1214+
(i['i']['f'], i['i']['k'], i['i']['t'])
1215+
for i in impressions_raw
1216+
)
1217+
1218+
assert as_tup_set == set(to_validate)
1219+
self.pluggable_storage_adapter.delete(imp_storage._impressions_queue_key)
1220+
1221+
def test_get_treatment(self):
1222+
"""Test client.get_treatment()."""
1223+
client = self.factory.client()
1224+
1225+
assert client.get_treatment('user1', 'sample_feature') == 'on'
1226+
self._validate_last_impressions(client, ('sample_feature', 'user1', 'on'))
1227+
1228+
assert client.get_treatment('invalidKey', 'sample_feature') == 'off'
1229+
self._validate_last_impressions(client, ('sample_feature', 'invalidKey', 'off'))
1230+
1231+
assert client.get_treatment('invalidKey', 'invalid_feature') == 'control'
1232+
self._validate_last_impressions(client)
1233+
1234+
# testing a killed feature. No matter what the key, must return default treatment
1235+
assert client.get_treatment('invalidKey', 'killed_feature') == 'defTreatment'
1236+
self._validate_last_impressions(client, ('killed_feature', 'invalidKey', 'defTreatment'))
1237+
1238+
# testing ALL matcher
1239+
assert client.get_treatment('invalidKey', 'all_feature') == 'on'
1240+
self._validate_last_impressions(client, ('all_feature', 'invalidKey', 'on'))
1241+
1242+
# testing WHITELIST matcher
1243+
assert client.get_treatment('whitelisted_user', 'whitelist_feature') == 'on'
1244+
self._validate_last_impressions(client, ('whitelist_feature', 'whitelisted_user', 'on'))
1245+
assert client.get_treatment('unwhitelisted_user', 'whitelist_feature') == 'off'
1246+
self._validate_last_impressions(client, ('whitelist_feature', 'unwhitelisted_user', 'off'))
1247+
1248+
# testing INVALID matcher
1249+
assert client.get_treatment('some_user_key', 'invalid_matcher_feature') == 'control'
1250+
self._validate_last_impressions(client)
1251+
1252+
# testing Dependency matcher
1253+
assert client.get_treatment('somekey', 'dependency_test') == 'off'
1254+
self._validate_last_impressions(client, ('dependency_test', 'somekey', 'off'))
1255+
1256+
# testing boolean matcher
1257+
assert client.get_treatment('True', 'boolean_test') == 'on'
1258+
self._validate_last_impressions(client, ('boolean_test', 'True', 'on'))
1259+
1260+
# testing regex matcher
1261+
assert client.get_treatment('abc4', 'regex_test') == 'on'
1262+
self._validate_last_impressions(client, ('regex_test', 'abc4', 'on'))
1263+
1264+
def test_get_treatment_with_config(self):
1265+
"""Test client.get_treatment_with_config()."""
1266+
client = self.factory.client()
1267+
1268+
result = client.get_treatment_with_config('user1', 'sample_feature')
1269+
assert result == ('on', '{"size":15,"test":20}')
1270+
self._validate_last_impressions(client, ('sample_feature', 'user1', 'on'))
1271+
1272+
result = client.get_treatment_with_config('invalidKey', 'sample_feature')
1273+
assert result == ('off', None)
1274+
self._validate_last_impressions(client, ('sample_feature', 'invalidKey', 'off'))
1275+
1276+
result = client.get_treatment_with_config('invalidKey', 'invalid_feature')
1277+
assert result == ('control', None)
1278+
self._validate_last_impressions(client)
1279+
1280+
# testing a killed feature. No matter what the key, must return default treatment
1281+
result = client.get_treatment_with_config('invalidKey', 'killed_feature')
1282+
assert ('defTreatment', '{"size":15,"defTreatment":true}') == result
1283+
self._validate_last_impressions(client, ('killed_feature', 'invalidKey', 'defTreatment'))
1284+
1285+
# testing ALL matcher
1286+
result = client.get_treatment_with_config('invalidKey', 'all_feature')
1287+
assert result == ('on', None)
1288+
self._validate_last_impressions(client, ('all_feature', 'invalidKey', 'on'))
1289+
1290+
def test_get_treatments(self):
1291+
"""Test client.get_treatments()."""
1292+
client = self.factory.client()
1293+
1294+
result = client.get_treatments('user1', ['sample_feature'])
1295+
assert len(result) == 1
1296+
assert result['sample_feature'] == 'on'
1297+
self._validate_last_impressions(client, ('sample_feature', 'user1', 'on'))
1298+
1299+
result = client.get_treatments('invalidKey', ['sample_feature'])
1300+
assert len(result) == 1
1301+
assert result['sample_feature'] == 'off'
1302+
self._validate_last_impressions(client, ('sample_feature', 'invalidKey', 'off'))
1303+
1304+
result = client.get_treatments('invalidKey', ['invalid_feature'])
1305+
assert len(result) == 1
1306+
assert result['invalid_feature'] == 'control'
1307+
self._validate_last_impressions(client)
1308+
1309+
# testing a killed feature. No matter what the key, must return default treatment
1310+
result = client.get_treatments('invalidKey', ['killed_feature'])
1311+
assert len(result) == 1
1312+
assert result['killed_feature'] == 'defTreatment'
1313+
self._validate_last_impressions(client, ('killed_feature', 'invalidKey', 'defTreatment'))
1314+
1315+
# testing ALL matcher
1316+
result = client.get_treatments('invalidKey', ['all_feature'])
1317+
assert len(result) == 1
1318+
assert result['all_feature'] == 'on'
1319+
self._validate_last_impressions(client, ('all_feature', 'invalidKey', 'on'))
1320+
1321+
# testing multiple splitNames
1322+
result = client.get_treatments('invalidKey', [
1323+
'all_feature',
1324+
'killed_feature',
1325+
'invalid_feature',
1326+
'sample_feature'
1327+
])
1328+
assert len(result) == 4
1329+
assert result['all_feature'] == 'on'
1330+
assert result['killed_feature'] == 'defTreatment'
1331+
assert result['invalid_feature'] == 'control'
1332+
assert result['sample_feature'] == 'off'
1333+
self._validate_last_impressions(
1334+
client,
1335+
('all_feature', 'invalidKey', 'on'),
1336+
('killed_feature', 'invalidKey', 'defTreatment'),
1337+
('sample_feature', 'invalidKey', 'off')
1338+
)
1339+
1340+
def test_get_treatments_with_config(self):
1341+
"""Test client.get_treatments_with_config()."""
1342+
client = self.factory.client()
1343+
1344+
result = client.get_treatments_with_config('user1', ['sample_feature'])
1345+
assert len(result) == 1
1346+
assert result['sample_feature'] == ('on', '{"size":15,"test":20}')
1347+
self._validate_last_impressions(client, ('sample_feature', 'user1', 'on'))
1348+
1349+
result = client.get_treatments_with_config('invalidKey', ['sample_feature'])
1350+
assert len(result) == 1
1351+
assert result['sample_feature'] == ('off', None)
1352+
self._validate_last_impressions(client, ('sample_feature', 'invalidKey', 'off'))
1353+
1354+
result = client.get_treatments_with_config('invalidKey', ['invalid_feature'])
1355+
assert len(result) == 1
1356+
assert result['invalid_feature'] == ('control', None)
1357+
self._validate_last_impressions(client)
1358+
1359+
# testing a killed feature. No matter what the key, must return default treatment
1360+
result = client.get_treatments_with_config('invalidKey', ['killed_feature'])
1361+
assert len(result) == 1
1362+
assert result['killed_feature'] == ('defTreatment', '{"size":15,"defTreatment":true}')
1363+
self._validate_last_impressions(client, ('killed_feature', 'invalidKey', 'defTreatment'))
1364+
1365+
# testing ALL matcher
1366+
result = client.get_treatments_with_config('invalidKey', ['all_feature'])
1367+
assert len(result) == 1
1368+
assert result['all_feature'] == ('on', None)
1369+
self._validate_last_impressions(client, ('all_feature', 'invalidKey', 'on'))
1370+
1371+
# testing multiple splitNames
1372+
result = client.get_treatments_with_config('invalidKey', [
1373+
'all_feature',
1374+
'killed_feature',
1375+
'invalid_feature',
1376+
'sample_feature'
1377+
])
1378+
assert len(result) == 4
1379+
assert result['all_feature'] == ('on', None)
1380+
assert result['killed_feature'] == ('defTreatment', '{"size":15,"defTreatment":true}')
1381+
assert result['invalid_feature'] == ('control', None)
1382+
assert result['sample_feature'] == ('off', None)
1383+
self._validate_last_impressions(
1384+
client,
1385+
('all_feature', 'invalidKey', 'on'),
1386+
('killed_feature', 'invalidKey', 'defTreatment'),
1387+
('sample_feature', 'invalidKey', 'off'),
1388+
)
1389+
1390+
def test_track(self):
1391+
"""Test client.track()."""
1392+
client = self.factory.client()
1393+
assert(client.track('user1', 'user', 'conversion', 1, {"prop1": "value1"}))
1394+
assert(not client.track(None, 'user', 'conversion'))
1395+
assert(not client.track('user1', None, 'conversion'))
1396+
assert(not client.track('user1', 'user', None))
1397+
self._validate_last_events(
1398+
client,
1399+
('user1', 'user', 'conversion', 1, "{'prop1': 'value1'}")
1400+
)
1401+
1402+
def test_manager_methods(self):
1403+
"""Test manager.split/splits."""
1404+
try:
1405+
manager = self.factory.manager()
1406+
except:
1407+
pass
1408+
result = manager.split('all_feature')
1409+
assert result.name == 'all_feature'
1410+
assert result.traffic_type is None
1411+
assert result.killed is False
1412+
assert len(result.treatments) == 2
1413+
assert result.change_number == 123
1414+
assert result.configs == {}
1415+
1416+
result = manager.split('killed_feature')
1417+
assert result.name == 'killed_feature'
1418+
assert result.traffic_type is None
1419+
assert result.killed is True
1420+
assert len(result.treatments) == 2
1421+
assert result.change_number == 123
1422+
assert result.configs['defTreatment'] == '{"size":15,"defTreatment":true}'
1423+
assert result.configs['off'] == '{"size":15,"test":20}'
1424+
1425+
result = manager.split('sample_feature')
1426+
assert result.name == 'sample_feature'
1427+
assert result.traffic_type is None
1428+
assert result.killed is False
1429+
assert len(result.treatments) == 2
1430+
assert result.change_number == 123
1431+
assert result.configs['on'] == '{"size":15,"test":20}'
1432+
1433+
assert len(manager.split_names()) == 7
1434+
assert len(manager.splits()) == 7
1435+
1436+
def teardown_method(self):
1437+
"""Clear redis cache."""
1438+
keys_to_delete = [
1439+
"SPLITIO.segment.human_beigns",
1440+
"SPLITIO.segment.employees.till",
1441+
"SPLITIO.split.sample_feature",
1442+
"SPLITIO.splits.till",
1443+
"SPLITIO.split.killed_feature",
1444+
"SPLITIO.split.all_feature",
1445+
"SPLITIO.split.whitelist_feature",
1446+
"SPLITIO.segment.employees",
1447+
"SPLITIO.split.regex_test",
1448+
"SPLITIO.segment.human_beigns.till",
1449+
"SPLITIO.split.boolean_test",
1450+
"SPLITIO.split.dependency_test"
1451+
]
1452+
1453+
for key in keys_to_delete:
1454+
self.pluggable_storage_adapter.delete(key)

0 commit comments

Comments
 (0)