def test_normal_operation(self, mocker): """Test that the task works properly under normal circumstances.""" storage = mocker.Mock(spec=EventStorage) events = [ Event('key1', 'user', 'purchase', 5.3, 123456, None), Event('key2', 'user', 'purchase', 5.3, 123456, None), Event('key3', 'user', 'purchase', 5.3, 123456, None), Event('key4', 'user', 'purchase', 5.3, 123456, None), Event('key5', 'user', 'purchase', 5.3, 123456, None), ] storage.pop_many.return_value = events api = mocker.Mock(spec=EventsAPI) api.flush_events.return_value = HttpResponse(200, '') event_synchronizer = EventSynchronizer(api, storage, 5) task = events_sync.EventsSyncTask( event_synchronizer.synchronize_events, 1) task.start() time.sleep(2) assert task.is_running() assert storage.pop_many.mock_calls[0] == mocker.call(5) assert api.flush_events.mock_calls[0] == mocker.call(events) stop_event = threading.Event() calls_now = len(api.flush_events.mock_calls) task.stop(stop_event) stop_event.wait(5) assert stop_event.is_set() assert len(api.flush_events.mock_calls) > calls_now
def test_add_events(self, mocker): """Test that adding impressions to storage works.""" adapter = mocker.Mock(spec=RedisAdapter) metadata = get_metadata({}) storage = RedisEventsStorage(adapter, metadata) events = [ EventWrapper(event=Event('key1', 'user', 'purchase', 10, 123456, None), size=32768), EventWrapper(event=Event('key2', 'user', 'purchase', 10, 123456, None), size=32768), EventWrapper(event=Event('key3', 'user', 'purchase', 10, 123456, None), size=32768), EventWrapper(event=Event('key4', 'user', 'purchase', 10, 123456, None), size=32768), ] assert storage.put(events) is True list_of_raw_events = [json.dumps({ 'm': { # METADATA PORTION 's': metadata.sdk_version, 'n': metadata.instance_name, 'i': metadata.instance_ip, }, 'e': { # EVENT PORTION 'key': e.event.key, 'trafficTypeName': e.event.traffic_type_name, 'eventTypeId': e.event.event_type_id, 'value': e.event.value, 'timestamp': e.event.timestamp, 'properties': e.event.properties, } }) for e in events] # To deal with python2 & 3 differences in hashing/order when dumping json. list_of_raw_json_strings_called = adapter.rpush.mock_calls[0][1][1:] list_of_events_called = [ json.loads(event) for event in list_of_raw_json_strings_called ] list_of_events_sent = [ json.loads(event) for event in list_of_raw_events ] for item in list_of_events_sent: assert item in list_of_events_called # assert adapter.rpush.mock_calls == [mocker.call('SPLITIO.events', to_validate)] # Assert that if an exception is thrown it's caught and False is returned adapter.reset_mock() def _raise_exc(*_): raise RedisAdapterException('something') adapter.rpush.side_effect = _raise_exc assert storage.put(events) is False
def test_put_pop_events(self, mocker): """Test storing and fetching events.""" uwsgi = get_uwsgi(True) storage = UWSGIEventStorage(uwsgi) events = [ Event('key1', 'user', 'purchase', 10, 123456), Event('key2', 'user', 'purchase', 10, 123456), Event('key3', 'user', 'purchase', 10, 123456), Event('key4', 'user', 'purchase', 10, 123456), ] storage.put(events) res = storage.pop_many(10) assert res == events
def test_synchronize_events_error(self, mocker): storage = mocker.Mock(spec=EventStorage) storage.pop_many.return_value = [ Event('key1', 'user', 'purchase', 5.3, 123456, None), Event('key2', 'user', 'purchase', 5.3, 123456, None), ] api = mocker.Mock() def run(x): raise APIException("something broke") api.flush_events.side_effect = run event_synchronizer = EventSynchronizer(api, storage, 5) event_synchronizer.synchronize_events() assert event_synchronizer._failed.qsize() == 2
def test_track(self, mocker): """Test that destroy/destroyed calls are forwarded to the factory.""" split_storage = mocker.Mock(spec=SplitStorage) segment_storage = mocker.Mock(spec=SegmentStorage) impression_storage = mocker.Mock(spec=ImpressionStorage) event_storage = mocker.Mock(spec=EventStorage) event_storage.put.return_value = True def _get_storage_mock(name): return { 'splits': split_storage, 'segments': segment_storage, 'impressions': impression_storage, 'events': event_storage, }[name] factory = mocker.Mock(spec=SplitFactory) factory._get_storage = _get_storage_mock destroyed_mock = mocker.PropertyMock() destroyed_mock.return_value = False factory._waiting_fork.return_value = False type(factory).destroyed = destroyed_mock factory._apikey = 'test' mocker.patch('splitio.client.client.utctime_ms', new=lambda: 1000) impmanager = mocker.Mock(spec=ImpressionManager) recorder = StandardRecorder(impmanager, event_storage, impression_storage) client = Client(factory, recorder, True) assert client.track('key', 'user', 'purchase', 12) is True assert mocker.call([ EventWrapper( event=Event('key', 'user', 'purchase', 12, 1000, None), size=1024 ) ]) in event_storage.put.mock_calls
def track(self, key, traffic_type, event_type, value=None, properties=None): """ Track an event. :param key: user key associated to the event :type key: str :param traffic_type: traffic type name :type traffic_type: str :param event_type: event type name :type event_type: str :param value: (Optional) value associated to the event :type value: Number :param properties: (Optional) properties associated to the event :type properties: dict :return: Whether the event was created or not. :rtype: bool """ if self.destroyed: _LOGGER.error( "Client has already been destroyed - no calls possible") return False if self._factory._waiting_fork(): _LOGGER.error("Client is not ready - no calls possible") return False key = input_validator.validate_track_key(key) event_type = input_validator.validate_event_type(event_type) should_validate_existance = self.ready and self._factory._apikey != 'localhost' # pylint: disable=protected-access traffic_type = input_validator.validate_traffic_type( traffic_type, should_validate_existance, self._factory._get_storage('splits'), # pylint: disable=protected-access ) value = input_validator.validate_value(value) valid, properties, size = input_validator.valid_properties(properties) if key is None or event_type is None or traffic_type is None or value is False \ or valid is False: return False event = Event( key=key, traffic_type_name=traffic_type, event_type_id=event_type, value=value, timestamp=utctime_ms(), properties=properties, ) return self._recorder.record_track_stats( [EventWrapper( event=event, size=size, )])
def track(self, key, traffic_type, event_type, value=None): """ Track an event. :param key: user key associated to the event :type key: str :param traffic_type: traffic type name :type traffic_type: str :param event_type: event type name :type event_type: str :param value: (Optional) value associated to the event :type value: Number :return: Whether the event was created or not. :rtype: bool """ if self.destroyed: self._logger.error( "Client has already been destroyed - no calls possible") return False key = input_validator.validate_track_key(key) event_type = input_validator.validate_event_type(event_type) traffic_type = input_validator.validate_traffic_type(traffic_type) value = input_validator.validate_value(value) if key is None or event_type is None or traffic_type is None or value is False: return False event = Event(key=key, traffic_type_name=traffic_type, event_type_id=event_type, value=value, timestamp=int(time.time() * 1000)) return self._events_storage.put([event])
def test_track(self, mocker): """Test that destroy/destroyed calls are forwarded to the factory.""" split_storage = mocker.Mock(spec=SplitStorage) segment_storage = mocker.Mock(spec=SegmentStorage) impression_storage = mocker.Mock(spec=ImpressionStorage) event_storage = mocker.Mock(spec=EventStorage) event_storage.put.return_value = True telemetry_storage = mocker.Mock(spec=TelemetryStorage) def _get_storage_mock(name): return { 'splits': split_storage, 'segments': segment_storage, 'impressions': impression_storage, 'events': event_storage, 'telemetry': telemetry_storage }[name] factory = mocker.Mock(spec=SplitFactory) factory._get_storage = _get_storage_mock destroyed_mock = mocker.PropertyMock() destroyed_mock.return_value = False type(factory).destroyed = destroyed_mock factory._apikey = 'test' mocker.patch('splitio.client.client.time.time', new=lambda: 1) client = Client(factory) assert client.track('key', 'user', 'purchase', 12) is True assert mocker.call([ EventWrapper( event=Event('key', 'user', 'purchase', 12, 1000, None), size=1024 ) ]) in event_storage.put.mock_calls
def test_queue_full_hook_properties(self, mocker): """Test queue_full_hook is executed when the queue is full regarding properties.""" storage = InMemoryEventStorage(200) queue_full_hook = mocker.Mock() storage.set_queue_full_hook(queue_full_hook) events = [EventWrapper(event=Event('key%d' % i, 'user', 'purchase', 12.5, 1, None), size=32768) for i in range(160)] storage.put(events) assert queue_full_hook.mock_calls == [mocker.call()]
def test_queue_full_hook(self, mocker): """Test queue_full_hook is executed when the queue is full.""" storage = InMemoryEventStorage(100) queue_full_hook = mocker.Mock() storage.set_queue_full_hook(queue_full_hook) events = [EventWrapper(event=Event('key%d' % i, 'user', 'purchase', 12.5, 321654, None), size=1024) for i in range(0, 101)] storage.put(events) assert queue_full_hook.mock_calls == [mocker.call()]
def test_synchronize_impressions(self, mocker): storage = mocker.Mock(spec=EventStorage) storage.pop_many.return_value = [ Event('key1', 'user', 'purchase', 5.3, 123456, None), Event('key2', 'user', 'purchase', 5.3, 123456, None), ] api = mocker.Mock() def run(x): run._called += 1 return HttpResponse(200, '') api.flush_events.side_effect = run run._called = 0 event_synchronizer = EventSynchronizer(api, storage, 5) event_synchronizer.synchronize_events() assert run._called == 1 assert event_synchronizer._failed.qsize() == 0
def test_clear(self): """Test clear method.""" storage = InMemoryEventStorage(100) storage.put([ EventWrapper( event=Event('key1', 'user', 'purchase', 3.5, 123456, None), size=1024, ) ]) assert storage._events.qsize() == 1 storage.clear() assert storage._events.qsize() == 0
def pop_many(self, count): """ Pop the oldest N events from storage. :param count: Number of events to pop. :type count: int """ with UWSGILock(self._uwsgi, self._LOCK_EVENTS_KEY): try: current = json.loads( self._uwsgi.cache_get(self._EVENTS_KEY, _SPLITIO_EVENTS_CACHE_NAMESPACE)) except TypeError: return [] self._uwsgi.cache_update(self._EVENTS_KEY, json.dumps(current[count:]), 0, _SPLITIO_EVENTS_CACHE_NAMESPACE) return [ Event(event['key'], event['traffic_type_name'], event['event_type_id'], event['value'], event['timestamp'], event['properties']) for event in current[:count] ]
def test_push_pop_events(self): """Test pushing and retrieving events.""" storage = InMemoryEventStorage(100) storage.put([ EventWrapper( event=Event('key1', 'user', 'purchase', 3.5, 123456, None), size=1024, ) ]) storage.put([ EventWrapper( event=Event('key2', 'user', 'purchase', 3.5, 123456, None), size=1024, ) ]) storage.put([ EventWrapper( event=Event('key3', 'user', 'purchase', 3.5, 123456, None), size=1024, ) ]) # Assert impressions are retrieved in the same order they are inserted. assert storage.pop_many(1) == [ Event('key1', 'user', 'purchase', 3.5, 123456, None) ] assert storage.pop_many(1) == [ Event('key2', 'user', 'purchase', 3.5, 123456, None) ] assert storage.pop_many(1) == [ Event('key3', 'user', 'purchase', 3.5, 123456, None) ] # Assert inserting multiple impressions at once works and maintains order. events = [ EventWrapper( event=Event('key1', 'user', 'purchase', 3.5, 123456, None), size=1024, ), EventWrapper( event=Event('key2', 'user', 'purchase', 3.5, 123456, None), size=1024, ), EventWrapper( event=Event('key3', 'user', 'purchase', 3.5, 123456, None), size=1024, ), ] assert storage.put(events) # Assert events are retrieved in the same order they are inserted. assert storage.pop_many(1) == [ Event('key1', 'user', 'purchase', 3.5, 123456, None) ] assert storage.pop_many(1) == [ Event('key2', 'user', 'purchase', 3.5, 123456, None) ] assert storage.pop_many(1) == [ Event('key3', 'user', 'purchase', 3.5, 123456, None) ]
class EventsAPITests(object): """Impressions API test cases.""" events = [ Event('k1', 'user', 'purchase', 12.50, 123456, None), Event('k2', 'user', 'purchase', 12.50, 123456, None), Event('k3', 'user', 'purchase', None, 123456, {"test": 1234}), Event('k4', 'user', 'purchase', None, 123456, None) ] eventsExpected = [ { 'key': 'k1', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': 12.50, 'timestamp': 123456, 'properties': None }, { 'key': 'k2', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': 12.50, 'timestamp': 123456, 'properties': None }, { 'key': 'k3', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': None, 'timestamp': 123456, 'properties': { "test": 1234 } }, { 'key': 'k4', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': None, 'timestamp': 123456, 'properties': None }, ] def test_post_events(self, mocker): """Test impressions posting API call.""" httpclient = mocker.Mock(spec=client.HttpClient) httpclient.post.return_value = client.HttpResponse(200, '') cfg = DEFAULT_CONFIG.copy() cfg.update({ 'IPAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123' }) sdk_metadata = get_metadata(cfg) events_api = events.EventsAPI(httpclient, 'some_api_key', sdk_metadata) response = events_api.flush_events(self.events) call_made = httpclient.post.mock_calls[0] # validate positional arguments assert call_made[1] == ('events', '/events/bulk', 'some_api_key') # validate key-value args (headers) assert call_made[2]['extra_headers'] == { 'SplitSDKVersion': 'python-%s' % __version__, 'SplitSDKMachineIP': '123.123.123.123', 'SplitSDKMachineName': 'some_machine_name' } # validate key-value args (body) assert call_made[2]['body'] == self.eventsExpected httpclient.reset_mock() def raise_exception(*args, **kwargs): raise client.HttpClientException('some_message') httpclient.post.side_effect = raise_exception with pytest.raises(APIException) as exc_info: response = events_api.flush_events(self.events) assert exc_info.type == APIException assert exc_info.value.message == 'some_message' def test_post_events_ip_address_disabled(self, mocker): """Test impressions posting API call.""" httpclient = mocker.Mock(spec=client.HttpClient) httpclient.post.return_value = client.HttpResponse(200, '') cfg = DEFAULT_CONFIG.copy() cfg.update({'IPAddressesEnabled': False}) sdk_metadata = get_metadata(cfg) events_api = events.EventsAPI(httpclient, 'some_api_key', sdk_metadata) response = events_api.flush_events(self.events) call_made = httpclient.post.mock_calls[0] # validate positional arguments assert call_made[1] == ('events', '/events/bulk', 'some_api_key') # validate key-value args (headers) assert call_made[2]['extra_headers'] == { 'SplitSDKVersion': 'python-%s' % __version__, } # validate key-value args (body) assert call_made[2]['body'] == self.eventsExpected
def test_post_events(self, mocker): """Test impressions posting API call.""" httpclient = mocker.Mock(spec=client.HttpClient) httpclient.post.return_value = client.HttpResponse(200, '') sdk_metadata = SdkMetadata('python-1.2.3', 'some_machine_name', '123.123.123.123') events_api = events.EventsAPI(httpclient, 'some_api_key', sdk_metadata) response = events_api.flush_events([ Event('k1', 'user', 'purchase', 12.50, 123456), Event('k2', 'user', 'purchase', 12.50, 123456), Event('k3', 'user', 'purchase', None, 123456), Event('k4', 'user', 'purchase', None, 123456) ]) call_made = httpclient.post.mock_calls[0] # validate positional arguments assert call_made[1] == ('events', '/events/bulk', 'some_api_key') # validate key-value args (headers) assert call_made[2]['extra_headers'] == { 'SplitSDKVersion': 'python-1.2.3', 'SplitSDKMachineIP': '123.123.123.123', 'SplitSDKMachineName': 'some_machine_name' } # validate key-value args (body) assert call_made[2]['body'] == [{ 'key': 'k1', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': 12.50, 'timestamp': 123456 }, { 'key': 'k2', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': 12.50, 'timestamp': 123456 }, { 'key': 'k3', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': None, 'timestamp': 123456 }, { 'key': 'k4', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': None, 'timestamp': 123456 }] httpclient.reset_mock() def raise_exception(*args, **kwargs): raise client.HttpClientException('some_message') httpclient.post.side_effect = raise_exception with pytest.raises(APIException) as exc_info: response = events_api.flush_events([ Event('k1', 'user', 'purchase', 12.50, 123456), Event('k2', 'user', 'purchase', 12.50, 123456), Event('k3', 'user', 'purchase', None, 123456), Event('k4', 'user', 'purchase', None, 123456) ]) assert exc_info.type == APIException assert exc_info.value.message == 'some_message'