Ejemplo n.º 1
0
def test_flag_returns_error_if_rule_has_rollout_with_no_variations():
    rule = { 'id': 'id', 'clauses': [{'attribute': 'key', 'op': 'in', 'values': ['userkey']}],
        'rollout': {'variations': []} }
    flag = make_boolean_flag_with_rules([rule])
    user = { 'key': 'userkey' }
    detail = EvaluationDetail(None, None, {'kind': 'ERROR', 'errorKind': 'MALFORMED_FLAG'})
    assert evaluate(flag, user, empty_store, event_factory) == EvalResult(detail, [])
Ejemplo n.º 2
0
def test_flag_returns_off_variation_and_event_if_prerequisite_is_off():
    store = InMemoryFeatureStore()
    flag = {
        'key': 'feature0',
        'on': True,
        'prerequisites': [{'key': 'feature1', 'variation': 1}],
        'fallthrough': { 'variation': 0 },
        'offVariation': 1,
        'variations': ['a', 'b', 'c'],
        'version': 1
    }
    flag1 = {
        'key': 'feature1',
        'off': False,
        'offVariation': 1,
        # note that even though it returns the desired variation, it is still off and therefore not a match
        'fallthrough': { 'variation': 0 },
        'variations': ['d', 'e'],
        'version': 2,
        'trackEvents': False
    }
    store.upsert(FEATURES, flag1)
    user = { 'key': 'x' }
    detail = EvaluationDetail('b', 1, {'kind': 'PREREQUISITE_FAILED', 'prerequisiteKey': 'feature1'})
    events_should_be = [{'kind': 'feature', 'key': 'feature1', 'variation': 1, 'value': 'e', 'default': None,
        'version': 2, 'user': user, 'prereqOf': 'feature0'}]
    assert evaluate(flag, user, store, event_factory) == EvalResult(detail, events_should_be)
Ejemplo n.º 3
0
def test_flag_returns_fallthrough_and_event_if_prereq_is_met_and_there_are_no_rules():
    store = InMemoryFeatureStore()
    flag = {
        'key': 'feature0',
        'on': True,
        'prerequisites': [{ 'key': 'feature1', 'variation': 1 }],
        'fallthrough': { 'variation': 0 },
        'offVariation': 1,
        'variations': ['a', 'b', 'c'],
        'version': 1
    }
    flag1 = {
        'key': 'feature1',
        'on': True,
        'fallthrough': { 'variation': 1 },
        'variations': ['d', 'e'],
        'version': 2,
        'trackEvents': False
    }
    store.upsert(FEATURES, flag1)
    user = { 'key': 'x' }
    detail = EvaluationDetail('a', 0, {'kind': 'FALLTHROUGH'})
    events_should_be = [{'kind': 'feature', 'key': 'feature1', 'variation': 1, 'value': 'e', 'default': None,
        'version': 2, 'user': user, 'prereqOf': 'feature0'}]
    assert evaluate(flag, user, store, event_factory) == EvalResult(detail, events_should_be)
def test_variation_when_user_has_no_key():
    feature = make_off_flag_with_value('feature.key', 'value')
    store = InMemoryFeatureStore()
    store.init({FEATURES: {'feature.key': feature}})
    client = make_client(store)
    expected = EvaluationDetail('default', None, {'kind': 'ERROR', 'errorKind': 'USER_NOT_SPECIFIED'})
    assert expected == client.variation_detail('feature.key', { }, default='default')
def test_variation_detail_for_existing_feature():
    feature = make_off_flag_with_value('feature.key', 'value')
    store = InMemoryFeatureStore()
    store.init({FEATURES: {'feature.key': feature}})
    client = make_client(store)
    expected = EvaluationDetail('value', 0, {'kind': 'OFF'})
    assert expected == client.variation_detail('feature.key', user, default='default')
Ejemplo n.º 6
0
def test_flag_returns_none_if_flag_is_off_and_off_variation_is_unspecified():
    flag = {
        'key': 'feature',
        'on': False,
        'variations': ['a', 'b', 'c']
    }
    user = { 'key': 'x' }
    detail = EvaluationDetail(None, None, {'kind': 'OFF'})
    assert evaluate(flag, user, empty_store, event_factory) == EvalResult(detail, [])
