From 9c380cd336edcb3b7990303f41bbe755e27bda7a Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 12 Jul 2019 15:15:07 -0700 Subject: [PATCH 1/4] add get_feature_variable method, change private method --- optimizely/optimizely.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/optimizely/optimizely.py b/optimizely/optimizely.py index 7bd125e3..4faa230e 100644 --- a/optimizely/optimizely.py +++ b/optimizely/optimizely.py @@ -204,7 +204,9 @@ def _get_feature_variable_for_type(self, feature_key, variable_key, variable_typ return None # Return None if type differs - if variable.type != variable_type: + if not variable.type: + variableType = variable.type + elif variable.type != variable_type: self.logger.warning( 'Requested variable type "%s", but variable is of type "%s". ' 'Use correct API to retrieve value. Returning None.' % (variable_type, variable.type) @@ -513,6 +515,24 @@ def get_enabled_features(self, user_id, attributes=None): return enabled_features + def get_feature_variable(self, feature_key, variable_key, user_id, attributes=None): + """ Returns value for a certain boolean variable attached to a feature flag. + + Args: + feature_key: Key of the feature whose variable's value is being accessed. + variable_key: Key of the variable whose value is to be accessed. + user_id: ID for user. + attributes: Dict representing user attributes. + + Returns: + Boolean value of the variable. None if: + - Feature key is invalid. + - Variable key is invalid. + - Mismatch with type of variable. + """ + + return self._get_feature_variable_for_type(feature_key, variable_key, None, user_id, attributes) + def get_feature_variable_boolean(self, feature_key, variable_key, user_id, attributes=None): """ Returns value for a certain boolean variable attached to a feature flag. From 4391c2901d7784da891caa1049c246512a84b702 Mon Sep 17 00:00:00 2001 From: Brandon Date: Mon, 22 Jul 2019 16:17:00 -0700 Subject: [PATCH 2/4] Working on unit tests --- optimizely/optimizely.py | 7 +- tests/test_optimizely.py | 136 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 4 deletions(-) diff --git a/optimizely/optimizely.py b/optimizely/optimizely.py index 4faa230e..2ac18e8f 100644 --- a/optimizely/optimizely.py +++ b/optimizely/optimizely.py @@ -203,7 +203,7 @@ def _get_feature_variable_for_type(self, feature_key, variable_key, variable_typ if not variable: return None - # Return None if type differs + # For non-typed method, use type of variable; else, return None if type differs if not variable.type: variableType = variable.type elif variable.type != variable_type: @@ -516,7 +516,7 @@ def get_enabled_features(self, user_id, attributes=None): return enabled_features def get_feature_variable(self, feature_key, variable_key, user_id, attributes=None): - """ Returns value for a certain boolean variable attached to a feature flag. + """ Returns value for a variable attached to a feature flag. Args: feature_key: Key of the feature whose variable's value is being accessed. @@ -525,10 +525,9 @@ def get_feature_variable(self, feature_key, variable_key, user_id, attributes=No attributes: Dict representing user attributes. Returns: - Boolean value of the variable. None if: + Value of the variable. None if: - Feature key is invalid. - Variable key is invalid. - - Mismatch with type of variable. """ return self._get_feature_variable_for_type(feature_key, variable_key, None, user_id, attributes) diff --git a/tests/test_optimizely.py b/tests/test_optimizely.py index cf9dc8ef..e4e808ff 100644 --- a/tests/test_optimizely.py +++ b/tests/test_optimizely.py @@ -2148,6 +2148,142 @@ def test_get_feature_variable_string(self): } ) + def test_get_feature_variable(self): + """ Test that get_feature_variable returns variable value as expected \ + and broadcasts decision with proper parameters. """ + + opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_features)) + mock_experiment = opt_obj.config.get_experiment_from_key('test_experiment') + mock_variation = opt_obj.config.get_variation_from_id('test_experiment', '111129') + # Boolean + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, + mock_variation, + enums.DecisionSources.FEATURE_TEST)), \ + mock.patch.object(opt_obj.config, 'logger') as mock_config_logging, \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_decision: + self.assertTrue(opt_obj.get_feature_variable('test_feature_in_experiment', 'is_working', 'test_user')) + + mock_config_logging.info.assert_called_once_with( + 'Value for variable "is_working" for variation "variation" is "true".' + ) + + mock_broadcast_decision.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'feature-variable', + 'test_user', + {}, + { + 'feature_key': 'test_feature_in_experiment', + 'feature_enabled': True, + 'source': 'feature-test', + 'variable_key': 'is_working', + 'variable_value': True, + 'variable_type': 'boolean', + 'source_info': { + 'experiment_key': 'test_experiment', + 'variation_key': 'variation' + } + } + ) + # Double + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, + mock_variation, + enums.DecisionSources.FEATURE_TEST)), \ + mock.patch.object(opt_obj.config, 'logger') as mock_config_logging, \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_decision: + self.assertEqual(10.02, opt_obj.get_feature_variable('test_feature_in_experiment', 'cost', 'test_user')) + + mock_config_logging.info.assert_called_once_with( + 'Value for variable "cost" for variation "variation" is "10.02".' + ) + + mock_broadcast_decision.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'feature-variable', + 'test_user', + {}, + { + 'feature_key': 'test_feature_in_experiment', + 'feature_enabled': True, + 'source': 'feature-test', + 'variable_key': 'cost', + 'variable_value': 10.02, + 'variable_type': 'double', + 'source_info': { + 'experiment_key': 'test_experiment', + 'variation_key': 'variation' + } + } + ) + # Integer + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, + mock_variation, + enums.DecisionSources.FEATURE_TEST)), \ + mock.patch.object(opt_obj.config, 'logger') as mock_config_logging, \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_decision: + self.assertEqual(4243, opt_obj.get_feature_variable('test_feature_in_experiment', 'count', 'test_user')) + + mock_config_logging.info.assert_called_once_with( + 'Value for variable "count" for variation "variation" is "4243".' + ) + + mock_broadcast_decision.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'feature-variable', + 'test_user', + {}, + { + 'feature_key': 'test_feature_in_experiment', + 'feature_enabled': True, + 'source': 'feature-test', + 'variable_key': 'count', + 'variable_value': 4243, + 'variable_type': 'integer', + 'source_info': { + 'experiment_key': 'test_experiment', + 'variation_key': 'variation' + } + } + ) + # String + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, + mock_variation, + enums.DecisionSources.FEATURE_TEST)), \ + mock.patch.object(opt_obj.config, 'logger') as mock_config_logging, \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_decision: + self.assertEqual( + 'staging', + opt_obj.get_feature_variable('test_feature_in_experiment', 'environment', 'test_user') + ) + + mock_config_logging.info.assert_called_once_with( + 'Value for variable "environment" for variation "variation" is "staging".' + ) + + mock_broadcast_decision.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'feature-variable', + 'test_user', + {}, + { + 'feature_key': 'test_feature_in_experiment', + 'feature_enabled': True, + 'source': 'feature-test', + 'variable_key': 'environment', + 'variable_value': 'staging', + 'variable_type': 'string', + 'source_info': { + 'experiment_key': 'test_experiment', + 'variation_key': 'variation' + } + } + ) + + def test_get_feature_variable_boolean_for_feature_in_rollout(self): """ Test that get_feature_variable_boolean returns Boolean value as expected \ and broadcasts decision with proper parameters. """ From 88da24c83a9b620c4fa90926f169226f7ce3f5d5 Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 23 Jul 2019 11:01:40 -0700 Subject: [PATCH 3/4] change variableType to variable_type, add unit tests --- optimizely/optimizely.py | 4 +- tests/test_optimizely.py | 474 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 473 insertions(+), 5 deletions(-) diff --git a/optimizely/optimizely.py b/optimizely/optimizely.py index 2ac18e8f..63aefa77 100644 --- a/optimizely/optimizely.py +++ b/optimizely/optimizely.py @@ -204,8 +204,8 @@ def _get_feature_variable_for_type(self, feature_key, variable_key, variable_typ return None # For non-typed method, use type of variable; else, return None if type differs - if not variable.type: - variableType = variable.type + if not variable_type: + variable_type = variable.type elif variable.type != variable_type: self.logger.warning( 'Requested variable type "%s", but variable is of type "%s". ' diff --git a/tests/test_optimizely.py b/tests/test_optimizely.py index e4e808ff..335e6aa6 100644 --- a/tests/test_optimizely.py +++ b/tests/test_optimizely.py @@ -2436,6 +2436,132 @@ def test_get_feature_variable_string_for_feature_in_rollout(self): } ) + def test_get_feature_variable_for_feature_in_rollout(self): + """ Test that get_feature_variable returns Double value as expected \ + and broadcasts decision with proper parameters. """ + + opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_features)) + mock_experiment = opt_obj.config.get_experiment_from_key('211127') + mock_variation = opt_obj.config.get_variation_from_id('211127', '211129') + user_attributes = {'test_attribute': 'test_value'} + + # Boolean + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, + mock_variation, + enums.DecisionSources.ROLLOUT)), \ + mock.patch.object(opt_obj.config, 'logger') as mock_config_logging, \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_decision: + self.assertTrue(opt_obj.get_feature_variable('test_feature_in_rollout', 'is_running', 'test_user', + attributes=user_attributes)) + + mock_config_logging.info.assert_called_once_with( + 'Value for variable "is_running" for variation "211129" is "true".' + ) + + mock_broadcast_decision.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'feature-variable', + 'test_user', + {'test_attribute': 'test_value'}, + { + 'feature_key': 'test_feature_in_rollout', + 'feature_enabled': True, + 'source': 'rollout', + 'variable_key': 'is_running', + 'variable_value': True, + 'variable_type': 'boolean', + 'source_info': {} + } + ) + # Double + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, + mock_variation, + enums.DecisionSources.ROLLOUT)), \ + mock.patch.object(opt_obj.config, 'logger') as mock_config_logging, \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_decision: + self.assertTrue(opt_obj.get_feature_variable('test_feature_in_rollout', 'price', 'test_user', + attributes=user_attributes)) + + mock_config_logging.info.assert_called_once_with( + 'Value for variable "price" for variation "211129" is "39.99".' + ) + + mock_broadcast_decision.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'feature-variable', + 'test_user', + {'test_attribute': 'test_value'}, + { + 'feature_key': 'test_feature_in_rollout', + 'feature_enabled': True, + 'source': 'rollout', + 'variable_key': 'price', + 'variable_value': 39.99, + 'variable_type': 'double', + 'source_info': {} + } + ) + # Integer + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, + mock_variation, + enums.DecisionSources.ROLLOUT)), \ + mock.patch.object(opt_obj.config, 'logger') as mock_config_logging, \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_decision: + self.assertTrue(opt_obj.get_feature_variable('test_feature_in_rollout', 'count', 'test_user', + attributes=user_attributes)) + + mock_config_logging.info.assert_called_once_with( + 'Value for variable "count" for variation "211129" is "399".' + ) + + mock_broadcast_decision.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'feature-variable', + 'test_user', + {'test_attribute': 'test_value'}, + { + 'feature_key': 'test_feature_in_rollout', + 'feature_enabled': True, + 'source': 'rollout', + 'variable_key': 'count', + 'variable_value': 399, + 'variable_type': 'integer', + 'source_info': {} + } + ) + # String + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, + mock_variation, + enums.DecisionSources.ROLLOUT)), \ + mock.patch.object(opt_obj.config, 'logger') as mock_config_logging, \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_decision: + self.assertTrue(opt_obj.get_feature_variable('test_feature_in_rollout', 'message', 'test_user', + attributes=user_attributes)) + + mock_config_logging.info.assert_called_once_with( + 'Value for variable "message" for variation "211129" is "Hello audience".' + ) + + mock_broadcast_decision.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'feature-variable', + 'test_user', + {'test_attribute': 'test_value'}, + { + 'feature_key': 'test_feature_in_rollout', + 'feature_enabled': True, + 'source': 'rollout', + 'variable_key': 'message', + 'variable_value': 'Hello audience', + 'variable_type': 'string', + 'source_info': {} + } + ) + def test_get_feature_variable__returns_default_value_if_variable_usage_not_in_variation(self): """ Test that get_feature_variable_* returns default value if variable usage not present in variation. """ @@ -2497,6 +2623,54 @@ def test_get_feature_variable__returns_default_value_if_variable_usage_not_in_va ) mock_config_logger.info.reset_mock() + # Non-typed + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, mock_variation, + enums.DecisionSources.FEATURE_TEST)), \ + mock.patch.object(opt_obj.config, 'logger') as mock_config_logger: + self.assertTrue(opt_obj.get_feature_variable('test_feature_in_experiment', 'is_working', 'test_user')) + + mock_config_logger.info.assert_called_once_with( + 'Variable "is_working" is not used in variation "variation". Assigning default value "true".' + ) + mock_config_logger.info.reset_mock() + + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, mock_variation, + enums.DecisionSources.FEATURE_TEST)), \ + mock.patch.object(opt_obj.config, 'logger') as mock_config_logger: + self.assertEqual(10.99, + opt_obj.get_feature_variable('test_feature_in_experiment', 'cost', 'test_user')) + + mock_config_logger.info.assert_called_once_with( + 'Variable "cost" is not used in variation "variation". Assigning default value "10.99".' + ) + mock_config_logger.info.reset_mock() + + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, mock_variation, + enums.DecisionSources.FEATURE_TEST)), \ + mock.patch.object(opt_obj.config, 'logger') as mock_config_logger: + self.assertEqual(999, + opt_obj.get_feature_variable('test_feature_in_experiment', 'count', 'test_user')) + + mock_config_logger.info.assert_called_once_with( + 'Variable "count" is not used in variation "variation". Assigning default value "999".' + ) + mock_config_logger.info.reset_mock() + + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, mock_variation, + enums.DecisionSources.FEATURE_TEST)), \ + mock.patch.object(opt_obj.config, 'logger') as mock_config_logger: + self.assertEqual('devel', + opt_obj.get_feature_variable('test_feature_in_experiment', 'environment', 'test_user')) + + mock_config_logger.info.assert_called_once_with( + 'Variable "environment" is not used in variation "variation". Assigning default value "devel".' + ) + mock_config_logger.info.reset_mock() + def test_get_feature_variable__returns_default_value_if_no_variation(self): """ Test that get_feature_variable_* returns default value if no variation \ and broadcasts decision with proper parameters. """ @@ -2628,6 +2802,128 @@ def test_get_feature_variable__returns_default_value_if_no_variation(self): } ) + # Non-typed + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(None, None, + enums.DecisionSources.ROLLOUT)), \ + mock.patch.object(opt_obj, 'logger') as mock_client_logger, \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_decision: + self.assertTrue(opt_obj.get_feature_variable('test_feature_in_experiment', 'is_working', 'test_user')) + + mock_client_logger.info.assert_called_once_with( + 'User "test_user" is not in any variation or rollout rule. ' + 'Returning default value for variable "is_working" of feature flag "test_feature_in_experiment".' + ) + + mock_broadcast_decision.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'feature-variable', + 'test_user', + {}, + { + 'feature_key': 'test_feature_in_experiment', + 'feature_enabled': False, + 'source': 'rollout', + 'variable_key': 'is_working', + 'variable_value': True, + 'variable_type': 'boolean', + 'source_info': {} + } + ) + + mock_client_logger.info.reset_mock() + + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(None, None, + enums.DecisionSources.ROLLOUT)), \ + mock.patch.object(opt_obj, 'logger') as mock_client_logger, \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_decision: + self.assertEqual(10.99, + opt_obj.get_feature_variable('test_feature_in_experiment', 'cost', 'test_user')) + + mock_client_logger.info.assert_called_once_with( + 'User "test_user" is not in any variation or rollout rule. ' + 'Returning default value for variable "cost" of feature flag "test_feature_in_experiment".' + ) + + mock_broadcast_decision.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'feature-variable', + 'test_user', + {}, + { + 'feature_key': 'test_feature_in_experiment', + 'feature_enabled': False, + 'source': 'rollout', + 'variable_key': 'cost', + 'variable_value': 10.99, + 'variable_type': 'double', + 'source_info': {} + } + ) + + mock_client_logger.info.reset_mock() + + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(None, None, + enums.DecisionSources.ROLLOUT)), \ + mock.patch.object(opt_obj, 'logger') as mock_client_logger, \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_decision: + self.assertEqual(999, + opt_obj.get_feature_variable('test_feature_in_experiment', 'count', 'test_user')) + + mock_client_logger.info.assert_called_once_with( + 'User "test_user" is not in any variation or rollout rule. ' + 'Returning default value for variable "count" of feature flag "test_feature_in_experiment".' + ) + + mock_broadcast_decision.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'feature-variable', + 'test_user', + {}, + { + 'feature_key': 'test_feature_in_experiment', + 'feature_enabled': False, + 'source': 'rollout', + 'variable_key': 'count', + 'variable_value': 999, + 'variable_type': 'integer', + 'source_info': {} + } + ) + + mock_client_logger.info.reset_mock() + + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(None, None, + enums.DecisionSources.ROLLOUT)), \ + mock.patch.object(opt_obj, 'logger') as mock_client_logger, \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_decision: + self.assertEqual('devel', + opt_obj.get_feature_variable('test_feature_in_experiment', 'environment', 'test_user')) + + mock_client_logger.info.assert_called_once_with( + 'User "test_user" is not in any variation or rollout rule. ' + 'Returning default value for variable "environment" of feature flag "test_feature_in_experiment".' + ) + + mock_broadcast_decision.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'feature-variable', + 'test_user', + {}, + { + 'feature_key': 'test_feature_in_experiment', + 'feature_enabled': False, + 'source': 'rollout', + 'variable_key': 'environment', + 'variable_value': 'devel', + 'variable_type': 'string', + 'source_info': {} + } + ) + def test_get_feature_variable__returns_none_if_none_feature_key(self): """ Test that get_feature_variable_* returns None for None feature key. """ @@ -2653,6 +2949,11 @@ def test_get_feature_variable__returns_none_if_none_feature_key(self): mock_client_logger.error.assert_called_with('Provided "feature_key" is in an invalid format.') mock_client_logger.reset_mock() + # Check for non-typed + self.assertIsNone(opt_obj.get_feature_variable(None, 'variable_key', 'test_user')) + mock_client_logger.error.assert_called_with('Provided "feature_key" is in an invalid format.') + mock_client_logger.reset_mock() + def test_get_feature_variable__returns_none_if_none_variable_key(self): """ Test that get_feature_variable_* returns None for None variable key. """ @@ -2678,6 +2979,11 @@ def test_get_feature_variable__returns_none_if_none_variable_key(self): mock_client_logger.error.assert_called_with('Provided "variable_key" is in an invalid format.') mock_client_logger.reset_mock() + # Check for non-typed + self.assertIsNone(opt_obj.get_feature_variable('feature_key', None, 'test-User')) + mock_client_logger.error.assert_called_with('Provided "variable_key" is in an invalid format.') + mock_client_logger.reset_mock() + def test_get_feature_variable__returns_none_if_none_user_id(self): """ Test that get_feature_variable_* returns None for None user ID. """ @@ -2703,6 +3009,11 @@ def test_get_feature_variable__returns_none_if_none_user_id(self): mock_client_logger.error.assert_called_with('Provided "user_id" is in an invalid format.') mock_client_logger.reset_mock() + # Check for non-typed + self.assertIsNone(opt_obj.get_feature_variable('feature_key', 'variable_key', None)) + mock_client_logger.error.assert_called_with('Provided "user_id" is in an invalid format.') + mock_client_logger.reset_mock() + def test_get_feature_variable__invalid_attributes(self): """ Test that get_feature_variable_* returns None for invalid attributes. """ @@ -2749,6 +3060,41 @@ def test_get_feature_variable__invalid_attributes(self): mock_validator.reset_mock() mock_client_logging.reset_mock() + # get_feature_variable + self.assertIsNone( + opt_obj.get_feature_variable('test_feature_in_experiment', + 'is_working', 'test_user', attributes='invalid') + ) + mock_validator.assert_called_once_with('invalid') + mock_client_logging.error.assert_called_once_with('Provided attributes are in an invalid format.') + mock_validator.reset_mock() + mock_client_logging.reset_mock() + + self.assertIsNone( + opt_obj.get_feature_variable('test_feature_in_experiment', 'cost', 'test_user', attributes='invalid') + ) + mock_validator.assert_called_once_with('invalid') + mock_client_logging.error.assert_called_once_with('Provided attributes are in an invalid format.') + mock_validator.reset_mock() + mock_client_logging.reset_mock() + + self.assertIsNone( + opt_obj.get_feature_variable('test_feature_in_experiment', 'count', 'test_user', attributes='invalid') + ) + mock_validator.assert_called_once_with('invalid') + mock_client_logging.error.assert_called_once_with('Provided attributes are in an invalid format.') + mock_validator.reset_mock() + mock_client_logging.reset_mock() + + self.assertIsNone( + opt_obj.get_feature_variable('test_feature_in_experiment', + 'environment', 'test_user', attributes='invalid') + ) + mock_validator.assert_called_once_with('invalid') + mock_client_logging.error.assert_called_once_with('Provided attributes are in an invalid format.') + mock_validator.reset_mock() + mock_client_logging.reset_mock() + def test_get_feature_variable__returns_none_if_invalid_feature_key(self): """ Test that get_feature_variable_* returns None for invalid feature key. """ @@ -2758,9 +3104,18 @@ def test_get_feature_variable__returns_none_if_invalid_feature_key(self): self.assertIsNone(opt_obj.get_feature_variable_double('invalid_feature', 'cost', 'test_user')) self.assertIsNone(opt_obj.get_feature_variable_integer('invalid_feature', 'count', 'test_user')) self.assertIsNone(opt_obj.get_feature_variable_string('invalid_feature', 'environment', 'test_user')) + self.assertIsNone(opt_obj.get_feature_variable('invalid_feature', 'is_working', 'test_user')) + self.assertIsNone(opt_obj.get_feature_variable('invalid_feature', 'cost', 'test_user')) + self.assertIsNone(opt_obj.get_feature_variable('invalid_feature', 'count', 'test_user')) + self.assertIsNone(opt_obj.get_feature_variable('invalid_feature', 'environment', 'test_user')) + - self.assertEqual(4, mock_config_logger.error.call_count) + self.assertEqual(8, mock_config_logger.error.call_count) mock_config_logger.error.assert_has_calls([ + mock.call('Feature "invalid_feature" is not in datafile.'), + mock.call('Feature "invalid_feature" is not in datafile.'), + mock.call('Feature "invalid_feature" is not in datafile.'), + mock.call('Feature "invalid_feature" is not in datafile.'), mock.call('Feature "invalid_feature" is not in datafile.'), mock.call('Feature "invalid_feature" is not in datafile.'), mock.call('Feature "invalid_feature" is not in datafile.'), @@ -2784,11 +3139,16 @@ def test_get_feature_variable__returns_none_if_invalid_variable_key(self): self.assertIsNone(opt_obj.get_feature_variable_string('test_feature_in_experiment', 'invalid_variable', 'test_user')) - self.assertEqual(4, mock_config_logger.error.call_count) + self.assertIsNone(opt_obj.get_feature_variable('test_feature_in_experiment', + 'invalid_variable', + 'test_user')) + + self.assertEqual(5, mock_config_logger.error.call_count) mock_config_logger.error.assert_has_calls([ mock.call('Variable with key "invalid_variable" not found in the datafile.'), mock.call('Variable with key "invalid_variable" not found in the datafile.'), mock.call('Variable with key "invalid_variable" not found in the datafile.'), + mock.call('Variable with key "invalid_variable" not found in the datafile.'), mock.call('Variable with key "invalid_variable" not found in the datafile.') ]) @@ -2851,6 +3211,55 @@ def test_get_feature_variable__returns_default_value_if_feature_not_enabled(self 'Returning the default variable value "devel".' ) + # Non-typed + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, mock_variation, + enums.DecisionSources.FEATURE_TEST)), \ + mock.patch.object(opt_obj, 'logger') as mock_client_logger: + + self.assertTrue(opt_obj.get_feature_variable('test_feature_in_experiment', 'is_working', 'test_user')) + + mock_client_logger.info.assert_called_once_with( + 'Feature "test_feature_in_experiment" for variation "control" is not enabled. ' + 'Returning the default variable value "true".' + ) + + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, mock_variation, + enums.DecisionSources.FEATURE_TEST)), \ + mock.patch.object(opt_obj, 'logger') as mock_client_logger: + self.assertEqual(10.99, + opt_obj.get_feature_variable('test_feature_in_experiment', 'cost', 'test_user')) + + mock_client_logger.info.assert_called_once_with( + 'Feature "test_feature_in_experiment" for variation "control" is not enabled. ' + 'Returning the default variable value "10.99".' + ) + + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, mock_variation, + enums.DecisionSources.FEATURE_TEST)), \ + mock.patch.object(opt_obj, 'logger') as mock_client_logger: + self.assertEqual(999, + opt_obj.get_feature_variable('test_feature_in_experiment', 'count', 'test_user')) + + mock_client_logger.info.assert_called_once_with( + 'Feature "test_feature_in_experiment" for variation "control" is not enabled. ' + 'Returning the default variable value "999".' + ) + + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, mock_variation, + enums.DecisionSources.FEATURE_TEST)), \ + mock.patch.object(opt_obj, 'logger') as mock_client_logger: + self.assertEqual('devel', + opt_obj.get_feature_variable('test_feature_in_experiment', 'environment', 'test_user')) + + mock_client_logger.info.assert_called_once_with( + 'Feature "test_feature_in_experiment" for variation "control" is not enabled. ' + 'Returning the default variable value "devel".' + ) + def test_get_feature_variable__returns_default_value_if_feature_not_enabled_in_rollout(self): """ Test that get_feature_variable_* returns default value if feature is not enabled for the user. """ @@ -2908,6 +3317,53 @@ def test_get_feature_variable__returns_default_value_if_feature_not_enabled_in_r 'Returning the default variable value "Hello".' ) + # Non-typed + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, mock_variation, + enums.DecisionSources.ROLLOUT)), \ + mock.patch.object(opt_obj, 'logger') as mock_client_logger: + self.assertFalse(opt_obj.get_feature_variable('test_feature_in_rollout', 'is_running', 'test_user')) + + mock_client_logger.info.assert_called_once_with( + 'Feature "test_feature_in_rollout" for variation "211229" is not enabled. ' + 'Returning the default variable value "false".' + ) + + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, mock_variation, + enums.DecisionSources.ROLLOUT)), \ + mock.patch.object(opt_obj, 'logger') as mock_client_logger: + self.assertEqual(99.99, + opt_obj.get_feature_variable('test_feature_in_rollout', 'price', 'test_user')) + + mock_client_logger.info.assert_called_once_with( + 'Feature "test_feature_in_rollout" for variation "211229" is not enabled. ' + 'Returning the default variable value "99.99".' + ) + + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, mock_variation, + enums.DecisionSources.ROLLOUT)), \ + mock.patch.object(opt_obj, 'logger') as mock_client_logger: + self.assertEqual(999, + opt_obj.get_feature_variable('test_feature_in_rollout', 'count', 'test_user')) + + mock_client_logger.info.assert_called_once_with( + 'Feature "test_feature_in_rollout" for variation "211229" is not enabled. ' + 'Returning the default variable value "999".' + ) + + with mock.patch('optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, mock_variation, + enums.DecisionSources.ROLLOUT)), \ + mock.patch.object(opt_obj, 'logger') as mock_client_logger: + self.assertEqual('Hello', + opt_obj.get_feature_variable('test_feature_in_rollout', 'message', 'test_user')) + mock_client_logger.info.assert_called_once_with( + 'Feature "test_feature_in_rollout" for variation "211229" is not enabled. ' + 'Returning the default variable value "Hello".' + ) + def test_get_feature_variable__returns_none_if_type_mismatch(self): """ Test that get_feature_variable_* returns None if type mismatch. """ @@ -2971,8 +3427,8 @@ def test_get_feature_variable_returns__variable_value__typed_audience_match(self 'Got variable value "xyz" for variable "x" of feature flag "feat_with_var".' ) - def test_get_feature_variable_returns__default_value__typed_audience_match(self): """ Test that get_feature_variable_* return default value with typed audience mismatch. """ + def test_get_feature_variable_returns__default_value__typed_audience_match(self): opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_typed_audiences)) @@ -2980,6 +3436,10 @@ def test_get_feature_variable_returns__default_value__typed_audience_match(self) 'x', opt_obj.get_feature_variable_string('feat_with_var', 'x', 'user1', {'lasers': 50}) ) + self.assertEqual( + 'x', + opt_obj.get_feature_variable('feat_with_var', 'x', 'user1', {'lasers': 50}) + ) def test_get_feature_variable_returns__variable_value__complex_audience_match(self): """ Test that get_feature_variable_* return variable value with complex audience match. """ @@ -2993,6 +3453,10 @@ def test_get_feature_variable_returns__variable_value__complex_audience_match(se 150, opt_obj.get_feature_variable_integer('feat2_with_var', 'z', 'user1', user_attr) ) + self.assertEqual( + 150, + opt_obj.get_feature_variable('feat2_with_var', 'z', 'user1', user_attr) + ) def test_get_feature_variable_returns__default_value__complex_audience_match(self): """ Test that get_feature_variable_* return default value with complex audience mismatch. """ @@ -3004,6 +3468,10 @@ def test_get_feature_variable_returns__default_value__complex_audience_match(sel 10, opt_obj.get_feature_variable_integer('feat2_with_var', 'z', 'user1', {}) ) + self.assertEqual( + 10, + opt_obj.get_feature_variable('feat2_with_var', 'z', 'user1', {}) + ) class OptimizelyWithExceptionTest(base.BaseTest): From 4c1e99675e1f9315426c799f623ed9b9eaf6f4de Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 23 Jul 2019 11:34:11 -0700 Subject: [PATCH 4/4] linting --- tests/test_optimizely.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_optimizely.py b/tests/test_optimizely.py index 335e6aa6..dab18f36 100644 --- a/tests/test_optimizely.py +++ b/tests/test_optimizely.py @@ -2282,7 +2282,6 @@ def test_get_feature_variable(self): } } ) - def test_get_feature_variable_boolean_for_feature_in_rollout(self): """ Test that get_feature_variable_boolean returns Boolean value as expected \ @@ -3108,7 +3107,6 @@ def test_get_feature_variable__returns_none_if_invalid_feature_key(self): self.assertIsNone(opt_obj.get_feature_variable('invalid_feature', 'cost', 'test_user')) self.assertIsNone(opt_obj.get_feature_variable('invalid_feature', 'count', 'test_user')) self.assertIsNone(opt_obj.get_feature_variable('invalid_feature', 'environment', 'test_user')) - self.assertEqual(8, mock_config_logger.error.call_count) mock_config_logger.error.assert_has_calls([ @@ -3142,7 +3140,7 @@ def test_get_feature_variable__returns_none_if_invalid_variable_key(self): self.assertIsNone(opt_obj.get_feature_variable('test_feature_in_experiment', 'invalid_variable', 'test_user')) - + self.assertEqual(5, mock_config_logger.error.call_count) mock_config_logger.error.assert_has_calls([ mock.call('Variable with key "invalid_variable" not found in the datafile.'),