4
4
# You may obtain a copy of the License at
5
5
#
6
6
# http://www.apache.org/licenses/LICENSE-2.0
7
-
8
7
# Unless required by applicable law or agreed to in writing, software
9
8
# distributed under the License is distributed on an "AS IS" BASIS,
10
9
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
10
# See the License for the specific language governing permissions and
12
11
# limitations under the License.
13
12
14
13
import json
14
+ from unittest .mock import patch
15
15
16
16
from optimizely import optimizely , project_config
17
17
from optimizely import optimizely_config
18
+ from optimizely import logger
18
19
from . import base
19
20
20
21
@@ -23,7 +24,8 @@ def setUp(self):
23
24
base .BaseTest .setUp (self )
24
25
opt_instance = optimizely .Optimizely (json .dumps (self .config_dict_with_features ))
25
26
self .project_config = opt_instance .config_manager .get_config ()
26
- self .opt_config_service = optimizely_config .OptimizelyConfigService (self .project_config )
27
+ self .opt_config_service = optimizely_config .OptimizelyConfigService (self .project_config ,
28
+ logger = logger .SimpleLogger ())
27
29
28
30
self .expected_config = {
29
31
'sdk_key' : 'features-test' ,
@@ -1452,7 +1454,7 @@ def test__get_config(self):
1452
1454
def test__get_config__invalid_project_config (self ):
1453
1455
""" Test that get_config returns None when invalid project config supplied. """
1454
1456
1455
- opt_service = optimizely_config .OptimizelyConfigService ({"key" : "invalid" })
1457
+ opt_service = optimizely_config .OptimizelyConfigService ({"key" : "invalid" }, None )
1456
1458
self .assertIsNone (opt_service .get_config ())
1457
1459
1458
1460
def test__get_experiments_maps (self ):
@@ -1473,6 +1475,81 @@ def test__get_experiments_maps(self):
1473
1475
1474
1476
self .assertEqual (expected_id_map , self .to_dict (actual_id_map ))
1475
1477
1478
+ def test__duplicate_experiment_keys (self ):
1479
+ """ Test that multiple features don't have the same experiment key. """
1480
+
1481
+ # update the test datafile with an additional feature flag with the same experiment rule key
1482
+ new_experiment = {
1483
+ 'key' : 'test_experiment' , # added duplicate "test_experiment"
1484
+ 'status' : 'Running' ,
1485
+ 'layerId' : '8' ,
1486
+ "audienceConditions" : [
1487
+ "or" ,
1488
+ "11160"
1489
+ ],
1490
+ 'audienceIds' : ['11160' ],
1491
+ 'id' : '111137' ,
1492
+ 'forcedVariations' : {},
1493
+ 'trafficAllocation' : [
1494
+ {'entityId' : '222242' , 'endOfRange' : 8000 },
1495
+ {'entityId' : '' , 'endOfRange' : 10000 }
1496
+ ],
1497
+ 'variations' : [
1498
+ {
1499
+ 'id' : '222242' ,
1500
+ 'key' : 'control' ,
1501
+ 'variables' : [],
1502
+ }
1503
+ ],
1504
+ }
1505
+
1506
+ new_feature = {
1507
+ 'id' : '91117' ,
1508
+ 'key' : 'new_feature' ,
1509
+ 'experimentIds' : ['111137' ],
1510
+ 'rolloutId' : '' ,
1511
+ 'variables' : [
1512
+ {'id' : '127' , 'key' : 'is_working' , 'defaultValue' : 'true' , 'type' : 'boolean' },
1513
+ {'id' : '128' , 'key' : 'environment' , 'defaultValue' : 'devel' , 'type' : 'string' },
1514
+ {'id' : '129' , 'key' : 'cost' , 'defaultValue' : '10.99' , 'type' : 'double' },
1515
+ {'id' : '130' , 'key' : 'count' , 'defaultValue' : '999' , 'type' : 'integer' },
1516
+ {'id' : '131' , 'key' : 'variable_without_usage' , 'defaultValue' : '45' , 'type' : 'integer' },
1517
+ {'id' : '132' , 'key' : 'object' , 'defaultValue' : '{"test": 12}' , 'type' : 'string' ,
1518
+ 'subType' : 'json' },
1519
+ {'id' : '133' , 'key' : 'true_object' , 'defaultValue' : '{"true_test": 23.54}' , 'type' : 'json' },
1520
+ ],
1521
+ }
1522
+
1523
+ # add new experiment rule with the same key and a new feature with the same rule key
1524
+ self .config_dict_with_features ['experiments' ].append (new_experiment )
1525
+ self .config_dict_with_features ['featureFlags' ].append (new_feature )
1526
+
1527
+ config_with_duplicate_key = self .config_dict_with_features
1528
+ opt_instance = optimizely .Optimizely (json .dumps (config_with_duplicate_key ))
1529
+ self .project_config = opt_instance .config_manager .get_config ()
1530
+
1531
+ with patch ('optimizely.logger.SimpleLogger.warning' ) as mock_logger :
1532
+ self .opt_config_service = optimizely_config .OptimizelyConfigService (self .project_config ,
1533
+ logger = logger .SimpleLogger ())
1534
+
1535
+ actual_key_map , actual_id_map = self .opt_config_service ._get_experiments_maps ()
1536
+
1537
+ self .assertIsInstance (actual_key_map , dict )
1538
+ for exp in actual_key_map .values ():
1539
+ self .assertIsInstance (exp , optimizely_config .OptimizelyExperiment )
1540
+
1541
+ # Assert that the warning method of the mock logger was called with the expected message
1542
+ expected_warning_message = f"Duplicate experiment keys found in datafile: { new_experiment ['key' ]} "
1543
+ mock_logger .assert_called_with (expected_warning_message )
1544
+
1545
+ # assert we get ID of the duplicated experiment
1546
+ assert actual_key_map .get ('test_experiment' ).id == "111137"
1547
+
1548
+ # assert we get one duplicated experiment
1549
+ keys_list = list (actual_key_map .keys ())
1550
+ assert "test_experiment" in keys_list , "Key 'test_experiment' not found in actual key map"
1551
+ assert keys_list .count ("test_experiment" ) == 1 , "Key 'test_experiment' found more than once in actual key map"
1552
+
1476
1553
def test__get_features_map (self ):
1477
1554
""" Test that get_features_map returns expected features map. """
1478
1555
@@ -1674,7 +1751,7 @@ def test_get_audiences(self):
1674
1751
error_handler = None
1675
1752
)
1676
1753
1677
- config_service = optimizely_config .OptimizelyConfigService (proj_conf )
1754
+ config_service = optimizely_config .OptimizelyConfigService (proj_conf , logger = logger . SimpleLogger () )
1678
1755
1679
1756
for audience in config_service .audiences :
1680
1757
self .assertIsInstance (audience , optimizely_config .OptimizelyAudience )
@@ -1742,7 +1819,7 @@ def test_stringify_audience_conditions_all_cases(self):
1742
1819
'("us" OR ("female" AND "adult")) AND ("fr" AND ("male" OR "adult"))'
1743
1820
]
1744
1821
1745
- config_service = optimizely_config .OptimizelyConfigService (config )
1822
+ config_service = optimizely_config .OptimizelyConfigService (config , None )
1746
1823
1747
1824
for i in range (len (audiences_input )):
1748
1825
result = config_service .stringify_conditions (audiences_input [i ], audiences_map )
@@ -1760,7 +1837,7 @@ def test_optimizely_audience_conversion(self):
1760
1837
error_handler = None
1761
1838
)
1762
1839
1763
- config_service = optimizely_config .OptimizelyConfigService (proj_conf )
1840
+ config_service = optimizely_config .OptimizelyConfigService (proj_conf , None )
1764
1841
1765
1842
for audience in config_service .audiences :
1766
1843
self .assertIsInstance (audience , optimizely_config .OptimizelyAudience )
@@ -1776,7 +1853,7 @@ def test_get_variations_from_experiments_map(self):
1776
1853
error_handler = None
1777
1854
)
1778
1855
1779
- config_service = optimizely_config .OptimizelyConfigService (proj_conf )
1856
+ config_service = optimizely_config .OptimizelyConfigService (proj_conf , None )
1780
1857
1781
1858
experiments_key_map , experiments_id_map = config_service ._get_experiments_maps ()
1782
1859
0 commit comments