예제 #1
0
    def test_headers_from_metadata(self, mocker):
        """Test headers from metadata call."""
        metadata = headers_from_metadata(SdkMetadata('1.0', 'some', '1.2.3.4'))
        assert metadata['SplitSDKVersion'] == '1.0'
        assert metadata['SplitSDKMachineIP'] == '1.2.3.4'
        assert metadata['SplitSDKMachineName'] == 'some'
        assert 'SplitSDKClientKey' not in metadata

        metadata = headers_from_metadata(SdkMetadata('1.0', 'some', '1.2.3.4'),
                                         'abcd')
        assert metadata['SplitSDKVersion'] == '1.0'
        assert metadata['SplitSDKMachineIP'] == '1.2.3.4'
        assert metadata['SplitSDKMachineName'] == 'some'
        assert metadata['SplitSDKClientKey'] == 'abcd'

        metadata = headers_from_metadata(SdkMetadata('1.0', 'some', 'NA'))
        assert metadata['SplitSDKVersion'] == '1.0'
        assert 'SplitSDKMachineIP' not in metadata
        assert 'SplitSDKMachineName' not in metadata
        assert 'SplitSDKClientKey' not in metadata

        metadata = headers_from_metadata(SdkMetadata('1.0', 'some', 'unknown'))
        assert metadata['SplitSDKVersion'] == '1.0'
        assert 'SplitSDKMachineIP' not in metadata
        assert 'SplitSDKMachineName' not in metadata
        assert 'SplitSDKClientKey' not in metadata
예제 #2
0
    def test_fetch_split_changes(self, mocker):
        """Test split changes fetching API call."""
        httpclient = mocker.Mock(spec=client.HttpClient)
        httpclient.get.return_value = client.HttpResponse(
            200, '{"prop1": "value1"}')
        split_api = splits.SplitsAPI(httpclient, 'some_api_key',
                                     SdkMetadata('1.0', 'some', '1.2.3.4'))
        response = split_api.fetch_splits(123)

        assert response['prop1'] == 'value1'
        assert httpclient.get.mock_calls == [
            mocker.call('sdk',
                        '/splitChanges',
                        'some_api_key',
                        extra_headers={
                            'SplitSDKVersion': '1.0',
                            'SplitSDKMachineIP': '1.2.3.4',
                            'SplitSDKMachineName': 'some'
                        },
                        query={'since': 123})
        ]

        httpclient.reset_mock()

        def raise_exception(*args, **kwargs):
            raise client.HttpClientException('some_message')

        httpclient.get.side_effect = raise_exception
        with pytest.raises(APIException) as exc_info:
            response = split_api.fetch_splits(123)
            assert exc_info.type == APIException
            assert exc_info.value.message == 'some_message'
예제 #3
0
    def test_split_sse_error(self):
        """Test correct initialization. Client ends the connection."""
        events = []

        def handler(event):
            """Handler."""
            events.append(event)

        request_queue = Queue()
        server = SSEMockServer(request_queue)
        server.start()

        status = {
            'on_connect': False,
            'on_disconnect': False,
        }

        def on_connect():
            """On connect handler."""
            status['on_connect'] = True

        def on_disconnect():
            """On disconnect handler."""
            status['on_disconnect'] = True

        client = SplitSSEClient(handler,
                                SdkMetadata('1.0', 'some', '1.2.3.4'),
                                on_connect,
                                on_disconnect,
                                "abcd",
                                base_url='http://localhost:' +
                                str(server.port()))

        token = Token(
            True, 'some', {
                'chan1': ['subscribe'],
                'chan2': ['subscribe', 'channel-metadata:publishers']
            }, 1, 2)

        server.publish({'event':
                        'error'})  # send an error event early to unblock start
        assert not client.start(token)

        request = request_queue.get(1)
        assert request.path == '/event-stream?v=1.1&accessToken=some&channels=chan1,[?occupancy=metrics.publishers]chan2'
        assert request.headers['accept'] == 'text/event-stream'
        assert request.headers['SplitSDKVersion'] == '1.0'
        assert request.headers['SplitSDKMachineIP'] == '1.2.3.4'
        assert request.headers['SplitSDKMachineName'] == 'some'
        assert request.headers['SplitSDKClientKey'] == 'abcd'

        server.publish(SSEMockServer.VIOLENT_REQUEST_END)
        server.stop()

        time.sleep(1)

        assert status['on_connect']
        assert status['on_disconnect']