Ejemplo n.º 7
0
def test_variation_detail_for_flag_that_evaluates_to_none():
    empty_flag = {'key': 'feature.key', 'on': False, 'offVariation': None}
    store = InMemoryFeatureStore()
    store.init({FEATURES: {'feature.key': empty_flag}})
    client = make_client(store)
    expected = EvaluationDetail('default', None, {'kind': 'OFF'})
    actual = client.variation_detail('feature.key', user, default='default')
    assert expected == actual
    assert actual.is_default_value() == True
def test_variation_detail_when_feature_store_throws_error(caplog):
    store = ErroringFeatureStore()
    client = make_client(store)
    expected = EvaluationDetail('default', None, {'kind': 'ERROR', 'errorKind': 'EXCEPTION'})
    actual = client.variation_detail('feature.key', { "key": "user" }, default='default')
    assert expected == actual
    assert actual.is_default_value() == True
    errlog = get_log_lines(caplog, 'ERROR')
    assert errlog == [ 'Unexpected error while retrieving feature flag "feature.key": NotImplementedError()' ]
Ejemplo n.º 9
0
def test_flag_returns_error_if_off_variation_is_negative():
    flag = {
        'key': 'feature',
        'on': False,
        'offVariation': -1,
        'variations': ['a', 'b', 'c']
    }
    user = { 'key': 'x' }
    detail = EvaluationDetail(None, None, {'kind': 'ERROR', 'errorKind': 'MALFORMED_FLAG'})
    assert evaluate(flag, user, empty_store, event_factory) == EvalResult(detail, [])
Ejemplo n.º 10
0
def test_flag_returns_error_if_fallthrough_has_no_variation_or_rollout():
    flag = {
        'key': 'feature',
        'on': True,
        'fallthrough': {},
        'variations': ['a', 'b', 'c']
    }
    user = { 'key': 'x' }
    detail = EvaluationDetail(None, None, {'kind': 'ERROR', 'errorKind': 'MALFORMED_FLAG'})
    assert evaluate(flag, user, empty_store, event_factory) == EvalResult(detail, [])
Ejemplo n.º 11
0
def test_flag_returns_off_variation_if_flag_is_off():
    flag = {
        'key': 'feature',
        'on': False,
        'offVariation': 1,
        'variations': ['a', 'b', 'c']
    }
    user = {'key': 'x'}
    detail = EvaluationDetail('b', 1, {'kind': 'OFF'})
    assert evaluate(flag, user, empty_store) == EvalResult(detail, [])
Ejemplo n.º 12
0
def evaluate(flag, user, store, event_factory):
    sanitized_user = stringify_attrs(
        user, __USER_ATTRS_TO_STRINGIFY_FOR_EVALUATION__)
    value = flag.get('value')
    version = flag.get('version')
    variation_index = flag.get('variation')
    reason = flag.get('reason')
    detail = EvaluationDetail(value, variation_index, reason)

    return EvalResult(detail=detail, events=None)
Ejemplo n.º 13
0
def test_variation_detail_for_unknown_feature():
    store = InMemoryFeatureStore()
    client = make_client(store)
    expected = EvaluationDetail('default', None, {
        'kind': 'ERROR',
        'errorKind': 'FLAG_NOT_FOUND'
    })
    assert expected == client.variation_detail('feature.key',
                                               user,
                                               default='default')
Ejemplo n.º 14
0
def test_flag_returns_off_variation_if_prerequisite_not_found():
    flag = {
        'key': 'feature0',
        'on': True,
        'prerequisites': [{'key': 'badfeature', 'variation': 1}],
        'fallthrough': { 'variation': 0 },
        'offVariation': 1,
        'variations': ['a', 'b', 'c']
    }
    user = { 'key': 'x' }
    detail = EvaluationDetail('b', 1, {'kind': 'PREREQUISITE_FAILED', 'prerequisiteKey': 'badfeature'})
    assert evaluate(flag, user, empty_store, event_factory) == EvalResult(detail, [])
