diff --git a/optimizely/helpers/enums.py b/optimizely/helpers/enums.py index b1857574..fc81d451 100644 --- a/optimizely/helpers/enums.py +++ b/optimizely/helpers/enums.py @@ -89,6 +89,13 @@ class NotificationTypes(object): Experiment experiment, str user_id, dict attributes (can be None), Variation variation, Event event TRACK notification listener has the following parameters: str event_key, str user_id, dict attributes (can be None), event_tags (can be None), Event event + DECISION notification listener has the following parameters: + DecisionInfoTypes type, str user_id, dict attributes (can be None), dict decision_info """ ACTIVATE = "ACTIVATE:experiment, user_id, attributes, variation, event" TRACK = "TRACK:event_key, user_id, attributes, event_tags, event" + DECISION = "DECISON:type, user_id, attributes, decision_info" + + +class DecisionInfoTypes(object): + EXPERIMENT = "experiment" diff --git a/optimizely/optimizely.py b/optimizely/optimizely.py index f27f0ded..801c1536 100644 --- a/optimizely/optimizely.py +++ b/optimizely/optimizely.py @@ -336,6 +336,7 @@ def get_variation(self, experiment_key, user_id, attributes=None): return None experiment = self.config.get_experiment_from_key(experiment_key) + variation_key = None if not experiment: self.logger.info('Experiment key "%s" is invalid. Not activating user "%s".' % ( @@ -349,9 +350,20 @@ def get_variation(self, experiment_key, user_id, attributes=None): variation = self.decision_service.get_variation(experiment, user_id, attributes) if variation: - return variation.key - - return None + variation_key = variation.key + + self.notification_center.send_notifications( + enums.NotificationTypes.DECISION, + enums.DecisionInfoTypes.EXPERIMENT, + user_id, + attributes or {}, + { + 'experiment_key': experiment_key, + 'variation_key': variation_key + } + ) + + return variation_key def is_feature_enabled(self, feature_key, user_id, attributes=None): """ Returns true if the feature is enabled for the given user. diff --git a/tests/test_optimizely.py b/tests/test_optimizely.py index 499fad62..82311620 100644 --- a/tests/test_optimizely.py +++ b/tests/test_optimizely.py @@ -398,42 +398,92 @@ def test_remove_listener(self): self.assertEqual(0, len(self.optimizely.notification_center.notifications[enums.NotificationTypes.TRACK])) self.assertEqual(0, len(self.optimizely.notification_center.notifications[enums.NotificationTypes.ACTIVATE])) - def test_activate_listener(self): - """ Test that activate calls broadcast activate with proper parameters. """ + def test_activate_and_decision_listener(self): + """ Test that activate calls broadcast activate and decision with proper parameters. """ with mock.patch( 'optimizely.decision_service.DecisionService.get_variation', return_value=self.project_config.get_variation_from_id('test_experiment', '111129')), \ mock.patch('optimizely.event_dispatcher.EventDispatcher.dispatch_event') as mock_dispatch, \ - mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_activate: + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast: self.assertEqual('variation', self.optimizely.activate('test_experiment', 'test_user')) - mock_broadcast_activate.assert_called_once_with(enums.NotificationTypes.ACTIVATE, - self.project_config.get_experiment_from_key('test_experiment'), - 'test_user', None, - self.project_config.get_variation_from_id('test_experiment', - '111129'), - mock_dispatch.call_args[0][0]) + self.assertEqual(mock_broadcast.call_count, 2) - def test_activate_listener_with_attr(self): - """ Test that activate calls broadcast activate with proper parameters. """ + mock_broadcast.assert_has_calls([ + mock.call( + enums.NotificationTypes.DECISION, + 'experiment', + 'test_user', + {}, + { + 'experiment_key': 'test_experiment', + 'variation_key': 'variation' + } + ), + mock.call( + enums.NotificationTypes.ACTIVATE, + self.project_config.get_experiment_from_key('test_experiment'), + 'test_user', None, + self.project_config.get_variation_from_id('test_experiment', '111129'), + mock_dispatch.call_args[0][0] + ) + ]) + + def test_activate_and_decision_listener_with_attr(self): + """ Test that activate calls broadcast activate and decision with proper parameters. """ with mock.patch( 'optimizely.decision_service.DecisionService.get_variation', return_value=self.project_config.get_variation_from_id('test_experiment', '111129')), \ mock.patch('optimizely.event_dispatcher.EventDispatcher.dispatch_event') as mock_dispatch, \ - mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_activate: + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast: self.assertEqual('variation', self.optimizely.activate('test_experiment', 'test_user', {'test_attribute': 'test_value'})) - mock_broadcast_activate.assert_called_once_with(enums.NotificationTypes.ACTIVATE, - self.project_config.get_experiment_from_key('test_experiment'), - 'test_user', {'test_attribute': 'test_value'}, - self.project_config.get_variation_from_id( - 'test_experiment', '111129' - ), - mock_dispatch.call_args[0][0] - ) + self.assertEqual(mock_broadcast.call_count, 2) + + mock_broadcast.assert_has_calls([ + mock.call( + enums.NotificationTypes.DECISION, + 'experiment', + 'test_user', + {'test_attribute': 'test_value'}, + { + 'experiment_key': 'test_experiment', + 'variation_key': 'variation' + } + ), + mock.call( + enums.NotificationTypes.ACTIVATE, + self.project_config.get_experiment_from_key('test_experiment'), + 'test_user', {'test_attribute': 'test_value'}, + self.project_config.get_variation_from_id('test_experiment', '111129'), + mock_dispatch.call_args[0][0] + ) + ]) + + def test_decision_listener__user_not_in_experiment(self): + """ Test that activate calls broadcast decision with variation_key 'None' \ + when user not in experiment. """ + + with mock.patch( + 'optimizely.decision_service.DecisionService.get_variation', + return_value=None), \ + mock.patch('optimizely.event_dispatcher.EventDispatcher.dispatch_event'), \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast_decision: + self.assertEqual(None, self.optimizely.activate('test_experiment', 'test_user')) + + mock_broadcast_decision.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'experiment', + 'test_user', + {}, + { + 'experiment_key': 'test_experiment', + 'variation_key': None + } + ) def test_track_listener(self): """ Test that track calls notification broadcaster. """ @@ -1395,6 +1445,50 @@ def test_track__invalid_user_id(self): self.assertIsNone(self.optimizely.track('test_event', 99)) mock_client_logging.error.assert_called_once_with('Provided "user_id" is in an invalid format.') + def test_get_variation(self): + """ Test that get_variation returns valid variation and broadcasts decision with proper parameters. """ + + with mock.patch( + 'optimizely.decision_service.DecisionService.get_variation', + return_value=self.project_config.get_variation_from_id('test_experiment', '111129')), \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast: + self.assertEqual('variation', self.optimizely.get_variation('test_experiment', 'test_user')) + + self.assertEqual(mock_broadcast.call_count, 1) + + mock_broadcast.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'experiment', + 'test_user', + {}, + { + 'experiment_key': 'test_experiment', + 'variation_key': 'variation' + } + ) + + def test_get_variation__returns_none(self): + """ Test that get_variation returns no variation and broadcasts decision with proper parameters. """ + + with mock.patch( + 'optimizely.decision_service.DecisionService.get_variation', return_value=None), \ + mock.patch('optimizely.notification_center.NotificationCenter.send_notifications') as mock_broadcast: + self.assertEqual(None, self.optimizely.get_variation('test_experiment', 'test_user', + attributes={'test_attribute': 'test_value'})) + + self.assertEqual(mock_broadcast.call_count, 1) + + mock_broadcast.assert_called_once_with( + enums.NotificationTypes.DECISION, + 'experiment', + 'test_user', + {'test_attribute': 'test_value'}, + { + 'experiment_key': 'test_experiment', + 'variation_key': None + } + ) + def test_get_variation__invalid_object(self): """ Test that get_variation logs error if Optimizely object is not created correctly. """