예제 #1
0
    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
예제 #2
0
    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
예제 #3
0
    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
예제 #4
0
    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
예제 #5
0
    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
예제 #6
0
    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,
            )])
예제 #7
0
    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])
예제 #8
0
    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
예제 #9
0
 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()]
예제 #10
0
 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()]
예제 #11
0
    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
예제 #12
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
예제 #13
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]
        ]
예제 #14
0
    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)
        ]
예제 #15
0
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
예제 #16
0
    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'