@@ -1890,3 +1890,209 @@ def test_get_variation_for_feature_returns_rollout_in_experiment_bucket_range_25
18901890 mock_config_logging .debug .assert_called_with (
18911891 'Assigned bucket 4000 to user with bucketing ID "test_user".' )
18921892 mock_generate_bucket_value .assert_called_with ("test_user211147" )
1893+
1894+ def test_get_variation_cmab_experiment_skips_ups_lookup (self ):
1895+ """Test that CMAB experiments skip user profile service lookup."""
1896+
1897+ user = optimizely_user_context .OptimizelyUserContext (
1898+ optimizely_client = None ,
1899+ logger = None ,
1900+ user_id = "test_user" ,
1901+ user_attributes = {}
1902+ )
1903+
1904+ cmab_experiment = entities .Experiment (
1905+ '111150' ,
1906+ 'cmab_experiment' ,
1907+ 'Running' ,
1908+ '111150' ,
1909+ [],
1910+ {},
1911+ [
1912+ entities .Variation ('111151' , 'variation_1' ),
1913+ entities .Variation ('111152' , 'variation_2' )
1914+ ],
1915+ [
1916+ {'entityId' : '111151' , 'endOfRange' : 5000 },
1917+ {'entityId' : '111152' , 'endOfRange' : 10000 }
1918+ ],
1919+ cmab = {'trafficAllocation' : 5000 }
1920+ )
1921+
1922+ # Create a user profile tracker with a stored variation for this experiment
1923+ ups = user_profile .UserProfileService ()
1924+ tracker = user_profile .UserProfileTracker ("test_user" , ups , self .decision_service .logger )
1925+ tracker .user_profile = user_profile .UserProfile (
1926+ "test_user" ,
1927+ {"111150" : {"variation_id" : "111151" }}
1928+ )
1929+
1930+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' , return_value = True ), \
1931+ mock .patch ('optimizely.helpers.audience.does_user_meet_audience_conditions' , return_value = [True , []]), \
1932+ mock .patch .object (self .decision_service .bucketer , 'bucket_to_entity_id' ,
1933+ return_value = ['$' , []]), \
1934+ mock .patch .object (self .decision_service , 'cmab_service' ) as mock_cmab_service , \
1935+ mock .patch .object (self .project_config , 'get_variation_from_id' ,
1936+ return_value = entities .Variation ('111151' , 'variation_1' )), \
1937+ mock .patch .object (self .decision_service , 'get_stored_variation' ) as mock_get_stored , \
1938+ mock .patch .object (self .decision_service ,
1939+ 'logger' ) as mock_logger :
1940+
1941+ mock_cmab_service .get_decision .return_value = (
1942+ {
1943+ 'variation_id' : '111151' ,
1944+ 'cmab_uuid' : 'test-cmab-uuid-123'
1945+ },
1946+ []
1947+ )
1948+
1949+ variation_result = self .decision_service .get_variation (
1950+ self .project_config ,
1951+ cmab_experiment ,
1952+ user ,
1953+ tracker
1954+ )
1955+
1956+ # Verify get_stored_variation was NOT called for CMAB experiment
1957+ mock_get_stored .assert_not_called ()
1958+
1959+ # Verify the UPS skip reason is in the decision reasons
1960+ reasons = variation_result ['reasons' ]
1961+ self .assertIn (
1962+ 'Skipping user profile service for CMAB experiment "cmab_experiment".' ,
1963+ reasons
1964+ )
1965+
1966+ # Verify the variation was still resolved via CMAB service
1967+ self .assertEqual (entities .Variation ('111151' , 'variation_1' ), variation_result ['variation' ])
1968+ self .assertEqual ('test-cmab-uuid-123' , variation_result ['cmab_uuid' ])
1969+ self .assertStrictFalse (variation_result ['error' ])
1970+
1971+ mock_logger .info .assert_any_call (
1972+ 'Skipping user profile service for CMAB experiment "cmab_experiment".'
1973+ )
1974+
1975+ def test_get_variation_cmab_experiment_skips_ups_save (self ):
1976+ """Test that CMAB experiments do not save decisions to user profile service."""
1977+
1978+ user = optimizely_user_context .OptimizelyUserContext (
1979+ optimizely_client = None ,
1980+ logger = None ,
1981+ user_id = "test_user" ,
1982+ user_attributes = {}
1983+ )
1984+
1985+ cmab_experiment = entities .Experiment (
1986+ '111150' ,
1987+ 'cmab_experiment' ,
1988+ 'Running' ,
1989+ '111150' ,
1990+ [],
1991+ {},
1992+ [
1993+ entities .Variation ('111151' , 'variation_1' ),
1994+ entities .Variation ('111152' , 'variation_2' )
1995+ ],
1996+ [
1997+ {'entityId' : '111151' , 'endOfRange' : 5000 },
1998+ {'entityId' : '111152' , 'endOfRange' : 10000 }
1999+ ],
2000+ cmab = {'trafficAllocation' : 5000 }
2001+ )
2002+
2003+ ups = user_profile .UserProfileService ()
2004+ tracker = user_profile .UserProfileTracker ("test_user" , ups , self .decision_service .logger )
2005+
2006+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' , return_value = True ), \
2007+ mock .patch ('optimizely.helpers.audience.does_user_meet_audience_conditions' , return_value = [True , []]), \
2008+ mock .patch .object (self .decision_service .bucketer , 'bucket_to_entity_id' ,
2009+ return_value = ['$' , []]), \
2010+ mock .patch .object (self .decision_service , 'cmab_service' ) as mock_cmab_service , \
2011+ mock .patch .object (self .project_config , 'get_variation_from_id' ,
2012+ return_value = entities .Variation ('111151' , 'variation_1' )), \
2013+ mock .patch .object (tracker , 'update_user_profile' ) as mock_update_profile , \
2014+ mock .patch .object (self .decision_service , 'logger' ):
2015+
2016+ mock_cmab_service .get_decision .return_value = (
2017+ {
2018+ 'variation_id' : '111151' ,
2019+ 'cmab_uuid' : 'test-cmab-uuid-123'
2020+ },
2021+ []
2022+ )
2023+
2024+ variation_result = self .decision_service .get_variation (
2025+ self .project_config ,
2026+ cmab_experiment ,
2027+ user ,
2028+ tracker
2029+ )
2030+
2031+ # Verify update_user_profile was NOT called for CMAB experiment
2032+ mock_update_profile .assert_not_called ()
2033+
2034+ # Verify the variation was still returned correctly
2035+ self .assertEqual (entities .Variation ('111151' , 'variation_1' ), variation_result ['variation' ])
2036+ self .assertEqual ('test-cmab-uuid-123' , variation_result ['cmab_uuid' ])
2037+ self .assertStrictFalse (variation_result ['error' ])
2038+
2039+ def test_get_variation_non_cmab_experiment_uses_ups (self ):
2040+ """Test that non-CMAB experiments still use user profile service normally."""
2041+
2042+ user = optimizely_user_context .OptimizelyUserContext (
2043+ optimizely_client = None ,
2044+ logger = None ,
2045+ user_id = "test_user" ,
2046+ user_attributes = {}
2047+ )
2048+
2049+ # Create a non-CMAB experiment (cmab=None)
2050+ non_cmab_experiment = entities .Experiment (
2051+ '111127' ,
2052+ 'test_experiment' ,
2053+ 'Running' ,
2054+ '111182' ,
2055+ [],
2056+ {},
2057+ [
2058+ entities .Variation ('111128' , 'control' ),
2059+ entities .Variation ('111129' , 'variation' )
2060+ ],
2061+ [
2062+ {'entityId' : '111128' , 'endOfRange' : 5000 },
2063+ {'entityId' : '111129' , 'endOfRange' : 10000 }
2064+ ],
2065+ cmab = None
2066+ )
2067+
2068+ ups = user_profile .UserProfileService ()
2069+ tracker = user_profile .UserProfileTracker ("test_user" , ups , self .decision_service .logger )
2070+ tracker .user_profile = user_profile .UserProfile (
2071+ "test_user" ,
2072+ {"111127" : {"variation_id" : "111128" }}
2073+ )
2074+
2075+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' , return_value = True ), \
2076+ mock .patch .object (self .decision_service , 'get_stored_variation' ,
2077+ return_value = entities .Variation ('111128' , 'control' )) as mock_get_stored , \
2078+ mock .patch .object (self .decision_service , 'logger' ) as mock_logger :
2079+
2080+ variation_result = self .decision_service .get_variation (
2081+ self .project_config ,
2082+ non_cmab_experiment ,
2083+ user ,
2084+ tracker
2085+ )
2086+
2087+ # Verify get_stored_variation WAS called for non-CMAB experiment
2088+ mock_get_stored .assert_called_once ()
2089+
2090+ # Verify the stored variation was returned
2091+ self .assertEqual (entities .Variation ('111128' , 'control' ), variation_result ['variation' ])
2092+ self .assertIsNone (variation_result ['cmab_uuid' ])
2093+ self .assertStrictFalse (variation_result ['error' ])
2094+
2095+ # Verify no UPS skip message in reasons
2096+ reasons = variation_result ['reasons' ]
2097+ for reason in reasons :
2098+ self .assertNotIn ('Skipping user profile service' , reason )
0 commit comments