From ac7e81f5237c87b3d730579caf1907b8dbd4ed18 Mon Sep 17 00:00:00 2001 From: Amna Ejaz Date: Thu, 30 Jul 2020 12:50:45 +0500 Subject: [PATCH 01/34] feat:semantic versioning --- optimizely/helpers/condition.py | 201 ++++++++++++++++++++++- optimizely/helpers/enums.py | 7 + tests/base.py | 42 ++++- tests/helpers_tests/test_condition.py | 222 ++++++++++++++++++++++++++ tests/test_optimizely.py | 42 +++++ 5 files changed, 510 insertions(+), 4 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 0676aecb..42c52b86 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -18,6 +18,7 @@ from . import validator from .enums import CommonAudienceEvaluationLogs as audience_logs +from .enums import Errors, SemverType class ConditionOperatorTypes(object): @@ -31,6 +32,11 @@ class ConditionMatchTypes(object): EXISTS = 'exists' GREATER_THAN = 'gt' LESS_THAN = 'lt' + SEMVER_EQ = 'semver_eq' + SEMVER_GE = 'semver_ge' + SEMVER_GT = 'semver_gt' + SEMVER_LE = 'semver_le' + SEMVER_LT = 'semver_lt' SUBSTRING = 'substring' @@ -233,12 +239,205 @@ def substring_evaluator(self, index): return condition_value in user_value + def semver_equal_evaluator(self, index): + """ Evaluate the given semver equal match target version for the user version. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user version is equal (==) to the target version. + - False if the user version is not equal (!=) to the target version. + None: + - if the user version value is not string type or is null. + """ + return self.compare_user_version_with_target_version(index) == 0 + + def semver_greater_than_evaluator(self, index): + """ Evaluate the given semver greater than match target version for the user version. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user version is greater than the target version. + - False if the user version is less than or equal to the target version. + None: + - if the user version value is not string type or is null. + """ + return self.compare_user_version_with_target_version(index) == 1 + + def semver_less_than_evaluator(self, index): + """ Evaluate the given semver less than match target version for the user version. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user version is less than the target version. + - False if the user version is greater than or equal to the target version. + None: + - if the user version value is not string type or is null. + """ + return self.compare_user_version_with_target_version(index) == -1 + + def semver_less_than_or_equal_evaluator(self, index): + """ Evaluate the given semver less than or equal to match target version for the user version. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user version is less than or equal to the target version. + - False if the user version is greater than the target version. + None: + - if the user version value is not string type or is null. + """ + return self.compare_user_version_with_target_version(index) <= 0 + + def semver_greater_than_or_equal_evaluator(self, index): + """ Evaluate the given semver greater than or equal to match target version for the user version. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user version is greater than or equal to the target version. + - False if the user version is less than the target version. + None: + - if the user version value is not string type or is null. + """ + return self.compare_user_version_with_target_version(index) >= 0 + + def split_semantic_version(self, target): + """ Method to split the given version. + + Args: + target: Given version. + + Returns: + List: + - The array of version split into smaller parts i.e major, minor, patch etc + Exception: + - if the given version is invalid in format + """ + target_prefix = target + target_suffix = "" + + if self.is_pre_release(target) or self.is_build(target): + target_parts = target.split(SemverType.IS_PRE_RELEASE if self.is_pre_release(target) else SemverType.IS_BUILD) + if len(target_parts) < 1: + raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + + target_prefix = str(target_parts[0]) + target_suffix = target_parts[1:] + + target_version_parts = target_prefix.split(".") + for part in target_version_parts: + if not part.isdigit(): + raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + + if target_suffix: + target_version_parts.extend(target_suffix) + return target_version_parts + + def is_pre_release(self, target): + """ Method to check if the given version contains "-" + + Args: + target: Given version in string. + + Returns: + Boolean: + - True if the given version does contain "-" + - False if it doesn't + """ + return SemverType.IS_PRE_RELEASE in target + + def is_patch_pre_release(self, idx, idx_value): + return idx == SemverType.PATCH_INDEX and idx_value in SemverType.IS_PATCH_PRE_RELEASE + + + def is_build(self, target): + """ Method to check if the given version contains "+" + + Args: + target: Given version in string. + + Returns: + Boolean: + - True if the given version does contain "+" + - False if it doesn't + """ + return SemverType.IS_BUILD in target + + def compare_user_version_with_target_version(self, index): + """ Method to compare user version with target version. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Int: + - 0 if user version is equal to target version. + - 1 if user version is greater than target version. + - -1 if user version is less than target version or, in case of exact string match, doesn't match the target version. + None: + - if the user version value is not string type or is null. + """ + condition_name = self.condition_data[index][0] + target_version = self.condition_data[index][1] + user_version = self.attributes.get(condition_name) + + if not isinstance(user_version, string_types): + self.logger.warning( + audience_logs.UNEXPECTED_TYPE.format(self._get_condition_json(index), type(user_version), condition_name) + ) + return None + + target_version_parts = self.split_semantic_version(target_version) + user_version_parts = self.split_semantic_version(user_version) + user_version_parts_len = len(user_version_parts) + target_version_parts_len = len(target_version_parts) + + for (idx, _) in enumerate(target_version_parts): + if user_version_parts_len <= idx: + return -1 + # compare strings e.g: alpha/beta/release + elif not user_version_parts[idx].isdigit(): + if user_version_parts[idx] < target_version_parts[idx]: + return -1 + elif user_version_parts[idx] > target_version_parts[idx]: + return 1 + # compare numbers e.g: n1.n2.n3 + else: + user_version_part = int(user_version_parts[idx]) + target_version_part = int(target_version_parts[idx]) + if user_version_part > target_version_part: + return 1 + elif user_version_part < target_version_part: + return -1 + if user_version_parts_len > target_version_parts_len: + if self.is_patch_pre_release(user_version_parts_len-1, user_version_parts[user_version_parts_len-1]): + return -1 + return 0 + EVALUATORS_BY_MATCH_TYPE = { ConditionMatchTypes.EXACT: exact_evaluator, ConditionMatchTypes.EXISTS: exists_evaluator, ConditionMatchTypes.GREATER_THAN: greater_than_evaluator, ConditionMatchTypes.LESS_THAN: less_than_evaluator, - ConditionMatchTypes.SUBSTRING: substring_evaluator, + ConditionMatchTypes.SEMVER_EQ: semver_equal_evaluator, + ConditionMatchTypes.SEMVER_GE: semver_greater_than_or_equal_evaluator, + ConditionMatchTypes.SEMVER_GT: semver_greater_than_evaluator, + ConditionMatchTypes.SEMVER_LE: semver_less_than_or_equal_evaluator, + ConditionMatchTypes.SEMVER_LT: semver_less_than_evaluator, + ConditionMatchTypes.SUBSTRING: substring_evaluator } def evaluate(self, index): diff --git a/optimizely/helpers/enums.py b/optimizely/helpers/enums.py index beaba157..1d45618b 100644 --- a/optimizely/helpers/enums.py +++ b/optimizely/helpers/enums.py @@ -157,3 +157,10 @@ class NotificationTypes(object): OPTIMIZELY_CONFIG_UPDATE = 'OPTIMIZELY_CONFIG_UPDATE' TRACK = 'TRACK:event_key, user_id, attributes, event_tags, event' LOG_EVENT = 'LOG_EVENT:log_event' + + +class SemverType(object): + IS_PRE_RELEASE = '-' + IS_BUILD = '+' + PATCH_INDEX = 3 + IS_PATCH_PRE_RELEASE = ['release', 'beta'] diff --git a/tests/base.py b/tests/base.py index 432d5287..dbd55050 100644 --- a/tests/base.py +++ b/tests/base.py @@ -518,6 +518,7 @@ def setUp(self, config_dict='config_dict'): '3468206647', '3468206644', '3468206643', + '18278344267' ], 'variations': [ {'variables': [], 'id': '11557362669', 'key': '11557362669', 'featureEnabled': True} @@ -556,7 +557,7 @@ def setUp(self, config_dict='config_dict'): 'audienceConditions': [ 'and', ['or', '3468206642', '3988293898'], - ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643'], + ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643', '18278344267'], ], 'variations': [ {'variables': [], 'id': '11557362670', 'key': '11557362670', 'featureEnabled': True} @@ -626,6 +627,7 @@ def setUp(self, config_dict='config_dict'): '3468206647', '3468206644', '3468206643', + '18278344267' ], 'variations': [ { @@ -653,6 +655,7 @@ def setUp(self, config_dict='config_dict'): '3468206647', '3468206644', '3468206643', + '18278344267' ], 'forcedVariations': {}, }, @@ -667,7 +670,7 @@ def setUp(self, config_dict='config_dict'): 'audienceConditions': [ 'and', ['or', '3468206642', '3988293898'], - ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643'], + ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643', '18278344267'], ], 'forcedVariations': {}, }, @@ -689,7 +692,7 @@ def setUp(self, config_dict='config_dict'): 'audienceConditions': [ 'and', ['or', '3468206642', '3988293898'], - ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643'], + ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643', '18278344267'], ], 'forcedVariations': {}, }, @@ -837,6 +840,37 @@ def setUp(self, config_dict='config_dict'): ], ], }, + { + "id": "18278344267", + "name": "semverReleaseLt1.2.3Gt1.0.0", + "conditions": [ + "and", + [ + "or", + [ + "or", + { + "value": "1.2.3", + "type": "custom_attribute", + "name": "android-release", + "match": "semver_lt" + } + ] + ], + [ + "or", + [ + "or", + { + "value": "1.0.0", + "type": "custom_attribute", + "name": "android-release", + "match": "semver_gt" + } + ] + ] + ] + } ], 'groups': [], 'attributes': [ @@ -844,6 +878,8 @@ def setUp(self, config_dict='config_dict'): {'key': 'lasers', 'id': '594016'}, {'key': 'should_do_it', 'id': '594017'}, {'key': 'favorite_ice_cream', 'id': '594018'}, + {'key': 'android-release', 'id': '594019'}, + ], 'botFiltering': False, 'accountId': '4879520872', diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index b4dee368..17fa9898 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -24,6 +24,20 @@ integerCondition = ['num_users', 10, 'custom_attribute', 'exact'] doubleCondition = ['pi_value', 3.14, 'custom_attribute', 'exact'] +semver_equal_2_0_0_condition_list = [['Android', "2.0.0", 'custom_attribute', 'semver_eq']] +semver_equal_2_condition_list = [['Android', "2", 'custom_attribute', 'semver_eq']] +semver_equal_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_eq']] +semver_equal_2_0_1_beta_condition_list = [['Android', "2.0.1-beta", 'custom_attribute', 'semver_eq']] +semver_greater_than_2_0_0_condition_list = [['Android', "2.0.0", 'custom_attribute', 'semver_gt']] +semver_greater_than_2_0_0_beta_condition_list = [['Android', "2.0.0-beta", 'custom_attribute', 'semver_gt']] +semver_greater_than_or_equal_2_0_9_beta_condition_list = [['Android', "2.0.9-beta", 'custom_attribute', 'semver_ge']] +semver_greater_than_or_equal_2_0_9_condition_list = [['Android', "2.0.9", 'custom_attribute', 'semver_ge']] +semver_less_than_2_0_0_condition_list = [['Android', "2.0.0", 'custom_attribute', 'semver_lt']] +semver_less_than_2_0_0_release_condition_list = [['Android', "2.0.0-release", 'custom_attribute', 'semver_lt']] +semver_less_than_2_0_0_beta_condition_list = [['Android', "2.0.0-beta", 'custom_attribute', 'semver_lt']] +semver_less_than_or_equal_2_0_1_beta_condition_list = [['Android', "2.0.1-beta", 'custom_attribute', 'semver_le']] +semver_less_than_or_equal_2_0_1_condition_list = [['Android', "2.0.1", 'custom_attribute', 'semver_le']] + exists_condition_list = [['input_value', None, 'custom_attribute', 'exists']] exact_string_condition_list = [['favorite_constellation', 'Lacerta', 'custom_attribute', 'exact']] exact_int_condition_list = [['lasers_count', 9000, 'custom_attribute', 'exact']] @@ -108,6 +122,214 @@ def test_evaluate__returns_null__when_condition_has_an_invalid_type_property(sel self.assertIsNone(evaluator.evaluate(0)) + def test_evaluate__returns_true__when_user_version_matches_target_version(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': '2.0'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_0_condition_list, {'Android': '2.0.0'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_1_beta_condition_list, {'Android': '2.0.1-beta'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_0_condition_list, {'Android': '2.0.0.123'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_condition_list, {'Android': '2.123'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_false__when_user_version_does_not_match_target_version(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': '1.0'}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_0_condition_list, {'Android': '2.0'}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_is_greater_than_target_version(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_2_0_0_beta_condition_list, {'Android': '2.0.0-release'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_2_0_0_condition_list, {'Android': '2.0.1-release'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_false__when_user_version_is_not_greater_than_target_version(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_2_0_0_condition_list, {'Android': '1.1.1'}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_is_less_than_target_version(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_2_0_0_condition_list, {'Android': '1.9.1'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_2_0_0_condition_list, {'Android': '1.9.0-beta'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_2_0_0_release_condition_list, {'Android': '2.0.0-beta'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_2_0_0_condition_list, {'Android': '2.0.0-beta'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_2_0_0_condition_list, {'Android': '2.0.0-release'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_false__when_user_version_is_not_less_than_target_version(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_2_0_0_beta_condition_list, {'Android': '2.0.0-release'}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + + + def test_evaluate__returns_true__when_user_version_is_greater_than_or_equal_to_target_version(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_or_equal_2_0_9_condition_list, {'Android': '2.0.9'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_or_equal_2_0_9_condition_list, {'Android': '2.3'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_or_equal_2_0_9_beta_condition_list, {'Android': '2.0.9-beta'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_false__when_user_version_is_not_greater_than_or_equal_to_target_version(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_or_equal_2_0_9_condition_list, {'Android': '1.0.0'}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_is_less_than_or_equal_to_target_version(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_1_condition_list, {'Android': '2.0.1'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_1_condition_list, {'Android': '1.1'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_1_beta_condition_list, {'Android': '2.0.1-beta'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_false__when_user_version_is_not_less_than_or_equal_to_target_version(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_1_condition_list, {'Android': '3.0.1'}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_evaluate__returns_null__when_no_user_version_provided(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_evaluate__returns_null__when_user_provided_version_is_null(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': None}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_evaluate__returns_null__when_user_provided_version_is_invalid(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "+"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "+--"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "...+"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "+test"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + def test_exists__returns_false__when_no_user_provided_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( diff --git a/tests/test_optimizely.py b/tests/test_optimizely.py index 94783a7a..f586c44c 100644 --- a/tests/test_optimizely.py +++ b/tests/test_optimizely.py @@ -844,6 +844,48 @@ def test_activate__with_attributes__typed_audience_match(self): self.assertTrue(expected_attr in [x.__dict__ for x in mock_process.call_args[0][0].visitor_attributes]) + def test_activate__with_attributes__typed_audience_with_semver_match(self): + """ Test that activate calls process with right params and returns expected + variation when attributes are provided and typed audience conditions are met. """ + opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_typed_audiences)) + + with mock.patch('optimizely.event.event_processor.ForwardingEventProcessor.process') as mock_process: + # Should be included via exact match string audience with id '18278344267' + self.assertEqual( + 'A', opt_obj.activate('typed_audience_experiment', 'test_user', {'android-release': '1.0.1'}), + ) + expected_attr = { + 'type': 'custom', + 'value': '1.0.1', + 'entity_id': '594019', + 'key': 'android-release', + } + + self.assertTrue(expected_attr in [x.__dict__ for x in mock_process.call_args[0][0].visitor_attributes]) + + mock_process.reset() + + with mock.patch('optimizely.event.event_processor.ForwardingEventProcessor.process') as mock_process: + self.assertEqual( + 'A', opt_obj.activate('typed_audience_experiment', 'test_user', {'android-release': "1.2.2"}), + ) + expected_attr = { + 'type': 'custom', + 'value': "1.2.2", + 'entity_id': '594019', + 'key': 'android-release', + } + + self.assertTrue(expected_attr in [x.__dict__ for x in mock_process.call_args[0][0].visitor_attributes]) + + def test_activate__with_attributes__typed_audience_with_semver_mismatch(self): + """ Test that activate returns None when typed audience conditions do not match. """ + opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_typed_audiences)) + + with mock.patch('optimizely.event.event_processor.ForwardingEventProcessor.process') as mock_process: + self.assertIsNone(opt_obj.activate('typed_audience_experiment', 'test_user', {'android-release': '1.2.9'})) + self.assertEqual(0, mock_process.call_count) + def test_activate__with_attributes__typed_audience_mismatch(self): """ Test that activate returns None when typed audience conditions do not match. """ opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_typed_audiences)) From ee296748a5547eab11358f1e8e4755de3ad58280 Mon Sep 17 00:00:00 2001 From: Amna Ejaz Date: Thu, 30 Jul 2020 17:53:45 +0500 Subject: [PATCH 02/34] semver updated --- optimizely/helpers/condition.py | 36 +++++---- tests/base.py | 4 +- tests/helpers_tests/test_condition.py | 103 +++++++++++++------------- 3 files changed, 76 insertions(+), 67 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 42c52b86..64c5fd35 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -240,7 +240,7 @@ def substring_evaluator(self, index): return condition_value in user_value def semver_equal_evaluator(self, index): - """ Evaluate the given semver equal match target version for the user version. + """ Evaluate the given semantic version equal match target version for the user version. Args: index: Index of the condition to be evaluated. @@ -255,7 +255,7 @@ def semver_equal_evaluator(self, index): return self.compare_user_version_with_target_version(index) == 0 def semver_greater_than_evaluator(self, index): - """ Evaluate the given semver greater than match target version for the user version. + """ Evaluate the given semantic version greater than match target version for the user version. Args: index: Index of the condition to be evaluated. @@ -267,10 +267,10 @@ def semver_greater_than_evaluator(self, index): None: - if the user version value is not string type or is null. """ - return self.compare_user_version_with_target_version(index) == 1 + return self.compare_user_version_with_target_version(index) > 0 def semver_less_than_evaluator(self, index): - """ Evaluate the given semver less than match target version for the user version. + """ Evaluate the given semantic version less than match target version for the user version. Args: index: Index of the condition to be evaluated. @@ -282,10 +282,10 @@ def semver_less_than_evaluator(self, index): None: - if the user version value is not string type or is null. """ - return self.compare_user_version_with_target_version(index) == -1 + return self.compare_user_version_with_target_version(index) < 0 def semver_less_than_or_equal_evaluator(self, index): - """ Evaluate the given semver less than or equal to match target version for the user version. + """ Evaluate the given semantic version less than or equal to match target version for the user version. Args: index: Index of the condition to be evaluated. @@ -300,7 +300,7 @@ def semver_less_than_or_equal_evaluator(self, index): return self.compare_user_version_with_target_version(index) <= 0 def semver_greater_than_or_equal_evaluator(self, index): - """ Evaluate the given semver greater than or equal to match target version for the user version. + """ Evaluate the given semantic version greater than or equal to match target version for the user version. Args: index: Index of the condition to be evaluated. @@ -328,9 +328,14 @@ def split_semantic_version(self, target): """ target_prefix = target target_suffix = "" + target_parts = [] - if self.is_pre_release(target) or self.is_build(target): - target_parts = target.split(SemverType.IS_PRE_RELEASE if self.is_pre_release(target) else SemverType.IS_BUILD) + if self.is_pre_release(target): + target_parts = target.split(SemverType.IS_PRE_RELEASE) + elif self.is_build(target): + target_parts = target.split(SemverType.IS_BUILD) + + if target_parts: if len(target_parts) < 1: raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) @@ -362,7 +367,6 @@ def is_pre_release(self, target): def is_patch_pre_release(self, idx, idx_value): return idx == SemverType.PATCH_INDEX and idx_value in SemverType.IS_PATCH_PRE_RELEASE - def is_build(self, target): """ Method to check if the given version contains "+" @@ -386,7 +390,8 @@ def compare_user_version_with_target_version(self, index): Int: - 0 if user version is equal to target version. - 1 if user version is greater than target version. - - -1 if user version is less than target version or, in case of exact string match, doesn't match the target version. + - -1 if user version is less than target version or, in case of exact string match, doesn't match the target + version. None: - if the user version value is not string type or is null. """ @@ -396,7 +401,8 @@ def compare_user_version_with_target_version(self, index): if not isinstance(user_version, string_types): self.logger.warning( - audience_logs.UNEXPECTED_TYPE.format(self._get_condition_json(index), type(user_version), condition_name) + audience_logs.UNEXPECTED_TYPE.format(self._get_condition_json(index), type(user_version), + condition_name) ) return None @@ -408,13 +414,13 @@ def compare_user_version_with_target_version(self, index): for (idx, _) in enumerate(target_version_parts): if user_version_parts_len <= idx: return -1 - # compare strings e.g: alpha/beta/release + # compare strings elif not user_version_parts[idx].isdigit(): if user_version_parts[idx] < target_version_parts[idx]: return -1 elif user_version_parts[idx] > target_version_parts[idx]: return 1 - # compare numbers e.g: n1.n2.n3 + # compare numbers else: user_version_part = int(user_version_parts[idx]) target_version_part = int(target_version_parts[idx]) @@ -423,7 +429,7 @@ def compare_user_version_with_target_version(self, index): elif user_version_part < target_version_part: return -1 if user_version_parts_len > target_version_parts_len: - if self.is_patch_pre_release(user_version_parts_len-1, user_version_parts[user_version_parts_len-1]): + if self.is_patch_pre_release(user_version_parts_len - 1, user_version_parts[user_version_parts_len - 1]): return -1 return 0 diff --git a/tests/base.py b/tests/base.py index dbd55050..9dceec2d 100644 --- a/tests/base.py +++ b/tests/base.py @@ -557,7 +557,8 @@ def setUp(self, config_dict='config_dict'): 'audienceConditions': [ 'and', ['or', '3468206642', '3988293898'], - ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643', '18278344267'], + ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643', + '18278344267'], ], 'variations': [ {'variables': [], 'id': '11557362670', 'key': '11557362670', 'featureEnabled': True} @@ -879,7 +880,6 @@ def setUp(self, config_dict='config_dict'): {'key': 'should_do_it', 'id': '594017'}, {'key': 'favorite_ice_cream', 'id': '594018'}, {'key': 'android-release', 'id': '594019'}, - ], 'botFiltering': False, 'accountId': '4879520872', diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 17fa9898..856279f8 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -1,4 +1,4 @@ -# Copyright 2016-2019, Optimizely +# Copyright 2016-2020, Optimizely # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -122,45 +122,39 @@ def test_evaluate__returns_null__when_condition_has_an_invalid_type_property(sel self.assertIsNone(evaluator.evaluate(0)) - def test_evaluate__returns_true__when_user_version_matches_target_version(self): + def test_evaluate__returns_true__when_user_version_2_matches_target_version_2(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': '2.0'}, self.mock_client_logger + semver_equal_2_condition_list, {'Android': '2'}, self.mock_client_logger ) self.assertStrictTrue(evaluator.evaluate(0)) - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_0_condition_list, {'Android': '2.0.0'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) + def test_evaluate__returns_true__when_user_version_2_2_matches_target_version_2(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_1_beta_condition_list, {'Android': '2.0.1-beta'}, self.mock_client_logger + semver_equal_2_condition_list, {'Android': '2.2'}, self.mock_client_logger ) self.assertStrictTrue(evaluator.evaluate(0)) - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_0_condition_list, {'Android': '2.0.0.123'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) + def test_evaluate__returns_true__when_user_version_2_0_matches_target_version_2_0(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_condition_list, {'Android': '2.123'}, self.mock_client_logger + semver_equal_2_0_condition_list, {'Android': '2.0'}, self.mock_client_logger ) self.assertStrictTrue(evaluator.evaluate(0)) - def test_evaluate__returns_false__when_user_version_does_not_match_target_version(self): + def test_evaluate__returns_true__when_user_version_2_0_0_matches_target_version_2_0_0(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': '1.0'}, self.mock_client_logger + semver_equal_2_0_0_condition_list, {'Android': '2.0.0'}, self.mock_client_logger ) - self.assertStrictFalse(evaluator.evaluate(0)) + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_false__when_user_version_2_0_does_not_match_target_version_2_0(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_0_condition_list, {'Android': '2.0'}, self.mock_client_logger @@ -168,7 +162,8 @@ def test_evaluate__returns_false__when_user_version_does_not_match_target_versio self.assertStrictFalse(evaluator.evaluate(0)) - def test_evaluate__returns_true__when_user_version_is_greater_than_target_version(self): + def test_evaluate__returns_true__when_user_version_2_0_0_release_is_greater_than_target_version_2_0_0_beta( + self): evaluator = condition_helper.CustomAttributeConditionEvaluator( semver_greater_than_2_0_0_beta_condition_list, {'Android': '2.0.0-release'}, self.mock_client_logger @@ -176,53 +171,47 @@ def test_evaluate__returns_true__when_user_version_is_greater_than_target_versio self.assertStrictTrue(evaluator.evaluate(0)) - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_greater_than_2_0_0_condition_list, {'Android': '2.0.1-release'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_false__when_user_version_is_not_greater_than_target_version(self): + def test_evaluate__returns_true__when_user_version_2_0_1_is_greater_than_target_version__2_0_0(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_greater_than_2_0_0_condition_list, {'Android': '1.1.1'}, self.mock_client_logger + semver_greater_than_2_0_0_condition_list, {'Android': '2.0.1'}, self.mock_client_logger ) - self.assertStrictFalse(evaluator.evaluate(0)) + self.assertStrictTrue(evaluator.evaluate(0)) - def test_evaluate__returns_true__when_user_version_is_less_than_target_version(self): + def test_evaluate__returns_true__when_user_version_1_9_9_is_less_than_target_version_2_0_0(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_2_0_0_condition_list, {'Android': '1.9.1'}, self.mock_client_logger + semver_less_than_2_0_0_condition_list, {'Android': '1.9.9'}, self.mock_client_logger ) self.assertStrictTrue(evaluator.evaluate(0)) + def test_evaluate__returns_true__when_user_version_1_9_0_beta_is_less_than_target_version_2_0_0(self): + evaluator = condition_helper.CustomAttributeConditionEvaluator( semver_less_than_2_0_0_condition_list, {'Android': '1.9.0-beta'}, self.mock_client_logger ) self.assertStrictTrue(evaluator.evaluate(0)) - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_2_0_0_release_condition_list, {'Android': '2.0.0-beta'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) + def test_evaluate__returns_true__when_user_version_2_0_0_release_is_less_than_target_version_2_0_0(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_2_0_0_condition_list, {'Android': '2.0.0-beta'}, self.mock_client_logger + semver_less_than_2_0_0_condition_list, {'Android': '2.0.0-release'}, self.mock_client_logger ) self.assertStrictTrue(evaluator.evaluate(0)) + def test_evaluate__returns_true__when_user_version_2_0_0_beta_is_less_than_target_version_2_0_0_release(self): + evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_2_0_0_condition_list, {'Android': '2.0.0-release'}, self.mock_client_logger + semver_less_than_2_0_0_release_condition_list, {'Android': '2.0.0-beta'}, self.mock_client_logger ) self.assertStrictTrue(evaluator.evaluate(0)) - def test_evaluate__returns_false__when_user_version_is_not_less_than_target_version(self): + def test_evaluate__returns_false__when_user_version_2_0_0_release_is_not_less_than_target_version_2_0_0_beta(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( semver_less_than_2_0_0_beta_condition_list, {'Android': '2.0.0-release'}, self.mock_client_logger @@ -230,9 +219,7 @@ def test_evaluate__returns_false__when_user_version_is_not_less_than_target_vers self.assertStrictFalse(evaluator.evaluate(0)) - - - def test_evaluate__returns_true__when_user_version_is_greater_than_or_equal_to_target_version(self): + def test_evaluate__returns_true__when_user_version_2_0_9_is_greater_than_or_equal_to_target_version_2_0_9(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( semver_greater_than_or_equal_2_0_9_condition_list, {'Android': '2.0.9'}, self.mock_client_logger @@ -240,19 +227,25 @@ def test_evaluate__returns_true__when_user_version_is_greater_than_or_equal_to_t self.assertStrictTrue(evaluator.evaluate(0)) + def test_evaluate__returns_true__when_user_version_2_3_is_greater_than_or_equal_to_target_version_2_0_9(self): + evaluator = condition_helper.CustomAttributeConditionEvaluator( semver_greater_than_or_equal_2_0_9_condition_list, {'Android': '2.3'}, self.mock_client_logger ) self.assertStrictTrue(evaluator.evaluate(0)) + def test_evaluate__returns_true__when_user_version_2_0_9_beta_is_greater_than_or_equal_to_target_version_2_0_9_beta( + self): + evaluator = condition_helper.CustomAttributeConditionEvaluator( semver_greater_than_or_equal_2_0_9_beta_condition_list, {'Android': '2.0.9-beta'}, self.mock_client_logger ) self.assertStrictTrue(evaluator.evaluate(0)) - def test_evaluate__returns_false__when_user_version_is_not_greater_than_or_equal_to_target_version(self): + def test_evaluate__returns_false__when_user_version_1_0_0_is_not_greater_than_or_equal_to_target_version_2_0_9( + self): evaluator = condition_helper.CustomAttributeConditionEvaluator( semver_greater_than_or_equal_2_0_9_condition_list, {'Android': '1.0.0'}, self.mock_client_logger @@ -260,7 +253,7 @@ def test_evaluate__returns_false__when_user_version_is_not_greater_than_or_equal self.assertStrictFalse(evaluator.evaluate(0)) - def test_evaluate__returns_true__when_user_version_is_less_than_or_equal_to_target_version(self): + def test_evaluate__returns_true__when_user_version_2_0_1_is_less_than_or_equal_to_target_version_2_0_1(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( semver_less_than_or_equal_2_0_1_condition_list, {'Android': '2.0.1'}, self.mock_client_logger @@ -268,19 +261,23 @@ def test_evaluate__returns_true__when_user_version_is_less_than_or_equal_to_targ self.assertStrictTrue(evaluator.evaluate(0)) + def test_evaluate__returns_true__when_user_version_1_1_is_less_than_or_equal_to_target_version_2_0_1(self): + evaluator = condition_helper.CustomAttributeConditionEvaluator( semver_less_than_or_equal_2_0_1_condition_list, {'Android': '1.1'}, self.mock_client_logger ) self.assertStrictTrue(evaluator.evaluate(0)) + def test_evaluate__returns_true__when_user_version_2_0_1_beta_is_less_than_or_equal_to_target_version_2_0_1(self): + evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_or_equal_2_0_1_beta_condition_list, {'Android': '2.0.1-beta'}, self.mock_client_logger + semver_less_than_or_equal_2_0_1_condition_list, {'Android': '2.0.1-beta'}, self.mock_client_logger ) self.assertStrictTrue(evaluator.evaluate(0)) - def test_evaluate__returns_false__when_user_version_is_not_less_than_or_equal_to_target_version(self): + def test_evaluate__returns_false__when_user_version_3_0_1_is_not_less_than_or_equal_to_target_version_2_0_1(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( semver_less_than_or_equal_2_0_1_condition_list, {'Android': '3.0.1'}, self.mock_client_logger @@ -304,27 +301,33 @@ def test_evaluate__returns_null__when_user_provided_version_is_null(self): self.assertIsNone(evaluator.evaluate(0)) - def test_evaluate__returns_null__when_user_provided_version_is_invalid(self): + def test_evaluate__returns_exception__when_user_provided_version_is_invalid1(self): - evaluator = condition_helper.CustomAttributeConditionEvaluator( + condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': "+"}, self.mock_client_logger ) self.assertRaises(Exception) - evaluator = condition_helper.CustomAttributeConditionEvaluator( + def test_evalduate__returns_exception__when_user_provided_version_is_invalid2(self): + + condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': "+--"}, self.mock_client_logger ) self.assertRaises(Exception) - evaluator = condition_helper.CustomAttributeConditionEvaluator( + def test_evalduate__returns_exception__when_user_provided_version_is_invalid3(self): + + condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': "...+"}, self.mock_client_logger ) self.assertRaises(Exception) - evaluator = condition_helper.CustomAttributeConditionEvaluator( + def test_evalduate__returns_exception__when_user_provided_version_is_invalid4(self): + + condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': "+test"}, self.mock_client_logger ) From 4a5e75da78cd80ce935c3e122e8871f69e0ef4fe Mon Sep 17 00:00:00 2001 From: Amna Ejaz Date: Wed, 5 Aug 2020 12:55:33 +0500 Subject: [PATCH 03/34] more validation for invalid cases added --- optimizely/helpers/condition.py | 40 ++++++++++++------- optimizely/helpers/enums.py | 3 +- tests/helpers_tests/test_condition.py | 56 +++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 17 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 64c5fd35..15a22ef1 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -330,6 +330,9 @@ def split_semantic_version(self, target): target_suffix = "" target_parts = [] + if self.has_white_space(target): + raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + if self.is_pre_release(target): target_parts = target.split(SemverType.IS_PRE_RELEASE) elif self.is_build(target): @@ -338,11 +341,16 @@ def split_semantic_version(self, target): if target_parts: if len(target_parts) < 1: raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) - target_prefix = str(target_parts[0]) target_suffix = target_parts[1:] + dot_count = target_prefix.count(".") + if dot_count > 2: + raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + target_version_parts = target_prefix.split(".") + if len(target_version_parts) != dot_count + 1: + raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) for part in target_version_parts: if not part.isdigit(): raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) @@ -380,6 +388,19 @@ def is_build(self, target): """ return SemverType.IS_BUILD in target + def has_white_space(self, target): + """ Method to check if the given version contains " " (white space) + + Args: + target: Given version in string. + + Returns: + Boolean: + - True if the given version does contain " " + - False if it doesn't + """ + return SemverType.HAS_WHITE_SPACE in target + def compare_user_version_with_target_version(self, index): """ Method to compare user version with target version. @@ -399,28 +420,18 @@ def compare_user_version_with_target_version(self, index): target_version = self.condition_data[index][1] user_version = self.attributes.get(condition_name) - if not isinstance(user_version, string_types): - self.logger.warning( - audience_logs.UNEXPECTED_TYPE.format(self._get_condition_json(index), type(user_version), - condition_name) - ) - return None - target_version_parts = self.split_semantic_version(target_version) user_version_parts = self.split_semantic_version(user_version) user_version_parts_len = len(user_version_parts) - target_version_parts_len = len(target_version_parts) for (idx, _) in enumerate(target_version_parts): if user_version_parts_len <= idx: - return -1 - # compare strings + return 1 if self.is_pre_release(target_version) else -1 elif not user_version_parts[idx].isdigit(): if user_version_parts[idx] < target_version_parts[idx]: return -1 elif user_version_parts[idx] > target_version_parts[idx]: return 1 - # compare numbers else: user_version_part = int(user_version_parts[idx]) target_version_part = int(target_version_parts[idx]) @@ -428,9 +439,8 @@ def compare_user_version_with_target_version(self, index): return 1 elif user_version_part < target_version_part: return -1 - if user_version_parts_len > target_version_parts_len: - if self.is_patch_pre_release(user_version_parts_len - 1, user_version_parts[user_version_parts_len - 1]): - return -1 + if self.is_pre_release(user_version) and not self.is_pre_release(target_version): + return -1 return 0 EVALUATORS_BY_MATCH_TYPE = { diff --git a/optimizely/helpers/enums.py b/optimizely/helpers/enums.py index 1d45618b..466cbd7a 100644 --- a/optimizely/helpers/enums.py +++ b/optimizely/helpers/enums.py @@ -161,6 +161,5 @@ class NotificationTypes(object): class SemverType(object): IS_PRE_RELEASE = '-' + HAS_WHITE_SPACE = " " IS_BUILD = '+' - PATCH_INDEX = 3 - IS_PATCH_PRE_RELEASE = ['release', 'beta'] diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 856279f8..68b7beef 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -333,6 +333,62 @@ def test_evalduate__returns_exception__when_user_provided_version_is_invalid4(se self.assertRaises(Exception) + def test_evalduate__returns_exception__when_user_provided_version_is_invalid5(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "3.6"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid6(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "2"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid7(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "3.90"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid8(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "3.90.2.8"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid9(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "-2.4"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid10(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': True}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid11(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': False}, self.mock_client_logger + ) + + self.assertRaises(Exception) + def test_exists__returns_false__when_no_user_provided_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( From 01ed4da0e2721811e4f010e2930ee35ef2b138f0 Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Fri, 21 Aug 2020 15:38:53 +0500 Subject: [PATCH 04/34] feat: Semantic Version --- optimizely/helpers/condition.py | 217 +++++++++++++++++++- optimizely/helpers/enums.py | 6 + tests/base.py | 42 +++- tests/helpers_tests/test_condition.py | 283 +++++++++++++++++++++++++- tests/test_optimizely.py | 42 ++++ 5 files changed, 585 insertions(+), 5 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 0676aecb..15a22ef1 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -18,6 +18,7 @@ from . import validator from .enums import CommonAudienceEvaluationLogs as audience_logs +from .enums import Errors, SemverType class ConditionOperatorTypes(object): @@ -31,6 +32,11 @@ class ConditionMatchTypes(object): EXISTS = 'exists' GREATER_THAN = 'gt' LESS_THAN = 'lt' + SEMVER_EQ = 'semver_eq' + SEMVER_GE = 'semver_ge' + SEMVER_GT = 'semver_gt' + SEMVER_LE = 'semver_le' + SEMVER_LT = 'semver_lt' SUBSTRING = 'substring' @@ -233,12 +239,221 @@ def substring_evaluator(self, index): return condition_value in user_value + def semver_equal_evaluator(self, index): + """ Evaluate the given semantic version equal match target version for the user version. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user version is equal (==) to the target version. + - False if the user version is not equal (!=) to the target version. + None: + - if the user version value is not string type or is null. + """ + return self.compare_user_version_with_target_version(index) == 0 + + def semver_greater_than_evaluator(self, index): + """ Evaluate the given semantic version greater than match target version for the user version. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user version is greater than the target version. + - False if the user version is less than or equal to the target version. + None: + - if the user version value is not string type or is null. + """ + return self.compare_user_version_with_target_version(index) > 0 + + def semver_less_than_evaluator(self, index): + """ Evaluate the given semantic version less than match target version for the user version. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user version is less than the target version. + - False if the user version is greater than or equal to the target version. + None: + - if the user version value is not string type or is null. + """ + return self.compare_user_version_with_target_version(index) < 0 + + def semver_less_than_or_equal_evaluator(self, index): + """ Evaluate the given semantic version less than or equal to match target version for the user version. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user version is less than or equal to the target version. + - False if the user version is greater than the target version. + None: + - if the user version value is not string type or is null. + """ + return self.compare_user_version_with_target_version(index) <= 0 + + def semver_greater_than_or_equal_evaluator(self, index): + """ Evaluate the given semantic version greater than or equal to match target version for the user version. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user version is greater than or equal to the target version. + - False if the user version is less than the target version. + None: + - if the user version value is not string type or is null. + """ + return self.compare_user_version_with_target_version(index) >= 0 + + def split_semantic_version(self, target): + """ Method to split the given version. + + Args: + target: Given version. + + Returns: + List: + - The array of version split into smaller parts i.e major, minor, patch etc + Exception: + - if the given version is invalid in format + """ + target_prefix = target + target_suffix = "" + target_parts = [] + + if self.has_white_space(target): + raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + + if self.is_pre_release(target): + target_parts = target.split(SemverType.IS_PRE_RELEASE) + elif self.is_build(target): + target_parts = target.split(SemverType.IS_BUILD) + + if target_parts: + if len(target_parts) < 1: + raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + target_prefix = str(target_parts[0]) + target_suffix = target_parts[1:] + + dot_count = target_prefix.count(".") + if dot_count > 2: + raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + + target_version_parts = target_prefix.split(".") + if len(target_version_parts) != dot_count + 1: + raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + for part in target_version_parts: + if not part.isdigit(): + raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + + if target_suffix: + target_version_parts.extend(target_suffix) + return target_version_parts + + def is_pre_release(self, target): + """ Method to check if the given version contains "-" + + Args: + target: Given version in string. + + Returns: + Boolean: + - True if the given version does contain "-" + - False if it doesn't + """ + return SemverType.IS_PRE_RELEASE in target + + def is_patch_pre_release(self, idx, idx_value): + return idx == SemverType.PATCH_INDEX and idx_value in SemverType.IS_PATCH_PRE_RELEASE + + def is_build(self, target): + """ Method to check if the given version contains "+" + + Args: + target: Given version in string. + + Returns: + Boolean: + - True if the given version does contain "+" + - False if it doesn't + """ + return SemverType.IS_BUILD in target + + def has_white_space(self, target): + """ Method to check if the given version contains " " (white space) + + Args: + target: Given version in string. + + Returns: + Boolean: + - True if the given version does contain " " + - False if it doesn't + """ + return SemverType.HAS_WHITE_SPACE in target + + def compare_user_version_with_target_version(self, index): + """ Method to compare user version with target version. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Int: + - 0 if user version is equal to target version. + - 1 if user version is greater than target version. + - -1 if user version is less than target version or, in case of exact string match, doesn't match the target + version. + None: + - if the user version value is not string type or is null. + """ + condition_name = self.condition_data[index][0] + target_version = self.condition_data[index][1] + user_version = self.attributes.get(condition_name) + + target_version_parts = self.split_semantic_version(target_version) + user_version_parts = self.split_semantic_version(user_version) + user_version_parts_len = len(user_version_parts) + + for (idx, _) in enumerate(target_version_parts): + if user_version_parts_len <= idx: + return 1 if self.is_pre_release(target_version) else -1 + elif not user_version_parts[idx].isdigit(): + if user_version_parts[idx] < target_version_parts[idx]: + return -1 + elif user_version_parts[idx] > target_version_parts[idx]: + return 1 + else: + user_version_part = int(user_version_parts[idx]) + target_version_part = int(target_version_parts[idx]) + if user_version_part > target_version_part: + return 1 + elif user_version_part < target_version_part: + return -1 + if self.is_pre_release(user_version) and not self.is_pre_release(target_version): + return -1 + return 0 + EVALUATORS_BY_MATCH_TYPE = { ConditionMatchTypes.EXACT: exact_evaluator, ConditionMatchTypes.EXISTS: exists_evaluator, ConditionMatchTypes.GREATER_THAN: greater_than_evaluator, ConditionMatchTypes.LESS_THAN: less_than_evaluator, - ConditionMatchTypes.SUBSTRING: substring_evaluator, + ConditionMatchTypes.SEMVER_EQ: semver_equal_evaluator, + ConditionMatchTypes.SEMVER_GE: semver_greater_than_or_equal_evaluator, + ConditionMatchTypes.SEMVER_GT: semver_greater_than_evaluator, + ConditionMatchTypes.SEMVER_LE: semver_less_than_or_equal_evaluator, + ConditionMatchTypes.SEMVER_LT: semver_less_than_evaluator, + ConditionMatchTypes.SUBSTRING: substring_evaluator } def evaluate(self, index): diff --git a/optimizely/helpers/enums.py b/optimizely/helpers/enums.py index beaba157..466cbd7a 100644 --- a/optimizely/helpers/enums.py +++ b/optimizely/helpers/enums.py @@ -157,3 +157,9 @@ class NotificationTypes(object): OPTIMIZELY_CONFIG_UPDATE = 'OPTIMIZELY_CONFIG_UPDATE' TRACK = 'TRACK:event_key, user_id, attributes, event_tags, event' LOG_EVENT = 'LOG_EVENT:log_event' + + +class SemverType(object): + IS_PRE_RELEASE = '-' + HAS_WHITE_SPACE = " " + IS_BUILD = '+' diff --git a/tests/base.py b/tests/base.py index 432d5287..9dceec2d 100644 --- a/tests/base.py +++ b/tests/base.py @@ -518,6 +518,7 @@ def setUp(self, config_dict='config_dict'): '3468206647', '3468206644', '3468206643', + '18278344267' ], 'variations': [ {'variables': [], 'id': '11557362669', 'key': '11557362669', 'featureEnabled': True} @@ -556,7 +557,8 @@ def setUp(self, config_dict='config_dict'): 'audienceConditions': [ 'and', ['or', '3468206642', '3988293898'], - ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643'], + ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643', + '18278344267'], ], 'variations': [ {'variables': [], 'id': '11557362670', 'key': '11557362670', 'featureEnabled': True} @@ -626,6 +628,7 @@ def setUp(self, config_dict='config_dict'): '3468206647', '3468206644', '3468206643', + '18278344267' ], 'variations': [ { @@ -653,6 +656,7 @@ def setUp(self, config_dict='config_dict'): '3468206647', '3468206644', '3468206643', + '18278344267' ], 'forcedVariations': {}, }, @@ -667,7 +671,7 @@ def setUp(self, config_dict='config_dict'): 'audienceConditions': [ 'and', ['or', '3468206642', '3988293898'], - ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643'], + ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643', '18278344267'], ], 'forcedVariations': {}, }, @@ -689,7 +693,7 @@ def setUp(self, config_dict='config_dict'): 'audienceConditions': [ 'and', ['or', '3468206642', '3988293898'], - ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643'], + ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643', '18278344267'], ], 'forcedVariations': {}, }, @@ -837,6 +841,37 @@ def setUp(self, config_dict='config_dict'): ], ], }, + { + "id": "18278344267", + "name": "semverReleaseLt1.2.3Gt1.0.0", + "conditions": [ + "and", + [ + "or", + [ + "or", + { + "value": "1.2.3", + "type": "custom_attribute", + "name": "android-release", + "match": "semver_lt" + } + ] + ], + [ + "or", + [ + "or", + { + "value": "1.0.0", + "type": "custom_attribute", + "name": "android-release", + "match": "semver_gt" + } + ] + ] + ] + } ], 'groups': [], 'attributes': [ @@ -844,6 +879,7 @@ def setUp(self, config_dict='config_dict'): {'key': 'lasers', 'id': '594016'}, {'key': 'should_do_it', 'id': '594017'}, {'key': 'favorite_ice_cream', 'id': '594018'}, + {'key': 'android-release', 'id': '594019'}, ], 'botFiltering': False, 'accountId': '4879520872', diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index b4dee368..68b7beef 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -1,4 +1,4 @@ -# Copyright 2016-2019, Optimizely +# Copyright 2016-2020, Optimizely # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -24,6 +24,20 @@ integerCondition = ['num_users', 10, 'custom_attribute', 'exact'] doubleCondition = ['pi_value', 3.14, 'custom_attribute', 'exact'] +semver_equal_2_0_0_condition_list = [['Android', "2.0.0", 'custom_attribute', 'semver_eq']] +semver_equal_2_condition_list = [['Android', "2", 'custom_attribute', 'semver_eq']] +semver_equal_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_eq']] +semver_equal_2_0_1_beta_condition_list = [['Android', "2.0.1-beta", 'custom_attribute', 'semver_eq']] +semver_greater_than_2_0_0_condition_list = [['Android', "2.0.0", 'custom_attribute', 'semver_gt']] +semver_greater_than_2_0_0_beta_condition_list = [['Android', "2.0.0-beta", 'custom_attribute', 'semver_gt']] +semver_greater_than_or_equal_2_0_9_beta_condition_list = [['Android', "2.0.9-beta", 'custom_attribute', 'semver_ge']] +semver_greater_than_or_equal_2_0_9_condition_list = [['Android', "2.0.9", 'custom_attribute', 'semver_ge']] +semver_less_than_2_0_0_condition_list = [['Android', "2.0.0", 'custom_attribute', 'semver_lt']] +semver_less_than_2_0_0_release_condition_list = [['Android', "2.0.0-release", 'custom_attribute', 'semver_lt']] +semver_less_than_2_0_0_beta_condition_list = [['Android', "2.0.0-beta", 'custom_attribute', 'semver_lt']] +semver_less_than_or_equal_2_0_1_beta_condition_list = [['Android', "2.0.1-beta", 'custom_attribute', 'semver_le']] +semver_less_than_or_equal_2_0_1_condition_list = [['Android', "2.0.1", 'custom_attribute', 'semver_le']] + exists_condition_list = [['input_value', None, 'custom_attribute', 'exists']] exact_string_condition_list = [['favorite_constellation', 'Lacerta', 'custom_attribute', 'exact']] exact_int_condition_list = [['lasers_count', 9000, 'custom_attribute', 'exact']] @@ -108,6 +122,273 @@ def test_evaluate__returns_null__when_condition_has_an_invalid_type_property(sel self.assertIsNone(evaluator.evaluate(0)) + def test_evaluate__returns_true__when_user_version_2_matches_target_version_2(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_condition_list, {'Android': '2'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_2_2_matches_target_version_2(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_condition_list, {'Android': '2.2'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_2_0_matches_target_version_2_0(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': '2.0'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_2_0_0_matches_target_version_2_0_0(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_0_condition_list, {'Android': '2.0.0'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_false__when_user_version_2_0_does_not_match_target_version_2_0(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_0_condition_list, {'Android': '2.0'}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_2_0_0_release_is_greater_than_target_version_2_0_0_beta( + self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_2_0_0_beta_condition_list, {'Android': '2.0.0-release'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_2_0_1_is_greater_than_target_version__2_0_0(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_2_0_0_condition_list, {'Android': '2.0.1'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_1_9_9_is_less_than_target_version_2_0_0(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_2_0_0_condition_list, {'Android': '1.9.9'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_1_9_0_beta_is_less_than_target_version_2_0_0(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_2_0_0_condition_list, {'Android': '1.9.0-beta'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_2_0_0_release_is_less_than_target_version_2_0_0(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_2_0_0_condition_list, {'Android': '2.0.0-release'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_2_0_0_beta_is_less_than_target_version_2_0_0_release(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_2_0_0_release_condition_list, {'Android': '2.0.0-beta'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_false__when_user_version_2_0_0_release_is_not_less_than_target_version_2_0_0_beta(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_2_0_0_beta_condition_list, {'Android': '2.0.0-release'}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_2_0_9_is_greater_than_or_equal_to_target_version_2_0_9(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_or_equal_2_0_9_condition_list, {'Android': '2.0.9'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_2_3_is_greater_than_or_equal_to_target_version_2_0_9(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_or_equal_2_0_9_condition_list, {'Android': '2.3'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_2_0_9_beta_is_greater_than_or_equal_to_target_version_2_0_9_beta( + self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_or_equal_2_0_9_beta_condition_list, {'Android': '2.0.9-beta'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_false__when_user_version_1_0_0_is_not_greater_than_or_equal_to_target_version_2_0_9( + self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_or_equal_2_0_9_condition_list, {'Android': '1.0.0'}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_2_0_1_is_less_than_or_equal_to_target_version_2_0_1(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_1_condition_list, {'Android': '2.0.1'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_1_1_is_less_than_or_equal_to_target_version_2_0_1(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_1_condition_list, {'Android': '1.1'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_true__when_user_version_2_0_1_beta_is_less_than_or_equal_to_target_version_2_0_1(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_1_condition_list, {'Android': '2.0.1-beta'}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_evaluate__returns_false__when_user_version_3_0_1_is_not_less_than_or_equal_to_target_version_2_0_1(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_1_condition_list, {'Android': '3.0.1'}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_evaluate__returns_null__when_no_user_version_provided(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_evaluate__returns_null__when_user_provided_version_is_null(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': None}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_evaluate__returns_exception__when_user_provided_version_is_invalid1(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "+"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid2(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "+--"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid3(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "...+"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid4(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "+test"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid5(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "3.6"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid6(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "2"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid7(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "3.90"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid8(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "3.90.2.8"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid9(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': "-2.4"}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid10(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': True}, self.mock_client_logger + ) + + self.assertRaises(Exception) + + def test_evalduate__returns_exception__when_user_provided_version_is_invalid11(self): + + condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': False}, self.mock_client_logger + ) + + self.assertRaises(Exception) + def test_exists__returns_false__when_no_user_provided_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( diff --git a/tests/test_optimizely.py b/tests/test_optimizely.py index 94783a7a..f586c44c 100644 --- a/tests/test_optimizely.py +++ b/tests/test_optimizely.py @@ -844,6 +844,48 @@ def test_activate__with_attributes__typed_audience_match(self): self.assertTrue(expected_attr in [x.__dict__ for x in mock_process.call_args[0][0].visitor_attributes]) + def test_activate__with_attributes__typed_audience_with_semver_match(self): + """ Test that activate calls process with right params and returns expected + variation when attributes are provided and typed audience conditions are met. """ + opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_typed_audiences)) + + with mock.patch('optimizely.event.event_processor.ForwardingEventProcessor.process') as mock_process: + # Should be included via exact match string audience with id '18278344267' + self.assertEqual( + 'A', opt_obj.activate('typed_audience_experiment', 'test_user', {'android-release': '1.0.1'}), + ) + expected_attr = { + 'type': 'custom', + 'value': '1.0.1', + 'entity_id': '594019', + 'key': 'android-release', + } + + self.assertTrue(expected_attr in [x.__dict__ for x in mock_process.call_args[0][0].visitor_attributes]) + + mock_process.reset() + + with mock.patch('optimizely.event.event_processor.ForwardingEventProcessor.process') as mock_process: + self.assertEqual( + 'A', opt_obj.activate('typed_audience_experiment', 'test_user', {'android-release': "1.2.2"}), + ) + expected_attr = { + 'type': 'custom', + 'value': "1.2.2", + 'entity_id': '594019', + 'key': 'android-release', + } + + self.assertTrue(expected_attr in [x.__dict__ for x in mock_process.call_args[0][0].visitor_attributes]) + + def test_activate__with_attributes__typed_audience_with_semver_mismatch(self): + """ Test that activate returns None when typed audience conditions do not match. """ + opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_typed_audiences)) + + with mock.patch('optimizely.event.event_processor.ForwardingEventProcessor.process') as mock_process: + self.assertIsNone(opt_obj.activate('typed_audience_experiment', 'test_user', {'android-release': '1.2.9'})) + self.assertEqual(0, mock_process.call_count) + def test_activate__with_attributes__typed_audience_mismatch(self): """ Test that activate returns None when typed audience conditions do not match. """ opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_typed_audiences)) From 796af4d4bbb657aa71c4310a7e3da767a70c1a3e Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Mon, 24 Aug 2020 10:40:19 +0500 Subject: [PATCH 05/34] GE and LE with test cases --- optimizely/helpers/condition.py | 89 +++++- tests/helpers_tests/test_condition.py | 371 ++++++++++++++++++++++++++ 2 files changed, 454 insertions(+), 6 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 15a22ef1..fecc47d4 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -31,7 +31,9 @@ class ConditionMatchTypes(object): EXACT = 'exact' EXISTS = 'exists' GREATER_THAN = 'gt' + GREATER_THAN_OR_EQUAL = 'ge' LESS_THAN = 'lt' + LESS_THAN_OR_EQUAL = 'le' SEMVER_EQ = 'semver_eq' SEMVER_GE = 'semver_ge' SEMVER_GT = 'semver_gt' @@ -177,6 +179,40 @@ def greater_than_evaluator(self, index): return user_value > condition_value + def greater_than_or_equal_evaluator(self, index): + """ Evaluate the given greater than or equal to match condition for the user attributes. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user attribute value is greater than or equal to the condition value. + - False if the user attribute value is less than the condition value. + None: if the condition value isn't finite or the user attribute value isn't finite. + """ + condition_name = self.condition_data[index][0] + condition_value = self.condition_data[index][1] + user_value = self.attributes.get(condition_name) + + if not validator.is_finite_number(condition_value): + self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index))) + return None + + if not self.is_value_a_number(user_value): + self.logger.warning( + audience_logs.UNEXPECTED_TYPE.format(self._get_condition_json(index), type(user_value), condition_name) + ) + return None + + if not validator.is_finite_number(user_value): + self.logger.warning( + audience_logs.INFINITE_ATTRIBUTE_VALUE.format(self._get_condition_json(index), condition_name) + ) + return None + + return user_value >= condition_value + def less_than_evaluator(self, index): """ Evaluate the given less than match condition for the user attributes. @@ -211,6 +247,40 @@ def less_than_evaluator(self, index): return user_value < condition_value + def less_than_or_equal_evaluator(self, index): + """ Evaluate the given less than or equal to match condition for the user attributes. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user attribute value is less than or equal to the condition value. + - False if the user attribute value is greater than the condition value. + None: if the condition value isn't finite or the user attribute value isn't finite. + """ + condition_name = self.condition_data[index][0] + condition_value = self.condition_data[index][1] + user_value = self.attributes.get(condition_name) + + if not validator.is_finite_number(condition_value): + self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index))) + return None + + if not self.is_value_a_number(user_value): + self.logger.warning( + audience_logs.UNEXPECTED_TYPE.format(self._get_condition_json(index), type(user_value), condition_name) + ) + return None + + if not validator.is_finite_number(user_value): + self.logger.warning( + audience_logs.INFINITE_ATTRIBUTE_VALUE.format(self._get_condition_json(index), condition_name) + ) + return None + + return user_value <= condition_value + def substring_evaluator(self, index): """ Evaluate the given substring match condition for the given user attributes. @@ -331,7 +401,8 @@ def split_semantic_version(self, target): target_parts = [] if self.has_white_space(target): - raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) + return None if self.is_pre_release(target): target_parts = target.split(SemverType.IS_PRE_RELEASE) @@ -340,20 +411,24 @@ def split_semantic_version(self, target): if target_parts: if len(target_parts) < 1: - raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) + return None target_prefix = str(target_parts[0]) target_suffix = target_parts[1:] dot_count = target_prefix.count(".") if dot_count > 2: - raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) + return None target_version_parts = target_prefix.split(".") if len(target_version_parts) != dot_count + 1: - raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) + return None for part in target_version_parts: if not part.isdigit(): - raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) + return None if target_suffix: target_version_parts.extend(target_suffix) @@ -453,7 +528,9 @@ def compare_user_version_with_target_version(self, index): ConditionMatchTypes.SEMVER_GT: semver_greater_than_evaluator, ConditionMatchTypes.SEMVER_LE: semver_less_than_or_equal_evaluator, ConditionMatchTypes.SEMVER_LT: semver_less_than_evaluator, - ConditionMatchTypes.SUBSTRING: substring_evaluator + ConditionMatchTypes.SUBSTRING: substring_evaluator, + ConditionMatchTypes.LESS_THAN_OR_EQUAL: less_than_or_equal_evaluator, + ConditionMatchTypes.GREATER_THAN_OR_EQUAL: greater_than_or_equal_evaluator, } def evaluate(self, index): diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 68b7beef..a789b9c3 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -46,8 +46,12 @@ substring_condition_list = [['headline_text', 'buy now', 'custom_attribute', 'substring']] gt_int_condition_list = [['meters_travelled', 48, 'custom_attribute', 'gt']] gt_float_condition_list = [['meters_travelled', 48.2, 'custom_attribute', 'gt']] +ge_int_condition_list = [['meters_travelled', 48, 'custom_attribute', 'ge']] +ge_float_condition_list = [['meters_travelled', 48.2, 'custom_attribute', 'ge']] lt_int_condition_list = [['meters_travelled', 48, 'custom_attribute', 'lt']] lt_float_condition_list = [['meters_travelled', 48.2, 'custom_attribute', 'lt']] +le_int_condition_list = [['meters_travelled', 48, 'custom_attribute', 'le']] +le_float_condition_list = [['meters_travelled', 48.2, 'custom_attribute', 'le']] class CustomAttributeConditionEvaluator(base.BaseTest): @@ -788,6 +792,153 @@ def test_greater_than_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) + def test_greater_than_or_equal_int__returns_true__when_user_value_greater_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 48}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 49}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + gt_int_condition_list, {'meters_travelled': long(49)}, self.mock_client_logger, + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + gt_int_condition_list, {'meters_travelled': long(48)}, self.mock_client_logger, + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_greater_than_or_equal_float__returns_true__when_user_value_greater_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': 49}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': long(49)}, self.mock_client_logger, + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_greater_than_or_equal_int__returns_false__when_user_value_not_greater_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 47}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': long(47)}, self.mock_client_logger, + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_greater_than_or_equal_float__returns_false__when_user_value_not_greater_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': 48}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': long(48)}, self.mock_client_logger, + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_greater_than_or_equal_int__returns_null__when_user_value_is_not_a_number(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 'a long way'}, self.mock_client_logger, + ) + + self.assertIsNone(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': False}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_greater_than_or_equal_float__returns_null__when_user_value_is_not_a_number(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': 'a long way'}, self.mock_client_logger, + ) + + self.assertIsNone(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': False}, self.mock_client_logger, + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_greater_than_or_equal_int__returns_null__when_no_user_provided_value(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_greater_than_or_equal_float__returns_null__when_no_user_provided_value(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + def test_less_than_int__returns_true__when_user_value_less_than_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -904,6 +1055,140 @@ def test_less_than_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) + def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': 47}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': 48}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': long(47)}, self.mock_client_logger, + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': long(48)}, self.mock_client_logger, + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': 48}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': long(48)}, self.mock_client_logger, + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': 49}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': long(49)}, self.mock_client_logger, + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': 49}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': long(49)}, self.mock_client_logger, + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_less_than_or_equal_int__returns_null__when_user_value_is_not_a_number(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': False}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_less_than_or_equal_float__returns_null__when_user_value_is_not_a_number(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': False}, self.mock_client_logger, + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_less_than_or_equal_int__returns_null__when_no_user_provided_value(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_less_than_or_equal_float__returns_null__when_no_user_provided_value(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + def test_greater_than__calls_is_finite_number(self): """ Test that CustomAttributeConditionEvaluator.evaluate returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ @@ -990,6 +1275,92 @@ def is_finite_number__accepting_both_values(value): ): self.assertTrue(evaluator.evaluate(0)) + def test_greater_than_or_equal__calls_is_finite_number(self): + """ Test that CustomAttributeConditionEvaluator.evaluate returns True + if is_finite_number returns True. Returns None if is_finite_number returns False. """ + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger + ) + + def is_finite_number__rejecting_condition_value(value): + if value == 48: + return False + return True + + with mock.patch( + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__rejecting_condition_value, + ) as mock_is_finite: + self.assertIsNone(evaluator.evaluate(0)) + + # assert that isFiniteNumber only needs to reject condition value to stop evaluation. + mock_is_finite.assert_called_once_with(48) + + def is_finite_number__rejecting_user_attribute_value(value): + if value == 48.1: + return False + return True + + with mock.patch( + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_user_attribute_value, + ) as mock_is_finite: + self.assertIsNone(evaluator.evaluate(0)) + + # assert that isFiniteNumber evaluates user value only if it has accepted condition value. + mock_is_finite.assert_has_calls([mock.call(48), mock.call(48.1)]) + + def is_finite_number__accepting_both_values(value): + return True + + with mock.patch( + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, + ): + self.assertTrue(evaluator.evaluate(0)) + + def test_less_than_or_equal__calls_is_finite_number(self): + """ Test that CustomAttributeConditionEvaluator.evaluate returns True + if is_finite_number returns True. Returns None if is_finite_number returns False. """ + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': 47}, self.mock_client_logger + ) + + def is_finite_number__rejecting_condition_value(value): + if value == 48: + return False + return True + + with mock.patch( + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__rejecting_condition_value, + ) as mock_is_finite: + self.assertIsNone(evaluator.evaluate(0)) + + # assert that isFiniteNumber only needs to reject condition value to stop evaluation. + mock_is_finite.assert_called_once_with(48) + + def is_finite_number__rejecting_user_attribute_value(value): + if value == 47: + return False + return True + + with mock.patch( + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_user_attribute_value, + ) as mock_is_finite: + self.assertIsNone(evaluator.evaluate(0)) + + # assert that isFiniteNumber evaluates user value only if it has accepted condition value. + mock_is_finite.assert_has_calls([mock.call(48), mock.call(47)]) + + def is_finite_number__accepting_both_values(value): + return True + + with mock.patch( + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, + ): + self.assertTrue(evaluator.evaluate(0)) + class ConditionDecoderTests(base.BaseTest): def test_loads(self): From f3189955563d69a8d0481666c162c7a393ddb979 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Mon, 24 Aug 2020 16:55:17 +0500 Subject: [PATCH 06/34] Update test_condition.py --- tests/helpers_tests/test_condition.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index a789b9c3..9f4ede7f 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -819,12 +819,6 @@ def test_greater_than_or_equal_int__returns_true__when_user_value_greater_than_o self.assertStrictTrue(evaluator.evaluate(0)) - evaluator = condition_helper.CustomAttributeConditionEvaluator( - gt_int_condition_list, {'meters_travelled': long(48)}, self.mock_client_logger, - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_or_equal_float__returns_true__when_user_value_greater_than_or_equal_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( From e016cd86614bd2c29f6386229c8d95b8dd45d32f Mon Sep 17 00:00:00 2001 From: Sohail Hussain Date: Mon, 24 Aug 2020 11:40:09 -0700 Subject: [PATCH 07/34] added ge le --- optimizely/helpers/condition.py | 89 ++++++- tests/helpers_tests/test_condition.py | 363 ++++++++++++++++++++++++++ 2 files changed, 446 insertions(+), 6 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 15a22ef1..fecc47d4 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -31,7 +31,9 @@ class ConditionMatchTypes(object): EXACT = 'exact' EXISTS = 'exists' GREATER_THAN = 'gt' + GREATER_THAN_OR_EQUAL = 'ge' LESS_THAN = 'lt' + LESS_THAN_OR_EQUAL = 'le' SEMVER_EQ = 'semver_eq' SEMVER_GE = 'semver_ge' SEMVER_GT = 'semver_gt' @@ -177,6 +179,40 @@ def greater_than_evaluator(self, index): return user_value > condition_value + def greater_than_or_equal_evaluator(self, index): + """ Evaluate the given greater than or equal to match condition for the user attributes. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user attribute value is greater than or equal to the condition value. + - False if the user attribute value is less than the condition value. + None: if the condition value isn't finite or the user attribute value isn't finite. + """ + condition_name = self.condition_data[index][0] + condition_value = self.condition_data[index][1] + user_value = self.attributes.get(condition_name) + + if not validator.is_finite_number(condition_value): + self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index))) + return None + + if not self.is_value_a_number(user_value): + self.logger.warning( + audience_logs.UNEXPECTED_TYPE.format(self._get_condition_json(index), type(user_value), condition_name) + ) + return None + + if not validator.is_finite_number(user_value): + self.logger.warning( + audience_logs.INFINITE_ATTRIBUTE_VALUE.format(self._get_condition_json(index), condition_name) + ) + return None + + return user_value >= condition_value + def less_than_evaluator(self, index): """ Evaluate the given less than match condition for the user attributes. @@ -211,6 +247,40 @@ def less_than_evaluator(self, index): return user_value < condition_value + def less_than_or_equal_evaluator(self, index): + """ Evaluate the given less than or equal to match condition for the user attributes. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user attribute value is less than or equal to the condition value. + - False if the user attribute value is greater than the condition value. + None: if the condition value isn't finite or the user attribute value isn't finite. + """ + condition_name = self.condition_data[index][0] + condition_value = self.condition_data[index][1] + user_value = self.attributes.get(condition_name) + + if not validator.is_finite_number(condition_value): + self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index))) + return None + + if not self.is_value_a_number(user_value): + self.logger.warning( + audience_logs.UNEXPECTED_TYPE.format(self._get_condition_json(index), type(user_value), condition_name) + ) + return None + + if not validator.is_finite_number(user_value): + self.logger.warning( + audience_logs.INFINITE_ATTRIBUTE_VALUE.format(self._get_condition_json(index), condition_name) + ) + return None + + return user_value <= condition_value + def substring_evaluator(self, index): """ Evaluate the given substring match condition for the given user attributes. @@ -331,7 +401,8 @@ def split_semantic_version(self, target): target_parts = [] if self.has_white_space(target): - raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) + return None if self.is_pre_release(target): target_parts = target.split(SemverType.IS_PRE_RELEASE) @@ -340,20 +411,24 @@ def split_semantic_version(self, target): if target_parts: if len(target_parts) < 1: - raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) + return None target_prefix = str(target_parts[0]) target_suffix = target_parts[1:] dot_count = target_prefix.count(".") if dot_count > 2: - raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) + return None target_version_parts = target_prefix.split(".") if len(target_version_parts) != dot_count + 1: - raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) + return None for part in target_version_parts: if not part.isdigit(): - raise Exception(Errors.INVALID_ATTRIBUTE_FORMAT) + self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) + return None if target_suffix: target_version_parts.extend(target_suffix) @@ -453,7 +528,9 @@ def compare_user_version_with_target_version(self, index): ConditionMatchTypes.SEMVER_GT: semver_greater_than_evaluator, ConditionMatchTypes.SEMVER_LE: semver_less_than_or_equal_evaluator, ConditionMatchTypes.SEMVER_LT: semver_less_than_evaluator, - ConditionMatchTypes.SUBSTRING: substring_evaluator + ConditionMatchTypes.SUBSTRING: substring_evaluator, + ConditionMatchTypes.LESS_THAN_OR_EQUAL: less_than_or_equal_evaluator, + ConditionMatchTypes.GREATER_THAN_OR_EQUAL: greater_than_or_equal_evaluator, } def evaluate(self, index): diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 68b7beef..b74bce65 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -46,8 +46,12 @@ substring_condition_list = [['headline_text', 'buy now', 'custom_attribute', 'substring']] gt_int_condition_list = [['meters_travelled', 48, 'custom_attribute', 'gt']] gt_float_condition_list = [['meters_travelled', 48.2, 'custom_attribute', 'gt']] +ge_int_condition_list = [['meters_travelled', 48, 'custom_attribute', 'ge']] +ge_float_condition_list = [['meters_travelled', 48.2, 'custom_attribute', 'ge']] lt_int_condition_list = [['meters_travelled', 48, 'custom_attribute', 'lt']] lt_float_condition_list = [['meters_travelled', 48.2, 'custom_attribute', 'lt']] +le_int_condition_list = [['meters_travelled', 48, 'custom_attribute', 'le']] +le_float_condition_list = [['meters_travelled', 48.2, 'custom_attribute', 'le']] class CustomAttributeConditionEvaluator(base.BaseTest): @@ -788,6 +792,145 @@ def test_greater_than_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) + def test_greater_than_or_equal_int__returns_true__when_user_value_greater_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 48}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 49}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + gt_int_condition_list, {'meters_travelled': long(49)}, self.mock_client_logger, + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_greater_than_or_equal_float__returns_true__when_user_value_greater_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': 49}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': long(49)}, self.mock_client_logger, + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_greater_than_or_equal_int__returns_false__when_user_value_not_greater_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 47}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': long(47)}, self.mock_client_logger, + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_greater_than_or_equal_float__returns_false__when_user_value_not_greater_than_or_equal_condition(self,): + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': 48}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': long(48)}, self.mock_client_logger, + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_greater_than_or_equal_int__returns_null__when_user_value_is_not_a_number(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 'a long way'}, self.mock_client_logger, + ) + + self.assertIsNone(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': False}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_greater_than_or_equal_float__returns_null__when_user_value_is_not_a_number(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': 'a long way'}, self.mock_client_logger, + ) + + self.assertIsNone(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {'meters_travelled': False}, self.mock_client_logger, + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_greater_than_or_equal_int__returns_null__when_no_user_provided_value(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_greater_than_or_equal_float__returns_null__when_no_user_provided_value(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_float_condition_list, {}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + def test_less_than_int__returns_true__when_user_value_less_than_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -904,6 +1047,140 @@ def test_less_than_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) + def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': 47}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': 48}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': long(47)}, self.mock_client_logger, + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': long(48)}, self.mock_client_logger, + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': 48}, self.mock_client_logger + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': long(48)}, self.mock_client_logger, + ) + + self.assertStrictTrue(evaluator.evaluate(0)) + + def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': 49}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': long(49)}, self.mock_client_logger, + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self,): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': 49}, self.mock_client_logger + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': long(49)}, self.mock_client_logger, + ) + + self.assertStrictFalse(evaluator.evaluate(0)) + + def test_less_than_or_equal_int__returns_null__when_user_value_is_not_a_number(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': False}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_less_than_or_equal_float__returns_null__when_user_value_is_not_a_number(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {'meters_travelled': False}, self.mock_client_logger, + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_less_than_or_equal_int__returns_null__when_no_user_provided_value(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + + def test_less_than_or_equal_float__returns_null__when_no_user_provided_value(self): + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_float_condition_list, {}, self.mock_client_logger + ) + + self.assertIsNone(evaluator.evaluate(0)) + def test_greater_than__calls_is_finite_number(self): """ Test that CustomAttributeConditionEvaluator.evaluate returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ @@ -990,6 +1267,92 @@ def is_finite_number__accepting_both_values(value): ): self.assertTrue(evaluator.evaluate(0)) + def test_greater_than_or_equal__calls_is_finite_number(self): + """ Test that CustomAttributeConditionEvaluator.evaluate returns True + if is_finite_number returns True. Returns None if is_finite_number returns False. """ + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + ge_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger + ) + + def is_finite_number__rejecting_condition_value(value): + if value == 48: + return False + return True + + with mock.patch( + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__rejecting_condition_value, + ) as mock_is_finite: + self.assertIsNone(evaluator.evaluate(0)) + + # assert that isFiniteNumber only needs to reject condition value to stop evaluation. + mock_is_finite.assert_called_once_with(48) + + def is_finite_number__rejecting_user_attribute_value(value): + if value == 48.1: + return False + return True + + with mock.patch( + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_user_attribute_value, + ) as mock_is_finite: + self.assertIsNone(evaluator.evaluate(0)) + + # assert that isFiniteNumber evaluates user value only if it has accepted condition value. + mock_is_finite.assert_has_calls([mock.call(48), mock.call(48.1)]) + + def is_finite_number__accepting_both_values(value): + return True + + with mock.patch( + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, + ): + self.assertTrue(evaluator.evaluate(0)) + + def test_less_than_or_equal__calls_is_finite_number(self): + """ Test that CustomAttributeConditionEvaluator.evaluate returns True + if is_finite_number returns True. Returns None if is_finite_number returns False. """ + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + le_int_condition_list, {'meters_travelled': 47}, self.mock_client_logger + ) + + def is_finite_number__rejecting_condition_value(value): + if value == 48: + return False + return True + + with mock.patch( + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__rejecting_condition_value, + ) as mock_is_finite: + self.assertIsNone(evaluator.evaluate(0)) + + # assert that isFiniteNumber only needs to reject condition value to stop evaluation. + mock_is_finite.assert_called_once_with(48) + + def is_finite_number__rejecting_user_attribute_value(value): + if value == 47: + return False + return True + + with mock.patch( + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_user_attribute_value, + ) as mock_is_finite: + self.assertIsNone(evaluator.evaluate(0)) + + # assert that isFiniteNumber evaluates user value only if it has accepted condition value. + mock_is_finite.assert_has_calls([mock.call(48), mock.call(47)]) + + def is_finite_number__accepting_both_values(value): + return True + + with mock.patch( + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, + ): + self.assertTrue(evaluator.evaluate(0)) + class ConditionDecoderTests(base.BaseTest): def test_loads(self): From 857e8de3427896e31bdccb0dd78c7f2f11fb4994 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Tue, 25 Aug 2020 19:23:22 +0500 Subject: [PATCH 08/34] PR comments resolved --- optimizely/helpers/condition.py | 360 +++++++++++++------------- tests/helpers_tests/test_condition.py | 20 +- 2 files changed, 190 insertions(+), 190 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index fecc47d4..6dd21bac 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -55,12 +55,12 @@ def __init__(self, condition_data, attributes, logger): def _get_condition_json(self, index): """ Method to generate json for logging audience condition. - Args: - index: Index of the condition. + Args: + index: Index of the condition. - Returns: - String: Audience condition JSON. - """ + Returns: + String: Audience condition JSON. + """ condition = self.condition_data[index] condition_log = { 'name': condition[0], @@ -74,12 +74,12 @@ def _get_condition_json(self, index): def is_value_type_valid_for_exact_conditions(self, value): """ Method to validate if the value is valid for exact match type evaluation. - Args: - value: Value to validate. + Args: + value: Value to validate. - Returns: - Boolean: True if value is a string, boolean, or number. Otherwise False. - """ + Returns: + Boolean: True if value is a string, boolean, or number. Otherwise False. + """ # No need to check for bool since bool is a subclass of int if isinstance(value, string_types) or isinstance(value, (numbers.Integral, float)): return True @@ -95,29 +95,29 @@ def is_value_a_number(self, value): def exact_evaluator(self, index): """ Evaluate the given exact match condition for the user attributes. - Args: - index: Index of the condition to be evaluated. - - Returns: - Boolean: - - True if the user attribute value is equal (===) to the condition value. - - False if the user attribute value is not equal (!==) to the condition value. - None: - - if the condition value or user attribute value has an invalid type. - - if there is a mismatch between the user attribute type and the condition value type. - """ + Args: + index: Index of the condition to be evaluated. + + Returns: + Boolean: + - True if the user attribute value is equal (===) to the condition value. + - False if the user attribute value is not equal (!==) to the condition value. + None: + - if the condition value or user attribute value has an invalid type. + - if there is a mismatch between the user attribute type and the condition value type. + """ condition_name = self.condition_data[index][0] condition_value = self.condition_data[index][1] user_value = self.attributes.get(condition_name) if not self.is_value_type_valid_for_exact_conditions(condition_value) or ( - self.is_value_a_number(condition_value) and not validator.is_finite_number(condition_value) + self.is_value_a_number(condition_value) and not validator.is_finite_number(condition_value) ): self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index))) return None if not self.is_value_type_valid_for_exact_conditions(user_value) or not validator.are_values_same_type( - condition_value, user_value + condition_value, user_value ): self.logger.warning( audience_logs.UNEXPECTED_TYPE.format(self._get_condition_json(index), type(user_value), condition_name) @@ -135,28 +135,28 @@ def exact_evaluator(self, index): def exists_evaluator(self, index): """ Evaluate the given exists match condition for the user attributes. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: True if the user attributes have a non-null value for the given condition, - otherwise False. - """ + Returns: + Boolean: True if the user attributes have a non-null value for the given condition, + otherwise False. + """ attr_name = self.condition_data[index][0] return self.attributes.get(attr_name) is not None def greater_than_evaluator(self, index): """ Evaluate the given greater than match condition for the user attributes. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user attribute value is greater than the condition value. - - False if the user attribute value is less than or equal to the condition value. - None: if the condition value isn't finite or the user attribute value isn't finite. - """ + Returns: + Boolean: + - True if the user attribute value is greater than the condition value. + - False if the user attribute value is less than or equal to the condition value. + None: if the condition value isn't finite or the user attribute value isn't finite. + """ condition_name = self.condition_data[index][0] condition_value = self.condition_data[index][1] user_value = self.attributes.get(condition_name) @@ -182,15 +182,15 @@ def greater_than_evaluator(self, index): def greater_than_or_equal_evaluator(self, index): """ Evaluate the given greater than or equal to match condition for the user attributes. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user attribute value is greater than or equal to the condition value. - - False if the user attribute value is less than the condition value. - None: if the condition value isn't finite or the user attribute value isn't finite. - """ + Returns: + Boolean: + - True if the user attribute value is greater than or equal to the condition value. + - False if the user attribute value is less than the condition value. + None: if the condition value isn't finite or the user attribute value isn't finite. + """ condition_name = self.condition_data[index][0] condition_value = self.condition_data[index][1] user_value = self.attributes.get(condition_name) @@ -216,15 +216,15 @@ def greater_than_or_equal_evaluator(self, index): def less_than_evaluator(self, index): """ Evaluate the given less than match condition for the user attributes. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user attribute value is less than the condition value. - - False if the user attribute value is greater than or equal to the condition value. - None: if the condition value isn't finite or the user attribute value isn't finite. - """ + Returns: + Boolean: + - True if the user attribute value is less than the condition value. + - False if the user attribute value is greater than or equal to the condition value. + None: if the condition value isn't finite or the user attribute value isn't finite. + """ condition_name = self.condition_data[index][0] condition_value = self.condition_data[index][1] user_value = self.attributes.get(condition_name) @@ -250,15 +250,15 @@ def less_than_evaluator(self, index): def less_than_or_equal_evaluator(self, index): """ Evaluate the given less than or equal to match condition for the user attributes. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user attribute value is less than or equal to the condition value. - - False if the user attribute value is greater than the condition value. - None: if the condition value isn't finite or the user attribute value isn't finite. - """ + Returns: + Boolean: + - True if the user attribute value is less than or equal to the condition value. + - False if the user attribute value is greater than the condition value. + None: if the condition value isn't finite or the user attribute value isn't finite. + """ condition_name = self.condition_data[index][0] condition_value = self.condition_data[index][1] user_value = self.attributes.get(condition_name) @@ -284,21 +284,21 @@ def less_than_or_equal_evaluator(self, index): def substring_evaluator(self, index): """ Evaluate the given substring match condition for the given user attributes. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the condition value is a substring of the user attribute value. - - False if the condition value is not a substring of the user attribute value. - None: if the condition value isn't a string or the user attribute value isn't a string. - """ + Returns: + Boolean: + - True if the condition value is a substring of the user attribute value. + - False if the condition value is not a substring of the user attribute value. + None: if the condition value isn't a string or the user attribute value isn't a string. + """ condition_name = self.condition_data[index][0] condition_value = self.condition_data[index][1] user_value = self.attributes.get(condition_name) if not isinstance(condition_value, string_types): - self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index),)) + self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index), )) return None if not isinstance(user_value, string_types): @@ -312,90 +312,90 @@ def substring_evaluator(self, index): def semver_equal_evaluator(self, index): """ Evaluate the given semantic version equal match target version for the user version. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user version is equal (==) to the target version. - - False if the user version is not equal (!=) to the target version. - None: - - if the user version value is not string type or is null. - """ + Returns: + Boolean: + - True if the user version is equal (==) to the target version. + - False if the user version is not equal (!=) to the target version. + None: + - if the user version value is not string type or is null. + """ return self.compare_user_version_with_target_version(index) == 0 def semver_greater_than_evaluator(self, index): """ Evaluate the given semantic version greater than match target version for the user version. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user version is greater than the target version. - - False if the user version is less than or equal to the target version. - None: - - if the user version value is not string type or is null. - """ + Returns: + Boolean: + - True if the user version is greater than the target version. + - False if the user version is less than or equal to the target version. + None: + - if the user version value is not string type or is null. + """ return self.compare_user_version_with_target_version(index) > 0 def semver_less_than_evaluator(self, index): """ Evaluate the given semantic version less than match target version for the user version. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user version is less than the target version. - - False if the user version is greater than or equal to the target version. - None: - - if the user version value is not string type or is null. - """ + Returns: + Boolean: + - True if the user version is less than the target version. + - False if the user version is greater than or equal to the target version. + None: + - if the user version value is not string type or is null. + """ return self.compare_user_version_with_target_version(index) < 0 def semver_less_than_or_equal_evaluator(self, index): """ Evaluate the given semantic version less than or equal to match target version for the user version. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user version is less than or equal to the target version. - - False if the user version is greater than the target version. - None: - - if the user version value is not string type or is null. - """ + Returns: + Boolean: + - True if the user version is less than or equal to the target version. + - False if the user version is greater than the target version. + None: + - if the user version value is not string type or is null. + """ return self.compare_user_version_with_target_version(index) <= 0 def semver_greater_than_or_equal_evaluator(self, index): """ Evaluate the given semantic version greater than or equal to match target version for the user version. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user version is greater than or equal to the target version. - - False if the user version is less than the target version. - None: - - if the user version value is not string type or is null. - """ + Returns: + Boolean: + - True if the user version is greater than or equal to the target version. + - False if the user version is less than the target version. + None: + - if the user version value is not string type or is null. + """ return self.compare_user_version_with_target_version(index) >= 0 def split_semantic_version(self, target): """ Method to split the given version. - Args: - target: Given version. + Args: + target: Given version. - Returns: - List: - - The array of version split into smaller parts i.e major, minor, patch etc - Exception: - - if the given version is invalid in format - """ + Returns: + List: + - The array of version split into smaller parts i.e major, minor, patch etc + Exception: + - if the given version is invalid in format + """ target_prefix = target target_suffix = "" target_parts = [] @@ -437,14 +437,14 @@ def split_semantic_version(self, target): def is_pre_release(self, target): """ Method to check if the given version contains "-" - Args: - target: Given version in string. + Args: + target: Given version in string. - Returns: - Boolean: + Returns: + Boolean: - True if the given version does contain "-" - False if it doesn't - """ + """ return SemverType.IS_PRE_RELEASE in target def is_patch_pre_release(self, idx, idx_value): @@ -453,44 +453,44 @@ def is_patch_pre_release(self, idx, idx_value): def is_build(self, target): """ Method to check if the given version contains "+" - Args: - target: Given version in string. + Args: + target: Given version in string. - Returns: - Boolean: + Returns: + Boolean: - True if the given version does contain "+" - False if it doesn't - """ + """ return SemverType.IS_BUILD in target def has_white_space(self, target): """ Method to check if the given version contains " " (white space) - Args: - target: Given version in string. + Args: + target: Given version in string. - Returns: - Boolean: + Returns: + Boolean: - True if the given version does contain " " - False if it doesn't - """ + """ return SemverType.HAS_WHITE_SPACE in target def compare_user_version_with_target_version(self, index): """ Method to compare user version with target version. - Args: - index: Index of the condition to be evaluated. - - Returns: - Int: - - 0 if user version is equal to target version. - - 1 if user version is greater than target version. - - -1 if user version is less than target version or, in case of exact string match, doesn't match the target - version. - None: - - if the user version value is not string type or is null. - """ + Args: + index: Index of the condition to be evaluated. + + Returns: + Int: + - 0 if user version is equal to target version. + - 1 if user version is greater than target version. + - -1 if user version is less than target version or, in case of exact string match, doesn't match the target + version. + None: + - if the user version value is not string type or is null. + """ condition_name = self.condition_data[index][0] target_version = self.condition_data[index][1] user_version = self.attributes.get(condition_name) @@ -537,15 +537,15 @@ def evaluate(self, index): """ Given a custom attribute audience condition and user attributes, evaluate the condition against the attributes. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user attributes match the given condition. - - False if the user attributes don't match the given condition. - None: if the user attributes and condition can't be evaluated. - """ + Returns: + Boolean: + - True if the user attributes match the given condition. + - False if the user attributes don't match the given condition. + None: if the user attributes and condition can't be evaluated. + """ if self.condition_data[index][2] != self.CUSTOM_ATTRIBUTE_CONDITION_TYPE: self.logger.warning(audience_logs.UNKNOWN_CONDITION_TYPE.format(self._get_condition_json(index))) @@ -578,7 +578,7 @@ def evaluate(self, index): class ConditionDecoder(object): """ Class which provides an object_hook method for decoding dict - objects into a list when given a condition_decoder. """ + objects into a list when given a condition_decoder. """ def __init__(self, condition_decoder): self.condition_list = [] @@ -587,16 +587,16 @@ def __init__(self, condition_decoder): def object_hook(self, object_dict): """ Hook which when passed into a json.JSONDecoder will replace each dict - in a json string with its index and convert the dict to an object as defined - by the passed in condition_decoder. The newly created condition object is - appended to the conditions_list. + in a json string with its index and convert the dict to an object as defined + by the passed in condition_decoder. The newly created condition object is + appended to the conditions_list. - Args: - object_dict: Dict representing an object. + Args: + object_dict: Dict representing an object. - Returns: - An index which will be used as the placeholder in the condition_structure - """ + Returns: + An index which will be used as the placeholder in the condition_structure + """ instance = self.decoder(object_dict) self.condition_list.append(instance) self.index += 1 @@ -606,12 +606,12 @@ def object_hook(self, object_dict): def _audience_condition_deserializer(obj_dict): """ Deserializer defining how dict objects need to be decoded for audience conditions. - Args: - obj_dict: Dict representing one audience condition. + Args: + obj_dict: Dict representing one audience condition. - Returns: - List consisting of condition key with corresponding value, type and match. - """ + Returns: + List consisting of condition key with corresponding value, type and match. + """ return [ obj_dict.get('name'), obj_dict.get('value'), @@ -622,16 +622,16 @@ def _audience_condition_deserializer(obj_dict): def loads(conditions_string): """ Deserializes the conditions property into its corresponding - components: the condition_structure and the condition_list. + components: the condition_structure and the condition_list. - Args: - conditions_string: String defining valid and/or conditions. + Args: + conditions_string: String defining valid and/or conditions. - Returns: - A tuple of (condition_structure, condition_list). - condition_structure: nested list of operators and placeholders for operands. - condition_list: list of conditions whose index correspond to the values of the placeholders. - """ + Returns: + A tuple of (condition_structure, condition_list). + condition_structure: nested list of operators and placeholders for operands. + condition_list: list of conditions whose index correspond to the values of the placeholders. + """ decoder = ConditionDecoder(_audience_condition_deserializer) # Create a custom JSONDecoder using the ConditionDecoder's object_hook method diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 9f4ede7f..b92905ba 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -313,7 +313,7 @@ def test_evaluate__returns_exception__when_user_provided_version_is_invalid1(sel self.assertRaises(Exception) - def test_evalduate__returns_exception__when_user_provided_version_is_invalid2(self): + def test_evaluate__returns_exception__when_user_provided_version_is_invalid2(self): condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': "+--"}, self.mock_client_logger @@ -321,7 +321,7 @@ def test_evalduate__returns_exception__when_user_provided_version_is_invalid2(se self.assertRaises(Exception) - def test_evalduate__returns_exception__when_user_provided_version_is_invalid3(self): + def test_evaluate__returns_exception__when_user_provided_version_is_invalid3(self): condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': "...+"}, self.mock_client_logger @@ -329,7 +329,7 @@ def test_evalduate__returns_exception__when_user_provided_version_is_invalid3(se self.assertRaises(Exception) - def test_evalduate__returns_exception__when_user_provided_version_is_invalid4(self): + def test_evaluate__returns_exception__when_user_provided_version_is_invalid4(self): condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': "+test"}, self.mock_client_logger @@ -337,7 +337,7 @@ def test_evalduate__returns_exception__when_user_provided_version_is_invalid4(se self.assertRaises(Exception) - def test_evalduate__returns_exception__when_user_provided_version_is_invalid5(self): + def test_evaluate__returns_exception__when_user_provided_version_is_invalid5(self): condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': "3.6"}, self.mock_client_logger @@ -345,7 +345,7 @@ def test_evalduate__returns_exception__when_user_provided_version_is_invalid5(se self.assertRaises(Exception) - def test_evalduate__returns_exception__when_user_provided_version_is_invalid6(self): + def test_evaluate__returns_exception__when_user_provided_version_is_invalid6(self): condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': "2"}, self.mock_client_logger @@ -353,7 +353,7 @@ def test_evalduate__returns_exception__when_user_provided_version_is_invalid6(se self.assertRaises(Exception) - def test_evalduate__returns_exception__when_user_provided_version_is_invalid7(self): + def test_evaluate__returns_exception__when_user_provided_version_is_invalid7(self): condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': "3.90"}, self.mock_client_logger @@ -361,7 +361,7 @@ def test_evalduate__returns_exception__when_user_provided_version_is_invalid7(se self.assertRaises(Exception) - def test_evalduate__returns_exception__when_user_provided_version_is_invalid8(self): + def test_evaluate__returns_exception__when_user_provided_version_is_invalid8(self): condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': "3.90.2.8"}, self.mock_client_logger @@ -369,7 +369,7 @@ def test_evalduate__returns_exception__when_user_provided_version_is_invalid8(se self.assertRaises(Exception) - def test_evalduate__returns_exception__when_user_provided_version_is_invalid9(self): + def test_evaluate__returns_exception__when_user_provided_version_is_invalid9(self): condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': "-2.4"}, self.mock_client_logger @@ -377,7 +377,7 @@ def test_evalduate__returns_exception__when_user_provided_version_is_invalid9(se self.assertRaises(Exception) - def test_evalduate__returns_exception__when_user_provided_version_is_invalid10(self): + def test_evaluate__returns_exception__when_user_provided_version_is_invalid10(self): condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': True}, self.mock_client_logger @@ -385,7 +385,7 @@ def test_evalduate__returns_exception__when_user_provided_version_is_invalid10(s self.assertRaises(Exception) - def test_evalduate__returns_exception__when_user_provided_version_is_invalid11(self): + def test_evaluate__returns_exception__when_user_provided_version_is_invalid11(self): condition_helper.CustomAttributeConditionEvaluator( semver_equal_2_0_condition_list, {'Android': False}, self.mock_client_logger From 5f793a3ffe43145f49972456fb636ea9cf9b9657 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Tue, 25 Aug 2020 20:47:01 +0500 Subject: [PATCH 09/34] comments resolved --- optimizely/helpers/condition.py | 205 +++++++++++++++++--------------- optimizely/helpers/enums.py | 2 +- 2 files changed, 107 insertions(+), 100 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 6dd21bac..cd0853e4 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -92,6 +92,90 @@ def is_value_a_number(self, value): return False + def is_pre_release(self, target): + """ Method to check if the given version contains "-" + + Args: + target: Given version in string. + + Returns: + Boolean: + - True if the given version does contain "-" + - False if it doesn't + """ + return SemverType.IS_PRE_RELEASE in target + + def is_patch_pre_release(self, idx, idx_value): + return idx == SemverType.PATCH_INDEX and idx_value in SemverType.IS_PATCH_PRE_RELEASE + + def is_build(self, target): + """ Method to check if the given version contains "+" + + Args: + target: Given version in string. + + Returns: + Boolean: + - True if the given version does contain "+" + - False if it doesn't + """ + return SemverType.IS_BUILD in target + + def has_white_space(self, target): + """ Method to check if the given version contains " " (white space) + + Args: + target: Given version in string. + + Returns: + Boolean: + - True if the given version does contain " " + - False if it doesn't + """ + return SemverType.HAS_WHITE_SPACE in target + + def compare_user_version_with_target_version(self, index): + """ Method to compare user version with target version. + + Args: + index: Index of the condition to be evaluated. + + Returns: + Int: + - 0 if user version is equal to target version. + - 1 if user version is greater than target version. + - -1 if user version is less than target version or, in case of exact string match, doesn't match the target + version. + None: + - if the user version value is not string type or is null. + """ + condition_name = self.condition_data[index][0] + target_version = self.condition_data[index][1] + user_version = self.attributes.get(condition_name) + + target_version_parts = self.split_semantic_version(target_version) + user_version_parts = self.split_semantic_version(user_version) + user_version_parts_len = len(user_version_parts) + + for (idx, _) in enumerate(target_version_parts): + if user_version_parts_len <= idx: + return 1 if self.is_pre_release(target_version) else -1 + elif not user_version_parts[idx].isdigit(): + if user_version_parts[idx] < target_version_parts[idx]: + return -1 + elif user_version_parts[idx] > target_version_parts[idx]: + return 1 + else: + user_version_part = int(user_version_parts[idx]) + target_version_part = int(target_version_parts[idx]) + if user_version_part > target_version_part: + return 1 + elif user_version_part < target_version_part: + return -1 + if self.is_pre_release(user_version) and not self.is_pre_release(target_version): + return -1 + return 0 + def exact_evaluator(self, index): """ Evaluate the given exact match condition for the user attributes. @@ -384,6 +468,21 @@ def semver_greater_than_or_equal_evaluator(self, index): """ return self.compare_user_version_with_target_version(index) >= 0 + EVALUATORS_BY_MATCH_TYPE = { + ConditionMatchTypes.EXACT: exact_evaluator, + ConditionMatchTypes.EXISTS: exists_evaluator, + ConditionMatchTypes.GREATER_THAN: greater_than_evaluator, + ConditionMatchTypes.LESS_THAN: less_than_evaluator, + ConditionMatchTypes.SEMVER_EQ: semver_equal_evaluator, + ConditionMatchTypes.SEMVER_GE: semver_greater_than_or_equal_evaluator, + ConditionMatchTypes.SEMVER_GT: semver_greater_than_evaluator, + ConditionMatchTypes.SEMVER_LE: semver_less_than_or_equal_evaluator, + ConditionMatchTypes.SEMVER_LT: semver_less_than_evaluator, + ConditionMatchTypes.SUBSTRING: substring_evaluator, + ConditionMatchTypes.LESS_THAN_OR_EQUAL: less_than_or_equal_evaluator, + ConditionMatchTypes.GREATER_THAN_OR_EQUAL: greater_than_or_equal_evaluator, + } + def split_semantic_version(self, target): """ Method to split the given version. @@ -400,15 +499,21 @@ def split_semantic_version(self, target): target_suffix = "" target_parts = [] + """ remove spaces from target version string """ + if self.has_white_space(target): self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) return None + """ check for pre release e.g. 1.0.0-alpha where 'alpha' is a pre release otherwise + check for build e.g. 1.0.0+001 where 001 is a build metadata""" + if self.is_pre_release(target): target_parts = target.split(SemverType.IS_PRE_RELEASE) elif self.is_build(target): target_parts = target.split(SemverType.IS_BUILD) + """ validate target version into prefix and suffix """ if target_parts: if len(target_parts) < 1: self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) @@ -416,6 +521,7 @@ def split_semantic_version(self, target): target_prefix = str(target_parts[0]) target_suffix = target_parts[1:] + """ validate dot counts in a target version """ dot_count = target_prefix.count(".") if dot_count > 2: self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) @@ -434,105 +540,6 @@ def split_semantic_version(self, target): target_version_parts.extend(target_suffix) return target_version_parts - def is_pre_release(self, target): - """ Method to check if the given version contains "-" - - Args: - target: Given version in string. - - Returns: - Boolean: - - True if the given version does contain "-" - - False if it doesn't - """ - return SemverType.IS_PRE_RELEASE in target - - def is_patch_pre_release(self, idx, idx_value): - return idx == SemverType.PATCH_INDEX and idx_value in SemverType.IS_PATCH_PRE_RELEASE - - def is_build(self, target): - """ Method to check if the given version contains "+" - - Args: - target: Given version in string. - - Returns: - Boolean: - - True if the given version does contain "+" - - False if it doesn't - """ - return SemverType.IS_BUILD in target - - def has_white_space(self, target): - """ Method to check if the given version contains " " (white space) - - Args: - target: Given version in string. - - Returns: - Boolean: - - True if the given version does contain " " - - False if it doesn't - """ - return SemverType.HAS_WHITE_SPACE in target - - def compare_user_version_with_target_version(self, index): - """ Method to compare user version with target version. - - Args: - index: Index of the condition to be evaluated. - - Returns: - Int: - - 0 if user version is equal to target version. - - 1 if user version is greater than target version. - - -1 if user version is less than target version or, in case of exact string match, doesn't match the target - version. - None: - - if the user version value is not string type or is null. - """ - condition_name = self.condition_data[index][0] - target_version = self.condition_data[index][1] - user_version = self.attributes.get(condition_name) - - target_version_parts = self.split_semantic_version(target_version) - user_version_parts = self.split_semantic_version(user_version) - user_version_parts_len = len(user_version_parts) - - for (idx, _) in enumerate(target_version_parts): - if user_version_parts_len <= idx: - return 1 if self.is_pre_release(target_version) else -1 - elif not user_version_parts[idx].isdigit(): - if user_version_parts[idx] < target_version_parts[idx]: - return -1 - elif user_version_parts[idx] > target_version_parts[idx]: - return 1 - else: - user_version_part = int(user_version_parts[idx]) - target_version_part = int(target_version_parts[idx]) - if user_version_part > target_version_part: - return 1 - elif user_version_part < target_version_part: - return -1 - if self.is_pre_release(user_version) and not self.is_pre_release(target_version): - return -1 - return 0 - - EVALUATORS_BY_MATCH_TYPE = { - ConditionMatchTypes.EXACT: exact_evaluator, - ConditionMatchTypes.EXISTS: exists_evaluator, - ConditionMatchTypes.GREATER_THAN: greater_than_evaluator, - ConditionMatchTypes.LESS_THAN: less_than_evaluator, - ConditionMatchTypes.SEMVER_EQ: semver_equal_evaluator, - ConditionMatchTypes.SEMVER_GE: semver_greater_than_or_equal_evaluator, - ConditionMatchTypes.SEMVER_GT: semver_greater_than_evaluator, - ConditionMatchTypes.SEMVER_LE: semver_less_than_or_equal_evaluator, - ConditionMatchTypes.SEMVER_LT: semver_less_than_evaluator, - ConditionMatchTypes.SUBSTRING: substring_evaluator, - ConditionMatchTypes.LESS_THAN_OR_EQUAL: less_than_or_equal_evaluator, - ConditionMatchTypes.GREATER_THAN_OR_EQUAL: greater_than_or_equal_evaluator, - } - def evaluate(self, index): """ Given a custom attribute audience condition and user attributes, evaluate the condition against the attributes. diff --git a/optimizely/helpers/enums.py b/optimizely/helpers/enums.py index 466cbd7a..44dfe030 100644 --- a/optimizely/helpers/enums.py +++ b/optimizely/helpers/enums.py @@ -161,5 +161,5 @@ class NotificationTypes(object): class SemverType(object): IS_PRE_RELEASE = '-' - HAS_WHITE_SPACE = " " + HAS_WHITE_SPACE = ' ' IS_BUILD = '+' From cfb12df6d4f931b06b6dc98c37f6809846da67d0 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Tue, 25 Aug 2020 21:58:25 +0500 Subject: [PATCH 10/34] Update condition.py --- optimizely/helpers/condition.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index cd0853e4..65366529 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -505,15 +505,15 @@ def split_semantic_version(self, target): self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) return None - """ check for pre release e.g. 1.0.0-alpha where 'alpha' is a pre release otherwise - check for build e.g. 1.0.0+001 where 001 is a build metadata""" + # check for pre release e.g. 1.0.0-alpha where 'alpha' is a pre release + # otherwise check for build e.g. 1.0.0+001 where 001 is a build metadata""" if self.is_pre_release(target): target_parts = target.split(SemverType.IS_PRE_RELEASE) elif self.is_build(target): target_parts = target.split(SemverType.IS_BUILD) - """ validate target version into prefix and suffix """ + # validate target version into prefix and suffix if target_parts: if len(target_parts) < 1: self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) @@ -521,7 +521,7 @@ def split_semantic_version(self, target): target_prefix = str(target_parts[0]) target_suffix = target_parts[1:] - """ validate dot counts in a target version """ + # validate dot counts in a target version dot_count = target_prefix.count(".") if dot_count > 2: self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) From 1c37fbfd0f679bf0c799eb438fa7073be38d851a Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Wed, 26 Aug 2020 14:13:21 +0500 Subject: [PATCH 11/34] invalid test case for semver and comment fixed --- optimizely/helpers/condition.py | 7 +- tests/helpers_tests/test_condition.py | 109 +++++++++++++++----------- 2 files changed, 64 insertions(+), 52 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 65366529..3078bbc6 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -105,9 +105,6 @@ def is_pre_release(self, target): """ return SemverType.IS_PRE_RELEASE in target - def is_patch_pre_release(self, idx, idx_value): - return idx == SemverType.PATCH_INDEX and idx_value in SemverType.IS_PATCH_PRE_RELEASE - def is_build(self, target): """ Method to check if the given version contains "+" @@ -499,14 +496,14 @@ def split_semantic_version(self, target): target_suffix = "" target_parts = [] - """ remove spaces from target version string """ + # remove spaces from target version string if self.has_white_space(target): self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) return None # check for pre release e.g. 1.0.0-alpha where 'alpha' is a pre release - # otherwise check for build e.g. 1.0.0+001 where 001 is a build metadata""" + # otherwise check for build e.g. 1.0.0+001 where 001 is a build metadata if self.is_pre_release(target): target_parts = target.split(SemverType.IS_PRE_RELEASE) diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index b92905ba..60e72bd9 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -13,6 +13,7 @@ import json import mock +import pytest from six import PY2 from optimizely.helpers import condition as condition_helper @@ -439,7 +440,7 @@ def test_exists__returns_true__when_user_provided_value_is_boolean(self): self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condition_value(self,): + def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': 'Lacerta'}, self.mock_client_logger, @@ -447,7 +448,7 @@ def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condit self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self,): + def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': 'The Big Dipper'}, self.mock_client_logger, @@ -455,7 +456,7 @@ def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_c self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_string__returns_null__when_user_provided_value_is_different_type_from_condition_value(self,): + def test_exact_string__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': False}, self.mock_client_logger, @@ -471,7 +472,7 @@ def test_exact_string__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition_value(self,): + def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): if PY2: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -492,7 +493,7 @@ def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_float__returns_true__when_user_provided_value_is_equal_to_condition_value(self,): + def test_exact_float__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): if PY2: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -513,7 +514,7 @@ def test_exact_float__returns_true__when_user_provided_value_is_equal_to_conditi self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self,): + def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_int_condition_list, {'lasers_count': 8000}, self.mock_client_logger @@ -521,7 +522,7 @@ def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_cond self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self,): + def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_float_condition_list, {'lasers_count': 8000.0}, self.mock_client_logger, @@ -529,7 +530,7 @@ def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_co self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_int__returns_null__when_user_provided_value_is_different_type_from_condition_value(self,): + def test_exact_int__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_int_condition_list, {'lasers_count': 'hi'}, self.mock_client_logger @@ -543,7 +544,7 @@ def test_exact_int__returns_null__when_user_provided_value_is_different_type_fro self.assertIsNone(evaluator.evaluate(0)) - def test_exact_float__returns_null__when_user_provided_value_is_different_type_from_condition_value(self,): + def test_exact_float__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_float_condition_list, {'lasers_count': 'hi'}, self.mock_client_logger @@ -600,7 +601,7 @@ def test_exact__given_number_values__calls_is_finite_number(self): mock_is_finite.assert_has_calls([mock.call(9000), mock.call(9000)]) - def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_condition_value(self,): + def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': False}, self.mock_client_logger, @@ -608,7 +609,7 @@ def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_conditio self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self,): + def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': True}, self.mock_client_logger, @@ -616,7 +617,7 @@ def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_con self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_bool__returns_null__when_user_provided_value_is_different_type_from_condition_value(self,): + def test_exact_bool__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': 0}, self.mock_client_logger @@ -632,7 +633,7 @@ def test_exact_bool__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_substring__returns_true__when_condition_value_is_substring_of_user_value(self,): + def test_substring__returns_true__when_condition_value_is_substring_of_user_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( substring_condition_list, {'headline_text': 'Limited time, buy now!'}, self.mock_client_logger, @@ -640,7 +641,7 @@ def test_substring__returns_true__when_condition_value_is_substring_of_user_valu self.assertStrictTrue(evaluator.evaluate(0)) - def test_substring__returns_false__when_condition_value_is_not_a_substring_of_user_value(self,): + def test_substring__returns_false__when_condition_value_is_not_a_substring_of_user_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( substring_condition_list, {'headline_text': 'Breaking news!'}, self.mock_client_logger, @@ -664,7 +665,7 @@ def test_substring__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_greater_than_int__returns_true__when_user_value_greater_than_condition_value(self,): + def test_greater_than_int__returns_true__when_user_value_greater_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -685,7 +686,7 @@ def test_greater_than_int__returns_true__when_user_value_greater_than_condition_ self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_float__returns_true__when_user_value_greater_than_condition_value(self,): + def test_greater_than_float__returns_true__when_user_value_greater_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger @@ -706,7 +707,7 @@ def test_greater_than_float__returns_true__when_user_value_greater_than_conditio self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_int__returns_false__when_user_value_not_greater_than_condition_value(self,): + def test_greater_than_int__returns_false__when_user_value_not_greater_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -727,7 +728,7 @@ def test_greater_than_int__returns_false__when_user_value_not_greater_than_condi self.assertStrictFalse(evaluator.evaluate(0)) - def test_greater_than_float__returns_false__when_user_value_not_greater_than_condition_value(self,): + def test_greater_than_float__returns_false__when_user_value_not_greater_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger @@ -792,7 +793,7 @@ def test_greater_than_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_greater_than_or_equal_int__returns_true__when_user_value_greater_than_or_equal_condition_value(self,): + def test_greater_than_or_equal_int__returns_true__when_user_value_greater_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( ge_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -819,7 +820,7 @@ def test_greater_than_or_equal_int__returns_true__when_user_value_greater_than_o self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_or_equal_float__returns_true__when_user_value_greater_than_or_equal_condition_value(self,): + def test_greater_than_or_equal_float__returns_true__when_user_value_greater_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( ge_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger @@ -846,7 +847,8 @@ def test_greater_than_or_equal_float__returns_true__when_user_value_greater_than self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_or_equal_int__returns_false__when_user_value_not_greater_than_or_equal_condition_value(self,): + def test_greater_than_or_equal_int__returns_false__when_user_value_not_greater_than_or_equal_condition_value( + self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( ge_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -854,7 +856,6 @@ def test_greater_than_or_equal_int__returns_false__when_user_value_not_greater_t self.assertStrictFalse(evaluator.evaluate(0)) - evaluator = condition_helper.CustomAttributeConditionEvaluator( ge_int_condition_list, {'meters_travelled': 47}, self.mock_client_logger ) @@ -868,7 +869,8 @@ def test_greater_than_or_equal_int__returns_false__when_user_value_not_greater_t self.assertStrictFalse(evaluator.evaluate(0)) - def test_greater_than_or_equal_float__returns_false__when_user_value_not_greater_than_or_equal_condition_value(self,): + def test_greater_than_or_equal_float__returns_false__when_user_value_not_greater_than_or_equal_condition_value( + self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( ge_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -933,7 +935,7 @@ def test_greater_than_or_equal_float__returns_null__when_no_user_provided_value( self.assertIsNone(evaluator.evaluate(0)) - def test_less_than_int__returns_true__when_user_value_less_than_condition_value(self,): + def test_less_than_int__returns_true__when_user_value_less_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -954,7 +956,7 @@ def test_less_than_int__returns_true__when_user_value_less_than_condition_value( self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self,): + def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -975,7 +977,7 @@ def test_less_than_float__returns_true__when_user_value_less_than_condition_valu self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_int__returns_false__when_user_value_not_less_than_condition_value(self,): + def test_less_than_int__returns_false__when_user_value_not_less_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -996,7 +998,7 @@ def test_less_than_int__returns_false__when_user_value_not_less_than_condition_v self.assertStrictFalse(evaluator.evaluate(0)) - def test_less_than_float__returns_false__when_user_value_not_less_than_condition_value(self,): + def test_less_than_float__returns_false__when_user_value_not_less_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger @@ -1049,7 +1051,7 @@ def test_less_than_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self,): + def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -1082,7 +1084,7 @@ def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equa self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self,): + def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -1109,7 +1111,7 @@ def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_eq self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self,): + def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -1130,7 +1132,7 @@ def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or self.assertStrictFalse(evaluator.evaluate(0)) - def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self,): + def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger @@ -1197,7 +1199,8 @@ def is_finite_number__rejecting_condition_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__rejecting_condition_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_condition_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1210,8 +1213,8 @@ def is_finite_number__rejecting_user_attribute_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', - side_effect=is_finite_number__rejecting_user_attribute_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_user_attribute_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1222,7 +1225,7 @@ def is_finite_number__accepting_both_values(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, ): self.assertTrue(evaluator.evaluate(0)) @@ -1240,7 +1243,8 @@ def is_finite_number__rejecting_condition_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__rejecting_condition_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_condition_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1253,8 +1257,8 @@ def is_finite_number__rejecting_user_attribute_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', - side_effect=is_finite_number__rejecting_user_attribute_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_user_attribute_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1265,7 +1269,7 @@ def is_finite_number__accepting_both_values(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, ): self.assertTrue(evaluator.evaluate(0)) @@ -1283,7 +1287,8 @@ def is_finite_number__rejecting_condition_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__rejecting_condition_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_condition_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1296,8 +1301,8 @@ def is_finite_number__rejecting_user_attribute_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', - side_effect=is_finite_number__rejecting_user_attribute_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_user_attribute_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1308,7 +1313,7 @@ def is_finite_number__accepting_both_values(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, ): self.assertTrue(evaluator.evaluate(0)) @@ -1326,7 +1331,8 @@ def is_finite_number__rejecting_condition_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__rejecting_condition_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_condition_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1339,8 +1345,8 @@ def is_finite_number__rejecting_user_attribute_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', - side_effect=is_finite_number__rejecting_user_attribute_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_user_attribute_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1351,7 +1357,7 @@ def is_finite_number__accepting_both_values(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, ): self.assertTrue(evaluator.evaluate(0)) @@ -2013,3 +2019,12 @@ def test_substring__condition_value_invalid(self): 'newer release of the Optimizely SDK.' ).format(json.dumps(expected_condition_log)) ) + + @pytest.mark.parametrize("test_input,expected", [(i, None) for i in ["-", ".", "..", "+", "+test", " ", "2 .0. 0", + "2.", ".0.0", "1.2.2.2", "2.x", ",", + "+build-prerelese"]]) + def test_invalid_semver__returns_null__when_semver_is_invalid(self, test_input, expected): + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_1_condition_list, {'Android': test_input}, self.mock_client_logger) + + assert eval(evaluator.evaluate(0)) == expected From dcd391f2a4c6e235b9cbe6c1c386d52e4935c9f2 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Wed, 26 Aug 2020 14:21:04 +0500 Subject: [PATCH 12/34] Update test_condition.py --- tests/helpers_tests/test_condition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 60e72bd9..065f2f89 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -2025,6 +2025,6 @@ def test_substring__condition_value_invalid(self): "+build-prerelese"]]) def test_invalid_semver__returns_null__when_semver_is_invalid(self, test_input, expected): evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_or_equal_2_0_1_condition_list, {'Android': test_input}, self.mock_client_logger) + semver_less_than_or_equal_2_0_1_condition_list, {'Android': test_input}, self.mock_client_logger) assert eval(evaluator.evaluate(0)) == expected From 2affb9d6d9c3923e9f36a729e3327efa3b92cf7e Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Wed, 26 Aug 2020 14:49:26 +0500 Subject: [PATCH 13/34] Update test_condition.py --- tests/helpers_tests/test_condition.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 065f2f89..148340a3 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -13,7 +13,6 @@ import json import mock -import pytest from six import PY2 from optimizely.helpers import condition as condition_helper From 8577f7cea7d5ff38f51e310626345d2435819b2e Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Thu, 27 Aug 2020 01:47:45 +0500 Subject: [PATCH 14/34] compare implemetation and invalid testcase fixed --- optimizely/helpers/condition.py | 18 +++++++++++++----- tests/helpers_tests/test_condition.py | 16 +++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 3078bbc6..b7709efe 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -152,6 +152,9 @@ def compare_user_version_with_target_version(self, index): target_version_parts = self.split_semantic_version(target_version) user_version_parts = self.split_semantic_version(user_version) + if not user_version_parts: + return None + user_version_parts_len = len(user_version_parts) for (idx, _) in enumerate(target_version_parts): @@ -403,7 +406,8 @@ def semver_equal_evaluator(self, index): None: - if the user version value is not string type or is null. """ - return self.compare_user_version_with_target_version(index) == 0 + return self.compare_user_version_with_target_version( + index) == 0 if self.compare_user_version_with_target_version(index) else None def semver_greater_than_evaluator(self, index): """ Evaluate the given semantic version greater than match target version for the user version. @@ -418,7 +422,8 @@ def semver_greater_than_evaluator(self, index): None: - if the user version value is not string type or is null. """ - return self.compare_user_version_with_target_version(index) > 0 + return self.compare_user_version_with_target_version( + index) > 0 if self.compare_user_version_with_target_version(index) else None def semver_less_than_evaluator(self, index): """ Evaluate the given semantic version less than match target version for the user version. @@ -433,7 +438,8 @@ def semver_less_than_evaluator(self, index): None: - if the user version value is not string type or is null. """ - return self.compare_user_version_with_target_version(index) < 0 + return self.compare_user_version_with_target_version( + index) < 0 if self.compare_user_version_with_target_version(index) else None def semver_less_than_or_equal_evaluator(self, index): """ Evaluate the given semantic version less than or equal to match target version for the user version. @@ -448,7 +454,8 @@ def semver_less_than_or_equal_evaluator(self, index): None: - if the user version value is not string type or is null. """ - return self.compare_user_version_with_target_version(index) <= 0 + return self.compare_user_version_with_target_version( + index) <= 0 if self.compare_user_version_with_target_version(index) else None def semver_greater_than_or_equal_evaluator(self, index): """ Evaluate the given semantic version greater than or equal to match target version for the user version. @@ -463,7 +470,8 @@ def semver_greater_than_or_equal_evaluator(self, index): None: - if the user version value is not string type or is null. """ - return self.compare_user_version_with_target_version(index) >= 0 + return self.compare_user_version_with_target_version( + index) >= 0 if self.compare_user_version_with_target_version(index) else None EVALUATORS_BY_MATCH_TYPE = { ConditionMatchTypes.EXACT: exact_evaluator, diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 148340a3..66807969 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -2019,11 +2019,13 @@ def test_substring__condition_value_invalid(self): ).format(json.dumps(expected_condition_log)) ) - @pytest.mark.parametrize("test_input,expected", [(i, None) for i in ["-", ".", "..", "+", "+test", " ", "2 .0. 0", - "2.", ".0.0", "1.2.2.2", "2.x", ",", - "+build-prerelese"]]) - def test_invalid_semver__returns_null__when_semver_is_invalid(self, test_input, expected): - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_or_equal_2_0_1_condition_list, {'Android': test_input}, self.mock_client_logger) + def test_invalid_semver__returns_None__when_semver_is_invalid(self): + invalid_test_cases = ["-", ".", "..", "+", "+test", " ", "2 .0. 0", + "2.", ".0.0", "1.2.2.2", "2.x", ",", + "+build-prerelese"] + + for invalid_test_case in invalid_test_cases: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_1_condition_list, {'Android': invalid_test_case}, self.mock_client_logger) - assert eval(evaluator.evaluate(0)) == expected + self.assertIsNone(evaluator.evaluate(0)) From 426d2b7303878e9dcdeb281d546b933ffd224191 Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Thu, 27 Aug 2020 15:26:58 +0500 Subject: [PATCH 15/34] Revert "compare implemetation and invalid testcase fixed" This reverts commit 8577f7cea7d5ff38f51e310626345d2435819b2e. --- optimizely/helpers/condition.py | 18 +++++------------- tests/helpers_tests/test_condition.py | 16 +++++++--------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index b7709efe..3078bbc6 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -152,9 +152,6 @@ def compare_user_version_with_target_version(self, index): target_version_parts = self.split_semantic_version(target_version) user_version_parts = self.split_semantic_version(user_version) - if not user_version_parts: - return None - user_version_parts_len = len(user_version_parts) for (idx, _) in enumerate(target_version_parts): @@ -406,8 +403,7 @@ def semver_equal_evaluator(self, index): None: - if the user version value is not string type or is null. """ - return self.compare_user_version_with_target_version( - index) == 0 if self.compare_user_version_with_target_version(index) else None + return self.compare_user_version_with_target_version(index) == 0 def semver_greater_than_evaluator(self, index): """ Evaluate the given semantic version greater than match target version for the user version. @@ -422,8 +418,7 @@ def semver_greater_than_evaluator(self, index): None: - if the user version value is not string type or is null. """ - return self.compare_user_version_with_target_version( - index) > 0 if self.compare_user_version_with_target_version(index) else None + return self.compare_user_version_with_target_version(index) > 0 def semver_less_than_evaluator(self, index): """ Evaluate the given semantic version less than match target version for the user version. @@ -438,8 +433,7 @@ def semver_less_than_evaluator(self, index): None: - if the user version value is not string type or is null. """ - return self.compare_user_version_with_target_version( - index) < 0 if self.compare_user_version_with_target_version(index) else None + return self.compare_user_version_with_target_version(index) < 0 def semver_less_than_or_equal_evaluator(self, index): """ Evaluate the given semantic version less than or equal to match target version for the user version. @@ -454,8 +448,7 @@ def semver_less_than_or_equal_evaluator(self, index): None: - if the user version value is not string type or is null. """ - return self.compare_user_version_with_target_version( - index) <= 0 if self.compare_user_version_with_target_version(index) else None + return self.compare_user_version_with_target_version(index) <= 0 def semver_greater_than_or_equal_evaluator(self, index): """ Evaluate the given semantic version greater than or equal to match target version for the user version. @@ -470,8 +463,7 @@ def semver_greater_than_or_equal_evaluator(self, index): None: - if the user version value is not string type or is null. """ - return self.compare_user_version_with_target_version( - index) >= 0 if self.compare_user_version_with_target_version(index) else None + return self.compare_user_version_with_target_version(index) >= 0 EVALUATORS_BY_MATCH_TYPE = { ConditionMatchTypes.EXACT: exact_evaluator, diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 66807969..148340a3 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -2019,13 +2019,11 @@ def test_substring__condition_value_invalid(self): ).format(json.dumps(expected_condition_log)) ) - def test_invalid_semver__returns_None__when_semver_is_invalid(self): - invalid_test_cases = ["-", ".", "..", "+", "+test", " ", "2 .0. 0", - "2.", ".0.0", "1.2.2.2", "2.x", ",", - "+build-prerelese"] - - for invalid_test_case in invalid_test_cases: - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_or_equal_2_0_1_condition_list, {'Android': invalid_test_case}, self.mock_client_logger) + @pytest.mark.parametrize("test_input,expected", [(i, None) for i in ["-", ".", "..", "+", "+test", " ", "2 .0. 0", + "2.", ".0.0", "1.2.2.2", "2.x", ",", + "+build-prerelese"]]) + def test_invalid_semver__returns_null__when_semver_is_invalid(self, test_input, expected): + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_1_condition_list, {'Android': test_input}, self.mock_client_logger) - self.assertIsNone(evaluator.evaluate(0)) + assert eval(evaluator.evaluate(0)) == expected From ac8113eb9fd8c4bd6a8b741ef35782965bb8ef8c Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Thu, 27 Aug 2020 15:59:55 +0500 Subject: [PATCH 16/34] passes fsc at this point --- optimizely/helpers/condition.py | 32 +++++++++++++++++++++++++++ tests/helpers_tests/test_condition.py | 16 ++++++++------ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 3078bbc6..a2ab3255 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -150,8 +150,24 @@ def compare_user_version_with_target_version(self, index): target_version = self.condition_data[index][1] user_version = self.attributes.get(condition_name) + if not isinstance(target_version, string_types): + self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index), )) + return None + + if not isinstance(user_version, string_types): + self.logger.warning( + audience_logs.UNEXPECTED_TYPE.format(self._get_condition_json(index), type(user_version), condition_name) + ) + return None + target_version_parts = self.split_semantic_version(target_version) + if target_version_parts is None: + return None + user_version_parts = self.split_semantic_version(user_version) + if user_version_parts is None: + return None + user_version_parts_len = len(user_version_parts) for (idx, _) in enumerate(target_version_parts): @@ -403,6 +419,10 @@ def semver_equal_evaluator(self, index): None: - if the user version value is not string type or is null. """ + result = self.compare_user_version_with_target_version(index) + if result is None: + return result + return self.compare_user_version_with_target_version(index) == 0 def semver_greater_than_evaluator(self, index): @@ -418,6 +438,9 @@ def semver_greater_than_evaluator(self, index): None: - if the user version value is not string type or is null. """ + result = self.compare_user_version_with_target_version(index) + if result is None: + return result return self.compare_user_version_with_target_version(index) > 0 def semver_less_than_evaluator(self, index): @@ -433,6 +456,9 @@ def semver_less_than_evaluator(self, index): None: - if the user version value is not string type or is null. """ + result = self.compare_user_version_with_target_version(index) + if result is None: + return result return self.compare_user_version_with_target_version(index) < 0 def semver_less_than_or_equal_evaluator(self, index): @@ -448,6 +474,9 @@ def semver_less_than_or_equal_evaluator(self, index): None: - if the user version value is not string type or is null. """ + result = self.compare_user_version_with_target_version(index) + if result is None: + return result return self.compare_user_version_with_target_version(index) <= 0 def semver_greater_than_or_equal_evaluator(self, index): @@ -463,6 +492,9 @@ def semver_greater_than_or_equal_evaluator(self, index): None: - if the user version value is not string type or is null. """ + result = self.compare_user_version_with_target_version(index) + if result is None: + return result return self.compare_user_version_with_target_version(index) >= 0 EVALUATORS_BY_MATCH_TYPE = { diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 148340a3..66807969 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -2019,11 +2019,13 @@ def test_substring__condition_value_invalid(self): ).format(json.dumps(expected_condition_log)) ) - @pytest.mark.parametrize("test_input,expected", [(i, None) for i in ["-", ".", "..", "+", "+test", " ", "2 .0. 0", - "2.", ".0.0", "1.2.2.2", "2.x", ",", - "+build-prerelese"]]) - def test_invalid_semver__returns_null__when_semver_is_invalid(self, test_input, expected): - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_or_equal_2_0_1_condition_list, {'Android': test_input}, self.mock_client_logger) + def test_invalid_semver__returns_None__when_semver_is_invalid(self): + invalid_test_cases = ["-", ".", "..", "+", "+test", " ", "2 .0. 0", + "2.", ".0.0", "1.2.2.2", "2.x", ",", + "+build-prerelese"] + + for invalid_test_case in invalid_test_cases: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_1_condition_list, {'Android': invalid_test_case}, self.mock_client_logger) - assert eval(evaluator.evaluate(0)) == expected + self.assertIsNone(evaluator.evaluate(0)) From 84f6c262985f078c6100d8cb3952830fd226bac1 Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Thu, 27 Aug 2020 17:33:15 +0500 Subject: [PATCH 17/34] fix:lint --- optimizely/helpers/condition.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index a2ab3255..ddf64c7a 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -156,7 +156,9 @@ def compare_user_version_with_target_version(self, index): if not isinstance(user_version, string_types): self.logger.warning( - audience_logs.UNEXPECTED_TYPE.format(self._get_condition_json(index), type(user_version), condition_name) + audience_logs.UNEXPECTED_TYPE.format( + self._get_condition_json(index), type(user_version), condition_name + ) ) return None From ca7984a6370d642bbdba8a23ea339b7a1a233416 Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Thu, 27 Aug 2020 18:03:57 +0500 Subject: [PATCH 18/34] remove: additional lint fixes --- optimizely/helpers/condition.py | 100 +++++++++++++------------- tests/helpers_tests/test_condition.py | 74 ++++++++++--------- 2 files changed, 87 insertions(+), 87 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index ddf64c7a..e95dc294 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -423,9 +423,9 @@ def semver_equal_evaluator(self, index): """ result = self.compare_user_version_with_target_version(index) if result is None: - return result + return None - return self.compare_user_version_with_target_version(index) == 0 + return result == 0 def semver_greater_than_evaluator(self, index): """ Evaluate the given semantic version greater than match target version for the user version. @@ -442,8 +442,9 @@ def semver_greater_than_evaluator(self, index): """ result = self.compare_user_version_with_target_version(index) if result is None: - return result - return self.compare_user_version_with_target_version(index) > 0 + return None + + return result > 0 def semver_less_than_evaluator(self, index): """ Evaluate the given semantic version less than match target version for the user version. @@ -460,8 +461,9 @@ def semver_less_than_evaluator(self, index): """ result = self.compare_user_version_with_target_version(index) if result is None: - return result - return self.compare_user_version_with_target_version(index) < 0 + return None + + return result < 0 def semver_less_than_or_equal_evaluator(self, index): """ Evaluate the given semantic version less than or equal to match target version for the user version. @@ -478,8 +480,9 @@ def semver_less_than_or_equal_evaluator(self, index): """ result = self.compare_user_version_with_target_version(index) if result is None: - return result - return self.compare_user_version_with_target_version(index) <= 0 + return None + + return result <= 0 def semver_greater_than_or_equal_evaluator(self, index): """ Evaluate the given semantic version greater than or equal to match target version for the user version. @@ -496,22 +499,23 @@ def semver_greater_than_or_equal_evaluator(self, index): """ result = self.compare_user_version_with_target_version(index) if result is None: - return result - return self.compare_user_version_with_target_version(index) >= 0 + return None + + return result >= 0 EVALUATORS_BY_MATCH_TYPE = { ConditionMatchTypes.EXACT: exact_evaluator, ConditionMatchTypes.EXISTS: exists_evaluator, ConditionMatchTypes.GREATER_THAN: greater_than_evaluator, + ConditionMatchTypes.GREATER_THAN_OR_EQUAL: greater_than_or_equal_evaluator, ConditionMatchTypes.LESS_THAN: less_than_evaluator, + ConditionMatchTypes.LESS_THAN_OR_EQUAL: less_than_or_equal_evaluator, ConditionMatchTypes.SEMVER_EQ: semver_equal_evaluator, ConditionMatchTypes.SEMVER_GE: semver_greater_than_or_equal_evaluator, ConditionMatchTypes.SEMVER_GT: semver_greater_than_evaluator, ConditionMatchTypes.SEMVER_LE: semver_less_than_or_equal_evaluator, ConditionMatchTypes.SEMVER_LT: semver_less_than_evaluator, - ConditionMatchTypes.SUBSTRING: substring_evaluator, - ConditionMatchTypes.LESS_THAN_OR_EQUAL: less_than_or_equal_evaluator, - ConditionMatchTypes.GREATER_THAN_OR_EQUAL: greater_than_or_equal_evaluator, + ConditionMatchTypes.SUBSTRING: substring_evaluator } def split_semantic_version(self, target): @@ -523,28 +527,26 @@ def split_semantic_version(self, target): Returns: List: - The array of version split into smaller parts i.e major, minor, patch etc - Exception: + None: - if the given version is invalid in format """ target_prefix = target target_suffix = "" target_parts = [] - # remove spaces from target version string - + # check that target shouldn't have white space if self.has_white_space(target): self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) return None # check for pre release e.g. 1.0.0-alpha where 'alpha' is a pre release # otherwise check for build e.g. 1.0.0+001 where 001 is a build metadata - if self.is_pre_release(target): target_parts = target.split(SemverType.IS_PRE_RELEASE) elif self.is_build(target): target_parts = target.split(SemverType.IS_BUILD) - # validate target version into prefix and suffix + # split target version into prefix and suffix if target_parts: if len(target_parts) < 1: self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) @@ -552,7 +554,7 @@ def split_semantic_version(self, target): target_prefix = str(target_parts[0]) target_suffix = target_parts[1:] - # validate dot counts in a target version + # check dot counts in target_prefix dot_count = target_prefix.count(".") if dot_count > 2: self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) @@ -575,15 +577,15 @@ def evaluate(self, index): """ Given a custom attribute audience condition and user attributes, evaluate the condition against the attributes. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user attributes match the given condition. - - False if the user attributes don't match the given condition. - None: if the user attributes and condition can't be evaluated. - """ + Returns: + Boolean: + - True if the user attributes match the given condition. + - False if the user attributes don't match the given condition. + None: if the user attributes and condition can't be evaluated. + """ if self.condition_data[index][2] != self.CUSTOM_ATTRIBUTE_CONDITION_TYPE: self.logger.warning(audience_logs.UNKNOWN_CONDITION_TYPE.format(self._get_condition_json(index))) @@ -616,7 +618,7 @@ def evaluate(self, index): class ConditionDecoder(object): """ Class which provides an object_hook method for decoding dict - objects into a list when given a condition_decoder. """ + objects into a list when given a condition_decoder. """ def __init__(self, condition_decoder): self.condition_list = [] @@ -625,16 +627,16 @@ def __init__(self, condition_decoder): def object_hook(self, object_dict): """ Hook which when passed into a json.JSONDecoder will replace each dict - in a json string with its index and convert the dict to an object as defined - by the passed in condition_decoder. The newly created condition object is - appended to the conditions_list. + in a json string with its index and convert the dict to an object as defined + by the passed in condition_decoder. The newly created condition object is + appended to the conditions_list. - Args: - object_dict: Dict representing an object. + Args: + object_dict: Dict representing an object. - Returns: - An index which will be used as the placeholder in the condition_structure - """ + Returns: + An index which will be used as the placeholder in the condition_structure + """ instance = self.decoder(object_dict) self.condition_list.append(instance) self.index += 1 @@ -644,12 +646,12 @@ def object_hook(self, object_dict): def _audience_condition_deserializer(obj_dict): """ Deserializer defining how dict objects need to be decoded for audience conditions. - Args: - obj_dict: Dict representing one audience condition. + Args: + obj_dict: Dict representing one audience condition. - Returns: - List consisting of condition key with corresponding value, type and match. - """ + Returns: + List consisting of condition key with corresponding value, type and match. + """ return [ obj_dict.get('name'), obj_dict.get('value'), @@ -660,16 +662,16 @@ def _audience_condition_deserializer(obj_dict): def loads(conditions_string): """ Deserializes the conditions property into its corresponding - components: the condition_structure and the condition_list. + components: the condition_structure and the condition_list. - Args: - conditions_string: String defining valid and/or conditions. + Args: + conditions_string: String defining valid and/or conditions. - Returns: - A tuple of (condition_structure, condition_list). - condition_structure: nested list of operators and placeholders for operands. - condition_list: list of conditions whose index correspond to the values of the placeholders. - """ + Returns: + A tuple of (condition_structure, condition_list). + condition_structure: nested list of operators and placeholders for operands. + condition_list: list of conditions whose index correspond to the values of the placeholders. + """ decoder = ConditionDecoder(_audience_condition_deserializer) # Create a custom JSONDecoder using the ConditionDecoder's object_hook method diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 66807969..56086735 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -439,7 +439,7 @@ def test_exists__returns_true__when_user_provided_value_is_boolean(self): self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): + def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': 'Lacerta'}, self.mock_client_logger, @@ -447,7 +447,7 @@ def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condit self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): + def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': 'The Big Dipper'}, self.mock_client_logger, @@ -455,7 +455,7 @@ def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_c self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_string__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): + def test_exact_string__returns_null__when_user_provided_value_is_different_type_from_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': False}, self.mock_client_logger, @@ -471,7 +471,7 @@ def test_exact_string__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): + def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition_value(self,): if PY2: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -513,7 +513,7 @@ def test_exact_float__returns_true__when_user_provided_value_is_equal_to_conditi self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): + def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_int_condition_list, {'lasers_count': 8000}, self.mock_client_logger @@ -521,7 +521,7 @@ def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_cond self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): + def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_float_condition_list, {'lasers_count': 8000.0}, self.mock_client_logger, @@ -529,7 +529,7 @@ def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_co self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_int__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): + def test_exact_int__returns_null__when_user_provided_value_is_different_type_from_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_int_condition_list, {'lasers_count': 'hi'}, self.mock_client_logger @@ -543,7 +543,7 @@ def test_exact_int__returns_null__when_user_provided_value_is_different_type_fro self.assertIsNone(evaluator.evaluate(0)) - def test_exact_float__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): + def test_exact_float__returns_null__when_user_provided_value_is_different_type_from_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_float_condition_list, {'lasers_count': 'hi'}, self.mock_client_logger @@ -600,7 +600,7 @@ def test_exact__given_number_values__calls_is_finite_number(self): mock_is_finite.assert_has_calls([mock.call(9000), mock.call(9000)]) - def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): + def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': False}, self.mock_client_logger, @@ -608,7 +608,7 @@ def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_conditio self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): + def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': True}, self.mock_client_logger, @@ -616,7 +616,7 @@ def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_con self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_bool__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): + def test_exact_bool__returns_null__when_user_provided_value_is_different_type_from_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': 0}, self.mock_client_logger @@ -632,7 +632,7 @@ def test_exact_bool__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_substring__returns_true__when_condition_value_is_substring_of_user_value(self, ): + def test_substring__returns_true__when_condition_value_is_substring_of_user_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( substring_condition_list, {'headline_text': 'Limited time, buy now!'}, self.mock_client_logger, @@ -640,7 +640,7 @@ def test_substring__returns_true__when_condition_value_is_substring_of_user_valu self.assertStrictTrue(evaluator.evaluate(0)) - def test_substring__returns_false__when_condition_value_is_not_a_substring_of_user_value(self, ): + def test_substring__returns_false__when_condition_value_is_not_a_substring_of_user_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( substring_condition_list, {'headline_text': 'Breaking news!'}, self.mock_client_logger, @@ -664,7 +664,7 @@ def test_substring__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_greater_than_int__returns_true__when_user_value_greater_than_condition_value(self, ): + def test_greater_than_int__returns_true__when_user_value_greater_than_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -685,7 +685,7 @@ def test_greater_than_int__returns_true__when_user_value_greater_than_condition_ self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_float__returns_true__when_user_value_greater_than_condition_value(self, ): + def test_greater_than_float__returns_true__when_user_value_greater_than_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger @@ -706,7 +706,7 @@ def test_greater_than_float__returns_true__when_user_value_greater_than_conditio self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_int__returns_false__when_user_value_not_greater_than_condition_value(self, ): + def test_greater_than_int__returns_false__when_user_value_not_greater_than_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -727,7 +727,7 @@ def test_greater_than_int__returns_false__when_user_value_not_greater_than_condi self.assertStrictFalse(evaluator.evaluate(0)) - def test_greater_than_float__returns_false__when_user_value_not_greater_than_condition_value(self, ): + def test_greater_than_float__returns_false__when_user_value_not_greater_than_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger @@ -792,7 +792,7 @@ def test_greater_than_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_greater_than_or_equal_int__returns_true__when_user_value_greater_than_or_equal_condition_value(self, ): + def test_greater_than_or_equal_int__returns_true__when_user_value_greater_than_or_equal_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( ge_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -819,7 +819,7 @@ def test_greater_than_or_equal_int__returns_true__when_user_value_greater_than_o self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_or_equal_float__returns_true__when_user_value_greater_than_or_equal_condition_value(self, ): + def test_greater_than_or_equal_float__returns_true__when_user_value_greater_than_or_equal_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( ge_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger @@ -847,7 +847,7 @@ def test_greater_than_or_equal_float__returns_true__when_user_value_greater_than self.assertStrictTrue(evaluator.evaluate(0)) def test_greater_than_or_equal_int__returns_false__when_user_value_not_greater_than_or_equal_condition_value( - self, ): + self): evaluator = condition_helper.CustomAttributeConditionEvaluator( ge_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -869,7 +869,7 @@ def test_greater_than_or_equal_int__returns_false__when_user_value_not_greater_t self.assertStrictFalse(evaluator.evaluate(0)) def test_greater_than_or_equal_float__returns_false__when_user_value_not_greater_than_or_equal_condition_value( - self, ): + self): evaluator = condition_helper.CustomAttributeConditionEvaluator( ge_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -934,7 +934,7 @@ def test_greater_than_or_equal_float__returns_null__when_no_user_provided_value( self.assertIsNone(evaluator.evaluate(0)) - def test_less_than_int__returns_true__when_user_value_less_than_condition_value(self, ): + def test_less_than_int__returns_true__when_user_value_less_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -955,7 +955,7 @@ def test_less_than_int__returns_true__when_user_value_less_than_condition_value( self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self, ): + def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -976,7 +976,7 @@ def test_less_than_float__returns_true__when_user_value_less_than_condition_valu self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_int__returns_false__when_user_value_not_less_than_condition_value(self, ): + def test_less_than_int__returns_false__when_user_value_not_less_than_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -997,7 +997,7 @@ def test_less_than_int__returns_false__when_user_value_not_less_than_condition_v self.assertStrictFalse(evaluator.evaluate(0)) - def test_less_than_float__returns_false__when_user_value_not_less_than_condition_value(self, ): + def test_less_than_float__returns_false__when_user_value_not_less_than_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger @@ -1050,7 +1050,7 @@ def test_less_than_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self, ): + def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -1083,7 +1083,7 @@ def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equa self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self, ): + def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -1110,7 +1110,7 @@ def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_eq self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self, ): + def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -1131,7 +1131,7 @@ def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or self.assertStrictFalse(evaluator.evaluate(0)) - def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self, ): + def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger @@ -1198,8 +1198,7 @@ def is_finite_number__rejecting_condition_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', - side_effect=is_finite_number__rejecting_condition_value, + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__rejecting_condition_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1212,8 +1211,8 @@ def is_finite_number__rejecting_user_attribute_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', - side_effect=is_finite_number__rejecting_user_attribute_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_user_attribute_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1224,7 +1223,7 @@ def is_finite_number__accepting_both_values(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, ): self.assertTrue(evaluator.evaluate(0)) @@ -1242,8 +1241,7 @@ def is_finite_number__rejecting_condition_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', - side_effect=is_finite_number__rejecting_condition_value, + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__rejecting_condition_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1256,8 +1254,8 @@ def is_finite_number__rejecting_user_attribute_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', - side_effect=is_finite_number__rejecting_user_attribute_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_user_attribute_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) From 9256e6a95d9106a94e18c201abda904a6788e30c Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Thu, 27 Aug 2020 18:09:58 +0500 Subject: [PATCH 19/34] additional removal --- optimizely/helpers/condition.py | 72 +++++++++++++-------------- tests/helpers_tests/test_condition.py | 4 +- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index e95dc294..f8d7c48c 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -55,12 +55,12 @@ def __init__(self, condition_data, attributes, logger): def _get_condition_json(self, index): """ Method to generate json for logging audience condition. - Args: - index: Index of the condition. + Args: + index: Index of the condition. - Returns: - String: Audience condition JSON. - """ + Returns: + String: Audience condition JSON. + """ condition = self.condition_data[index] condition_log = { 'name': condition[0], @@ -74,12 +74,12 @@ def _get_condition_json(self, index): def is_value_type_valid_for_exact_conditions(self, value): """ Method to validate if the value is valid for exact match type evaluation. - Args: - value: Value to validate. + Args: + value: Value to validate. - Returns: - Boolean: True if value is a string, boolean, or number. Otherwise False. - """ + Returns: + Boolean: True if value is a string, boolean, or number. Otherwise False. + """ # No need to check for bool since bool is a subclass of int if isinstance(value, string_types) or isinstance(value, (numbers.Integral, float)): return True @@ -194,29 +194,29 @@ def compare_user_version_with_target_version(self, index): def exact_evaluator(self, index): """ Evaluate the given exact match condition for the user attributes. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user attribute value is equal (===) to the condition value. - - False if the user attribute value is not equal (!==) to the condition value. - None: - - if the condition value or user attribute value has an invalid type. - - if there is a mismatch between the user attribute type and the condition value type. - """ + Returns: + Boolean: + - True if the user attribute value is equal (===) to the condition value. + - False if the user attribute value is not equal (!==) to the condition value. + None: + - if the condition value or user attribute value has an invalid type. + - if there is a mismatch between the user attribute type and the condition value type. + """ condition_name = self.condition_data[index][0] condition_value = self.condition_data[index][1] user_value = self.attributes.get(condition_name) if not self.is_value_type_valid_for_exact_conditions(condition_value) or ( - self.is_value_a_number(condition_value) and not validator.is_finite_number(condition_value) + self.is_value_a_number(condition_value) and not validator.is_finite_number(condition_value) ): self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index))) return None if not self.is_value_type_valid_for_exact_conditions(user_value) or not validator.are_values_same_type( - condition_value, user_value + condition_value, user_value ): self.logger.warning( audience_logs.UNEXPECTED_TYPE.format(self._get_condition_json(index), type(user_value), condition_name) @@ -234,28 +234,28 @@ def exact_evaluator(self, index): def exists_evaluator(self, index): """ Evaluate the given exists match condition for the user attributes. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: True if the user attributes have a non-null value for the given condition, - otherwise False. - """ + Returns: + Boolean: True if the user attributes have a non-null value for the given condition, + otherwise False. + """ attr_name = self.condition_data[index][0] return self.attributes.get(attr_name) is not None def greater_than_evaluator(self, index): """ Evaluate the given greater than match condition for the user attributes. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user attribute value is greater than the condition value. - - False if the user attribute value is less than or equal to the condition value. - None: if the condition value isn't finite or the user attribute value isn't finite. - """ + Returns: + Boolean: + - True if the user attribute value is greater than the condition value. + - False if the user attribute value is less than or equal to the condition value. + None: if the condition value isn't finite or the user attribute value isn't finite. + """ condition_name = self.condition_data[index][0] condition_value = self.condition_data[index][1] user_value = self.attributes.get(condition_name) diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 56086735..192a872b 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -492,7 +492,7 @@ def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_float__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): + def test_exact_float__returns_true__when_user_provided_value_is_equal_to_condition_value(self,): if PY2: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -955,7 +955,7 @@ def test_less_than_int__returns_true__when_user_value_less_than_condition_value( self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self): + def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self,): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger From 15fa71b8af5354308ed80135d3d7ad4d3c7b0155 Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Thu, 27 Aug 2020 18:16:26 +0500 Subject: [PATCH 20/34] further removal --- optimizely/helpers/condition.py | 34 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index f8d7c48c..3321f9f5 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -315,15 +315,15 @@ def greater_than_or_equal_evaluator(self, index): def less_than_evaluator(self, index): """ Evaluate the given less than match condition for the user attributes. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the user attribute value is less than the condition value. - - False if the user attribute value is greater than or equal to the condition value. - None: if the condition value isn't finite or the user attribute value isn't finite. - """ + Returns: + Boolean: + - True if the user attribute value is less than the condition value. + - False if the user attribute value is greater than or equal to the condition value. + None: if the condition value isn't finite or the user attribute value isn't finite. + """ condition_name = self.condition_data[index][0] condition_value = self.condition_data[index][1] user_value = self.attributes.get(condition_name) @@ -383,21 +383,21 @@ def less_than_or_equal_evaluator(self, index): def substring_evaluator(self, index): """ Evaluate the given substring match condition for the given user attributes. - Args: - index: Index of the condition to be evaluated. + Args: + index: Index of the condition to be evaluated. - Returns: - Boolean: - - True if the condition value is a substring of the user attribute value. - - False if the condition value is not a substring of the user attribute value. - None: if the condition value isn't a string or the user attribute value isn't a string. - """ + Returns: + Boolean: + - True if the condition value is a substring of the user attribute value. + - False if the condition value is not a substring of the user attribute value. + None: if the condition value isn't a string or the user attribute value isn't a string. + """ condition_name = self.condition_data[index][0] condition_value = self.condition_data[index][1] user_value = self.attributes.get(condition_name) if not isinstance(condition_value, string_types): - self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index), )) + self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index),)) return None if not isinstance(user_value, string_types): From 8414a9239f593a94d02101de0e3137d21c263088 Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Fri, 28 Aug 2020 20:34:56 +0500 Subject: [PATCH 21/34] address most comments --- optimizely/helpers/condition.py | 45 +++++++++++++++------------ optimizely/helpers/enums.py | 2 +- tests/helpers_tests/test_condition.py | 20 +++++------- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 3321f9f5..ab7da02c 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -18,7 +18,8 @@ from . import validator from .enums import CommonAudienceEvaluationLogs as audience_logs -from .enums import Errors, SemverType +from .enums import Errors +from .enums import VersionType class ConditionOperatorTypes(object): @@ -92,44 +93,48 @@ def is_value_a_number(self, value): return False - def is_pre_release(self, target): - """ Method to check if the given version contains "-" + def is_pre_release(self, version): + """ Method to check if given version is pre-release. + Criteria for pre-release includes: + - Version includes "-" Args: - target: Given version in string. + version: Given version in string. Returns: Boolean: - - True if the given version does contain "-" + - True if the given version is pre-release - False if it doesn't """ - return SemverType.IS_PRE_RELEASE in target + return VersionType.IS_PRE_RELEASE in version - def is_build(self, target): - """ Method to check if the given version contains "+" + def is_build(self, version): + """ Method to check given version is a build version. + Criteria for build version includes: + - Version includes "+" Args: - target: Given version in string. + version: Given version in string. Returns: Boolean: - - True if the given version does contain "+" + - True if the given version is a build version - False if it doesn't """ - return SemverType.IS_BUILD in target + return VersionType.IS_BUILD in version - def has_white_space(self, target): + def has_white_space(self, version): """ Method to check if the given version contains " " (white space) Args: - target: Given version in string. + version: Given version in string. Returns: Boolean: - - True if the given version does contain " " + - True if the given version does contain whitespace - False if it doesn't """ - return SemverType.HAS_WHITE_SPACE in target + return VersionType.HAS_WHITE_SPACE in version def compare_user_version_with_target_version(self, index): """ Method to compare user version with target version. @@ -162,11 +167,11 @@ def compare_user_version_with_target_version(self, index): ) return None - target_version_parts = self.split_semantic_version(target_version) + target_version_parts = self.split_version(target_version) if target_version_parts is None: return None - user_version_parts = self.split_semantic_version(user_version) + user_version_parts = self.split_version(user_version) if user_version_parts is None: return None @@ -518,7 +523,7 @@ def semver_greater_than_or_equal_evaluator(self, index): ConditionMatchTypes.SUBSTRING: substring_evaluator } - def split_semantic_version(self, target): + def split_version(self, target): """ Method to split the given version. Args: @@ -542,9 +547,9 @@ def split_semantic_version(self, target): # check for pre release e.g. 1.0.0-alpha where 'alpha' is a pre release # otherwise check for build e.g. 1.0.0+001 where 001 is a build metadata if self.is_pre_release(target): - target_parts = target.split(SemverType.IS_PRE_RELEASE) + target_parts = target.split(VersionType.IS_PRE_RELEASE) elif self.is_build(target): - target_parts = target.split(SemverType.IS_BUILD) + target_parts = target.split(VersionType.IS_BUILD) # split target version into prefix and suffix if target_parts: diff --git a/optimizely/helpers/enums.py b/optimizely/helpers/enums.py index 44dfe030..c8c44e2f 100644 --- a/optimizely/helpers/enums.py +++ b/optimizely/helpers/enums.py @@ -159,7 +159,7 @@ class NotificationTypes(object): LOG_EVENT = 'LOG_EVENT:log_event' -class SemverType(object): +class VersionType(object): IS_PRE_RELEASE = '-' HAS_WHITE_SPACE = ' ' IS_BUILD = '+' diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 192a872b..3a627011 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -54,7 +54,7 @@ le_float_condition_list = [['meters_travelled', 48.2, 'custom_attribute', 'le']] -class CustomAttributeConditionEvaluator(base.BaseTest): +class CustomAttributeConditionEvaluatorTest(base.BaseTest): def setUp(self): base.BaseTest.setUp(self) self.condition_list = [ @@ -191,14 +191,6 @@ def test_evaluate__returns_true__when_user_version_1_9_9_is_less_than_target_ver self.assertStrictTrue(evaluator.evaluate(0)) - def test_evaluate__returns_true__when_user_version_1_9_0_beta_is_less_than_target_version_2_0_0(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_2_0_0_condition_list, {'Android': '1.9.0-beta'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - def test_evaluate__returns_true__when_user_version_2_0_0_release_is_less_than_target_version_2_0_0(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -2020,10 +2012,12 @@ def test_substring__condition_value_invalid(self): def test_invalid_semver__returns_None__when_semver_is_invalid(self): invalid_test_cases = ["-", ".", "..", "+", "+test", " ", "2 .0. 0", "2.", ".0.0", "1.2.2.2", "2.x", ",", - "+build-prerelese"] + "+build-prerelease", "2..0", "2.2.0+build-prerelease"] - for invalid_test_case in invalid_test_cases: + for user_version in invalid_test_cases: evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_or_equal_2_0_1_condition_list, {'Android': invalid_test_case}, self.mock_client_logger) + semver_less_than_or_equal_2_0_1_condition_list, {'Android': user_version}, self.mock_client_logger) - self.assertIsNone(evaluator.evaluate(0)) + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertIsNone(result, custom_err_msg) From 78ce5242ea3728a009351e23ba6ddbf7298c92e8 Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Fri, 28 Aug 2020 21:05:08 +0500 Subject: [PATCH 22/34] reorganize --- optimizely/helpers/condition.py | 133 +++++++++++++++++++------- tests/helpers_tests/test_condition.py | 26 ++--- 2 files changed, 113 insertions(+), 46 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index ab7da02c..f7ea6445 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -136,11 +136,12 @@ def has_white_space(self, version): """ return VersionType.HAS_WHITE_SPACE in version - def compare_user_version_with_target_version(self, index): + def compare_user_version_with_target_version(self, target_version, user_version): """ Method to compare user version with target version. Args: - index: Index of the condition to be evaluated. + target_version: String representing condition value + user_version: String representing user value Returns: Int: @@ -149,23 +150,8 @@ def compare_user_version_with_target_version(self, index): - -1 if user version is less than target version or, in case of exact string match, doesn't match the target version. None: - - if the user version value is not string type or is null. + - if the user version value format is not a valid semantic version. """ - condition_name = self.condition_data[index][0] - target_version = self.condition_data[index][1] - user_version = self.attributes.get(condition_name) - - if not isinstance(target_version, string_types): - self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index), )) - return None - - if not isinstance(user_version, string_types): - self.logger.warning( - audience_logs.UNEXPECTED_TYPE.format( - self._get_condition_json(index), type(user_version), condition_name - ) - ) - return None target_version_parts = self.split_version(target_version) if target_version_parts is None: @@ -426,7 +412,24 @@ def semver_equal_evaluator(self, index): None: - if the user version value is not string type or is null. """ - result = self.compare_user_version_with_target_version(index) + + condition_name = self.condition_data[index][0] + target_version = self.condition_data[index][1] + user_version = self.attributes.get(condition_name) + + if not isinstance(target_version, string_types): + self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index), )) + return None + + if not isinstance(user_version, string_types): + self.logger.warning( + audience_logs.UNEXPECTED_TYPE.format( + self._get_condition_json(index), type(user_version), condition_name + ) + ) + return None + + result = self.compare_user_version_with_target_version(target_version, user_version) if result is None: return None @@ -445,7 +448,23 @@ def semver_greater_than_evaluator(self, index): None: - if the user version value is not string type or is null. """ - result = self.compare_user_version_with_target_version(index) + condition_name = self.condition_data[index][0] + target_version = self.condition_data[index][1] + user_version = self.attributes.get(condition_name) + + if not isinstance(target_version, string_types): + self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index), )) + return None + + if not isinstance(user_version, string_types): + self.logger.warning( + audience_logs.UNEXPECTED_TYPE.format( + self._get_condition_json(index), type(user_version), condition_name + ) + ) + return None + + result = self.compare_user_version_with_target_version(target_version, user_version) if result is None: return None @@ -464,7 +483,23 @@ def semver_less_than_evaluator(self, index): None: - if the user version value is not string type or is null. """ - result = self.compare_user_version_with_target_version(index) + condition_name = self.condition_data[index][0] + target_version = self.condition_data[index][1] + user_version = self.attributes.get(condition_name) + + if not isinstance(target_version, string_types): + self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index), )) + return None + + if not isinstance(user_version, string_types): + self.logger.warning( + audience_logs.UNEXPECTED_TYPE.format( + self._get_condition_json(index), type(user_version), condition_name + ) + ) + return None + + result = self.compare_user_version_with_target_version(target_version, user_version) if result is None: return None @@ -483,7 +518,23 @@ def semver_less_than_or_equal_evaluator(self, index): None: - if the user version value is not string type or is null. """ - result = self.compare_user_version_with_target_version(index) + condition_name = self.condition_data[index][0] + target_version = self.condition_data[index][1] + user_version = self.attributes.get(condition_name) + + if not isinstance(target_version, string_types): + self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index), )) + return None + + if not isinstance(user_version, string_types): + self.logger.warning( + audience_logs.UNEXPECTED_TYPE.format( + self._get_condition_json(index), type(user_version), condition_name + ) + ) + return None + + result = self.compare_user_version_with_target_version(target_version, user_version) if result is None: return None @@ -502,7 +553,23 @@ def semver_greater_than_or_equal_evaluator(self, index): None: - if the user version value is not string type or is null. """ - result = self.compare_user_version_with_target_version(index) + condition_name = self.condition_data[index][0] + target_version = self.condition_data[index][1] + user_version = self.attributes.get(condition_name) + + if not isinstance(target_version, string_types): + self.logger.warning(audience_logs.UNKNOWN_CONDITION_VALUE.format(self._get_condition_json(index), )) + return None + + if not isinstance(user_version, string_types): + self.logger.warning( + audience_logs.UNEXPECTED_TYPE.format( + self._get_condition_json(index), type(user_version), condition_name + ) + ) + return None + + result = self.compare_user_version_with_target_version(target_version, user_version) if result is None: return None @@ -523,11 +590,11 @@ def semver_greater_than_or_equal_evaluator(self, index): ConditionMatchTypes.SUBSTRING: substring_evaluator } - def split_version(self, target): + def split_version(self, version): """ Method to split the given version. Args: - target: Given version. + version: Given version. Returns: List: @@ -535,23 +602,23 @@ def split_version(self, target): None: - if the given version is invalid in format """ - target_prefix = target + target_prefix = version target_suffix = "" target_parts = [] - # check that target shouldn't have white space - if self.has_white_space(target): + # check that version shouldn't have white space + if self.has_white_space(version): self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) return None # check for pre release e.g. 1.0.0-alpha where 'alpha' is a pre release # otherwise check for build e.g. 1.0.0+001 where 001 is a build metadata - if self.is_pre_release(target): - target_parts = target.split(VersionType.IS_PRE_RELEASE) - elif self.is_build(target): - target_parts = target.split(VersionType.IS_BUILD) + if self.is_pre_release(version): + target_parts = version.split(VersionType.IS_PRE_RELEASE) + elif self.is_build(version): + target_parts = version.split(VersionType.IS_BUILD) - # split target version into prefix and suffix + # split version into prefix and suffix if target_parts: if len(target_parts) < 1: self.logger.warning(Errors.INVALID_ATTRIBUTE_FORMAT) diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 3a627011..7a7cf691 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -1350,6 +1350,19 @@ def is_finite_number__accepting_both_values(value): ): self.assertTrue(evaluator.evaluate(0)) + def test_invalid_semver__returns_None__when_semver_is_invalid(self): + invalid_test_cases = ["-", ".", "..", "+", "+test", " ", "2 .0. 0", + "2.", ".0.0", "1.2.2.2", "2.x", ",", + "+build-prerelease", "2..0", "2.2.0+build-prerelease"] + + for user_version in invalid_test_cases: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_1_condition_list, {'Android': user_version}, self.mock_client_logger) + + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertIsNone(result, custom_err_msg) + class ConditionDecoderTests(base.BaseTest): def test_loads(self): @@ -2008,16 +2021,3 @@ def test_substring__condition_value_invalid(self): 'newer release of the Optimizely SDK.' ).format(json.dumps(expected_condition_log)) ) - - def test_invalid_semver__returns_None__when_semver_is_invalid(self): - invalid_test_cases = ["-", ".", "..", "+", "+test", " ", "2 .0. 0", - "2.", ".0.0", "1.2.2.2", "2.x", ",", - "+build-prerelease", "2..0", "2.2.0+build-prerelease"] - - for user_version in invalid_test_cases: - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_or_equal_2_0_1_condition_list, {'Android': user_version}, self.mock_client_logger) - - result = evaluator.evaluate(0) - custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) - self.assertIsNone(result, custom_err_msg) From aa1fbafa38de96ea724e4137fce085d9c03b3870 Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Mon, 31 Aug 2020 21:05:32 +0500 Subject: [PATCH 23/34] tests: revised all unit tests --- tests/helpers_tests/test_condition.py | 512 +++++++++++--------------- 1 file changed, 221 insertions(+), 291 deletions(-) diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 7a7cf691..f5c15006 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -24,20 +24,6 @@ integerCondition = ['num_users', 10, 'custom_attribute', 'exact'] doubleCondition = ['pi_value', 3.14, 'custom_attribute', 'exact'] -semver_equal_2_0_0_condition_list = [['Android', "2.0.0", 'custom_attribute', 'semver_eq']] -semver_equal_2_condition_list = [['Android', "2", 'custom_attribute', 'semver_eq']] -semver_equal_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_eq']] -semver_equal_2_0_1_beta_condition_list = [['Android', "2.0.1-beta", 'custom_attribute', 'semver_eq']] -semver_greater_than_2_0_0_condition_list = [['Android', "2.0.0", 'custom_attribute', 'semver_gt']] -semver_greater_than_2_0_0_beta_condition_list = [['Android', "2.0.0-beta", 'custom_attribute', 'semver_gt']] -semver_greater_than_or_equal_2_0_9_beta_condition_list = [['Android', "2.0.9-beta", 'custom_attribute', 'semver_ge']] -semver_greater_than_or_equal_2_0_9_condition_list = [['Android', "2.0.9", 'custom_attribute', 'semver_ge']] -semver_less_than_2_0_0_condition_list = [['Android', "2.0.0", 'custom_attribute', 'semver_lt']] -semver_less_than_2_0_0_release_condition_list = [['Android', "2.0.0-release", 'custom_attribute', 'semver_lt']] -semver_less_than_2_0_0_beta_condition_list = [['Android', "2.0.0-beta", 'custom_attribute', 'semver_lt']] -semver_less_than_or_equal_2_0_1_beta_condition_list = [['Android', "2.0.1-beta", 'custom_attribute', 'semver_le']] -semver_less_than_or_equal_2_0_1_condition_list = [['Android', "2.0.1", 'custom_attribute', 'semver_le']] - exists_condition_list = [['input_value', None, 'custom_attribute', 'exists']] exact_string_condition_list = [['favorite_constellation', 'Lacerta', 'custom_attribute', 'exact']] exact_int_condition_list = [['lasers_count', 9000, 'custom_attribute', 'exact']] @@ -126,264 +112,205 @@ def test_evaluate__returns_null__when_condition_has_an_invalid_type_property(sel self.assertIsNone(evaluator.evaluate(0)) - def test_evaluate__returns_true__when_user_version_2_matches_target_version_2(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_condition_list, {'Android': '2'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_2_2_matches_target_version_2(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_condition_list, {'Android': '2.2'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_2_0_matches_target_version_2_0(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': '2.0'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_2_0_0_matches_target_version_2_0_0(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_0_condition_list, {'Android': '2.0.0'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_false__when_user_version_2_0_does_not_match_target_version_2_0(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_0_condition_list, {'Android': '2.0'}, self.mock_client_logger - ) - - self.assertStrictFalse(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_2_0_0_release_is_greater_than_target_version_2_0_0_beta( - self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_greater_than_2_0_0_beta_condition_list, {'Android': '2.0.0-release'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_2_0_1_is_greater_than_target_version__2_0_0(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_greater_than_2_0_0_condition_list, {'Android': '2.0.1'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_1_9_9_is_less_than_target_version_2_0_0(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_2_0_0_condition_list, {'Android': '1.9.9'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_2_0_0_release_is_less_than_target_version_2_0_0(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_2_0_0_condition_list, {'Android': '2.0.0-release'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_2_0_0_beta_is_less_than_target_version_2_0_0_release(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_2_0_0_release_condition_list, {'Android': '2.0.0-beta'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_false__when_user_version_2_0_0_release_is_not_less_than_target_version_2_0_0_beta(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_2_0_0_beta_condition_list, {'Android': '2.0.0-release'}, self.mock_client_logger - ) - - self.assertStrictFalse(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_2_0_9_is_greater_than_or_equal_to_target_version_2_0_9(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_greater_than_or_equal_2_0_9_condition_list, {'Android': '2.0.9'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_2_3_is_greater_than_or_equal_to_target_version_2_0_9(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_greater_than_or_equal_2_0_9_condition_list, {'Android': '2.3'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_2_0_9_beta_is_greater_than_or_equal_to_target_version_2_0_9_beta( - self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_greater_than_or_equal_2_0_9_beta_condition_list, {'Android': '2.0.9-beta'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_false__when_user_version_1_0_0_is_not_greater_than_or_equal_to_target_version_2_0_9( - self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_greater_than_or_equal_2_0_9_condition_list, {'Android': '1.0.0'}, self.mock_client_logger - ) - - self.assertStrictFalse(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_2_0_1_is_less_than_or_equal_to_target_version_2_0_1(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_or_equal_2_0_1_condition_list, {'Android': '2.0.1'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_1_1_is_less_than_or_equal_to_target_version_2_0_1(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_or_equal_2_0_1_condition_list, {'Android': '1.1'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_true__when_user_version_2_0_1_beta_is_less_than_or_equal_to_target_version_2_0_1(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_or_equal_2_0_1_condition_list, {'Android': '2.0.1-beta'}, self.mock_client_logger - ) - - self.assertStrictTrue(evaluator.evaluate(0)) - - def test_evaluate__returns_false__when_user_version_3_0_1_is_not_less_than_or_equal_to_target_version_2_0_1(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_less_than_or_equal_2_0_1_condition_list, {'Android': '3.0.1'}, self.mock_client_logger - ) - - self.assertStrictFalse(evaluator.evaluate(0)) - - def test_evaluate__returns_null__when_no_user_version_provided(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {}, self.mock_client_logger - ) - - self.assertIsNone(evaluator.evaluate(0)) - - def test_evaluate__returns_null__when_user_provided_version_is_null(self): - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': None}, self.mock_client_logger - ) - - self.assertIsNone(evaluator.evaluate(0)) - - def test_evaluate__returns_exception__when_user_provided_version_is_invalid1(self): - - condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': "+"}, self.mock_client_logger - ) - - self.assertRaises(Exception) - - def test_evaluate__returns_exception__when_user_provided_version_is_invalid2(self): - - condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': "+--"}, self.mock_client_logger - ) - - self.assertRaises(Exception) - - def test_evaluate__returns_exception__when_user_provided_version_is_invalid3(self): - - condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': "...+"}, self.mock_client_logger - ) - - self.assertRaises(Exception) - - def test_evaluate__returns_exception__when_user_provided_version_is_invalid4(self): - - condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': "+test"}, self.mock_client_logger - ) - - self.assertRaises(Exception) - - def test_evaluate__returns_exception__when_user_provided_version_is_invalid5(self): - - condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': "3.6"}, self.mock_client_logger - ) - - self.assertRaises(Exception) - - def test_evaluate__returns_exception__when_user_provided_version_is_invalid6(self): - - condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': "2"}, self.mock_client_logger - ) - - self.assertRaises(Exception) - - def test_evaluate__returns_exception__when_user_provided_version_is_invalid7(self): - - condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': "3.90"}, self.mock_client_logger - ) + def test_semver_eq__returns_true(self): + semver_equal_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_eq']] + user_versions = ['2.0.0', '2.0'] + for user_version in user_versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertTrue(result, custom_err_msg) - self.assertRaises(Exception) + def test_semver_eq__returns_false(self): + semver_equal_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_eq']] + user_versions = ['2.9', '1.9'] + for user_version in user_versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_equal_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertFalse(result, custom_err_msg) - def test_evaluate__returns_exception__when_user_provided_version_is_invalid8(self): + def test_semver_le__returns_true(self): + semver_less_than_or_equal_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_le']] + user_versions = ['2.0.0', '1.9'] + for user_version in user_versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertTrue(result, custom_err_msg) - condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': "3.90.2.8"}, self.mock_client_logger - ) + def test_semver_le__returns_false(self): + semver_less_than_or_equal_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_le']] + user_versions = ['2.5.1'] + for user_version in user_versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_or_equal_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertFalse(result, custom_err_msg) - self.assertRaises(Exception) + def test_semver_ge__returns_true(self): + semver_greater_than_or_equal_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_ge']] + user_versions = ['2.0.0', '2.9'] + for user_version in user_versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_or_equal_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertTrue(result, custom_err_msg) - def test_evaluate__returns_exception__when_user_provided_version_is_invalid9(self): + def test_semver_ge__returns_false(self): + semver_greater_than_or_equal_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_ge']] + user_versions = ['1.9'] + for user_version in user_versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_or_equal_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertFalse(result, custom_err_msg) - condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': "-2.4"}, self.mock_client_logger - ) + def test_semver_lt__returns_true(self): + semver_less_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_lt']] + user_versions = ['1.9'] + for user_version in user_versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertTrue(result, custom_err_msg) - self.assertRaises(Exception) + def test_semver_lt__returns_false(self): + semver_less_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_lt']] + user_versions = ['2.0.0', '2.5.1'] + for user_version in user_versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_less_than_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertFalse(result, custom_err_msg) - def test_evaluate__returns_exception__when_user_provided_version_is_invalid10(self): + def test_semver_gt__returns_true(self): + semver_greater_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_gt']] + user_versions = ['2.9'] + for user_version in user_versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertTrue(result, custom_err_msg) - condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': True}, self.mock_client_logger - ) + def test_semver_gt__returns_false(self): + semver_greater_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_gt']] + user_versions = ['2.0.0', '1.9'] + for user_version in user_versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertFalse(result, custom_err_msg) - self.assertRaises(Exception) + def test_evaluate__returns_None__when_user_version_is_not_string(self): + semver_greater_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_gt']] + user_versions = [True, 37] + for user_version in user_versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertIsNone(result, custom_err_msg) - def test_evaluate__returns_exception__when_user_provided_version_is_invalid11(self): + def test_evaluate__returns_None__when_user_version_with_invalid_semantic(self): + semver_greater_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_gt']] + user_versions = ['3.7.2.2', '+'] + for user_version in user_versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.evaluate(0) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertIsNone(result, custom_err_msg) - condition_helper.CustomAttributeConditionEvaluator( - semver_equal_2_0_condition_list, {'Android': False}, self.mock_client_logger - ) + def test_compare_user_version_with_target_version_equal_to_0(self): + semver_greater_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_gt']] + versions = [ + ('2.0.1', '2.0.1'), + ('2.0.0', '2.0.0'), + ('2.9', '2.9.1'), + ('2.9.9', '2.9.9'), + ('2.9.9-beta', '2.9.9-beta'), + ('2.1', '2.1.0'), + ('2.1', '2.1.215'), + ('2', '2.12'), + ('2', '2.785.13') + ] + for target_version, user_version in versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.compare_user_version_with_target_version(target_version, user_version) + custom_err_msg = "Got {} in result. Failed for user version:" \ + " {} and target version: {}".format(result, + user_version, + target_version + ) + self.assertEqual(result, 0, custom_err_msg) + + def test_compare_user_version_with_target_version_greater_than_0(self): + semver_greater_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_gt']] + versions = [ + ('2.0.0', '2.0.1'), + ('2.0', '3.0.1'), + ('2.0', '2.9.1'), + ('2.9.0', '2.9.1'), + ('2.1.2', '2.1.3-beta'), + ('2.1.2-beta', '2.1.2-release'), + ('2.1.3-beta', '2.1.3') + ] + for target_version, user_version in versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.compare_user_version_with_target_version(target_version, user_version) + custom_err_msg = "Got {} in result. Failed for user version:" \ + " {} and target version: {}".format(result, + user_version, + target_version) + self.assertEqual(result, 1, custom_err_msg) + + def test_compare_user_version_with_target_version_less_than_0(self): + semver_greater_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_gt']] + versions = [ + ('2.0.1', '2.0.0'), + ('3.0', '2.0.1'), + ('2.3', '2.0.1'), + ('2.3.5', '2.3.1'), + ('2.9.8', '2.9'), + ('2.1.2-release', '2.1.2-beta'), + ('2.1.3', '2.1.3-beta') + ] + for target_version, user_version in versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.compare_user_version_with_target_version(target_version, user_version) + custom_err_msg = "Got {} in result. Failed for user version: {} " \ + "and target version: {}".format(result, + user_version, + target_version) + self.assertEquals(result, -1, custom_err_msg) + + def test_compare_invalid_user_version_with(self): + semver_greater_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_gt']] + versions = [ + '-', '.', '..', '+', '+test', ' ', '2 .3. 0', '2.', '.2.2', '3.7.2.2', '3.x', ',', '+build-prerelease' + ] + target_version = '2.1.0' - self.assertRaises(Exception) + for user_version in versions: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + semver_greater_than_2_0_condition_list, {'Android': user_version}, self.mock_client_logger) + result = evaluator.compare_user_version_with_target_version(user_version, target_version) + custom_err_msg = "Got {} in result. Failed for user version: {}".format(result, user_version) + self.assertIsNone(result, custom_err_msg) def test_exists__returns_false__when_no_user_provided_value(self): @@ -431,7 +358,7 @@ def test_exists__returns_true__when_user_provided_value_is_boolean(self): self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condition_value(self,): + def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': 'Lacerta'}, self.mock_client_logger, @@ -439,7 +366,7 @@ def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condit self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self,): + def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': 'The Big Dipper'}, self.mock_client_logger, @@ -447,7 +374,7 @@ def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_c self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_string__returns_null__when_user_provided_value_is_different_type_from_condition_value(self,): + def test_exact_string__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': False}, self.mock_client_logger, @@ -463,7 +390,7 @@ def test_exact_string__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition_value(self,): + def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): if PY2: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -484,7 +411,7 @@ def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_float__returns_true__when_user_provided_value_is_equal_to_condition_value(self,): + def test_exact_float__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): if PY2: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -505,7 +432,7 @@ def test_exact_float__returns_true__when_user_provided_value_is_equal_to_conditi self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self,): + def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_int_condition_list, {'lasers_count': 8000}, self.mock_client_logger @@ -513,7 +440,7 @@ def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_cond self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self,): + def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_float_condition_list, {'lasers_count': 8000.0}, self.mock_client_logger, @@ -521,7 +448,7 @@ def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_co self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_int__returns_null__when_user_provided_value_is_different_type_from_condition_value(self,): + def test_exact_int__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_int_condition_list, {'lasers_count': 'hi'}, self.mock_client_logger @@ -535,7 +462,7 @@ def test_exact_int__returns_null__when_user_provided_value_is_different_type_fro self.assertIsNone(evaluator.evaluate(0)) - def test_exact_float__returns_null__when_user_provided_value_is_different_type_from_condition_value(self,): + def test_exact_float__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_float_condition_list, {'lasers_count': 'hi'}, self.mock_client_logger @@ -592,7 +519,7 @@ def test_exact__given_number_values__calls_is_finite_number(self): mock_is_finite.assert_has_calls([mock.call(9000), mock.call(9000)]) - def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_condition_value(self,): + def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': False}, self.mock_client_logger, @@ -600,7 +527,7 @@ def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_conditio self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self,): + def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': True}, self.mock_client_logger, @@ -608,7 +535,7 @@ def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_con self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_bool__returns_null__when_user_provided_value_is_different_type_from_condition_value(self,): + def test_exact_bool__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': 0}, self.mock_client_logger @@ -624,7 +551,7 @@ def test_exact_bool__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_substring__returns_true__when_condition_value_is_substring_of_user_value(self,): + def test_substring__returns_true__when_condition_value_is_substring_of_user_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( substring_condition_list, {'headline_text': 'Limited time, buy now!'}, self.mock_client_logger, @@ -632,7 +559,7 @@ def test_substring__returns_true__when_condition_value_is_substring_of_user_valu self.assertStrictTrue(evaluator.evaluate(0)) - def test_substring__returns_false__when_condition_value_is_not_a_substring_of_user_value(self,): + def test_substring__returns_false__when_condition_value_is_not_a_substring_of_user_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( substring_condition_list, {'headline_text': 'Breaking news!'}, self.mock_client_logger, @@ -656,7 +583,7 @@ def test_substring__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_greater_than_int__returns_true__when_user_value_greater_than_condition_value(self,): + def test_greater_than_int__returns_true__when_user_value_greater_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -677,7 +604,7 @@ def test_greater_than_int__returns_true__when_user_value_greater_than_condition_ self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_float__returns_true__when_user_value_greater_than_condition_value(self,): + def test_greater_than_float__returns_true__when_user_value_greater_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger @@ -698,7 +625,7 @@ def test_greater_than_float__returns_true__when_user_value_greater_than_conditio self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_int__returns_false__when_user_value_not_greater_than_condition_value(self,): + def test_greater_than_int__returns_false__when_user_value_not_greater_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -719,7 +646,7 @@ def test_greater_than_int__returns_false__when_user_value_not_greater_than_condi self.assertStrictFalse(evaluator.evaluate(0)) - def test_greater_than_float__returns_false__when_user_value_not_greater_than_condition_value(self,): + def test_greater_than_float__returns_false__when_user_value_not_greater_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger @@ -947,7 +874,7 @@ def test_less_than_int__returns_true__when_user_value_less_than_condition_value( self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self,): + def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -968,7 +895,7 @@ def test_less_than_float__returns_true__when_user_value_less_than_condition_valu self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_int__returns_false__when_user_value_not_less_than_condition_value(self,): + def test_less_than_int__returns_false__when_user_value_not_less_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -989,7 +916,7 @@ def test_less_than_int__returns_false__when_user_value_not_less_than_condition_v self.assertStrictFalse(evaluator.evaluate(0)) - def test_less_than_float__returns_false__when_user_value_not_less_than_condition_value(self,): + def test_less_than_float__returns_false__when_user_value_not_less_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger @@ -1042,7 +969,7 @@ def test_less_than_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self,): + def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -1075,7 +1002,7 @@ def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equa self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self,): + def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -1102,7 +1029,7 @@ def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_eq self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self,): + def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -1123,7 +1050,7 @@ def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or self.assertStrictFalse(evaluator.evaluate(0)) - def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self,): + def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger @@ -1190,7 +1117,8 @@ def is_finite_number__rejecting_condition_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__rejecting_condition_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_condition_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1203,8 +1131,8 @@ def is_finite_number__rejecting_user_attribute_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', - side_effect=is_finite_number__rejecting_user_attribute_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_user_attribute_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1215,7 +1143,7 @@ def is_finite_number__accepting_both_values(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, + 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__accepting_both_values, ): self.assertTrue(evaluator.evaluate(0)) @@ -1233,7 +1161,8 @@ def is_finite_number__rejecting_condition_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', side_effect=is_finite_number__rejecting_condition_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_condition_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1246,8 +1175,8 @@ def is_finite_number__rejecting_user_attribute_value(value): return True with mock.patch( - 'optimizely.helpers.validator.is_finite_number', - side_effect=is_finite_number__rejecting_user_attribute_value, + 'optimizely.helpers.validator.is_finite_number', + side_effect=is_finite_number__rejecting_user_attribute_value, ) as mock_is_finite: self.assertIsNone(evaluator.evaluate(0)) @@ -1351,6 +1280,7 @@ def is_finite_number__accepting_both_values(value): self.assertTrue(evaluator.evaluate(0)) def test_invalid_semver__returns_None__when_semver_is_invalid(self): + semver_less_than_or_equal_2_0_1_condition_list = [['Android', "2.0.1", 'custom_attribute', 'semver_le']] invalid_test_cases = ["-", ".", "..", "+", "+test", " ", "2 .0. 0", "2.", ".0.0", "1.2.2.2", "2.x", ",", "+build-prerelease", "2..0", "2.2.0+build-prerelease"] From 907fcf2078717bbab47632496e4ddd4b143fe437 Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Thu, 3 Sep 2020 01:07:23 +0500 Subject: [PATCH 24/34] address comments --- optimizely/helpers/condition.py | 37 +++++++++++++++++++-------- tests/helpers_tests/test_condition.py | 27 ++++++++++++------- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index f7ea6445..873182e2 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -13,6 +13,7 @@ import json import numbers +import sys from six import string_types @@ -106,7 +107,9 @@ def is_pre_release(self, version): - True if the given version is pre-release - False if it doesn't """ - return VersionType.IS_PRE_RELEASE in version + return VersionType.IS_PRE_RELEASE in version and \ + (version.find("-") if version.find("-") >= 0 else sys.maxsize) < \ + (version.find("+") if version.find("+") >= 0 else sys.maxsize) def is_build(self, version): """ Method to check given version is a build version. @@ -121,7 +124,9 @@ def is_build(self, version): - True if the given version is a build version - False if it doesn't """ - return VersionType.IS_BUILD in version + return VersionType.IS_BUILD in version and \ + (version.find("+") if version.find("+") >= 0 else sys.maxsize) < \ + (version.find("-") if version.find("-") >= 0 else sys.maxsize) def has_white_space(self, version): """ Method to check if the given version contains " " (white space) @@ -165,12 +170,18 @@ def compare_user_version_with_target_version(self, target_version, user_version) for (idx, _) in enumerate(target_version_parts): if user_version_parts_len <= idx: - return 1 if self.is_pre_release(target_version) else -1 + return 1 if self.is_pre_release(target_version) or self.is_build(target_version) else -1 elif not user_version_parts[idx].isdigit(): - if user_version_parts[idx] < target_version_parts[idx]: - return -1 - elif user_version_parts[idx] > target_version_parts[idx]: + if (self.is_pre_release(user_version) and self.is_pre_release(target_version)) or \ + (self.is_build(user_version) and self.is_build(target_version)): + if user_version_parts[idx] < target_version_parts[idx]: + return -1 + elif user_version_parts[idx] > target_version_parts[idx]: + return 1 + elif self.is_build(user_version): return 1 + elif self.is_build(target_version): + return -1 else: user_version_part = int(user_version_parts[idx]) target_version_part = int(target_version_parts[idx]) @@ -178,8 +189,13 @@ def compare_user_version_with_target_version(self, target_version, user_version) return 1 elif user_version_part < target_version_part: return -1 - if self.is_pre_release(user_version) and not self.is_pre_release(target_version): + + if (self.is_pre_release(user_version) or self.is_build(user_version)) and ( + not self.is_pre_release(target_version) and not self.is_build(target_version)): return -1 + elif (not self.is_pre_release(user_version) and not self.is_build(user_version)) and ( + self.is_pre_release(target_version) or self.is_build(target_version)): + return 1 return 0 def exact_evaluator(self, index): @@ -613,10 +629,9 @@ def split_version(self, version): # check for pre release e.g. 1.0.0-alpha where 'alpha' is a pre release # otherwise check for build e.g. 1.0.0+001 where 001 is a build metadata - if self.is_pre_release(version): - target_parts = version.split(VersionType.IS_PRE_RELEASE) - elif self.is_build(version): - target_parts = version.split(VersionType.IS_BUILD) + if self.is_pre_release(version) or self.is_build(version): + target_parts = version.split(VersionType.IS_PRE_RELEASE, 1) if self.is_pre_release(version) else \ + version.split(VersionType.IS_BUILD, 1) # split version into prefix and suffix if target_parts: diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index f5c15006..5bef74ad 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -236,14 +236,14 @@ def test_compare_user_version_with_target_version_equal_to_0(self): semver_greater_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_gt']] versions = [ ('2.0.1', '2.0.1'), - ('2.0.0', '2.0.0'), ('2.9', '2.9.1'), ('2.9.9', '2.9.9'), ('2.9.9-beta', '2.9.9-beta'), ('2.1', '2.1.0'), ('2.1', '2.1.215'), ('2', '2.12'), - ('2', '2.785.13') + ('2', '2.785.13'), + ('2', '2.785.13'), ] for target_version, user_version in versions: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -263,9 +263,14 @@ def test_compare_user_version_with_target_version_greater_than_0(self): ('2.0', '3.0.1'), ('2.0', '2.9.1'), ('2.9.0', '2.9.1'), - ('2.1.2', '2.1.3-beta'), ('2.1.2-beta', '2.1.2-release'), - ('2.1.3-beta', '2.1.3') + ('2.1.3-beta1', '2.1.3-beta2'), + ('2.9.9-beta', '2.9.9'), + ('2.9.9+beta', '2.9.9'), + ('3.7.0-prerelease+build', '3.7.0-prerelease+rc'), + ('2.2.3-beta-beta1', '2.2.3-beta-beta2'), + ('2.2.3-beta+beta1', '2.2.3-beta+beta2'), + ('2.2.3+beta2-beta1', '2.2.3+beta3-beta2') ] for target_version, user_version in versions: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -286,7 +291,12 @@ def test_compare_user_version_with_target_version_less_than_0(self): ('2.3.5', '2.3.1'), ('2.9.8', '2.9'), ('2.1.2-release', '2.1.2-beta'), - ('2.1.3', '2.1.3-beta') + ('2.1.3', '2.1.3-beta'), + ('2.1.3', '2.1.3+beta'), + ('2.9.9+beta', '2.9.9-beta'), + ('3.7.0+build3.7.0-prerelease+build', '3.7.0-prerelease'), + ('2.1.3-beta-beta2', '2.1.3-beta'), + ('2.1.3-beta1+beta3', '2.1.3-beta1+beta2') ] for target_version, user_version in versions: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -300,9 +310,8 @@ def test_compare_user_version_with_target_version_less_than_0(self): def test_compare_invalid_user_version_with(self): semver_greater_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_gt']] - versions = [ - '-', '.', '..', '+', '+test', ' ', '2 .3. 0', '2.', '.2.2', '3.7.2.2', '3.x', ',', '+build-prerelease' - ] + versions = ['-', '.', '..', '+', '+test', ' ', '2 .3. 0', '2.', '.2.2', '3.7.2.2', '3.x', ',', + '+build-prerelease', '2..2'] target_version = '2.1.0' for user_version in versions: @@ -1283,7 +1292,7 @@ def test_invalid_semver__returns_None__when_semver_is_invalid(self): semver_less_than_or_equal_2_0_1_condition_list = [['Android', "2.0.1", 'custom_attribute', 'semver_le']] invalid_test_cases = ["-", ".", "..", "+", "+test", " ", "2 .0. 0", "2.", ".0.0", "1.2.2.2", "2.x", ",", - "+build-prerelease", "2..0", "2.2.0+build-prerelease"] + "+build-prerelease", "2..0"] for user_version in invalid_test_cases: evaluator = condition_helper.CustomAttributeConditionEvaluator( From 8b4567c32234262f5ef16de2492110a543d804d7 Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Thu, 3 Sep 2020 22:45:55 +0500 Subject: [PATCH 25/34] add further checks --- optimizely/helpers/condition.py | 42 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 873182e2..be367f8c 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -13,7 +13,6 @@ import json import numbers -import sys from six import string_types @@ -107,9 +106,12 @@ def is_pre_release(self, version): - True if the given version is pre-release - False if it doesn't """ - return VersionType.IS_PRE_RELEASE in version and \ - (version.find("-") if version.find("-") >= 0 else sys.maxsize) < \ - (version.find("+") if version.find("+") >= 0 else sys.maxsize) + if VersionType.IS_PRE_RELEASE in version: + user_version_release_index = version.find(VersionType.IS_PRE_RELEASE) + user_version_build_index = version.find(VersionType.IS_BUILD) + if (user_version_release_index < user_version_build_index) or (user_version_build_index < 0): + return True + return False def is_build(self, version): """ Method to check given version is a build version. @@ -124,9 +126,12 @@ def is_build(self, version): - True if the given version is a build version - False if it doesn't """ - return VersionType.IS_BUILD in version and \ - (version.find("+") if version.find("+") >= 0 else sys.maxsize) < \ - (version.find("-") if version.find("-") >= 0 else sys.maxsize) + if VersionType.IS_BUILD in version: + user_version_release_index = version.find(VersionType.IS_PRE_RELEASE) + user_version_build_index = version.find(VersionType.IS_BUILD) + if (user_version_build_index < user_version_release_index) or (user_version_release_index < 0): + return True + return False def has_white_space(self, version): """ Method to check if the given version contains " " (white space) @@ -172,16 +177,12 @@ def compare_user_version_with_target_version(self, target_version, user_version) if user_version_parts_len <= idx: return 1 if self.is_pre_release(target_version) or self.is_build(target_version) else -1 elif not user_version_parts[idx].isdigit(): - if (self.is_pre_release(user_version) and self.is_pre_release(target_version)) or \ - (self.is_build(user_version) and self.is_build(target_version)): - if user_version_parts[idx] < target_version_parts[idx]: - return -1 - elif user_version_parts[idx] > target_version_parts[idx]: - return 1 - elif self.is_build(user_version): - return 1 - elif self.is_build(target_version): - return -1 + if user_version_parts[idx] < target_version_parts[idx]: + return 1 if self.is_pre_release(target_version) and not \ + self.is_pre_release(user_version) else -1 + elif user_version_parts[idx] > target_version_parts[idx]: + return -1 if not self.is_pre_release(target_version) and \ + self.is_pre_release(user_version) else 1 else: user_version_part = int(user_version_parts[idx]) target_version_part = int(target_version_parts[idx]) @@ -190,12 +191,9 @@ def compare_user_version_with_target_version(self, target_version, user_version) elif user_version_part < target_version_part: return -1 - if (self.is_pre_release(user_version) or self.is_build(user_version)) and ( - not self.is_pre_release(target_version) and not self.is_build(target_version)): + if (self.is_pre_release(user_version) and not self.is_pre_release(target_version)) or \ + (self.is_build(user_version) and not self.is_build(target_version)): return -1 - elif (not self.is_pre_release(user_version) and not self.is_build(user_version)) and ( - self.is_pre_release(target_version) or self.is_build(target_version)): - return 1 return 0 def exact_evaluator(self, index): From 88566652935022ff573f79160ac4f4730b0b5de6 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Wed, 9 Sep 2020 11:39:55 +0500 Subject: [PATCH 26/34] comments resolved --- optimizely/helpers/condition.py | 25 +++++++++++++------------ optimizely/helpers/enums.py | 1 - tests/helpers_tests/test_condition.py | 9 +-------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index be367f8c..666e9bbf 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -93,7 +93,7 @@ def is_value_a_number(self, value): return False - def is_pre_release(self, version): + def is_pre_release_version(self, version): """ Method to check if given version is pre-release. Criteria for pre-release includes: - Version includes "-" @@ -113,7 +113,7 @@ def is_pre_release(self, version): return True return False - def is_build(self, version): + def is_build_version(self, version): """ Method to check given version is a build version. Criteria for build version includes: - Version includes "+" @@ -144,7 +144,7 @@ def has_white_space(self, version): - True if the given version does contain whitespace - False if it doesn't """ - return VersionType.HAS_WHITE_SPACE in version + return ' ' in version def compare_user_version_with_target_version(self, target_version, user_version): """ Method to compare user version with target version. @@ -175,14 +175,14 @@ def compare_user_version_with_target_version(self, target_version, user_version) for (idx, _) in enumerate(target_version_parts): if user_version_parts_len <= idx: - return 1 if self.is_pre_release(target_version) or self.is_build(target_version) else -1 + return 1 if self.is_pre_release_version(target_version) or self.is_build_version(target_version) else -1 elif not user_version_parts[idx].isdigit(): if user_version_parts[idx] < target_version_parts[idx]: - return 1 if self.is_pre_release(target_version) and not \ - self.is_pre_release(user_version) else -1 + return 1 if self.is_pre_release_version(target_version) and not \ + self.is_pre_release_version(user_version) else -1 elif user_version_parts[idx] > target_version_parts[idx]: - return -1 if not self.is_pre_release(target_version) and \ - self.is_pre_release(user_version) else 1 + return -1 if not self.is_pre_release_version(target_version) and \ + self.is_pre_release_version(user_version) else 1 else: user_version_part = int(user_version_parts[idx]) target_version_part = int(target_version_parts[idx]) @@ -191,8 +191,9 @@ def compare_user_version_with_target_version(self, target_version, user_version) elif user_version_part < target_version_part: return -1 - if (self.is_pre_release(user_version) and not self.is_pre_release(target_version)) or \ - (self.is_build(user_version) and not self.is_build(target_version)): + # check if user version contains build or pre-release and target version doesn't + if (self.is_pre_release_version(user_version) and not self.is_pre_release_version(target_version)) or \ + (self.is_build_version(user_version) and not self.is_build_version(target_version)): return -1 return 0 @@ -627,8 +628,8 @@ def split_version(self, version): # check for pre release e.g. 1.0.0-alpha where 'alpha' is a pre release # otherwise check for build e.g. 1.0.0+001 where 001 is a build metadata - if self.is_pre_release(version) or self.is_build(version): - target_parts = version.split(VersionType.IS_PRE_RELEASE, 1) if self.is_pre_release(version) else \ + if self.is_pre_release_version(version) or self.is_build_version(version): + target_parts = version.split(VersionType.IS_PRE_RELEASE, 1) if self.is_pre_release_version(version) else \ version.split(VersionType.IS_BUILD, 1) # split version into prefix and suffix diff --git a/optimizely/helpers/enums.py b/optimizely/helpers/enums.py index c8c44e2f..3eed4a30 100644 --- a/optimizely/helpers/enums.py +++ b/optimizely/helpers/enums.py @@ -161,5 +161,4 @@ class NotificationTypes(object): class VersionType(object): IS_PRE_RELEASE = '-' - HAS_WHITE_SPACE = ' ' IS_BUILD = '+' diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 5bef74ad..6bdf06b2 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -236,14 +236,9 @@ def test_compare_user_version_with_target_version_equal_to_0(self): semver_greater_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_gt']] versions = [ ('2.0.1', '2.0.1'), - ('2.9', '2.9.1'), - ('2.9.9', '2.9.9'), ('2.9.9-beta', '2.9.9-beta'), ('2.1', '2.1.0'), - ('2.1', '2.1.215'), - ('2', '2.12'), - ('2', '2.785.13'), - ('2', '2.785.13'), + ('2', '2.12') ] for target_version, user_version in versions: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -261,8 +256,6 @@ def test_compare_user_version_with_target_version_greater_than_0(self): versions = [ ('2.0.0', '2.0.1'), ('2.0', '3.0.1'), - ('2.0', '2.9.1'), - ('2.9.0', '2.9.1'), ('2.1.2-beta', '2.1.2-release'), ('2.1.3-beta1', '2.1.3-beta2'), ('2.9.9-beta', '2.9.9'), From 7ed20f0eff88f329e48dde5fc294678823922099 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Wed, 9 Sep 2020 13:04:43 +0500 Subject: [PATCH 27/34] comments resolved --- optimizely/helpers/condition.py | 18 +++++++++++------- tests/helpers_tests/test_condition.py | 3 ++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 666e9bbf..15279f26 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -162,6 +162,10 @@ def compare_user_version_with_target_version(self, target_version, user_version) None: - if the user version value format is not a valid semantic version. """ + is_pre_release_in_target_version = self.is_pre_release_version(target_version) + is_pre_release_in_user_version = self.is_pre_release_version(user_version) + is_build_in_target_version = self.is_build_version(target_version) + is_build_in_user_version = self.is_build_version(user_version) target_version_parts = self.split_version(target_version) if target_version_parts is None: @@ -175,14 +179,14 @@ def compare_user_version_with_target_version(self, target_version, user_version) for (idx, _) in enumerate(target_version_parts): if user_version_parts_len <= idx: - return 1 if self.is_pre_release_version(target_version) or self.is_build_version(target_version) else -1 + return 1 if is_pre_release_in_target_version or is_build_in_target_version else -1 elif not user_version_parts[idx].isdigit(): if user_version_parts[idx] < target_version_parts[idx]: - return 1 if self.is_pre_release_version(target_version) and not \ - self.is_pre_release_version(user_version) else -1 + return 1 if is_pre_release_in_target_version and not \ + is_pre_release_in_user_version else -1 elif user_version_parts[idx] > target_version_parts[idx]: - return -1 if not self.is_pre_release_version(target_version) and \ - self.is_pre_release_version(user_version) else 1 + return -1 if not is_pre_release_in_target_version and \ + is_pre_release_in_user_version else 1 else: user_version_part = int(user_version_parts[idx]) target_version_part = int(target_version_parts[idx]) @@ -192,8 +196,8 @@ def compare_user_version_with_target_version(self, target_version, user_version) return -1 # check if user version contains build or pre-release and target version doesn't - if (self.is_pre_release_version(user_version) and not self.is_pre_release_version(target_version)) or \ - (self.is_build_version(user_version) and not self.is_build_version(target_version)): + if (is_pre_release_in_user_version and not is_pre_release_in_target_version) or \ + (is_build_in_user_version and not is_build_in_target_version): return -1 return 0 diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 6bdf06b2..e97dcc10 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -238,7 +238,8 @@ def test_compare_user_version_with_target_version_equal_to_0(self): ('2.0.1', '2.0.1'), ('2.9.9-beta', '2.9.9-beta'), ('2.1', '2.1.0'), - ('2', '2.12') + ('2', '2.12'), + ('2.9', '2.9.1') ] for target_version, user_version in versions: evaluator = condition_helper.CustomAttributeConditionEvaluator( From 4f2c05efe467840706c62263ace532561374cde8 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Mon, 14 Sep 2020 19:05:53 +0500 Subject: [PATCH 28/34] Update test_condition.py --- tests/helpers_tests/test_condition.py | 50 +++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index e97dcc10..d7302238 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -361,7 +361,7 @@ def test_exists__returns_true__when_user_provided_value_is_boolean(self): self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): + def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': 'Lacerta'}, self.mock_client_logger, @@ -369,7 +369,7 @@ def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condit self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): + def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': 'The Big Dipper'}, self.mock_client_logger, @@ -377,7 +377,7 @@ def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_c self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_string__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): + def test_exact_string__returns_null__when_user_provided_value_is_different_type_from_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': False}, self.mock_client_logger, @@ -393,7 +393,7 @@ def test_exact_string__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): + def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition_value(self): if PY2: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -414,7 +414,7 @@ def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_float__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): + def test_exact_float__returns_true__when_user_provided_value_is_equal_to_condition_value(self): if PY2: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -435,7 +435,7 @@ def test_exact_float__returns_true__when_user_provided_value_is_equal_to_conditi self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): + def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_int_condition_list, {'lasers_count': 8000}, self.mock_client_logger @@ -443,7 +443,7 @@ def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_cond self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): + def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_float_condition_list, {'lasers_count': 8000.0}, self.mock_client_logger, @@ -451,7 +451,7 @@ def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_co self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_int__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): + def test_exact_int__returns_null__when_user_provided_value_is_different_type_from_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_int_condition_list, {'lasers_count': 'hi'}, self.mock_client_logger @@ -465,7 +465,7 @@ def test_exact_int__returns_null__when_user_provided_value_is_different_type_fro self.assertIsNone(evaluator.evaluate(0)) - def test_exact_float__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): + def test_exact_float__returns_null__when_user_provided_value_is_different_type_from_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_float_condition_list, {'lasers_count': 'hi'}, self.mock_client_logger @@ -522,7 +522,7 @@ def test_exact__given_number_values__calls_is_finite_number(self): mock_is_finite.assert_has_calls([mock.call(9000), mock.call(9000)]) - def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): + def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': False}, self.mock_client_logger, @@ -530,7 +530,7 @@ def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_conditio self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): + def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': True}, self.mock_client_logger, @@ -538,7 +538,7 @@ def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_con self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_bool__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): + def test_exact_bool__returns_null__when_user_provided_value_is_different_type_from_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': 0}, self.mock_client_logger @@ -554,7 +554,7 @@ def test_exact_bool__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_substring__returns_true__when_condition_value_is_substring_of_user_value(self, ): + def test_substring__returns_true__when_condition_value_is_substring_of_user_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( substring_condition_list, {'headline_text': 'Limited time, buy now!'}, self.mock_client_logger, @@ -562,7 +562,7 @@ def test_substring__returns_true__when_condition_value_is_substring_of_user_valu self.assertStrictTrue(evaluator.evaluate(0)) - def test_substring__returns_false__when_condition_value_is_not_a_substring_of_user_value(self, ): + def test_substring__returns_false__when_condition_value_is_not_a_substring_of_user_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( substring_condition_list, {'headline_text': 'Breaking news!'}, self.mock_client_logger, @@ -586,7 +586,7 @@ def test_substring__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_greater_than_int__returns_true__when_user_value_greater_than_condition_value(self, ): + def test_greater_than_int__returns_true__when_user_value_greater_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -607,7 +607,7 @@ def test_greater_than_int__returns_true__when_user_value_greater_than_condition_ self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_float__returns_true__when_user_value_greater_than_condition_value(self, ): + def test_greater_than_float__returns_true__when_user_value_greater_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger @@ -628,7 +628,7 @@ def test_greater_than_float__returns_true__when_user_value_greater_than_conditio self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_int__returns_false__when_user_value_not_greater_than_condition_value(self, ): + def test_greater_than_int__returns_false__when_user_value_not_greater_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -649,7 +649,7 @@ def test_greater_than_int__returns_false__when_user_value_not_greater_than_condi self.assertStrictFalse(evaluator.evaluate(0)) - def test_greater_than_float__returns_false__when_user_value_not_greater_than_condition_value(self, ): + def test_greater_than_float__returns_false__when_user_value_not_greater_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger @@ -877,7 +877,7 @@ def test_less_than_int__returns_true__when_user_value_less_than_condition_value( self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self, ): + def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -898,7 +898,7 @@ def test_less_than_float__returns_true__when_user_value_less_than_condition_valu self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_int__returns_false__when_user_value_not_less_than_condition_value(self, ): + def test_less_than_int__returns_false__when_user_value_not_less_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -919,7 +919,7 @@ def test_less_than_int__returns_false__when_user_value_not_less_than_condition_v self.assertStrictFalse(evaluator.evaluate(0)) - def test_less_than_float__returns_false__when_user_value_not_less_than_condition_value(self, ): + def test_less_than_float__returns_false__when_user_value_not_less_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger @@ -972,7 +972,7 @@ def test_less_than_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self, ): + def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -1005,7 +1005,7 @@ def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equa self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self, ): + def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -1032,7 +1032,7 @@ def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_eq self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self, ): + def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -1053,7 +1053,7 @@ def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or self.assertStrictFalse(evaluator.evaluate(0)) - def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self, ): + def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger From 407080556eac0ff8b3b3e02635733d73d5afdd24 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Mon, 14 Sep 2020 19:11:47 +0500 Subject: [PATCH 29/34] Revert "Update test_condition.py" This reverts commit 4f2c05efe467840706c62263ace532561374cde8. --- tests/helpers_tests/test_condition.py | 50 +++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index d7302238..e97dcc10 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -361,7 +361,7 @@ def test_exists__returns_true__when_user_provided_value_is_boolean(self): self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condition_value(self): + def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': 'Lacerta'}, self.mock_client_logger, @@ -369,7 +369,7 @@ def test_exact_string__returns_true__when_user_provided_value_is_equal_to_condit self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self): + def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': 'The Big Dipper'}, self.mock_client_logger, @@ -377,7 +377,7 @@ def test_exact_string__returns_false__when_user_provided_value_is_not_equal_to_c self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_string__returns_null__when_user_provided_value_is_different_type_from_condition_value(self): + def test_exact_string__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_string_condition_list, {'favorite_constellation': False}, self.mock_client_logger, @@ -393,7 +393,7 @@ def test_exact_string__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition_value(self): + def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): if PY2: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -414,7 +414,7 @@ def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_float__returns_true__when_user_provided_value_is_equal_to_condition_value(self): + def test_exact_float__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): if PY2: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -435,7 +435,7 @@ def test_exact_float__returns_true__when_user_provided_value_is_equal_to_conditi self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self): + def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_int_condition_list, {'lasers_count': 8000}, self.mock_client_logger @@ -443,7 +443,7 @@ def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_cond self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self): + def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_float_condition_list, {'lasers_count': 8000.0}, self.mock_client_logger, @@ -451,7 +451,7 @@ def test_exact_float__returns_false__when_user_provided_value_is_not_equal_to_co self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_int__returns_null__when_user_provided_value_is_different_type_from_condition_value(self): + def test_exact_int__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_int_condition_list, {'lasers_count': 'hi'}, self.mock_client_logger @@ -465,7 +465,7 @@ def test_exact_int__returns_null__when_user_provided_value_is_different_type_fro self.assertIsNone(evaluator.evaluate(0)) - def test_exact_float__returns_null__when_user_provided_value_is_different_type_from_condition_value(self): + def test_exact_float__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_float_condition_list, {'lasers_count': 'hi'}, self.mock_client_logger @@ -522,7 +522,7 @@ def test_exact__given_number_values__calls_is_finite_number(self): mock_is_finite.assert_has_calls([mock.call(9000), mock.call(9000)]) - def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_condition_value(self): + def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': False}, self.mock_client_logger, @@ -530,7 +530,7 @@ def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_conditio self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self): + def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': True}, self.mock_client_logger, @@ -538,7 +538,7 @@ def test_exact_bool__returns_false__when_user_provided_value_is_not_equal_to_con self.assertStrictFalse(evaluator.evaluate(0)) - def test_exact_bool__returns_null__when_user_provided_value_is_different_type_from_condition_value(self): + def test_exact_bool__returns_null__when_user_provided_value_is_different_type_from_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( exact_bool_condition_list, {'did_register_user': 0}, self.mock_client_logger @@ -554,7 +554,7 @@ def test_exact_bool__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_substring__returns_true__when_condition_value_is_substring_of_user_value(self): + def test_substring__returns_true__when_condition_value_is_substring_of_user_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( substring_condition_list, {'headline_text': 'Limited time, buy now!'}, self.mock_client_logger, @@ -562,7 +562,7 @@ def test_substring__returns_true__when_condition_value_is_substring_of_user_valu self.assertStrictTrue(evaluator.evaluate(0)) - def test_substring__returns_false__when_condition_value_is_not_a_substring_of_user_value(self): + def test_substring__returns_false__when_condition_value_is_not_a_substring_of_user_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( substring_condition_list, {'headline_text': 'Breaking news!'}, self.mock_client_logger, @@ -586,7 +586,7 @@ def test_substring__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_greater_than_int__returns_true__when_user_value_greater_than_condition_value(self): + def test_greater_than_int__returns_true__when_user_value_greater_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -607,7 +607,7 @@ def test_greater_than_int__returns_true__when_user_value_greater_than_condition_ self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_float__returns_true__when_user_value_greater_than_condition_value(self): + def test_greater_than_float__returns_true__when_user_value_greater_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger @@ -628,7 +628,7 @@ def test_greater_than_float__returns_true__when_user_value_greater_than_conditio self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_int__returns_false__when_user_value_not_greater_than_condition_value(self): + def test_greater_than_int__returns_false__when_user_value_not_greater_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -649,7 +649,7 @@ def test_greater_than_int__returns_false__when_user_value_not_greater_than_condi self.assertStrictFalse(evaluator.evaluate(0)) - def test_greater_than_float__returns_false__when_user_value_not_greater_than_condition_value(self): + def test_greater_than_float__returns_false__when_user_value_not_greater_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( gt_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger @@ -877,7 +877,7 @@ def test_less_than_int__returns_true__when_user_value_less_than_condition_value( self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self): + def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -898,7 +898,7 @@ def test_less_than_float__returns_true__when_user_value_less_than_condition_valu self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_int__returns_false__when_user_value_not_less_than_condition_value(self): + def test_less_than_int__returns_false__when_user_value_not_less_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -919,7 +919,7 @@ def test_less_than_int__returns_false__when_user_value_not_less_than_condition_v self.assertStrictFalse(evaluator.evaluate(0)) - def test_less_than_float__returns_false__when_user_value_not_less_than_condition_value(self): + def test_less_than_float__returns_false__when_user_value_not_less_than_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( lt_float_condition_list, {'meters_travelled': 48.2}, self.mock_client_logger @@ -972,7 +972,7 @@ def test_less_than_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self): + def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -1005,7 +1005,7 @@ def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equa self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self): + def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -1032,7 +1032,7 @@ def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_eq self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self): + def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -1053,7 +1053,7 @@ def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or self.assertStrictFalse(evaluator.evaluate(0)) - def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self): + def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self, ): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger From 4c471bde8972e9da6400d1e706a62037c9249e92 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Mon, 14 Sep 2020 19:17:55 +0500 Subject: [PATCH 30/34] Update test_condition.py --- tests/helpers_tests/test_condition.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index e97dcc10..1d4805df 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -972,7 +972,7 @@ def test_less_than_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) - def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self, ): + def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equal_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_int_condition_list, {'meters_travelled': 47.9}, self.mock_client_logger @@ -1005,7 +1005,7 @@ def test_less_than_or_equal_int__returns_true__when_user_value_less_than_or_equa self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self, ): + def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_equal_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_float_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -1032,7 +1032,7 @@ def test_less_than_or_equal_float__returns_true__when_user_value_less_than_or_eq self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self, ): + def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or_equal_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_int_condition_list, {'meters_travelled': 48.1}, self.mock_client_logger @@ -1053,7 +1053,7 @@ def test_less_than_or_equal_int__returns_false__when_user_value_not_less_than_or self.assertStrictFalse(evaluator.evaluate(0)) - def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self, ): + def test_less_than_or_equal_float__returns_false__when_user_value_not_less_than_or_equal_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( le_float_condition_list, {'meters_travelled': 48.3}, self.mock_client_logger From 68708d9fc69aa69750c78ecee29c041bda7b597e Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Wed, 16 Sep 2020 17:43:30 +0500 Subject: [PATCH 31/34] Update test_condition.py --- tests/helpers_tests/test_condition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 1d4805df..85b5819b 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -300,7 +300,7 @@ def test_compare_user_version_with_target_version_less_than_0(self): "and target version: {}".format(result, user_version, target_version) - self.assertEquals(result, -1, custom_err_msg) + self.assertEqual(result, -1, custom_err_msg) def test_compare_invalid_user_version_with(self): semver_greater_than_2_0_condition_list = [['Android', "2.0", 'custom_attribute', 'semver_gt']] From ad17c0207a833015b00f24c6ce980f6bfd611b39 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Thu, 17 Sep 2020 11:13:58 +0500 Subject: [PATCH 32/34] testcase fixed --- optimizely/helpers/condition.py | 3 +-- tests/helpers_tests/test_condition.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 15279f26..a137e43b 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -196,8 +196,7 @@ def compare_user_version_with_target_version(self, target_version, user_version) return -1 # check if user version contains build or pre-release and target version doesn't - if (is_pre_release_in_user_version and not is_pre_release_in_target_version) or \ - (is_build_in_user_version and not is_build_in_target_version): + if is_pre_release_in_user_version and is_build_in_target_version: return -1 return 0 diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 85b5819b..1a20e9ae 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -239,7 +239,8 @@ def test_compare_user_version_with_target_version_equal_to_0(self): ('2.9.9-beta', '2.9.9-beta'), ('2.1', '2.1.0'), ('2', '2.12'), - ('2.9', '2.9.1') + ('2.9', '2.9.1'), + ('2.9.1', '2.9.1+beta') ] for target_version, user_version in versions: evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -285,8 +286,6 @@ def test_compare_user_version_with_target_version_less_than_0(self): ('2.3.5', '2.3.1'), ('2.9.8', '2.9'), ('2.1.2-release', '2.1.2-beta'), - ('2.1.3', '2.1.3-beta'), - ('2.1.3', '2.1.3+beta'), ('2.9.9+beta', '2.9.9-beta'), ('3.7.0+build3.7.0-prerelease+build', '3.7.0-prerelease'), ('2.1.3-beta-beta2', '2.1.3-beta'), From 93cbb2a3c94d411251d74e72eca769163249185a Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Thu, 17 Sep 2020 11:17:44 +0500 Subject: [PATCH 33/34] Update condition.py --- optimizely/helpers/condition.py | 1 - 1 file changed, 1 deletion(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index a137e43b..455e09aa 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -165,7 +165,6 @@ def compare_user_version_with_target_version(self, target_version, user_version) is_pre_release_in_target_version = self.is_pre_release_version(target_version) is_pre_release_in_user_version = self.is_pre_release_version(user_version) is_build_in_target_version = self.is_build_version(target_version) - is_build_in_user_version = self.is_build_version(user_version) target_version_parts = self.split_version(target_version) if target_version_parts is None: From 25ab44dbf76d98ec62a1e4e719cec68848d575d9 Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Thu, 17 Sep 2020 20:36:42 +0500 Subject: [PATCH 34/34] fix condition --- optimizely/helpers/condition.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/optimizely/helpers/condition.py b/optimizely/helpers/condition.py index 455e09aa..2cd80dde 100644 --- a/optimizely/helpers/condition.py +++ b/optimizely/helpers/condition.py @@ -194,8 +194,8 @@ def compare_user_version_with_target_version(self, target_version, user_version) elif user_version_part < target_version_part: return -1 - # check if user version contains build or pre-release and target version doesn't - if is_pre_release_in_user_version and is_build_in_target_version: + # check if user version contains pre-release and target version doesn't + if is_pre_release_in_user_version and not is_pre_release_in_target_version: return -1 return 0