From 7320a3c40bdf00f2b5541d125f106d303585959b Mon Sep 17 00:00:00 2001 From: Owais Date: Mon, 10 Dec 2018 16:05:49 +0500 Subject: [PATCH 1/4] feat(attribute_value): Don't target NAN, INF, -INF and Integers > 1e53 --- optimizely/helpers/validator.py | 37 +++++++++++++++++- tests/base.py | 6 +++ tests/helpers_tests/test_validator.py | 55 +++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/optimizely/helpers/validator.py b/optimizely/helpers/validator.py index 3e819f42..b1ec2ec9 100644 --- a/optimizely/helpers/validator.py +++ b/optimizely/helpers/validator.py @@ -13,6 +13,8 @@ import json import jsonschema +import math +import numbers from six import string_types from optimizely.user_profile import UserProfile @@ -185,7 +187,40 @@ def is_attribute_valid(attribute_key, attribute_value): if not isinstance(attribute_key, string_types): return False - if isinstance(attribute_value, string_types) or type(attribute_value) in (int, float, bool): + if isinstance(attribute_value, (string_types, bool)): return True + if isinstance(attribute_value, (numbers.Integral, float)): + return is_finite_number(attribute_value) + return False + + +def is_finite_number(value): + """ Validates if the given value is a number, enforces + limit of 1e53 for integers and restricts NAN, INF, -INF for doubles. + + Args: + value: Value to be validated + + Returns: + Boolean: True if value is a finite number else False + """ + + if not isinstance(value, (numbers.Integral, float)): + # numbers.Integral instead of int to accomodate long integer in python 2 + return False + + if isinstance(value, bool): + # bool is a subclass of int + return False + + if isinstance(value, numbers.Integral): + if value > 1e53: + return False + + if isinstance(value, float): + if math.isnan(value) or math.isinf(value): + return False + + return True diff --git a/tests/base.py b/tests/base.py index 6e3c2108..759a8635 100644 --- a/tests/base.py +++ b/tests/base.py @@ -14,11 +14,17 @@ import json import unittest +from six import PY3 + from optimizely import optimizely class BaseTest(unittest.TestCase): + if PY3: + def long(a): + raise NotImplementedError('Tests should only call `long` if running in PY2') + def setUp(self, config_dict='config_dict'): self.config_dict = { 'revision': '42', diff --git a/tests/helpers_tests/test_validator.py b/tests/helpers_tests/test_validator.py index 5f63a072..82618727 100644 --- a/tests/helpers_tests/test_validator.py +++ b/tests/helpers_tests/test_validator.py @@ -12,6 +12,9 @@ # limitations under the License. import json +import mock + +from six import PY2 from optimizely import error_handler from optimizely import event_dispatcher @@ -44,6 +47,7 @@ def test_is_event_dispatcher_valid__returns_false(self): """ Test that invalid event_dispatcher returns False. """ class CustomEventDispatcher(object): + def some_other_method(self): pass @@ -58,6 +62,7 @@ def test_is_logger_valid__returns_false(self): """ Test that invalid logger returns False. """ class CustomLogger(object): + def some_other_method(self): pass @@ -72,6 +77,7 @@ def test_is_error_handler_valid__returns_false(self): """ Test that invalid error_handler returns False. """ class CustomErrorHandler(object): + def some_other_method(self): pass @@ -168,6 +174,55 @@ def test_is_attribute_valid(self): self.assertTrue(validator.is_attribute_valid('test_attribute', "")) self.assertTrue(validator.is_attribute_valid('test_attribute', 'test_value')) + # test if attribute value is a number, it calls is_finite_number and returns it's result + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=True) as is_finite: + self.assertTrue(validator.is_attribute_valid('test_attribute', 5)) + + is_finite.assert_called_once_with(5) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=False) as is_finite: + self.assertFalse(validator.is_attribute_valid('test_attribute', 5.5)) + + is_finite.assert_called_once_with(5.5) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value='abc') as is_finite: + self.assertEqual('abc', validator.is_attribute_valid('test_attribute', 0)) + + is_finite.assert_called_once_with(0) + + def test_is_finite_number(self): + """ Test that it returns true if value is a number and is not more than 1e53 if an Integer, + and not one of NAN, INF or -INF if it's a double. """ + + # test non number values + self.assertFalse(validator.is_finite_number('HelloWorld')) + self.assertFalse(validator.is_finite_number(True)) + self.assertFalse(validator.is_finite_number(False)) + self.assertFalse(validator.is_finite_number(None)) + self.assertFalse(validator.is_finite_number({})) + self.assertFalse(validator.is_finite_number([])) + self.assertFalse(validator.is_finite_number(())) + + # test invalid numbers + self.assertFalse(validator.is_finite_number((float('inf')))) + self.assertFalse(validator.is_finite_number((float('-inf')))) + self.assertFalse(validator.is_finite_number((float('nan')))) + self.assertFalse(validator.is_finite_number(int(1e53) + 1)) + if PY2: + self.assertFalse(validator.is_finite_number(long(1e53) + 1)) + + # test valid numbers + self.assertTrue(validator.is_finite_number((0))) + self.assertTrue(validator.is_finite_number((5))) + self.assertTrue(validator.is_finite_number((5.5))) + self.assertTrue(validator.is_finite_number((float(1e53) + 1))) + self.assertTrue(validator.is_finite_number((int(1e53)))) + if PY2: + self.assertTrue(validator.is_finite_number((long(1e53)))) + class DatafileValidationTests(base.BaseTest): From c53252536061db089556b5fd7ad74a88b211e1a0 Mon Sep 17 00:00:00 2001 From: Owais Date: Mon, 10 Dec 2018 16:11:52 +0500 Subject: [PATCH 2/4] nit. blank lines --- tests/helpers_tests/test_validator.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/helpers_tests/test_validator.py b/tests/helpers_tests/test_validator.py index 82618727..4f3525cd 100644 --- a/tests/helpers_tests/test_validator.py +++ b/tests/helpers_tests/test_validator.py @@ -47,7 +47,6 @@ def test_is_event_dispatcher_valid__returns_false(self): """ Test that invalid event_dispatcher returns False. """ class CustomEventDispatcher(object): - def some_other_method(self): pass @@ -62,7 +61,6 @@ def test_is_logger_valid__returns_false(self): """ Test that invalid logger returns False. """ class CustomLogger(object): - def some_other_method(self): pass @@ -77,7 +75,6 @@ def test_is_error_handler_valid__returns_false(self): """ Test that invalid error_handler returns False. """ class CustomErrorHandler(object): - def some_other_method(self): pass From 66159a82abb32b73d9cdf348101425df8483467e Mon Sep 17 00:00:00 2001 From: Owais Date: Thu, 20 Dec 2018 15:07:42 +0500 Subject: [PATCH 3/4] :pen: Revise number --- optimizely/helpers/validator.py | 13 +- tests/base.py | 5 + tests/helpers_tests/test_condition.py | 212 +++++++++++++++++++++++++- tests/helpers_tests/test_validator.py | 42 ++--- 4 files changed, 246 insertions(+), 26 deletions(-) diff --git a/optimizely/helpers/validator.py b/optimizely/helpers/validator.py index b8cd3f42..9efe25ac 100644 --- a/optimizely/helpers/validator.py +++ b/optimizely/helpers/validator.py @@ -187,20 +187,24 @@ def is_attribute_valid(attribute_key, attribute_value): if not isinstance(attribute_key, string_types): return False - if isinstance(attribute_value, string_types) or type(attribute_value) in (int, float, bool): + if isinstance(attribute_value, (string_types, bool)): return True + if isinstance(attribute_value, (numbers.Integral, float)): + return is_finite_number(attribute_value) + return False def is_finite_number(value): - """ Method to validate if the given value is a number and not one of NAN, INF, -INF. + """ Validates if the given value is a number, enforces + absolute limit of 2^53 and restricts NAN, INF, -INF. Args: value: Value to be validated. Returns: - Boolean: True if value is a number and not NAN, INF or -INF else False. + Boolean: True if value is a number and not NAN, INF, -INF or greater than 2^53 else False. """ if not isinstance(value, (numbers.Integral, float)): # numbers.Integral instead of int to accomodate long integer in python 2 @@ -214,6 +218,9 @@ def is_finite_number(value): if math.isnan(value) or math.isinf(value): return False + if abs(value) > (2**53): + return False + return True diff --git a/tests/base.py b/tests/base.py index 7f79697b..ba3b5e02 100644 --- a/tests/base.py +++ b/tests/base.py @@ -13,9 +13,14 @@ import json import unittest +from six import PY3 from optimizely import optimizely +if PY3: + def long(a): + raise NotImplementedError('Tests should only call `long` if running in PY2') + class BaseTest(unittest.TestCase): diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 51021a02..7200e460 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -12,16 +12,12 @@ # limitations under the License. import mock -from six import PY2, PY3 +from six import PY2 from optimizely.helpers import condition as condition_helper from tests import base -if PY3: - def long(a): - raise NotImplementedError('Tests should only call `long` if running in PY2') - browserConditionSafari = ['browser_type', 'safari', 'custom_attribute', 'exact'] booleanCondition = ['is_firefox', True, 'custom_attribute', 'exact'] integerCondition = ['num_users', 10, 'custom_attribute', 'exact'] @@ -205,6 +201,37 @@ def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition self.assertStrictTrue(evaluator.evaluate(0)) + def test_exact_int__calls_is_finite_number(self): + """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + exact_int_condition_list, {'lasers_count': long(9000)} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=True) as is_finite: + self.assertStrictTrue(evaluator.evaluate(0)) + is_finite.assert_called_with(long(9000)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + exact_int_condition_list, {'lasers_count': 9000} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=False) as is_finite: + self.assertIsNone(evaluator.evaluate(0)) + is_finite.assert_called_with(9000) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + exact_int_condition_list, {'lasers_count': 9000.0} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=False) as is_finite: + self.assertIsNone(evaluator.evaluate(0)) + is_finite.assert_called_with(9000.0) + def test_exact_float__returns_true__when_user_provided_value_is_equal_to_condition_value(self): if PY2: @@ -226,6 +253,37 @@ def test_exact_float__returns_true__when_user_provided_value_is_equal_to_conditi self.assertStrictTrue(evaluator.evaluate(0)) + def test_exact_float__calls_is_finite_number(self): + """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + exact_float_condition_list, {'lasers_count': long(9000)} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=True) as is_finite: + self.assertStrictTrue(evaluator.evaluate(0)) + is_finite.assert_called_with(long(9000)) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + exact_float_condition_list, {'lasers_count': 9000} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=False) as is_finite: + self.assertIsNone(evaluator.evaluate(0)) + is_finite.assert_called_with(9000) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + exact_float_condition_list, {'lasers_count': 9000.0} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=False) as is_finite: + self.assertIsNone(evaluator.evaluate(0)) + is_finite.assert_called_with(9000.0) + def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -371,6 +429,42 @@ def test_greater_than_int__returns_true__when_user_value_greater_than_condition_ self.assertStrictTrue(evaluator.evaluate(0)) + def test_greater_than_int__calls_is_finite_number(self): + """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + gt_int_condition_list, {'meters_travelled': 48.1} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=True) as is_finite: + self.assertStrictTrue(evaluator.evaluate(0)) + is_finite.assert_called_with(48.1) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + gt_int_condition_list, {'meters_travelled': 49} + ) + + def side_effect(*args): + if args[0] == 49: + return False + return True + + with mock.patch('optimizely.helpers.validator.is_finite_number', + side_effect=side_effect) as is_finite: + self.assertIsNone(evaluator.evaluate(0)) + is_finite.assert_any_call(49) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + gt_int_condition_list, {'meters_travelled': long(49)} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + side_effect=side_effect) as is_finite: + self.assertIsNone(evaluator.evaluate(0)) + is_finite.assert_called_with(long(49)) + def test_greater_than_float__returns_true__when_user_value_greater_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -392,6 +486,42 @@ def test_greater_than_float__returns_true__when_user_value_greater_than_conditio self.assertStrictTrue(evaluator.evaluate(0)) + def test_greater_than_float__calls_is_finite_number(self): + """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + gt_float_condition_list, {'meters_travelled': 48.3} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=True) as is_finite: + self.assertStrictTrue(evaluator.evaluate(0)) + is_finite.assert_called_with(48.3) + + def side_effect(*args): + if args[0] == 49: + return False + return True + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + gt_float_condition_list, {'meters_travelled': 49} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + side_effect=side_effect) as is_finite: + self.assertIsNone(evaluator.evaluate(0)) + is_finite.assert_called_with(49) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + gt_float_condition_list, {'meters_travelled': long(49)} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + side_effect=side_effect) as is_finite: + self.assertIsNone(evaluator.evaluate(0)) + is_finite.assert_called_with(long(49)) + def test_greater_than_int__returns_false__when_user_value_not_greater_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -499,6 +629,42 @@ def test_less_than_int__returns_true__when_user_value_less_than_condition_value( self.assertStrictTrue(evaluator.evaluate(0)) + def test_less_than_int__calls_is_finite_number(self): + """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + lt_int_condition_list, {'meters_travelled': 47.9} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=True) as is_finite: + self.assertStrictTrue(evaluator.evaluate(0)) + is_finite.assert_called_with(47.9) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + lt_int_condition_list, {'meters_travelled': 47} + ) + + def side_effect(*args): + if args[0] == 47: + return False + return True + + with mock.patch('optimizely.helpers.validator.is_finite_number', + side_effect=side_effect) as is_finite: + self.assertIsNone(evaluator.evaluate(0)) + is_finite.assert_any_call(47) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + lt_int_condition_list, {'meters_travelled': long(47)} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + side_effect=side_effect) as is_finite: + self.assertIsNone(evaluator.evaluate(0)) + is_finite.assert_called_with(long(47)) + def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -520,6 +686,42 @@ def test_less_than_float__returns_true__when_user_value_less_than_condition_valu self.assertStrictTrue(evaluator.evaluate(0)) + def test_less_than_float__calls_is_finite_number(self): + """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + lt_float_condition_list, {'meters_travelled': 48.1} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=True) as is_finite: + self.assertStrictTrue(evaluator.evaluate(0)) + is_finite.assert_called_with(48.1) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + lt_float_condition_list, {'meters_travelled': 48} + ) + + def side_effect(*args): + if args[0] == 48: + return False + return True + + with mock.patch('optimizely.helpers.validator.is_finite_number', + side_effect=side_effect) as is_finite: + self.assertIsNone(evaluator.evaluate(0)) + is_finite.assert_any_call(48) + + if PY2: + evaluator = condition_helper.CustomAttributeConditionEvaluator( + lt_float_condition_list, {'meters_travelled': long(48)} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + side_effect=side_effect) as is_finite: + self.assertIsNone(evaluator.evaluate(0)) + is_finite.assert_called_with(long(48)) + def test_less_than_int__returns_false__when_user_value_not_less_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( diff --git a/tests/helpers_tests/test_validator.py b/tests/helpers_tests/test_validator.py index 4f3525cd..23b28757 100644 --- a/tests/helpers_tests/test_validator.py +++ b/tests/helpers_tests/test_validator.py @@ -184,16 +184,17 @@ def test_is_attribute_valid(self): is_finite.assert_called_once_with(5.5) - with mock.patch('optimizely.helpers.validator.is_finite_number', - return_value='abc') as is_finite: - self.assertEqual('abc', validator.is_attribute_valid('test_attribute', 0)) + if PY2: + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=None) as is_finite: + self.assertIsNone(validator.is_attribute_valid('test_attribute', long(5))) - is_finite.assert_called_once_with(0) + is_finite.assert_called_once_with(long(5)) def test_is_finite_number(self): - """ Test that it returns true if value is a number and is not more than 1e53 if an Integer, - and not one of NAN, INF or -INF if it's a double. """ - + """ Test that it returns true if value is a number and not NAN, INF, -INF or greater than 2^53. + Otherwise False. + """ # test non number values self.assertFalse(validator.is_finite_number('HelloWorld')) self.assertFalse(validator.is_finite_number(True)) @@ -204,21 +205,26 @@ def test_is_finite_number(self): self.assertFalse(validator.is_finite_number(())) # test invalid numbers - self.assertFalse(validator.is_finite_number((float('inf')))) - self.assertFalse(validator.is_finite_number((float('-inf')))) - self.assertFalse(validator.is_finite_number((float('nan')))) - self.assertFalse(validator.is_finite_number(int(1e53) + 1)) + self.assertFalse(validator.is_finite_number(float('inf'))) + self.assertFalse(validator.is_finite_number(float('-inf'))) + self.assertFalse(validator.is_finite_number(float('nan'))) + self.assertFalse(validator.is_finite_number(int(2**53) + 1)) + self.assertFalse(validator.is_finite_number(-int(2**53) - 1)) + self.assertFalse(validator.is_finite_number(float(2**53) + 2.0)) + self.assertFalse(validator.is_finite_number(-float(2**53) - 2.0)) if PY2: - self.assertFalse(validator.is_finite_number(long(1e53) + 1)) + self.assertFalse(validator.is_finite_number(long(2**53) + 1)) + self.assertFalse(validator.is_finite_number(-long(2**53) - 1)) # test valid numbers - self.assertTrue(validator.is_finite_number((0))) - self.assertTrue(validator.is_finite_number((5))) - self.assertTrue(validator.is_finite_number((5.5))) - self.assertTrue(validator.is_finite_number((float(1e53) + 1))) - self.assertTrue(validator.is_finite_number((int(1e53)))) + self.assertTrue(validator.is_finite_number(0)) + self.assertTrue(validator.is_finite_number(5)) + self.assertTrue(validator.is_finite_number(5.5)) + self.assertTrue(validator.is_finite_number(float(2**53) + 1.0)) + self.assertTrue(validator.is_finite_number(-float(2**53) - 1.0)) + self.assertTrue(validator.is_finite_number(int(2**53))) if PY2: - self.assertTrue(validator.is_finite_number((long(1e53)))) + self.assertTrue(validator.is_finite_number(long(2**53))) class DatafileValidationTests(base.BaseTest): From da583c8bdf83e77f2da6a0e2e39a499addf8572b Mon Sep 17 00:00:00 2001 From: Owais Date: Fri, 21 Dec 2018 13:00:41 +0500 Subject: [PATCH 4/4] tests: refact --- tests/helpers_tests/test_condition.py | 303 +++++++++----------------- tests/helpers_tests/test_validator.py | 1 + 2 files changed, 98 insertions(+), 206 deletions(-) diff --git a/tests/helpers_tests/test_condition.py b/tests/helpers_tests/test_condition.py index 7200e460..f5fb9aae 100644 --- a/tests/helpers_tests/test_condition.py +++ b/tests/helpers_tests/test_condition.py @@ -201,37 +201,6 @@ def test_exact_int__returns_true__when_user_provided_value_is_equal_to_condition self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_int__calls_is_finite_number(self): - """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ - - if PY2: - evaluator = condition_helper.CustomAttributeConditionEvaluator( - exact_int_condition_list, {'lasers_count': long(9000)} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - return_value=True) as is_finite: - self.assertStrictTrue(evaluator.evaluate(0)) - is_finite.assert_called_with(long(9000)) - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - exact_int_condition_list, {'lasers_count': 9000} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - return_value=False) as is_finite: - self.assertIsNone(evaluator.evaluate(0)) - is_finite.assert_called_with(9000) - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - exact_int_condition_list, {'lasers_count': 9000.0} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - return_value=False) as is_finite: - self.assertIsNone(evaluator.evaluate(0)) - is_finite.assert_called_with(9000.0) - def test_exact_float__returns_true__when_user_provided_value_is_equal_to_condition_value(self): if PY2: @@ -253,37 +222,6 @@ def test_exact_float__returns_true__when_user_provided_value_is_equal_to_conditi self.assertStrictTrue(evaluator.evaluate(0)) - def test_exact_float__calls_is_finite_number(self): - """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ - - if PY2: - evaluator = condition_helper.CustomAttributeConditionEvaluator( - exact_float_condition_list, {'lasers_count': long(9000)} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - return_value=True) as is_finite: - self.assertStrictTrue(evaluator.evaluate(0)) - is_finite.assert_called_with(long(9000)) - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - exact_float_condition_list, {'lasers_count': 9000} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - return_value=False) as is_finite: - self.assertIsNone(evaluator.evaluate(0)) - is_finite.assert_called_with(9000) - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - exact_float_condition_list, {'lasers_count': 9000.0} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - return_value=False) as is_finite: - self.assertIsNone(evaluator.evaluate(0)) - is_finite.assert_called_with(9000.0) - def test_exact_int__returns_false__when_user_provided_value_is_not_equal_to_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -344,6 +282,27 @@ def test_exact_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) + def test_exact__given_number_values__calls_is_finite_number(self): + """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + exact_int_condition_list, {'lasers_count': 9000} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=True) as is_finite: + self.assertTrue(evaluator.evaluate(0)) + is_finite.assert_called_with(9000) + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + exact_int_condition_list, {'lasers_count': 9000.0} + ) + + with mock.patch('optimizely.helpers.validator.is_finite_number', + return_value=False) as is_finite: + self.assertIsNone(evaluator.evaluate(0)) + is_finite.assert_called_with(9000.0) + def test_exact_bool__returns_true__when_user_provided_value_is_equal_to_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -429,42 +388,6 @@ def test_greater_than_int__returns_true__when_user_value_greater_than_condition_ self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_int__calls_is_finite_number(self): - """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - gt_int_condition_list, {'meters_travelled': 48.1} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - return_value=True) as is_finite: - self.assertStrictTrue(evaluator.evaluate(0)) - is_finite.assert_called_with(48.1) - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - gt_int_condition_list, {'meters_travelled': 49} - ) - - def side_effect(*args): - if args[0] == 49: - return False - return True - - with mock.patch('optimizely.helpers.validator.is_finite_number', - side_effect=side_effect) as is_finite: - self.assertIsNone(evaluator.evaluate(0)) - is_finite.assert_any_call(49) - - if PY2: - evaluator = condition_helper.CustomAttributeConditionEvaluator( - gt_int_condition_list, {'meters_travelled': long(49)} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - side_effect=side_effect) as is_finite: - self.assertIsNone(evaluator.evaluate(0)) - is_finite.assert_called_with(long(49)) - def test_greater_than_float__returns_true__when_user_value_greater_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -486,42 +409,6 @@ def test_greater_than_float__returns_true__when_user_value_greater_than_conditio self.assertStrictTrue(evaluator.evaluate(0)) - def test_greater_than_float__calls_is_finite_number(self): - """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - gt_float_condition_list, {'meters_travelled': 48.3} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - return_value=True) as is_finite: - self.assertStrictTrue(evaluator.evaluate(0)) - is_finite.assert_called_with(48.3) - - def side_effect(*args): - if args[0] == 49: - return False - return True - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - gt_float_condition_list, {'meters_travelled': 49} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - side_effect=side_effect) as is_finite: - self.assertIsNone(evaluator.evaluate(0)) - is_finite.assert_called_with(49) - - if PY2: - evaluator = condition_helper.CustomAttributeConditionEvaluator( - gt_float_condition_list, {'meters_travelled': long(49)} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - side_effect=side_effect) as is_finite: - self.assertIsNone(evaluator.evaluate(0)) - is_finite.assert_called_with(long(49)) - def test_greater_than_int__returns_false__when_user_value_not_greater_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -629,42 +516,6 @@ def test_less_than_int__returns_true__when_user_value_less_than_condition_value( self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_int__calls_is_finite_number(self): - """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - lt_int_condition_list, {'meters_travelled': 47.9} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - return_value=True) as is_finite: - self.assertStrictTrue(evaluator.evaluate(0)) - is_finite.assert_called_with(47.9) - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - lt_int_condition_list, {'meters_travelled': 47} - ) - - def side_effect(*args): - if args[0] == 47: - return False - return True - - with mock.patch('optimizely.helpers.validator.is_finite_number', - side_effect=side_effect) as is_finite: - self.assertIsNone(evaluator.evaluate(0)) - is_finite.assert_any_call(47) - - if PY2: - evaluator = condition_helper.CustomAttributeConditionEvaluator( - lt_int_condition_list, {'meters_travelled': long(47)} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - side_effect=side_effect) as is_finite: - self.assertIsNone(evaluator.evaluate(0)) - is_finite.assert_called_with(long(47)) - def test_less_than_float__returns_true__when_user_value_less_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -686,42 +537,6 @@ def test_less_than_float__returns_true__when_user_value_less_than_condition_valu self.assertStrictTrue(evaluator.evaluate(0)) - def test_less_than_float__calls_is_finite_number(self): - """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - lt_float_condition_list, {'meters_travelled': 48.1} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - return_value=True) as is_finite: - self.assertStrictTrue(evaluator.evaluate(0)) - is_finite.assert_called_with(48.1) - - evaluator = condition_helper.CustomAttributeConditionEvaluator( - lt_float_condition_list, {'meters_travelled': 48} - ) - - def side_effect(*args): - if args[0] == 48: - return False - return True - - with mock.patch('optimizely.helpers.validator.is_finite_number', - side_effect=side_effect) as is_finite: - self.assertIsNone(evaluator.evaluate(0)) - is_finite.assert_any_call(48) - - if PY2: - evaluator = condition_helper.CustomAttributeConditionEvaluator( - lt_float_condition_list, {'meters_travelled': long(48)} - ) - - with mock.patch('optimizely.helpers.validator.is_finite_number', - side_effect=side_effect) as is_finite: - self.assertIsNone(evaluator.evaluate(0)) - is_finite.assert_called_with(long(48)) - def test_less_than_int__returns_false__when_user_value_not_less_than_condition_value(self): evaluator = condition_helper.CustomAttributeConditionEvaluator( @@ -796,6 +611,82 @@ def test_less_than_float__returns_null__when_no_user_provided_value(self): self.assertIsNone(evaluator.evaluate(0)) + def test_greater_than__calls_is_finite_number(self): + """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + gt_int_condition_list, {'meters_travelled': 48.1} + ) + + 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 is_finite_mock: + self.assertIsNone(evaluator.evaluate(0)) + + # assert that isFiniteNumber only needs to reject condition value to stop evaluation. + is_finite_mock.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 is_finite_mock: + self.assertIsNone(evaluator.evaluate(0)) + + # assert that isFiniteNumber evaluates user value only if it has accepted condition value. + is_finite_mock.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__calls_is_finite_number(self): + """ Returns True if is_finite_number returns True. Returns None if is_finite_number returns False. """ + + evaluator = condition_helper.CustomAttributeConditionEvaluator( + lt_int_condition_list, {'meters_travelled': 47} + ) + + 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 is_finite_mock: + self.assertIsNone(evaluator.evaluate(0)) + + # assert that isFiniteNumber only needs to reject condition value to stop evaluation. + is_finite_mock.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 is_finite_mock: + self.assertIsNone(evaluator.evaluate(0)) + + # assert that isFiniteNumber evaluates user value only if it has accepted condition value. + is_finite_mock.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): diff --git a/tests/helpers_tests/test_validator.py b/tests/helpers_tests/test_validator.py index 23b28757..cca4a0c5 100644 --- a/tests/helpers_tests/test_validator.py +++ b/tests/helpers_tests/test_validator.py @@ -220,6 +220,7 @@ def test_is_finite_number(self): self.assertTrue(validator.is_finite_number(0)) self.assertTrue(validator.is_finite_number(5)) self.assertTrue(validator.is_finite_number(5.5)) + # float(2**53) + 1.0 evaluates to float(2**53) self.assertTrue(validator.is_finite_number(float(2**53) + 1.0)) self.assertTrue(validator.is_finite_number(-float(2**53) - 1.0)) self.assertTrue(validator.is_finite_number(int(2**53)))