Skip to content

Commit 3c2d2bb

Browse files
authored
Config v6 tests (#62)
* config v6 test fix * version 9.0.3 * extend_config_with_inline_salt_and_segment > fixup_config_salt_and_segments * review fixes
1 parent a698e36 commit 3c2d2bb

File tree

12 files changed

+103
-103
lines changed

12 files changed

+103
-103
lines changed

configcatclient/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ class Comparator(IntEnum):
284284
PREREQUISITE_COMPARATOR_TEXTS = ['EQUALS', 'DOES NOT EQUAL']
285285

286286

287-
def extend_config_with_inline_salt_and_segment(config):
287+
def fixup_config_salt_and_segments(config):
288288
"""
289289
Adds the inline salt and segment to the config.
290290
When using flag overrides, the original salt and segment indexes may become invalid. Therefore, we copy the

configcatclient/configcatclient.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -207,13 +207,14 @@ def get_key_and_value(self, variation_id): # noqa: C901
207207
targeting_rules = value.get(TARGETING_RULES, [])
208208
for targeting_rule in targeting_rules:
209209
served_value = targeting_rule.get(SERVED_VALUE)
210-
if served_value is not None and variation_id == served_value.get(VARIATION_ID):
211-
return KeyValue(key, get_value(served_value, setting_type))
212-
213-
targeting_rule_percentage_options = targeting_rule.get(PERCENTAGE_OPTIONS, [])
214-
for percentage_option in targeting_rule_percentage_options:
215-
if variation_id == percentage_option.get(VARIATION_ID):
216-
return KeyValue(key, get_value(percentage_option, setting_type))
210+
if served_value is not None:
211+
if variation_id == served_value.get(VARIATION_ID):
212+
return KeyValue(key, get_value(served_value, setting_type))
213+
else:
214+
targeting_rule_percentage_options = targeting_rule.get(PERCENTAGE_OPTIONS, [])
215+
for percentage_option in targeting_rule_percentage_options:
216+
if variation_id == percentage_option.get(VARIATION_ID):
217+
return KeyValue(key, get_value(percentage_option, setting_type))
217218

218219
percentage_options = value.get(PERCENTAGE_OPTIONS, [])
219220
for percentage_option in percentage_options:

configcatclient/configentry.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from math import floor
44

55
from . import utils
6-
from .config import extend_config_with_inline_salt_and_segment
6+
from .config import fixup_config_salt_and_segments
77
from .utils import unicode_to_utf8
88

99

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

configcatclient/configfetcher.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from requests import HTTPError
66
from requests import Timeout
77

8-
from .config import extend_config_with_inline_salt_and_segment
8+
from .config import fixup_config_salt_and_segments
99
from .configentry import ConfigEntry
1010
from .config import CONFIG_FILE_NAME, PREFERENCES, BASE_URL, REDIRECT
1111
from .datagovernance import DataGovernance
@@ -170,7 +170,7 @@ def _fetch(self, etag): # noqa: C901
170170
if response_etag is None:
171171
response_etag = ''
172172
config = response.json()
173-
extend_config_with_inline_salt_and_segment(config)
173+
fixup_config_salt_and_segments(config)
174174
if sys.version_info[0] == 2:
175175
config = unicode_to_utf8(config) # On Python 2.7, convert unicode to utf-8
176176
config_json_string = response.text.encode('utf-8')

configcatclient/localfiledatasource.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import codecs
22
import sys
33

4-
from .config import extend_config_with_inline_salt_and_segment, VALUE, FEATURE_FLAGS, BOOL_VALUE, STRING_VALUE, \
4+
from .config import fixup_config_salt_and_segments, VALUE, FEATURE_FLAGS, BOOL_VALUE, STRING_VALUE, \
55
INT_VALUE, DOUBLE_VALUE, SettingType, SETTING_TYPE, UNSUPPORTED_VALUE
66
from .overridedatasource import OverrideDataSource, FlagOverrides
77
import json
@@ -77,7 +77,7 @@ def _reload_file_content(self): # noqa: C901
7777
if setting_type is not None:
7878
self._config[FEATURE_FLAGS][key][SETTING_TYPE] = int(setting_type)
7979
else:
80-
extend_config_with_inline_salt_and_segment(data)
80+
fixup_config_salt_and_segments(data)
8181
self._config = data
8282
except OSError:
8383
self.log.exception('Failed to read the local config file \'%s\'.', self._file_path, event_id=1302)

configcatclient/rolloutevaluator.py

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from datetime import datetime
1818

1919
from .user import User
20-
from .utils import unicode_to_utf8, encode_utf8, get_seconds_since_epoch
20+
from .utils import unicode_to_utf8, encode_utf8, get_seconds_since_epoch, is_string_list
2121

2222

2323
def sha256(value_utf8, salt, context_salt):
@@ -151,7 +151,7 @@ def _user_attribute_value_to_string(self, value):
151151

152152
if isinstance(value, datetime):
153153
value = self._get_user_attribute_value_as_seconds_since_epoch(value)
154-
elif isinstance(value, list):
154+
elif is_string_list(value):
155155
value = self._get_user_attribute_value_as_string_list(value)
156156
return json.dumps(value, ensure_ascii=False, separators=(',', ':')) # Convert the list to a JSON string
157157

@@ -198,25 +198,15 @@ def _get_user_attribute_value_as_seconds_since_epoch(self, attribute_value):
198198
return self._convert_numeric_to_float(attribute_value)
199199

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

206-
# Check if the result is a list
207-
if not isinstance(attribute_value_list, list):
207+
if not is_string_list(attribute_value_list):
208208
raise ValueError()
209209

210-
# Check if all items in the list are strings
211-
for item in attribute_value_list:
212-
# Handle unicode strings on Python 2.7
213-
if sys.version_info[0] == 2:
214-
if not isinstance(attribute_value, (str, unicode)): # noqa: F821
215-
return attribute_value
216-
else:
217-
if not isinstance(item, str):
218-
raise ValueError()
219-
220210
return attribute_value_list
221211

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

278+
if log_builder:
279+
log_builder.new_line('Evaluating %% options based on the User.%s attribute:' % user_attribute_name)
280+
log_builder.new_line('- Computing hash in the [0..99] range from User.%s => %s '
281+
'(this value is sticky and consistent across all SDKs)' %
282+
(user_attribute_name, hash_val))
283+
288284
bucket = 0
289285
index = 1
290286
for percentage_option in percentage_options or []:
@@ -294,11 +290,6 @@ def _evaluate_percentage_options(self, percentage_options, context, percentage_r
294290
percentage_value = get_value(percentage_option, context.setting_type)
295291
variation_id = percentage_option.get(VARIATION_ID, default_variation_id)
296292
if log_builder:
297-
log_builder.new_line('Evaluating %% options based on the User.%s attribute:' %
298-
user_attribute_name)
299-
log_builder.new_line('- Computing hash in the [0..99] range from User.%s => %s '
300-
'(this value is sticky and consistent across all SDKs)' %
301-
(user_attribute_name, hash_val))
302293
log_builder.new_line("- Hash value %s selects %% option %s (%s%%), '%s'." %
303294
(hash_val, index, percentage, percentage_value))
304295
return True, percentage_value, variation_id, percentage_option
@@ -421,8 +412,7 @@ def _evaluate_prerequisite_flag_condition(self, prerequisite_flag_condition, con
421412
prerequisite_value, _, _, _, _ = self.evaluate(prerequisite_key, context.user, None, None, config,
422413
log_builder, context.visited_keys)
423414

424-
if visited_keys:
425-
visited_keys.pop()
415+
visited_keys.pop()
426416

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

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

528520
return segment_condition_result, error
529521

530-
return False, None
522+
raise ValueError('Comparison operator is missing or invalid.')
531523

532524
def _evaluate_user_condition(self, user_condition, context, context_salt, salt, log_builder): # noqa: C901, E501
533525
"""
@@ -562,7 +554,7 @@ def _evaluate_user_condition(self, user_condition, context, context_salt, salt,
562554
return False, error
563555

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

768762
return False, error

configcatclient/utils.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,24 @@ def get_utc_now_seconds_since_epoch():
8181
return get_seconds_since_epoch(get_utc_now())
8282

8383

84+
def is_string_list(value):
85+
# Check if the value is a list
86+
if not isinstance(value, list):
87+
return False
88+
89+
# Check if all items in the list are strings
90+
for item in value:
91+
# Handle unicode strings on Python 2.7
92+
if sys.version_info[0] == 2:
93+
if not isinstance(item, (str, unicode)): # noqa: F821
94+
return False
95+
else:
96+
if not isinstance(item, str):
97+
return False
98+
99+
return True
100+
101+
84102
def unicode_to_utf8(data):
85103
"""
86104
Convert unicode data in a collection to UTF-8 data. Used for supporting unicode config json strings on Python 2.7.

configcatclient/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
CONFIGCATCLIENT_VERSION = "9.0.2"
1+
CONFIGCATCLIENT_VERSION = "9.0.3"

configcatclienttests/data/evaluation/circular_dependency.json

Lines changed: 0 additions & 14 deletions
This file was deleted.

configcatclienttests/test_hooks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from configcatclient.configcatclient import ConfigCatClient
77
from configcatclient.config import FEATURE_FLAGS, VALUE, SERVED_VALUE, STRING_VALUE, \
8-
extend_config_with_inline_salt_and_segment
8+
fixup_config_salt_and_segments
99
from configcatclient.user import User
1010
from configcatclient.configcatoptions import ConfigCatOptions, Hooks
1111
from configcatclient.pollingmode import PollingMode
@@ -47,7 +47,7 @@ def test_init(self):
4747
self.assertTrue(hook_callbacks.is_ready)
4848
self.assertEqual(1, hook_callbacks.is_ready_call_count)
4949
extended_config = TEST_OBJECT
50-
extend_config_with_inline_salt_and_segment(extended_config)
50+
fixup_config_salt_and_segments(extended_config)
5151
self.assertEqual(extended_config.get(FEATURE_FLAGS), hook_callbacks.changed_config)
5252
self.assertEqual(1, hook_callbacks.changed_config_call_count)
5353
self.assertTrue(hook_callbacks.evaluation_details)

0 commit comments

Comments
 (0)