diff --git a/optimizely/optimizely_config.py b/optimizely/optimizely_config.py index 52887d43..50543f85 100644 --- a/optimizely/optimizely_config.py +++ b/optimizely/optimizely_config.py @@ -1,4 +1,4 @@ -# Copyright 2020, Optimizely +# Copyright 2020-2021, Optimizely # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -17,11 +17,13 @@ class OptimizelyConfig(object): - def __init__(self, revision, experiments_map, features_map, datafile=None): + def __init__(self, revision, experiments_map, features_map, datafile=None, sdk_key=None, environment_key=None): self.revision = revision self.experiments_map = experiments_map self.features_map = features_map self._datafile = datafile + self.sdk_key = sdk_key + self.environment_key = environment_key def get_datafile(self): """ Get the datafile associated with OptimizelyConfig. @@ -31,6 +33,22 @@ def get_datafile(self): """ return self._datafile + def get_sdk_key(self): + """ Get the sdk key associated with OptimizelyConfig. + + Returns: + A string containing sdk key. + """ + return self.sdk_key + + def get_environment_key(self): + """ Get the environemnt key associated with OptimizelyConfig. + + Returns: + A string containing environment key. + """ + return self.environment_key + class OptimizelyExperiment(object): def __init__(self, id, key, variations_map): @@ -82,6 +100,8 @@ def __init__(self, project_config): self.feature_flags = project_config.feature_flags self.groups = project_config.groups self.revision = project_config.revision + self.sdk_key = project_config.sdk_key + self.environment_key = project_config.environment_key self._create_lookup_maps() @@ -98,7 +118,13 @@ def get_config(self): experiments_key_map, experiments_id_map = self._get_experiments_maps() features_map = self._get_features_map(experiments_id_map) - return OptimizelyConfig(self.revision, experiments_key_map, features_map, self._datafile) + return OptimizelyConfig( + self.revision, + experiments_key_map, + features_map, + self._datafile, + self.sdk_key, + self.environment_key) def _create_lookup_maps(self): """ Creates lookup maps to avoid redundant iteration of config objects. """ diff --git a/optimizely/optimizely_factory.py b/optimizely/optimizely_factory.py index 5b7a879a..d9da72ba 100644 --- a/optimizely/optimizely_factory.py +++ b/optimizely/optimizely_factory.py @@ -113,7 +113,6 @@ def default_instance_with_config_manager(config_manager): def custom_instance(sdk_key, datafile=None, event_dispatcher=None, logger=None, error_handler=None, skip_json_validation=None, user_profile_service=None, config_manager=None, notification_center=None): - """ Returns a new optimizely instance. if max_event_batch_size and max_event_flush_interval are None then default batch_size and flush_interval will be used to setup BatchEventProcessor. diff --git a/optimizely/project_config.py b/optimizely/project_config.py index c0004495..ac97fac6 100644 --- a/optimizely/project_config.py +++ b/optimizely/project_config.py @@ -1,4 +1,4 @@ -# Copyright 2016-2019, Optimizely +# Copyright 2016-2019, 2021, Optimizely # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -52,6 +52,8 @@ def __init__(self, datafile, logger, error_handler): self.account_id = config.get('accountId') self.project_id = config.get('projectId') self.revision = config.get('revision') + self.sdk_key = config.get('sdkKey', None) + self.environment_key = config.get('environmentKey', None) self.groups = config.get('groups', []) self.experiments = config.get('experiments', []) self.events = config.get('events', []) @@ -213,6 +215,24 @@ def get_revision(self): return self.revision + def get_sdk_key(self): + """ Get sdk key from the datafile. + + Returns: + Revision of the sdk key. + """ + + return self.sdk_key + + def get_environment_key(self): + """ Get environment key from the datafile. + + Returns: + Revision of the environment key. + """ + + return self.environment_key + def get_account_id(self): """ Get account ID from the config. diff --git a/tests/test_optimizely.py b/tests/test_optimizely.py index ec68a03a..23454342 100644 --- a/tests/test_optimizely.py +++ b/tests/test_optimizely.py @@ -26,7 +26,6 @@ from optimizely import optimizely_config from optimizely import project_config from optimizely import version -from optimizely.decision.optimizely_decide_option import OptimizelyDecideOption as DecideOption from optimizely.event.event_factory import EventFactory from optimizely.helpers import enums from . import base @@ -677,24 +676,6 @@ def on_activate(experiment, user_id, attributes, variation, event): self.assertEqual(1, mock_process.call_count) self.assertEqual(True, access_callback[0]) - def test_decide_experiment(self): - """ Test that the feature is enabled for the user if bucketed into variation of a rollout. - Also confirm that no impression event is processed. """ - - opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_features)) - project_config = opt_obj.config_manager.get_config() - - mock_experiment = project_config.get_experiment_from_key('test_experiment') - mock_variation = project_config.get_variation_from_id('test_experiment', '111129') - with mock.patch( - 'optimizely.decision_service.DecisionService.get_variation_for_feature', - return_value=(decision_service.Decision(mock_experiment, - mock_variation, enums.DecisionSources.FEATURE_TEST), []), - ): - user_context = opt_obj.create_user_context('test_user') - decision = user_context.decide('test_feature_in_experiment', [DecideOption.DISABLE_DECISION_EVENT]) - self.assertTrue(decision.enabled, "decision should be enabled") - def test_activate__with_attributes__audience_match(self): """ Test that activate calls process with right params and returns expected variation when attributes are provided and audience conditions are met. """ diff --git a/tests/test_optimizely_config.py b/tests/test_optimizely_config.py index 94e1fb00..d86c7a74 100644 --- a/tests/test_optimizely_config.py +++ b/tests/test_optimizely_config.py @@ -1,4 +1,4 @@ -# Copyright 2020, Optimizely +# Copyright 2020-2021, Optimizely # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -26,6 +26,8 @@ def setUp(self): self.opt_config_service = optimizely_config.OptimizelyConfigService(self.project_config) self.expected_config = { + 'sdk_key': None, + 'environment_key': None, 'experiments_map': { 'test_experiment2': { 'variations_map': { @@ -732,3 +734,59 @@ def test__get_datafile(self): actual_datafile = self.actual_config.get_datafile() self.assertEqual(expected_datafile, actual_datafile) + + def test__get_sdk_key(self): + """ Test that get_sdk_key returns the expected value. """ + + config = optimizely_config.OptimizelyConfig( + revision='101', + experiments_map={}, + features_map={}, + sdk_key='testSdkKey', + ) + + expected_value = 'testSdkKey' + + self.assertEqual(expected_value, config.get_sdk_key()) + + def test__get_sdk_key_invalid(self): + """ Negative Test that tests get_sdk_key does not return the expected value. """ + + config = optimizely_config.OptimizelyConfig( + revision='101', + experiments_map={}, + features_map={}, + sdk_key='testSdkKey', + ) + + invalid_value = 123 + + self.assertNotEqual(invalid_value, config.get_sdk_key()) + + def test__get_environment_key(self): + """ Test that get_environment_key returns the expected value. """ + + config = optimizely_config.OptimizelyConfig( + revision='101', + experiments_map={}, + features_map={}, + environment_key='TestEnvironmentKey' + ) + + expected_value = 'TestEnvironmentKey' + + self.assertEqual(expected_value, config.get_environment_key()) + + def test__get_environment_key_invalid(self): + """ Negative Test that tests get_environment_key does not return the expected value. """ + + config = optimizely_config.OptimizelyConfig( + revision='101', + experiments_map={}, + features_map={}, + environment_key='testEnvironmentKey' + ) + + invalid_value = 321 + + self.assertNotEqual(invalid_value, config.get_environment_key()) diff --git a/tests/test_user_context.py b/tests/test_user_context.py index 3ecd7130..fcffc415 100644 --- a/tests/test_user_context.py +++ b/tests/test_user_context.py @@ -15,6 +15,7 @@ import mock from optimizely.decision.optimizely_decision import OptimizelyDecision +from optimizely.decision.optimizely_decide_option import OptimizelyDecideOption as DecideOption from optimizely.helpers import enums from . import base from optimizely import optimizely, decision_service @@ -60,6 +61,23 @@ def test_user_context(self): self.assertEqual("firefox", uc.get_user_attributes()["browser"]) self.assertEqual("red", uc.get_user_attributes()["color"]) + def test_user_and_attributes_as_json(self): + """ + tests user context as json + """ + uc = OptimizelyUserContext(self.optimizely, "test_user") + + # set an attribute + uc.set_attribute("browser", "safari") + + # set expected json obj + expected_json = { + "user_id": uc.user_id, + "attributes": uc.get_user_attributes(), + } + + self.assertEqual(uc.as_json(), expected_json) + def test_attributes_are_cloned_when_passed_to_user_context(self): user_id = 'test_user' attributes = {"browser": "chrome"} @@ -1247,3 +1265,34 @@ def test_decide_reasons__whitelisted_variation(self): expected_reasons = ['User "user_1" is forced in variation "control".'] self.assertEqual(expected_reasons, actual.reasons) + + def test_init__invalid_default_decide_options(self): + """ + Test to confirm that default decide options passed not as a list will trigger setting + self.deafulat_decide_options as an empty list. + """ + invalid_decide_options = {"testKey": "testOption"} + + mock_client_logger = mock.MagicMock() + with mock.patch('optimizely.logger.reset_logger', return_value=mock_client_logger): + opt_obj = optimizely.Optimizely(default_decide_options=invalid_decide_options) + + self.assertEqual(opt_obj.default_decide_options, []) + + def test_decide_experiment(self): + """ Test that the feature is enabled for the user if bucketed into variation of a rollout. + Also confirm that no impression event is processed. """ + + opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_features)) + project_config = opt_obj.config_manager.get_config() + + mock_experiment = project_config.get_experiment_from_key('test_experiment') + mock_variation = project_config.get_variation_from_id('test_experiment', '111129') + with mock.patch( + 'optimizely.decision_service.DecisionService.get_variation_for_feature', + return_value=(decision_service.Decision(mock_experiment, + mock_variation, enums.DecisionSources.FEATURE_TEST), []), + ): + user_context = opt_obj.create_user_context('test_user') + decision = user_context.decide('test_feature_in_experiment', [DecideOption.DISABLE_DECISION_EVENT]) + self.assertTrue(decision.enabled, "decision should be enabled")