Skip to content
32 changes: 29 additions & 3 deletions optimizely/optimizely_config.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
Expand All @@ -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):
Expand Down Expand Up @@ -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()

Expand All @@ -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. """
Expand Down
1 change: 0 additions & 1 deletion optimizely/optimizely_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
22 changes: 21 additions & 1 deletion optimizely/project_config.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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', [])
Expand Down Expand Up @@ -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.

Expand Down
19 changes: 0 additions & 19 deletions tests/test_optimizely.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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. """
Expand Down
60 changes: 59 additions & 1 deletion tests/test_optimizely_config.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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': {
Expand Down Expand Up @@ -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())
49 changes: 49 additions & 0 deletions tests/test_user_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"}
Expand Down Expand Up @@ -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")