diff --git a/optimizely/event/event_factory.py b/optimizely/event/event_factory.py index e2851bfc..f9e59b1b 100644 --- a/optimizely/event/event_factory.py +++ b/optimizely/event/event_factory.py @@ -89,8 +89,8 @@ def _create_visitor(cls, event, logger): """ if isinstance(event, user_event.ImpressionEvent): - decision = payload.Decision(event.experiment.layerId, event.experiment.id, event.variation.id,) - + metadata = payload.Metadata(event.flag_key, event.rule_key, event.rule_type, event.variation.key) + decision = payload.Decision(event.experiment.layerId, event.experiment.id, event.variation.id, metadata) snapshot_event = payload.SnapshotEvent( event.experiment.layerId, event.uuid, cls.ACTIVATE_EVENT_KEY, event.timestamp, ) diff --git a/optimizely/event/payload.py b/optimizely/event/payload.py index 450acd55..53b24b9e 100644 --- a/optimizely/event/payload.py +++ b/optimizely/event/payload.py @@ -61,10 +61,21 @@ def get_event_params(self): class Decision(object): """ Class respresenting Decision. """ - def __init__(self, campaign_id, experiment_id, variation_id): + def __init__(self, campaign_id, experiment_id, variation_id, metadata): self.campaign_id = campaign_id self.experiment_id = experiment_id self.variation_id = variation_id + self.metadata = metadata + + +class Metadata(object): + """ Class respresenting Metadata. """ + + def __init__(self, flag_key, rule_key, rule_type, variation_key): + self.flag_key = flag_key + self.rule_key = rule_key + self.rule_type = rule_type + self.variation_key = variation_key class Snapshot(object): diff --git a/optimizely/event/user_event.py b/optimizely/event/user_event.py index 6eb014f9..57b2c2e5 100644 --- a/optimizely/event/user_event.py +++ b/optimizely/event/user_event.py @@ -41,11 +41,15 @@ class ImpressionEvent(UserEvent): """ Class representing Impression Event. """ def __init__( - self, event_context, user_id, experiment, visitor_attributes, variation, bot_filtering=None, + self, event_context, user_id, experiment, visitor_attributes, variation, flag_key, rule_key, rule_type, + bot_filtering=None, ): super(ImpressionEvent, self).__init__(event_context, user_id, visitor_attributes, bot_filtering) self.experiment = experiment self.variation = variation + self.flag_key = flag_key + self.rule_key = rule_key + self.rule_type = rule_type class ConversionEvent(UserEvent): diff --git a/optimizely/event/user_event_factory.py b/optimizely/event/user_event_factory.py index 15908cc7..002bee17 100644 --- a/optimizely/event/user_event_factory.py +++ b/optimizely/event/user_event_factory.py @@ -13,6 +13,7 @@ from . import event_factory from . import user_event +from optimizely.helpers import enums class UserEventFactory(object): @@ -20,7 +21,7 @@ class UserEventFactory(object): @classmethod def create_impression_event( - cls, project_config, activated_experiment, variation_id, user_id, user_attributes, + cls, project_config, activated_experiment, variation_id, flag_key, rule_key, rule_type, user_id, user_attributes ): """ Create impression Event to be sent to the logging endpoint. @@ -28,6 +29,9 @@ def create_impression_event( project_config: Instance of ProjectConfig. experiment: Experiment for which impression needs to be recorded. variation_id: ID for variation which would be presented to user. + flag_key: key for a feature flag. + rule_key: key for an experiment. + rule_type: type for the source. user_id: ID for user. attributes: Dict representing user attributes and values which need to be recorded. @@ -36,12 +40,15 @@ def create_impression_event( - activated_experiment is None. """ - if not activated_experiment: + if not activated_experiment and rule_type is not enums.DecisionSources.ROLLOUT: return None - experiment_key = activated_experiment.key - variation = project_config.get_variation_from_id(experiment_key, variation_id) + variation, experiment_key = None, None + if activated_experiment: + experiment_key = activated_experiment.key + if variation_id and experiment_key: + variation = project_config.get_variation_from_id(experiment_key, variation_id) event_context = user_event.EventContext( project_config.account_id, project_config.project_id, project_config.revision, project_config.anonymize_ip, ) @@ -52,6 +59,9 @@ def create_impression_event( activated_experiment, event_factory.EventFactory.build_attribute_list(user_attributes, project_config), variation, + flag_key, + rule_key, + rule_type, project_config.get_bot_filtering_value(), ) diff --git a/optimizely/helpers/enums.py b/optimizely/helpers/enums.py index 3eed4a30..5685f9c8 100644 --- a/optimizely/helpers/enums.py +++ b/optimizely/helpers/enums.py @@ -89,6 +89,7 @@ class DecisionNotificationTypes(object): class DecisionSources(object): + EXPERIMENT = 'experiment' FEATURE_TEST = 'feature-test' ROLLOUT = 'rollout' diff --git a/optimizely/optimizely.py b/optimizely/optimizely.py index ff4f41a7..afd6f382 100644 --- a/optimizely/optimizely.py +++ b/optimizely/optimizely.py @@ -160,19 +160,23 @@ def _validate_user_inputs(self, attributes=None, event_tags=None): return True - def _send_impression_event(self, project_config, experiment, variation, user_id, attributes): + def _send_impression_event(self, project_config, experiment, variation, flag_key, rule_key, rule_type, user_id, + attributes): """ Helper method to send impression event. Args: project_config: Instance of ProjectConfig. experiment: Experiment for which impression event is being sent. variation: Variation picked for user for the given experiment. + flag_key: key for a feature flag. + rule_key: key for an experiment. + rule_type: type for the source. user_id: ID for user. attributes: Dict representing user attributes and values which need to be recorded. """ - + variation_id = variation.id if variation is not None else None user_event = user_event_factory.UserEventFactory.create_impression_event( - project_config, experiment, variation.id, user_id, attributes + project_config, experiment, variation_id, flag_key, rule_key, rule_type, user_id, attributes ) self.event_processor.process(user_event) @@ -422,7 +426,8 @@ def activate(self, experiment_key, user_id, attributes=None): # Create and dispatch impression event self.logger.info('Activating user "%s" in experiment "%s".' % (user_id, experiment.key)) - self._send_impression_event(project_config, experiment, variation, user_id, attributes) + self._send_impression_event(project_config, experiment, variation, '', experiment.key, + enums.DecisionSources.EXPERIMENT, user_id, attributes) return variation.key @@ -573,6 +578,13 @@ def is_feature_enabled(self, feature_key, user_id, attributes=None): source_info = {} decision = self.decision_service.get_variation_for_feature(project_config, feature, user_id, attributes) is_source_experiment = decision.source == enums.DecisionSources.FEATURE_TEST + is_source_rollout = decision.source == enums.DecisionSources.ROLLOUT + + if is_source_rollout and project_config.get_send_flag_decisions_value(): + self._send_impression_event( + project_config, decision.experiment, decision.variation, feature.key, decision.experiment.key if + decision.experiment else '', decision.source, user_id, attributes + ) if decision.variation: if decision.variation.featureEnabled is True: @@ -584,7 +596,8 @@ def is_feature_enabled(self, feature_key, user_id, attributes=None): 'variation_key': decision.variation.key, } self._send_impression_event( - project_config, decision.experiment, decision.variation, user_id, attributes, + project_config, decision.experiment, decision.variation, feature.key, decision.experiment.key, + decision.source, user_id, attributes ) if feature_enabled: diff --git a/optimizely/project_config.py b/optimizely/project_config.py index 8d608890..77b89e67 100644 --- a/optimizely/project_config.py +++ b/optimizely/project_config.py @@ -61,6 +61,7 @@ def __init__(self, datafile, logger, error_handler): self.feature_flags = config.get('featureFlags', []) self.rollouts = config.get('rollouts', []) self.anonymize_ip = config.get('anonymizeIP', False) + self.send_flag_decisions = config.get('sendFlagDecisions', False) self.bot_filtering = config.get('botFiltering', None) # Utility maps for quick lookup @@ -514,6 +515,15 @@ def get_anonymize_ip_value(self): return self.anonymize_ip + def get_send_flag_decisions_value(self): + """ Gets the Send Flag Decisions value. + + Returns: + A boolean value that indicates if we should send flag decisions. + """ + + return self.send_flag_decisions + def get_bot_filtering_value(self): """ Gets the bot filtering value. diff --git a/tests/base.py b/tests/base.py index 9dceec2d..88d5b73f 100644 --- a/tests/base.py +++ b/tests/base.py @@ -129,6 +129,7 @@ def setUp(self, config_dict='config_dict'): 'projectId': '111111', 'version': '4', 'botFiltering': True, + 'sendFlagDecisions': True, 'events': [{'key': 'test_event', 'experimentIds': ['111127'], 'id': '111095'}], 'experiments': [ { diff --git a/tests/test_config.py b/tests/test_config.py index 6ef70133..e8836471 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -613,6 +613,20 @@ def test_get_bot_filtering(self): self.config_dict_with_features['botFiltering'], project_config.get_bot_filtering_value(), ) + def test_get_send_flag_decisions(self): + """ Test that send_flag_decisions is retrieved correctly when using get_send_flag_decisions_value. """ + + # Assert send_flag_decisions is None when not provided in data file + self.assertTrue('sendFlagDecisions' not in self.config_dict) + self.assertFalse(self.project_config.get_send_flag_decisions_value()) + + # Assert send_flag_decisions is retrieved as provided in the data file + opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_features)) + project_config = opt_obj.config_manager.get_config() + self.assertEqual( + self.config_dict_with_features['sendFlagDecisions'], project_config.get_send_flag_decisions_value(), + ) + def test_get_experiment_from_key__valid_key(self): """ Test that experiment is retrieved correctly for valid experiment key. """ diff --git a/tests/test_event_factory.py b/tests/test_event_factory.py index 73a8054b..93e5db7c 100644 --- a/tests/test_event_factory.py +++ b/tests/test_event_factory.py @@ -74,7 +74,11 @@ def test_create_impression_event(self): 'snapshots': [ { 'decisions': [ - {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': 'flag_key', + 'rule_key': 'rule_key', + 'rule_type': 'experiment', + 'variation_key': 'variation'}} ], 'events': [ { @@ -102,6 +106,9 @@ def test_create_impression_event(self): self.project_config, self.project_config.get_experiment_from_key('test_experiment'), '111129', + 'flag_key', + 'rule_key', + 'experiment', 'test_user', None, ) @@ -128,7 +135,12 @@ def test_create_impression_event__with_attributes(self): 'snapshots': [ { 'decisions': [ - {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': 'flag_key', + 'rule_key': 'rule_key', + 'rule_type': 'experiment', + 'variation_key': 'variation'}, + } ], 'events': [ { @@ -156,6 +168,9 @@ def test_create_impression_event__with_attributes(self): self.project_config, self.project_config.get_experiment_from_key('test_experiment'), '111129', + 'flag_key', + 'rule_key', + 'experiment', 'test_user', {'test_attribute': 'test_value'}, ) @@ -180,7 +195,12 @@ def test_create_impression_event_when_attribute_is_not_in_datafile(self): 'snapshots': [ { 'decisions': [ - {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': 'flag_key', + 'rule_key': 'rule_key', + 'rule_type': 'experiment', + 'variation_key': 'variation'} + } ], 'events': [ { @@ -208,6 +228,9 @@ def test_create_impression_event_when_attribute_is_not_in_datafile(self): self.project_config, self.project_config.get_experiment_from_key('test_experiment'), '111129', + 'flag_key', + 'rule_key', + 'experiment', 'test_user', {'do_you_know_me': 'test_value'}, ) @@ -235,7 +258,11 @@ def test_create_impression_event_calls_is_attribute_valid(self): 'snapshots': [ { 'decisions': [ - {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': 'flag_key', + 'flag_type': 'experiment', + 'variation_key': 'variation'}, + } ], 'events': [ { @@ -280,6 +307,8 @@ def side_effect(*args, **kwargs): self.project_config, self.project_config.get_experiment_from_key('test_experiment'), '111129', + 'flag_key', + 'experiment', 'test_user', attributes, ) @@ -317,7 +346,12 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_enabled( 'snapshots': [ { 'decisions': [ - {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': 'flag_key', + 'rule_key': 'rule_key', + 'rule_type': 'experiment', + 'variation_key': 'variation'}, + } ], 'events': [ { @@ -347,6 +381,9 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_enabled( self.project_config, self.project_config.get_experiment_from_key('test_experiment'), '111129', + 'flag_key', + 'rule_key', + 'experiment', 'test_user', {'$opt_user_agent': 'Edge'}, ) @@ -379,7 +416,12 @@ def test_create_impression_event__with_empty_attributes_when_bot_filtering_is_en 'snapshots': [ { 'decisions': [ - {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': 'flag_key', + 'rule_key': 'rule_key', + 'rule_type': 'experiment', + 'variation_key': 'variation'}, + } ], 'events': [ { @@ -409,6 +451,9 @@ def test_create_impression_event__with_empty_attributes_when_bot_filtering_is_en self.project_config, self.project_config.get_experiment_from_key('test_experiment'), '111129', + 'flag_key', + 'rule_key', + 'experiment', 'test_user', None, ) @@ -447,7 +492,12 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_disabled 'snapshots': [ { 'decisions': [ - {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': 'flag_key', + 'rule_key': 'rule_key', + 'rule_type': 'experiment', + 'variation_key': 'variation'}, + } ], 'events': [ { @@ -477,6 +527,9 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_disabled self.project_config, self.project_config.get_experiment_from_key('test_experiment'), '111129', + 'flag_key', + 'rule_key', + 'experiment', 'test_user', {'$opt_user_agent': 'Chrome'}, ) diff --git a/tests/test_event_payload.py b/tests/test_event_payload.py index e8cd6fbc..ae168d8e 100644 --- a/tests/test_event_payload.py +++ b/tests/test_event_payload.py @@ -30,7 +30,12 @@ def test_impression_event_equals_serialized_payload(self): 'snapshots': [ { 'decisions': [ - {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': 'flag_key', + 'rule_key': 'rule_key', + 'rule_type': 'experiment', + 'variation_key': 'variation'}, + } ], 'events': [ { @@ -54,7 +59,8 @@ def test_impression_event_equals_serialized_payload(self): batch = payload.EventBatch('12001', '111001', '42', 'python-sdk', version.__version__, False, True) visitor_attr = payload.VisitorAttribute('111094', 'test_attribute', 'custom', 'test_value') event = payload.SnapshotEvent('111182', 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', 'campaign_activated', 42123,) - event_decision = payload.Decision('111182', '111127', '111129') + metadata = payload.Metadata('flag_key', 'rule_key', 'experiment', 'variation') + event_decision = payload.Decision('111182', '111127', '111129', metadata) snapshots = payload.Snapshot([event], [event_decision]) user = payload.Visitor([snapshots], [visitor_attr], 'test_user') diff --git a/tests/test_event_processor.py b/tests/test_event_processor.py index 14dc40ac..d1fffb08 100644 --- a/tests/test_event_processor.py +++ b/tests/test_event_processor.py @@ -83,7 +83,7 @@ def dispatch_event(self, actual_log_event): for visitor in visitors: for snapshot in visitor.snapshots: - decisions = snapshot.get('decisions') or [Decision(None, None, None)] + decisions = snapshot.get('decisions') or [Decision(None, None, None, None)] for decision in decisions: for event in snapshot.get('events'): attributes = visitor.attributes diff --git a/tests/test_optimizely.py b/tests/test_optimizely.py index f586c44c..35c0004c 100644 --- a/tests/test_optimizely.py +++ b/tests/test_optimizely.py @@ -321,7 +321,12 @@ def test_activate(self): 'snapshots': [ { 'decisions': [ - {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': '', + 'rule_key': 'test_experiment', + 'rule_type': 'experiment', + 'variation_key': 'variation'}, + } ], 'events': [ { @@ -663,9 +668,9 @@ def on_activate(experiment, user_id, attributes, variation, event): mock_decision.assert_called_once_with(project_config, feature, 'test_user', None) - # Check that impression event is not sent - self.assertEqual(0, mock_process.call_count) - self.assertEqual(False, access_callback[0]) + # Check that impression event is sent for rollout and send_flag_decisions = True + self.assertEqual(1, mock_process.call_count) + self.assertEqual(True, access_callback[0]) def test_activate__with_attributes__audience_match(self): """ Test that activate calls process with right params and returns expected @@ -694,7 +699,12 @@ def test_activate__with_attributes__audience_match(self): 'snapshots': [ { 'decisions': [ - {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': '', + 'rule_key': 'test_experiment', + 'rule_type': 'experiment', + 'variation_key': 'variation'}, + } ], 'events': [ { @@ -771,7 +781,12 @@ def test_activate__with_attributes_of_different_types(self): 'snapshots': [ { 'decisions': [ - {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': '', + 'rule_key': 'test_experiment', + 'rule_type': 'experiment', + 'variation_key': 'variation'}, + } ], 'events': [ { @@ -962,7 +977,12 @@ def test_activate__with_attributes__audience_match__forced_bucketing(self): 'snapshots': [ { 'decisions': [ - {'variation_id': '111128', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111128', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': '', + 'rule_key': 'test_experiment', + 'rule_type': 'experiment', + 'variation_key': 'control'}, + } ], 'events': [ { @@ -1032,7 +1052,12 @@ def test_activate__with_attributes__audience_match__bucketing_id_provided(self): 'snapshots': [ { 'decisions': [ - {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': '', + 'rule_key': 'test_experiment', + 'rule_type': 'experiment', + 'variation_key': 'variation'}, + } ], 'events': [ { @@ -1975,7 +2000,11 @@ def test_is_feature_enabled__returns_true_for_feature_experiment_if_feature_enab 'snapshots': [ { 'decisions': [ - {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': 'test_feature_in_experiment', + 'rule_key': 'test_experiment', + 'rule_type': 'feature-test', + 'variation_key': 'variation'}} ], 'events': [ { @@ -2069,7 +2098,11 @@ def test_is_feature_enabled__returns_false_for_feature_experiment_if_feature_dis 'snapshots': [ { 'decisions': [ - {'variation_id': '111128', 'experiment_id': '111127', 'campaign_id': '111182'} + {'variation_id': '111128', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': 'test_feature_in_experiment', + 'rule_key': 'test_experiment', + 'rule_type': 'feature-test', + 'variation_key': 'control'}} ], 'events': [ { @@ -2145,8 +2178,108 @@ def test_is_feature_enabled__returns_true_for_feature_rollout_if_feature_enabled }, ) - # Check that impression event is not sent - self.assertEqual(0, mock_process.call_count) + # Check that impression event is sent for rollout and send_flag_decisions = True + self.assertEqual(1, mock_process.call_count) + + def test_is_feature_enabled__returns_true_for_feature_rollout_if_feature_enabled_with_sending_decisions(self,): + """ Test that the feature is enabled for the user if bucketed into variation of a rollout and + the variation's featureEnabled property is True. Also confirm that an impression event is processed and + decision is broadcasted with proper parameters, as send_flag_decisions is set to true """ + + opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_features)) + project_config = opt_obj.config_manager.get_config() + project_config.send_flag_decisions = True + feature = project_config.get_feature_from_key('test_feature_in_experiment') + + mock_experiment = project_config.get_experiment_from_key('test_experiment') + mock_variation = project_config.get_variation_from_id('test_experiment', '111129') + + # Assert that featureEnabled property is True + self.assertTrue(mock_variation.featureEnabled) + + with mock.patch( + 'optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=decision_service.Decision(mock_experiment, mock_variation, enums.DecisionSources.ROLLOUT), + ) as mock_decision, mock.patch( + 'optimizely.event.event_processor.ForwardingEventProcessor.process' + ) as mock_process, mock.patch( + 'optimizely.notification_center.NotificationCenter.send_notifications' + ) as mock_broadcast_decision, mock.patch( + 'uuid.uuid4', return_value='a68cf1ad-0393-4e18-af87-efe8f01a7c9c' + ), mock.patch( + 'time.time', return_value=42 + ): + self.assertTrue(opt_obj.is_feature_enabled('test_feature_in_experiment', 'test_user')) + + mock_decision.assert_called_once_with(opt_obj.config_manager.get_config(), feature, 'test_user', None) + + mock_broadcast_decision.assert_called_with( + enums.NotificationTypes.DECISION, + 'feature', + 'test_user', + {}, + { + 'feature_key': 'test_feature_in_experiment', + 'feature_enabled': True, + 'source': 'rollout', + 'source_info': {}, + }, + ) + + # Check that impression event is sent + expected_params = { + 'account_id': '12001', + 'project_id': '111111', + 'visitors': [ + { + 'visitor_id': 'test_user', + 'attributes': [ + { + 'type': 'custom', + 'value': True, + 'entity_id': '$opt_bot_filtering', + 'key': '$opt_bot_filtering', + } + ], + 'snapshots': [ + { + 'decisions': [ + {'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182', + 'metadata': {'flag_key': 'test_feature_in_experiment', + 'rule_key': 'test_experiment', + 'rule_type': 'rollout', + 'variation_key': 'variation'}, + } + ], + 'events': [ + { + 'timestamp': 42000, + 'entity_id': '111182', + 'uuid': 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', + 'key': 'campaign_activated', + } + ], + } + ], + } + ], + 'client_version': version.__version__, + 'client_name': 'python-sdk', + 'enrich_decisions': True, + 'anonymize_ip': False, + 'revision': '1', + } + log_event = EventFactory.create_log_event(mock_process.call_args[0][0], self.optimizely.logger) + + # Check that impression event is sent + self.assertEqual(1, mock_process.call_count) + self._validate_event_object( + log_event.__dict__, + 'https://logx.optimizely.com/v1/events', + expected_params, + 'POST', + {'Content-Type': 'application/json'}, + ) def test_is_feature_enabled__returns_false_for_feature_rollout_if_feature_disabled(self,): """ Test that the feature is disabled for the user if bucketed into variation of a rollout and @@ -2192,8 +2325,8 @@ def test_is_feature_enabled__returns_false_for_feature_rollout_if_feature_disabl }, ) - # Check that impression event is not sent - self.assertEqual(0, mock_process.call_count) + # Check that impression event is sent for rollout and send_flag_decisions = True + self.assertEqual(1, mock_process.call_count) def test_is_feature_enabled__returns_false_when_user_is_not_bucketed_into_any_variation(self,): """ Test that the feature is not enabled for the user if user is neither bucketed for @@ -2217,8 +2350,8 @@ def test_is_feature_enabled__returns_false_when_user_is_not_bucketed_into_any_va ): self.assertFalse(opt_obj.is_feature_enabled('test_feature_in_experiment', 'test_user')) - # Check that impression event is not sent - self.assertEqual(0, mock_process.call_count) + # Check that impression event is sent for rollout and send_flag_decisions = True + self.assertEqual(1, mock_process.call_count) mock_decision.assert_called_once_with(opt_obj.config_manager.get_config(), feature, 'test_user', None) @@ -2235,8 +2368,8 @@ def test_is_feature_enabled__returns_false_when_user_is_not_bucketed_into_any_va }, ) - # Check that impression event is not sent - self.assertEqual(0, mock_process.call_count) + # Check that impression event is sent for rollout and send_flag_decisions = True + self.assertEqual(1, mock_process.call_count) def test_is_feature_enabled__invalid_object(self): """ Test that is_feature_enabled returns False and logs error if Optimizely instance is invalid. """ diff --git a/tests/test_user_event_factory.py b/tests/test_user_event_factory.py index b048bf5b..4e8c2845 100644 --- a/tests/test_user_event_factory.py +++ b/tests/test_user_event_factory.py @@ -28,7 +28,8 @@ def test_impression_event(self): variation = self.project_config.get_variation_from_id(experiment.key, '111128') user_id = 'test_user' - impression_event = UserEventFactory.create_impression_event(project_config, experiment, '111128', user_id, None) + impression_event = UserEventFactory.create_impression_event(project_config, experiment, '111128', 'flag_key', + 'rule_key', 'rule_type', user_id, None) self.assertEqual(self.project_config.project_id, impression_event.event_context.project_id) self.assertEqual(self.project_config.revision, impression_event.event_context.revision) @@ -50,7 +51,7 @@ def test_impression_event__with_attributes(self): user_attributes = {'test_attribute': 'test_value', 'boolean_key': True} impression_event = UserEventFactory.create_impression_event( - project_config, experiment, '111128', user_id, user_attributes + project_config, experiment, '111128', 'flag_key', 'rule_key', 'rule_type', user_id, user_attributes ) expected_attrs = EventFactory.build_attribute_list(user_attributes, project_config)