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)
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', 'version': 2, 'user': user, 'prereqOf': 'feature0', 'trackEvents': False, 'debugEventsUntilDate': None, 'reason': None}] assert evaluate(flag, user, store) == EvalResult(detail, events_should_be)
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', 'version': 2, 'user': user, 'prereqOf': 'feature0', 'trackEvents': False, 'debugEventsUntilDate': None, 'reason': None}] assert evaluate(flag, user, store) == EvalResult(detail, events_should_be)
def test_segment_match_clause_falls_through_with_no_errors_if_segment_not_found( ): user = {"key": "foo"} flag = { "key": "test", "variations": [False, True], "fallthrough": { "variation": 0 }, "on": True, "rules": [{ "clauses": [{ "attribute": "", "op": "segmentMatch", "values": ["segkey"] }], "variation": 1 }] } assert evaluate(flag, user, empty_store, event_factory).detail.value == False
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, [])
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) == EvalResult(detail, [])
def test_segment_match_clause_retrieves_segment_from_store(): store = InMemoryFeatureStore() segment = { "key": "segkey", "included": [ "foo" ], "version": 1 } store.upsert(SEGMENTS, segment) user = { "key": "foo" } flag = { "key": "test", "variations": [ False, True ], "fallthrough": { "variation": 0 }, "on": True, "rules": [ { "clauses": [ { "attribute": "", "op": "segmentMatch", "values": [ "segkey" ] } ], "variation": 1 } ] } assert evaluate(flag, user, store).detail.value == True
def test_segment_match_clause_retrieves_segment_from_store(): store = InMemoryFeatureStore() segment = { "key": "segkey", "included": [ "foo" ], "version": 1 } store.upsert(SEGMENTS, segment) user = { "key": "foo" } flag = { "key": "test", "variations": [ False, True ], "fallthrough": { "variation": 0 }, "on": True, "rules": [ { "clauses": [ { "attribute": "", "op": "segmentMatch", "values": [ "segkey" ] } ], "variation": 1 } ] } assert evaluate(flag, user, store, event_factory).detail.value == True
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_clause_returns_false_for_missing_attribute(): clause = { 'attribute': 'legs', 'op': 'in', 'values': [ 4 ] } user = { 'key': 'x', 'name': 'Bob' } flag = _make_bool_flag_from_clause(clause) assert evaluate(flag, user, empty_store).detail.value == False
def test_clause_matches_custom_attribute(): clause = { 'attribute': 'legs', 'op': 'in', 'values': [ 4 ] } user = { 'key': 'x', 'name': 'Bob', 'custom': { 'legs': 4 } } flag = _make_bool_flag_from_clause(clause) assert evaluate(flag, user, empty_store).detail.value == True
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) == EvalResult(detail, [])
def test_clause_matches_builtin_attribute(): clause = { 'attribute': 'name', 'op': 'in', 'values': [ 'Bob' ] } user = { 'key': 'x', 'name': 'Bob' } flag = _make_bool_flag_from_clause(clause) assert evaluate(flag, user, empty_store, event_factory).detail.value == True
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, [])
def test_clause_matches_custom_attribute(): clause = { 'attribute': 'legs', 'op': 'in', 'values': [ 4 ] } user = { 'key': 'x', 'name': 'Bob', 'custom': { 'legs': 4 } } flag = _make_bool_flag_from_clause(clause) assert evaluate(flag, user, empty_store, event_factory).detail.value == True
def test_clause_returns_false_for_missing_attribute(): clause = { 'attribute': 'legs', 'op': 'in', 'values': [ 4 ] } user = { 'key': 'x', 'name': 'Bob' } flag = _make_bool_flag_from_clause(clause) assert evaluate(flag, user, empty_store, event_factory).detail.value == False
def test_clause_matches_builtin_attribute(): clause = { 'attribute': 'name', 'op': 'in', 'values': [ 'Bob' ] } user = { 'key': 'x', 'name': 'Bob' } flag = _make_bool_flag_from_clause(clause) assert evaluate(flag, user, empty_store).detail.value == True
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)
def test_clause_can_be_negated(): clause = { 'attribute': 'name', 'op': 'in', 'values': [ 'Bob' ], 'negate': True } user = { 'key': 'x', 'name': 'Bob' } flag = _make_bool_flag_from_clause(clause) assert evaluate(flag, user, empty_store).detail.value == False
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) == EvalResult(detail, [])
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, [])
def test_clause_can_be_negated(): clause = { 'attribute': 'name', 'op': 'in', 'values': [ 'Bob' ], 'negate': True } user = { 'key': 'x', 'name': 'Bob' } flag = _make_bool_flag_from_clause(clause) assert evaluate(flag, user, empty_store, event_factory).detail.value == False
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, [])
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, [])
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) == EvalResult(detail, [])
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, [])
def test_flag_returns_none_if_flag_is_off_and_off_variation_is_unspecified(): flag = { 'key': 'feature', 'on': False, 'fallthrough': { 'variation': 0 }, 'variations': ['a', 'b', 'c'] } user = {'key': 'x'} assert evaluate(flag, user, empty_store) == (None, [])
def variation(self, key, user, default): default = self._config.get_default(key, default) self._sanitize_user(user) if self._config.offline: return default def send_event(value, version=None): self._send_event({ 'kind': 'feature', 'key': key, 'user': user, 'value': value, 'default': default, 'version': version }) if not self.is_initialized(): log.warn( "Feature Flag evaluation attempted before client has finished initializing! Returning default: " + str(default) + " for feature key: " + key) send_event(default) return default if user is None or user.get('key') is None: log.warn( "Missing user or user key when evaluating Feature Flag key: " + key + ". Returning default.") send_event(default) return default if 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(key) if not flag: log.warn("Feature Flag key: " + key + " not found in Feature Store. Returning default.") send_event(default) return default value, events = evaluate(flag, user, self._store) for event in events or []: self._send_event(event) log.debug("Sending event: " + str(event)) if value is not None: send_event(value, flag.get('version')) return value send_event(default, flag.get('version')) return default
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) == EvalResult(detail, [])
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, [])
def all_flags_state(self, user, **kwargs): """Returns an object that encapsulates the state of all feature flags for a given user, including the flag values and also metadata that can be used on the front end. This method does not send analytics events back to LaunchDarkly. :param dict user: the end user requesting the feature flags :param kwargs: optional parameters affecting how the state is computed: set `client_side_only=True` to limit it to only flags that are marked for use with the client-side SDK (by default, all flags are included); set `with_reasons=True` to include evaluation reasons in the state (see `variation_detail`) :return: a FeatureFlagsState object (will never be None; its 'valid' property will be False if the client is offline, has not been initialized, or the user is None or has no key) :rtype: FeatureFlagsState """ if self._config.offline: log.warn("all_flags_state() called, but client is in offline mode. Returning empty state") return FeatureFlagsState(False) if not self.is_initialized(): if self._store.initialized: log.warn("all_flags_state() called before client has finished initializing! Using last known values from feature store") else: log.warn("all_flags_state() called before client has finished initializing! Feature store unavailable - returning empty state") return FeatureFlagsState(False) if user is None or user.get('key') is None: log.warn("User or user key is None when calling all_flags_state(). Returning empty state.") return FeatureFlagsState(False) state = FeatureFlagsState(True) client_only = kwargs.get('client_side_only', False) with_reasons = kwargs.get('with_reasons', False) try: flags_map = self._store.all(FEATURES, lambda x: x) except Exception as e: log.error("Unable to read flags for all_flag_state: %s" % e) return FeatureFlagsState(False) for key, flag in flags_map.items(): if client_only and not flag.get('clientSide', False): continue try: detail = evaluate(flag, user, self._store, False).detail state.add_flag(flag, detail.value, detail.variation_index, detail.reason if with_reasons else None) except Exception as e: log.error("Error evaluating flag \"%s\" in all_flags_state: %s" % (key, e)) log.debug(traceback.format_exc()) reason = {'kind': 'ERROR', 'errorKind': 'EXCEPTION'} state.add_flag(flag, None, None, reason if with_reasons else None) return state
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) == EvalResult(detail, [])
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, [])
def test_flag_returns_off_variation_if_flag_is_off(): flag = { 'key': 'feature', 'on': False, 'offVariation': 1, 'fallthrough': { 'variation': 0 }, 'variations': ['a', 'b', 'c'] } user = {'key': 'x'} assert evaluate(flag, user, empty_store) == ('b', [])
def all_flags(self, user): if self._config.offline: log.warn("all_flags() called, but client is in offline mode. Returning None") return None if not self.is_initialized(): log.warn("all_flags() called before client has finished initializing! Returning None") return None if user is None or user.get('key') is None: log.warn("User or user key is None when calling all_flags(). Returning None.") return None return {k: evaluate(v, user, self._store)[0] for k, v in self._store.all().items() or {}}
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, [])
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'} assert evaluate(flag, user, empty_store) == ('c', [])
def test_secondary_key_is_coerced_to_string_for_evaluation(): # We can't really verify that the rollout calculation works correctly, but we can at least # make sure it doesn't error out if there's a non-string secondary value (ch35189) rule = { 'id': 'ruleid', 'clauses': [ { 'attribute': 'key', 'op': 'in', 'values': [ 'userkey' ] } ], 'rollout': { 'salt': '', 'variations': [ { 'weight': 100000, 'variation': 1 } ] } } flag = make_boolean_flag_with_rules([rule]) user = { 'key': 'userkey', 'secondary': 999 } assert evaluate(flag, user, empty_store, event_factory).detail.value == True
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'} assert evaluate(flag, user, empty_store) == ('b', [])
def test_secondary_key_is_coerced_to_string_for_evaluation(): # We can't really verify that the rollout calculation works correctly, but we can at least # make sure it doesn't error out if there's a non-string secondary value (ch35189) rule = { 'id': 'ruleid', 'clauses': [ { 'attribute': 'key', 'op': 'in', 'values': [ 'userkey' ] } ], 'rollout': { 'salt': '', 'variations': [ { 'weight': 100000, 'variation': 1 } ] } } flag = make_boolean_flag_with_rules([rule]) user = { 'key': 'userkey', 'secondary': 999 } assert evaluate(flag, user, empty_store).detail.value == True
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, [])
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)
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, [])
def test_segment_match_clause_falls_through_with_no_errors_if_segment_not_found(): user = { "key": "foo" } flag = { "key": "test", "variations": [ False, True ], "fallthrough": { "variation": 0 }, "on": True, "rules": [ { "clauses": [ { "attribute": "", "op": "segmentMatch", "values": [ "segkey" ] } ], "variation": 1 } ] } assert evaluate(flag, user, empty_store).detail.value == False
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 } store.upsert(FEATURES, flag1) user = {'key': 'x'} events_should_be = [{ 'kind': 'feature', 'key': 'feature1', 'value': 'e', 'version': 2, 'user': user, 'prereqOf': 'feature0' }] assert evaluate(flag, user, store) == ('a', events_should_be)
def test_flag_matches_user_from_rules(): flag = { 'key': 'feature0', 'on': True, 'rules': [{ 'clauses': [{ 'attribute': 'key', 'op': 'in', 'values': ['userkey'] }], 'variation': 2 }], 'fallthrough': { 'variation': 0 }, 'offVariation': 1, 'variations': ['a', 'b', 'c'] } user = {'key': 'userkey'} assert evaluate(flag, user, empty_store) == ('c', [])
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, [])
def test_user_key_is_coerced_to_string_for_evaluation(): clause = { 'attribute': 'key', 'op': 'in', 'values': [ '999' ] } flag = _make_bool_flag_from_clause(clause) user = { 'key': 999 } assert evaluate(flag, user, empty_store).detail.value == True