예제 #4
0
    def test_start_streaming_false(self, mocker):
        splits_ready_event = threading.Event()
        synchronizer = mocker.Mock(spec=Synchronizer)
        manager = Manager(splits_ready_event, synchronizer, mocker.Mock(), False, SdkMetadata('1.0', 'some', '1.2.3.4'))
        manager.start()

        splits_ready_event.wait(2)
        assert splits_ready_event.is_set()
        assert len(synchronizer.sync_all.mock_calls) == 1
        assert len(synchronizer.start_periodic_fetching.mock_calls) == 1
        assert len(synchronizer.start_periodic_data_recording.mock_calls) == 1
예제 #5
0
    def test_post_latencies(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')
        telemetry_api = telemetry.TelemetryAPI(httpclient, 'some_api_key',
                                               sdk_metadata)
        response = telemetry_api.flush_latencies({
            'l1': [
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
                19, 20, 21, 22
            ]
        })

        call_made = httpclient.post.mock_calls[0]

        # validate positional arguments
        assert call_made[1] == ('events', '/metrics/times', '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'] == [{
            'name':
            'l1',
            'latencies': [
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
                19, 20, 21, 22
            ]
        }]

        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 = telemetry_api.flush_latencies({
                'l1': [
                    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
                    18, 19, 20, 21, 22
                ]
            })
            assert exc_info.type == APIException
            assert exc_info.value.message == 'some_message'
예제 #6
0
    def setup_method(self):
        """Prepare storages with test data."""
        metadata = SdkMetadata('python-1.2.3', 'some_ip', 'some_name')
        redis_client = build(DEFAULT_CONFIG.copy())
        split_storage = RedisSplitStorage(redis_client, True)
        segment_storage = RedisSegmentStorage(redis_client)

        split_fn = os.path.join(os.path.dirname(__file__), 'files',
                                'splitChanges.json')
        with open(split_fn, 'r') as flo:
            data = json.loads(flo.read())
        for split in data['splits']:
            redis_client.set(split_storage._get_key(split['name']),
                             json.dumps(split))
        redis_client.set(split_storage._SPLIT_TILL_KEY, data['till'])

        segment_fn = os.path.join(os.path.dirname(__file__), 'files',
                                  'segmentEmployeesChanges.json')
        with open(segment_fn, 'r') as flo:
            data = json.loads(flo.read())
        redis_client.sadd(segment_storage._get_key(data['name']),
                          *data['added'])
        redis_client.set(segment_storage._get_till_key(data['name']),
                         data['till'])

        segment_fn = os.path.join(os.path.dirname(__file__), 'files',
                                  'segmentHumanBeignsChanges.json')
        with open(segment_fn, 'r') as flo:
            data = json.loads(flo.read())
        redis_client.sadd(segment_storage._get_key(data['name']),
                          *data['added'])
        redis_client.set(segment_storage._get_till_key(data['name']),
                         data['till'])

        storages = {
            'splits': split_storage,
            'segments': segment_storage,
            'impressions': RedisImpressionsStorage(redis_client, metadata),
            'events': RedisEventsStorage(redis_client, metadata),
        }
        impmanager = ImpressionsManager(storages['impressions'].put,
                                        ImpressionsMode.DEBUG)
        recorder = PipelinedRecorder(redis_client.pipeline, impmanager,
                                     storages['events'],
                                     storages['impressions'])
        self.factory = SplitFactory('some_api_key', storages, True, recorder)  # pylint:disable=attribute-defined-outside-init
예제 #7
0
    def setup_method(self):
        """Prepare storages with test data."""
        metadata = SdkMetadata('python-1.2.3', 'some_ip', 'some_name')
        redis_client = RedisAdapter(StrictRedis())
        split_storage = RedisSplitStorage(redis_client, True)
        segment_storage = RedisSegmentStorage(redis_client)

        split_fn = os.path.join(os.path.dirname(__file__), 'files',
                                'splitChanges.json')
        with open(split_fn, 'r') as flo:
            data = json.loads(flo.read())
        for split in data['splits']:
            redis_client.set(split_storage._get_key(split['name']),
                             json.dumps(split))
        redis_client.set(split_storage._SPLIT_TILL_KEY, data['till'])

        segment_fn = os.path.join(os.path.dirname(__file__), 'files',
                                  'segmentEmployeesChanges.json')
        with open(segment_fn, 'r') as flo:
            data = json.loads(flo.read())
        redis_client.sadd(segment_storage._get_key(data['name']),
                          *data['added'])
        redis_client.set(segment_storage._get_till_key(data['name']),
                         data['till'])

        segment_fn = os.path.join(os.path.dirname(__file__), 'files',
                                  'segmentHumanBeignsChanges.json')
        with open(segment_fn, 'r') as flo:
            data = json.loads(flo.read())
        redis_client.sadd(segment_storage._get_key(data['name']),
                          *data['added'])
        redis_client.set(segment_storage._get_till_key(data['name']),
                         data['till'])

        self.factory = SplitFactory(
            'some_api_key',
            {  #pylint:disable=attribute-defined-outside-init
                'splits': split_storage,
                'segments': segment_storage,
                'impressions': RedisImpressionsStorage(redis_client, metadata),
                'events': RedisEventsStorage(redis_client, metadata),
                'telemetry': RedisTelemetryStorage(redis_client, metadata)
            },
            True)
예제 #8
0
    def test_error(self, mocker):
        split_task = mocker.Mock(spec=SplitSynchronizationTask)
        split_tasks = SplitTasks(split_task, mocker.Mock(), mocker.Mock(), mocker.Mock(),
                                 mocker.Mock())

        storage = mocker.Mock(spec=SplitStorage)
        api = mocker.Mock()

        def run(x):
            raise APIException("something broke")

        api.fetch_splits.side_effect = run
        storage.get_change_number.return_value = -1

        split_sync = SplitSynchronizer(api, storage)
        synchronizers = SplitSynchronizers(split_sync, mocker.Mock(), mocker.Mock(),
                                           mocker.Mock(), mocker.Mock())

        synchronizer = Synchronizer(synchronizers, split_tasks)
        manager = Manager(threading.Event(), synchronizer,  mocker.Mock(), False, SdkMetadata('1.0', 'some', '1.2.3.4'))

        manager.start()  # should not throw!
예제 #9
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'
예제 #10
0
    def test_post_impressions(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')
        impressions_api = impressions.ImpressionsAPI(httpclient,
                                                     'some_api_key',
                                                     sdk_metadata)
        response = impressions_api.flush_impressions([
            Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654),
            Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654),
            Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654),
        ])

        call_made = httpclient.post.mock_calls[0]

        # validate positional arguments
        assert call_made[1] == ('events', '/testImpressions/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'] == [{
            'testName':
            'f1',
            'keyImpressions': [
                {
                    'keyName': 'k1',
                    'bucketingKey': 'b1',
                    'treatment': 'on',
                    'label': 'l1',
                    'time': 321654,
                    'changeNumber': 123456
                },
                {
                    'keyName': 'k3',
                    'bucketingKey': 'b1',
                    'treatment': 'on',
                    'label': 'l1',
                    'time': 321654,
                    'changeNumber': 123456
                },
            ],
        }, {
            'testName':
            'f2',
            'keyImpressions': [
                {
                    'keyName': 'k2',
                    'bucketingKey': 'b1',
                    'treatment': 'off',
                    'label': 'l1',
                    'time': 321654,
                    'changeNumber': 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 = impressions_api.flush_impressions([
                Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654),
                Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654),
                Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654),
            ])
            assert exc_info.type == APIException
            assert exc_info.value.message == 'some_message'