@@ -1452,3 +1452,272 @@ def teardown_method(self):
14521452
14531453 for key in keys_to_delete :
14541454 self .pluggable_storage_adapter .delete (key )
1455+
1456+ class PluggableOptimizedIntegrationTests (object ):
1457+ """Pluggable storage-based integration tests."""
1458+
1459+ def setup_method (self ):
1460+ """Prepare storages with test data."""
1461+ metadata = SdkMetadata ('python-1.2.3' , 'some_ip' , 'some_name' )
1462+ self .pluggable_storage_adapter = StorageMockAdapter ()
1463+ split_storage = PluggableSplitStorage (self .pluggable_storage_adapter , 'myprefix' )
1464+ segment_storage = PluggableSegmentStorage (self .pluggable_storage_adapter , 'myprefix' )
1465+
1466+ telemetry_pluggable_storage = PluggableTelemetryStorage (self .pluggable_storage_adapter , metadata , 'myprefix' )
1467+ telemetry_producer = TelemetryStorageProducer (telemetry_pluggable_storage )
1468+ telemetry_consumer = TelemetryStorageConsumer (telemetry_pluggable_storage )
1469+ telemetry_runtime_producer = telemetry_producer .get_telemetry_runtime_producer ()
1470+
1471+ storages = {
1472+ 'splits' : split_storage ,
1473+ 'segments' : segment_storage ,
1474+ 'impressions' : PluggableImpressionsStorage (self .pluggable_storage_adapter , metadata , 'myprefix' ),
1475+ 'events' : PluggableEventsStorage (self .pluggable_storage_adapter , metadata , 'myprefix' ),
1476+ 'telemetry' : telemetry_pluggable_storage
1477+ }
1478+
1479+ impmanager = ImpressionsManager (StrategyOptimizedMode (ImpressionsCounter ()), telemetry_runtime_producer ) # no listener
1480+ recorder = StandardRecorder (impmanager , storages ['events' ],
1481+ storages ['impressions' ], storages ['telemetry' ])
1482+
1483+ self .factory = SplitFactory ('some_api_key' ,
1484+ storages ,
1485+ True ,
1486+ recorder ,
1487+ RedisManager (PluggableSynchronizer ()),
1488+ sdk_ready_flag = None ,
1489+ telemetry_producer = telemetry_producer ,
1490+ telemetry_init_producer = telemetry_producer .get_telemetry_init_producer (),
1491+ ) # pylint:disable=attribute-defined-outside-init
1492+
1493+ # Adding data to storage
1494+ split_fn = os .path .join (os .path .dirname (__file__ ), 'files' , 'splitChanges.json' )
1495+ with open (split_fn , 'r' ) as flo :
1496+ data = json .loads (flo .read ())
1497+ for split in data ['splits' ]:
1498+ self .pluggable_storage_adapter .set (split_storage ._prefix .format (split_name = split ['name' ]), split )
1499+ self .pluggable_storage_adapter .set (split_storage ._split_till_prefix , data ['till' ])
1500+
1501+ segment_fn = os .path .join (os .path .dirname (__file__ ), 'files' , 'segmentEmployeesChanges.json' )
1502+ with open (segment_fn , 'r' ) as flo :
1503+ data = json .loads (flo .read ())
1504+ self .pluggable_storage_adapter .set (segment_storage ._prefix .format (segment_name = data ['name' ]), set (data ['added' ]))
1505+ self .pluggable_storage_adapter .set (segment_storage ._segment_till_prefix .format (segment_name = data ['name' ]), data ['till' ])
1506+
1507+ segment_fn = os .path .join (os .path .dirname (__file__ ), 'files' , 'segmentHumanBeignsChanges.json' )
1508+ with open (segment_fn , 'r' ) as flo :
1509+ data = json .loads (flo .read ())
1510+ self .pluggable_storage_adapter .set (segment_storage ._prefix .format (segment_name = data ['name' ]), set (data ['added' ]))
1511+ self .pluggable_storage_adapter .set (segment_storage ._segment_till_prefix .format (segment_name = data ['name' ]), data ['till' ])
1512+
1513+ def _validate_last_events (self , client , * to_validate ):
1514+ """Validate the last N impressions are present disregarding the order."""
1515+ event_storage = client ._factory ._get_storage ('events' )
1516+ events_raw = []
1517+ stored_events = self .pluggable_storage_adapter .pop_items (event_storage ._events_queue_key )
1518+ if stored_events is not None :
1519+ events_raw = [json .loads (im ) for im in stored_events ]
1520+
1521+ as_tup_set = set (
1522+ (i ['e' ]['key' ], i ['e' ]['trafficTypeName' ], i ['e' ]['eventTypeId' ], i ['e' ]['value' ], str (i ['e' ]['properties' ]))
1523+ for i in events_raw
1524+ )
1525+ assert as_tup_set == set (to_validate )
1526+
1527+ def _validate_last_impressions (self , client , * to_validate ):
1528+ """Validate the last N impressions are present disregarding the order."""
1529+ imp_storage = client ._factory ._get_storage ('impressions' )
1530+ impressions_raw = []
1531+ stored_impressions = self .pluggable_storage_adapter .pop_items (imp_storage ._impressions_queue_key )
1532+ if stored_impressions is not None :
1533+ impressions_raw = [json .loads (im ) for im in stored_impressions ]
1534+ as_tup_set = set (
1535+ (i ['i' ]['f' ], i ['i' ]['k' ], i ['i' ]['t' ])
1536+ for i in impressions_raw
1537+ )
1538+
1539+ assert as_tup_set == set (to_validate )
1540+
1541+ def test_get_treatment (self ):
1542+ """Test client.get_treatment()."""
1543+ client = self .factory .client ()
1544+
1545+ assert client .get_treatment ('user1' , 'sample_feature' ) == 'on'
1546+ self ._validate_last_impressions (client , ('sample_feature' , 'user1' , 'on' ))
1547+ client .get_treatment ('user1' , 'sample_feature' )
1548+ client .get_treatment ('user1' , 'sample_feature' )
1549+ client .get_treatment ('user1' , 'sample_feature' )
1550+
1551+ # Only one impression was added, and popped when validating, the rest were ignored
1552+ # pytest.set_trace()
1553+ assert self .pluggable_storage_adapter ._keys ['myprefix.SPLITIO.impressions' ] == []
1554+
1555+ assert client .get_treatment ('invalidKey' , 'sample_feature' ) == 'off'
1556+ self ._validate_last_impressions (client , ('sample_feature' , 'invalidKey' , 'off' ))
1557+
1558+ assert client .get_treatment ('invalidKey' , 'invalid_feature' ) == 'control'
1559+ self ._validate_last_impressions (client ) # No impressions should be present
1560+
1561+ # testing a killed feature. No matter what the key, must return default treatment
1562+ assert client .get_treatment ('invalidKey' , 'killed_feature' ) == 'defTreatment'
1563+ self ._validate_last_impressions (client , ('killed_feature' , 'invalidKey' , 'defTreatment' ))
1564+
1565+ # testing ALL matcher
1566+ assert client .get_treatment ('invalidKey' , 'all_feature' ) == 'on'
1567+ self ._validate_last_impressions (client , ('all_feature' , 'invalidKey' , 'on' ))
1568+
1569+ # testing WHITELIST matcher
1570+ assert client .get_treatment ('whitelisted_user' , 'whitelist_feature' ) == 'on'
1571+ self ._validate_last_impressions (client , ('whitelist_feature' , 'whitelisted_user' , 'on' ))
1572+ assert client .get_treatment ('unwhitelisted_user' , 'whitelist_feature' ) == 'off'
1573+ self ._validate_last_impressions (client , ('whitelist_feature' , 'unwhitelisted_user' , 'off' ))
1574+
1575+ # testing INVALID matcher
1576+ assert client .get_treatment ('some_user_key' , 'invalid_matcher_feature' ) == 'control'
1577+ self ._validate_last_impressions (client ) # No impressions should be present
1578+
1579+ # testing Dependency matcher
1580+ assert client .get_treatment ('somekey' , 'dependency_test' ) == 'off'
1581+ self ._validate_last_impressions (client , ('dependency_test' , 'somekey' , 'off' ))
1582+
1583+ # testing boolean matcher
1584+ assert client .get_treatment ('True' , 'boolean_test' ) == 'on'
1585+ self ._validate_last_impressions (client , ('boolean_test' , 'True' , 'on' ))
1586+
1587+ # testing regex matcher
1588+ assert client .get_treatment ('abc4' , 'regex_test' ) == 'on'
1589+ self ._validate_last_impressions (client , ('regex_test' , 'abc4' , 'on' ))
1590+
1591+ def test_get_treatments (self ):
1592+ """Test client.get_treatments()."""
1593+ client = self .factory .client ()
1594+
1595+ result = client .get_treatments ('user1' , ['sample_feature' ])
1596+ assert len (result ) == 1
1597+ assert result ['sample_feature' ] == 'on'
1598+ self ._validate_last_impressions (client , ('sample_feature' , 'user1' , 'on' ))
1599+
1600+ result = client .get_treatments ('invalidKey' , ['sample_feature' ])
1601+ assert len (result ) == 1
1602+ assert result ['sample_feature' ] == 'off'
1603+ self ._validate_last_impressions (client , ('sample_feature' , 'invalidKey' , 'off' ))
1604+
1605+ result = client .get_treatments ('invalidKey' , ['invalid_feature' ])
1606+ assert len (result ) == 1
1607+ assert result ['invalid_feature' ] == 'control'
1608+ self ._validate_last_impressions (client )
1609+
1610+ # testing a killed feature. No matter what the key, must return default treatment
1611+ result = client .get_treatments ('invalidKey' , ['killed_feature' ])
1612+ assert len (result ) == 1
1613+ assert result ['killed_feature' ] == 'defTreatment'
1614+ self ._validate_last_impressions (client , ('killed_feature' , 'invalidKey' , 'defTreatment' ))
1615+
1616+ # testing ALL matcher
1617+ result = client .get_treatments ('invalidKey' , ['all_feature' ])
1618+ assert len (result ) == 1
1619+ assert result ['all_feature' ] == 'on'
1620+ self ._validate_last_impressions (client , ('all_feature' , 'invalidKey' , 'on' ))
1621+
1622+ # testing multiple splitNames
1623+ result = client .get_treatments ('invalidKey' , [
1624+ 'all_feature' ,
1625+ 'killed_feature' ,
1626+ 'invalid_feature' ,
1627+ 'sample_feature'
1628+ ])
1629+ assert len (result ) == 4
1630+ assert result ['all_feature' ] == 'on'
1631+ assert result ['killed_feature' ] == 'defTreatment'
1632+ assert result ['invalid_feature' ] == 'control'
1633+ assert result ['sample_feature' ] == 'off'
1634+ assert self .pluggable_storage_adapter ._keys ['myprefix.SPLITIO.impressions' ] == []
1635+
1636+ def test_get_treatments_with_config (self ):
1637+ """Test client.get_treatments_with_config()."""
1638+ client = self .factory .client ()
1639+
1640+ result = client .get_treatments_with_config ('user1' , ['sample_feature' ])
1641+ assert len (result ) == 1
1642+ assert result ['sample_feature' ] == ('on' , '{"size":15,"test":20}' )
1643+ self ._validate_last_impressions (client , ('sample_feature' , 'user1' , 'on' ))
1644+
1645+ result = client .get_treatments_with_config ('invalidKey' , ['sample_feature' ])
1646+ assert len (result ) == 1
1647+ assert result ['sample_feature' ] == ('off' , None )
1648+ self ._validate_last_impressions (client , ('sample_feature' , 'invalidKey' , 'off' ))
1649+
1650+ result = client .get_treatments_with_config ('invalidKey' , ['invalid_feature' ])
1651+ assert len (result ) == 1
1652+ assert result ['invalid_feature' ] == ('control' , None )
1653+ self ._validate_last_impressions (client )
1654+
1655+ # testing a killed feature. No matter what the key, must return default treatment
1656+ result = client .get_treatments_with_config ('invalidKey' , ['killed_feature' ])
1657+ assert len (result ) == 1
1658+ assert result ['killed_feature' ] == ('defTreatment' , '{"size":15,"defTreatment":true}' )
1659+ self ._validate_last_impressions (client , ('killed_feature' , 'invalidKey' , 'defTreatment' ))
1660+
1661+ # testing ALL matcher
1662+ result = client .get_treatments_with_config ('invalidKey' , ['all_feature' ])
1663+ assert len (result ) == 1
1664+ assert result ['all_feature' ] == ('on' , None )
1665+ self ._validate_last_impressions (client , ('all_feature' , 'invalidKey' , 'on' ))
1666+
1667+ # testing multiple splitNames
1668+ result = client .get_treatments_with_config ('invalidKey' , [
1669+ 'all_feature' ,
1670+ 'killed_feature' ,
1671+ 'invalid_feature' ,
1672+ 'sample_feature'
1673+ ])
1674+ assert len (result ) == 4
1675+
1676+ assert result ['all_feature' ] == ('on' , None )
1677+ assert result ['killed_feature' ] == ('defTreatment' , '{"size":15,"defTreatment":true}' )
1678+ assert result ['invalid_feature' ] == ('control' , None )
1679+ assert result ['sample_feature' ] == ('off' , None )
1680+ assert self .pluggable_storage_adapter ._keys ['myprefix.SPLITIO.impressions' ] == []
1681+
1682+ def test_manager_methods (self ):
1683+ """Test manager.split/splits."""
1684+ manager = self .factory .manager ()
1685+ result = manager .split ('all_feature' )
1686+ assert result .name == 'all_feature'
1687+ assert result .traffic_type is None
1688+ assert result .killed is False
1689+ assert len (result .treatments ) == 2
1690+ assert result .change_number == 123
1691+ assert result .configs == {}
1692+
1693+ result = manager .split ('killed_feature' )
1694+ assert result .name == 'killed_feature'
1695+ assert result .traffic_type is None
1696+ assert result .killed is True
1697+ assert len (result .treatments ) == 2
1698+ assert result .change_number == 123
1699+ assert result .configs ['defTreatment' ] == '{"size":15,"defTreatment":true}'
1700+ assert result .configs ['off' ] == '{"size":15,"test":20}'
1701+
1702+ result = manager .split ('sample_feature' )
1703+ assert result .name == 'sample_feature'
1704+ assert result .traffic_type is None
1705+ assert result .killed is False
1706+ assert len (result .treatments ) == 2
1707+ assert result .change_number == 123
1708+ assert result .configs ['on' ] == '{"size":15,"test":20}'
1709+
1710+ assert len (manager .split_names ()) == 7
1711+ assert len (manager .splits ()) == 7
1712+
1713+ def test_track (self ):
1714+ """Test client.track()."""
1715+ client = self .factory .client ()
1716+ assert (client .track ('user1' , 'user' , 'conversion' , 1 , {"prop1" : "value1" }))
1717+ assert (not client .track (None , 'user' , 'conversion' ))
1718+ assert (not client .track ('user1' , None , 'conversion' ))
1719+ assert (not client .track ('user1' , 'user' , None ))
1720+ self ._validate_last_events (
1721+ client ,
1722+ ('user1' , 'user' , 'conversion' , 1 , "{'prop1': 'value1'}" )
1723+ )
0 commit comments