Ejemplo n.º 15
0
def test_flag_matches_user_from_targets():
    flag = {
        'key': 'feature0',
        'on': True,
        'targets': [{ 'values': ['whoever', 'userkey'], 'variation': 2 }],
        'fallthrough': { 'variation': 0 },
        'offVariation': 1,
        'variations': ['a', 'b', 'c']
    }
    user = { 'key': 'userkey' }
    detail = EvaluationDetail('c', 2, {'kind': 'TARGET_MATCH'})
    assert evaluate(flag, user, empty_store, event_factory) == EvalResult(detail, [])
Ejemplo n.º 16
0
    def _evaluate_internal(self, key, user, default, include_reasons_in_events):
        default = self._config.get_default(key, default)

        if self._config.offline:
            return EvaluationDetail(default, None, error_reason('CLIENT_NOT_READY'))
        
        if user is not None:
            self._sanitize_user(user)

        def send_event(value, variation=None, flag=None, reason=None):
            self._send_event({'kind': 'feature', 'key': key, 'user': user,
                              'value': value, 'variation': variation, 'default': default,
                              'version': flag.get('version') if flag else None,
                              'trackEvents': flag.get('trackEvents') if flag else None,
                              'debugEventsUntilDate': flag.get('debugEventsUntilDate') if flag else None,
                              'reason': reason if include_reasons_in_events else None})

        if not self.is_initialized():
            if self._store.initialized:
                log.warn("Feature Flag evaluation attempted before client has initialized - using last known values from feature store for feature key: " + key)
            else:
                log.warn("Feature Flag evaluation attempted before client has initialized! Feature store unavailable - returning default: "
                         + str(default) + " for feature key: " + key)
                reason = error_reason('CLIENT_NOT_READY')
                send_event(default, None, None, reason)
                return EvaluationDetail(default, None, reason)
        
        if user is not None and user.get('key', "") == "":
            log.warn("User key is blank. Flag evaluation will proceed, but the user will not be stored in LaunchDarkly.")

        flag = self._store.get(FEATURES, key, lambda x: x)
        if not flag:
            reason = error_reason('FLAG_NOT_FOUND')
            send_event(default, None, None, reason)
            return EvaluationDetail(default, None, reason)
        else:
            if user is None or user.get('key') is None:
                reason = error_reason('USER_NOT_SPECIFIED')
                send_event(default, None, flag, reason)
                return EvaluationDetail(default, None, reason)

            try:
                result = evaluate(flag, user, self._store, include_reasons_in_events)
                for event in result.events or []:
                    self._send_event(event)
                detail = result.detail
                if detail.is_default_value():
                    detail = EvaluationDetail(default, None, detail.reason)
                send_event(detail.value, detail.variation_index, flag, detail.reason)
                return detail
            except Exception as e:
                log.error("Unexpected error while evaluating feature flag \"%s\": %s" % (key, e))
                log.debug(traceback.format_exc())
                reason = error_reason('EXCEPTION')
                send_event(default, None, flag, reason)
                return EvaluationDetail(default, None, reason)
Ejemplo n.º 17
0
def test_flag_returns_error_if_fallthrough_variation_is_too_high():
    flag = {
        'key': 'feature',
        'on': True,
        'fallthrough': {
            'variation': 999
        },
        'variations': ['a', 'b', 'c']
    }
    user = {'key': 'x'}
    detail = EvaluationDetail(None, None, {
        'kind': 'ERROR',
        'errorKind': 'MALFORMED_FLAG'
    })
    assert evaluate(flag, user, empty_store) == EvalResult(detail, [])
Ejemplo n.º 18
0
def test_flag_returns_error_if_rule_variation_is_negative():
    rule = {
        'id': 'id',
        'clauses': [{
            'attribute': 'key',
            'op': 'in',
            'values': ['userkey']
        }],
        'variation': -1
    }
    flag = make_boolean_flag_with_rules([rule])
    user = {'key': 'userkey'}
    detail = EvaluationDetail(None, None, {
        'kind': 'ERROR',
        'errorKind': 'MALFORMED_FLAG'
    })
    assert evaluate(flag, user, empty_store) == EvalResult(detail, [])
