Skip to content

Commit

Permalink
Config v6 tests (#62)
Browse files Browse the repository at this point in the history
* config v6 test fix

* version 9.0.3

* extend_config_with_inline_salt_and_segment > fixup_config_salt_and_segments

* review fixes
  • Loading branch information
kp-cat authored Mar 8, 2024
1 parent a698e36 commit 3c2d2bb
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 103 deletions.
2 changes: 1 addition & 1 deletion configcatclient/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ class Comparator(IntEnum):
PREREQUISITE_COMPARATOR_TEXTS = ['EQUALS', 'DOES NOT EQUAL']


def extend_config_with_inline_salt_and_segment(config):
def fixup_config_salt_and_segments(config):
"""
Adds the inline salt and segment to the config.
When using flag overrides, the original salt and segment indexes may become invalid. Therefore, we copy the
Expand Down
15 changes: 8 additions & 7 deletions configcatclient/configcatclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,13 +207,14 @@ def get_key_and_value(self, variation_id): # noqa: C901
targeting_rules = value.get(TARGETING_RULES, [])
for targeting_rule in targeting_rules:
served_value = targeting_rule.get(SERVED_VALUE)
if served_value is not None and variation_id == served_value.get(VARIATION_ID):
return KeyValue(key, get_value(served_value, setting_type))

targeting_rule_percentage_options = targeting_rule.get(PERCENTAGE_OPTIONS, [])
for percentage_option in targeting_rule_percentage_options:
if variation_id == percentage_option.get(VARIATION_ID):
return KeyValue(key, get_value(percentage_option, setting_type))
if served_value is not None:
if variation_id == served_value.get(VARIATION_ID):
return KeyValue(key, get_value(served_value, setting_type))
else:
targeting_rule_percentage_options = targeting_rule.get(PERCENTAGE_OPTIONS, [])
for percentage_option in targeting_rule_percentage_options:
if variation_id == percentage_option.get(VARIATION_ID):
return KeyValue(key, get_value(percentage_option, setting_type))

percentage_options = value.get(PERCENTAGE_OPTIONS, [])
for percentage_option in percentage_options:
Expand Down
4 changes: 2 additions & 2 deletions configcatclient/configentry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from math import floor

from . import utils
from .config import extend_config_with_inline_salt_and_segment
from .config import fixup_config_salt_and_segments
from .utils import unicode_to_utf8


Expand Down Expand Up @@ -43,7 +43,7 @@ def create_from_string(cls, string):
config = json.loads(config_json)
if sys.version_info[0] == 2:
config = unicode_to_utf8(config) # On Python 2.7, convert unicode to utf-8
extend_config_with_inline_salt_and_segment(config)
fixup_config_salt_and_segments(config)
except ValueError as e:
raise ValueError('Invalid config JSON: {}. {}'.format(config_json, str(e)))

Expand Down
4 changes: 2 additions & 2 deletions configcatclient/configfetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from requests import HTTPError
from requests import Timeout

from .config import extend_config_with_inline_salt_and_segment
from .config import fixup_config_salt_and_segments
from .configentry import ConfigEntry
from .config import CONFIG_FILE_NAME, PREFERENCES, BASE_URL, REDIRECT
from .datagovernance import DataGovernance
Expand Down Expand Up @@ -170,7 +170,7 @@ def _fetch(self, etag): # noqa: C901
if response_etag is None:
response_etag = ''
config = response.json()
extend_config_with_inline_salt_and_segment(config)
fixup_config_salt_and_segments(config)
if sys.version_info[0] == 2:
config = unicode_to_utf8(config) # On Python 2.7, convert unicode to utf-8
config_json_string = response.text.encode('utf-8')
Expand Down
4 changes: 2 additions & 2 deletions configcatclient/localfiledatasource.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import codecs
import sys

from .config import extend_config_with_inline_salt_and_segment, VALUE, FEATURE_FLAGS, BOOL_VALUE, STRING_VALUE, \
from .config import fixup_config_salt_and_segments, VALUE, FEATURE_FLAGS, BOOL_VALUE, STRING_VALUE, \
INT_VALUE, DOUBLE_VALUE, SettingType, SETTING_TYPE, UNSUPPORTED_VALUE
from .overridedatasource import OverrideDataSource, FlagOverrides
import json
Expand Down Expand Up @@ -77,7 +77,7 @@ def _reload_file_content(self): # noqa: C901
if setting_type is not None:
self._config[FEATURE_FLAGS][key][SETTING_TYPE] = int(setting_type)
else:
extend_config_with_inline_salt_and_segment(data)
fixup_config_salt_and_segments(data)
self._config = data
except OSError:
self.log.exception('Failed to read the local config file \'%s\'.', self._file_path, event_id=1302)
Expand Down
42 changes: 18 additions & 24 deletions configcatclient/rolloutevaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from datetime import datetime

from .user import User
from .utils import unicode_to_utf8, encode_utf8, get_seconds_since_epoch
from .utils import unicode_to_utf8, encode_utf8, get_seconds_since_epoch, is_string_list


def sha256(value_utf8, salt, context_salt):
Expand Down Expand Up @@ -151,7 +151,7 @@ def _user_attribute_value_to_string(self, value):

if isinstance(value, datetime):
value = self._get_user_attribute_value_as_seconds_since_epoch(value)
elif isinstance(value, list):
elif is_string_list(value):
value = self._get_user_attribute_value_as_string_list(value)
return json.dumps(value, ensure_ascii=False, separators=(',', ':')) # Convert the list to a JSON string

Expand Down Expand Up @@ -198,25 +198,15 @@ def _get_user_attribute_value_as_seconds_since_epoch(self, attribute_value):
return self._convert_numeric_to_float(attribute_value)

def _get_user_attribute_value_as_string_list(self, attribute_value):
if not isinstance(attribute_value, list):
# Handle unicode strings on Python 2.7
if isinstance(attribute_value, str) or sys.version_info[0] == 2 and isinstance(attribute_value, unicode): # noqa: F821
attribute_value_list = json.loads(attribute_value)
else:
attribute_value_list = attribute_value

# Check if the result is a list
if not isinstance(attribute_value_list, list):
if not is_string_list(attribute_value_list):
raise ValueError()

# Check if all items in the list are strings
for item in attribute_value_list:
# Handle unicode strings on Python 2.7
if sys.version_info[0] == 2:
if not isinstance(attribute_value, (str, unicode)): # noqa: F821
return attribute_value
else:
if not isinstance(item, str):
raise ValueError()

return attribute_value_list

def _handle_invalid_user_attribute(self, comparison_attribute, comparator, comparison_value, key, validation_error):
Expand Down Expand Up @@ -285,6 +275,12 @@ def _evaluate_percentage_options(self, percentage_options, context, percentage_r
hash_candidate = ('%s%s' % (key, self._user_attribute_value_to_string(user_key))).encode('utf-8')
hash_val = int(hashlib.sha1(hash_candidate).hexdigest()[:7], 16) % 100

if log_builder:
log_builder.new_line('Evaluating %% options based on the User.%s attribute:' % user_attribute_name)
log_builder.new_line('- Computing hash in the [0..99] range from User.%s => %s '
'(this value is sticky and consistent across all SDKs)' %
(user_attribute_name, hash_val))

bucket = 0
index = 1
for percentage_option in percentage_options or []:
Expand All @@ -294,11 +290,6 @@ def _evaluate_percentage_options(self, percentage_options, context, percentage_r
percentage_value = get_value(percentage_option, context.setting_type)
variation_id = percentage_option.get(VARIATION_ID, default_variation_id)
if log_builder:
log_builder.new_line('Evaluating %% options based on the User.%s attribute:' %
user_attribute_name)
log_builder.new_line('- Computing hash in the [0..99] range from User.%s => %s '
'(this value is sticky and consistent across all SDKs)' %
(user_attribute_name, hash_val))
log_builder.new_line("- Hash value %s selects %% option %s (%s%%), '%s'." %
(hash_val, index, percentage, percentage_value))
return True, percentage_value, variation_id, percentage_option
Expand Down Expand Up @@ -421,8 +412,7 @@ def _evaluate_prerequisite_flag_condition(self, prerequisite_flag_condition, con
prerequisite_value, _, _, _, _ = self.evaluate(prerequisite_key, context.user, None, None, config,
log_builder, context.visited_keys)

if visited_keys:
visited_keys.pop()
visited_keys.pop()

if log_builder:
log_builder.new_line("Prerequisite flag evaluation result: '%s'." % str(prerequisite_value))
Expand All @@ -438,6 +428,8 @@ def _evaluate_prerequisite_flag_condition(self, prerequisite_flag_condition, con
elif prerequisite_comparator == PrerequisiteComparator.NOT_EQUALS:
if prerequisite_value != prerequisite_comparison_value:
prerequisite_condition_result = True
else:
raise ValueError('Comparison operator is missing or invalid.')

if log_builder:
log_builder.append('%s.' % ('true' if prerequisite_condition_result else 'false'))
Expand Down Expand Up @@ -527,7 +519,7 @@ def _evaluate_segment_condition(self, segment_condition, context, salt, log_buil

return segment_condition_result, error

return False, None
raise ValueError('Comparison operator is missing or invalid.')

def _evaluate_user_condition(self, user_condition, context, context_salt, salt, log_builder): # noqa: C901, E501
"""
Expand Down Expand Up @@ -562,7 +554,7 @@ def _evaluate_user_condition(self, user_condition, context, context_salt, salt,
return False, error

user_value = user.get_attribute(comparison_attribute)
if user_value is None or (not user_value and not isinstance(user_value, list)):
if user_value is None or (isinstance(user_value, str) and len(user_value) == 0):
self.log.warning('Cannot evaluate condition (%s) for setting \'%s\' '
'(the User.%s attribute is missing). You should set the User.%s attribute in order to make '
'targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/',
Expand Down Expand Up @@ -764,5 +756,7 @@ def _evaluate_user_condition(self, user_condition, context, context_salt, salt,
if comparison in user_value_list:
return False, None
return True, error
else:
raise ValueError('Comparison operator is missing or invalid.')

return False, error
18 changes: 18 additions & 0 deletions configcatclient/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ def get_utc_now_seconds_since_epoch():
return get_seconds_since_epoch(get_utc_now())


def is_string_list(value):
# Check if the value is a list
if not isinstance(value, list):
return False

# Check if all items in the list are strings
for item in value:
# Handle unicode strings on Python 2.7
if sys.version_info[0] == 2:
if not isinstance(item, (str, unicode)): # noqa: F821
return False
else:
if not isinstance(item, str):
return False

return True


def unicode_to_utf8(data):
"""
Convert unicode data in a collection to UTF-8 data. Used for supporting unicode config json strings on Python 2.7.
Expand Down
2 changes: 1 addition & 1 deletion configcatclient/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CONFIGCATCLIENT_VERSION = "9.0.2"
CONFIGCATCLIENT_VERSION = "9.0.3"
14 changes: 0 additions & 14 deletions configcatclienttests/data/evaluation/circular_dependency.json

This file was deleted.

4 changes: 2 additions & 2 deletions configcatclienttests/test_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from configcatclient.configcatclient import ConfigCatClient
from configcatclient.config import FEATURE_FLAGS, VALUE, SERVED_VALUE, STRING_VALUE, \
extend_config_with_inline_salt_and_segment
fixup_config_salt_and_segments
from configcatclient.user import User
from configcatclient.configcatoptions import ConfigCatOptions, Hooks
from configcatclient.pollingmode import PollingMode
Expand Down Expand Up @@ -47,7 +47,7 @@ def test_init(self):
self.assertTrue(hook_callbacks.is_ready)
self.assertEqual(1, hook_callbacks.is_ready_call_count)
extended_config = TEST_OBJECT
extend_config_with_inline_salt_and_segment(extended_config)
fixup_config_salt_and_segments(extended_config)
self.assertEqual(extended_config.get(FEATURE_FLAGS), hook_callbacks.changed_config)
self.assertEqual(1, hook_callbacks.changed_config_call_count)
self.assertTrue(hook_callbacks.evaluation_details)
Expand Down
Loading

0 comments on commit 3c2d2bb

Please sign in to comment.