Ejemplo n.º 19
0
def test_flag_returns_off_variation_and_event_if_prerequisite_is_not_met():
    store = InMemoryFeatureStore()
    flag = {
        'key': 'feature0',
        'on': True,
        'prerequisites': [{
            'key': 'feature1',
            'variation': 1
        }],
        'fallthrough': {
            'variation': 0
        },
        'offVariation': 1,
        'variations': ['a', 'b', 'c'],
        'version': 1
    }
    flag1 = {
        'key': 'feature1',
        'on': True,
        'fallthrough': {
            'variation': 0
        },
        'variations': ['d', 'e'],
        'version': 2,
        'trackEvents': False
    }
    store.upsert(FEATURES, flag1)
    user = {'key': 'x'}
    detail = EvaluationDetail('b', 1, {
        'kind': 'PREREQUISITE_FAILED',
        'prerequisiteKey': 'feature1'
    })
    events_should_be = [{
        'kind': 'feature',
        'key': 'feature1',
        'variation': 0,
        'value': 'd',
        'version': 2,
        'user': user,
        'prereqOf': 'feature0',
        'trackEvents': False,
        'debugEventsUntilDate': None,
        'reason': None
    }]
    assert evaluate(flag, user, store) == EvalResult(detail, events_should_be)
Ejemplo n.º 20
0
def test_flag_matches_user_from_rules():
    rule = {
        'id': 'id',
        'clauses': [{
            'attribute': 'key',
            'op': 'in',
            'values': ['userkey']
        }],
        'variation': 1
    }
    flag = make_boolean_flag_with_rules([rule])
    user = {'key': 'userkey'}
    detail = EvaluationDetail(True, 1, {
        'kind': 'RULE_MATCH',
        'ruleIndex': 0,
        'ruleId': 'id'
    })
    assert evaluate(flag, user, empty_store) == EvalResult(detail, [])
Ejemplo n.º 21
0
    def _evaluate_internal(self, key, user, default, event_factory):
        default = self._config.get_default(key, default)

        if self._config.offline:
            return EvaluationDetail(default, None,
                                    error_reason('CLIENT_NOT_READY'))

        if not self.is_initialized():
            if self._store.initialized:
                log.warning(
                    "Feature Flag evaluation attempted before client has initialized - using last known values from feature store for feature key: "
                    + key)
            else:
                log.warning(
                    "Feature Flag evaluation attempted before client has initialized! Feature store unavailable - returning default: "
                    + str(default) + " for feature key: " + key)
                reason = error_reason('CLIENT_NOT_READY')
                self._send_event(
                    event_factory.new_unknown_flag_event(
                        key, user, default, reason))
                return EvaluationDetail(default, None, reason)

        if user is not None and user.get('key', "") == "":
            log.warning(
                "User key is blank. Flag evaluation will proceed, but the user will not be stored in LaunchDarkly."
            )

        try:
            flag = self._store.get(FEATURES, key, lambda x: x)
        except Exception as e:
            log.error(
                "Unexpected error while retrieving feature flag \"%s\": %s" %
                (key, repr(e)))
            log.debug(traceback.format_exc())
            reason = error_reason('EXCEPTION')
            self._send_event(
                event_factory.new_unknown_flag_event(key, user, default,
                                                     reason))
            return EvaluationDetail(default, None, reason)
        if not flag:
            reason = error_reason('FLAG_NOT_FOUND')
            self._send_event(
                event_factory.new_unknown_flag_event(key, user, default,
                                                     reason))
            return EvaluationDetail(default, None, reason)
        else:
            if user is None or user.get('key') is None:
                reason = error_reason('USER_NOT_SPECIFIED')
                self._send_event(
                    event_factory.new_default_event(flag, user, default,
                                                    reason))
                return EvaluationDetail(default, None, reason)

            try:
                result = evaluate(flag, user, self._store, event_factory)
                for event in result.events or []:
                    self._send_event(event)
                detail = result.detail
                if detail.is_default_value():
                    detail = EvaluationDetail(default, None, detail.reason)
                self._send_event(
                    event_factory.new_eval_event(flag, user, detail, default))
                return detail
            except Exception as e:
                log.error(
                    "Unexpected error while evaluating feature flag \"%s\": %s"
                    % (key, repr(e)))
                log.debug(traceback.format_exc())
                reason = error_reason('EXCEPTION')
                self._send_event(
                    event_factory.new_default_event(flag, user, default,
                                                    reason))
                return EvaluationDetail(default, None, reason)