@@ -1074,6 +1074,93 @@ def test_get_variation_cmab_experiment_with_whitelisted_variation(self):
10741074 mock_bucket .assert_not_called ()
10751075 mock_cmab_decision .assert_not_called ()
10761076
1077+ def test_get_variation_cmab_experiment_excludes_user_profile_service (self ):
1078+ """Test that CMAB experiments exclude UserProfileService for both load and save operations."""
1079+
1080+ # Create a user context
1081+ user = optimizely_user_context .OptimizelyUserContext (
1082+ optimizely_client = None ,
1083+ logger = None ,
1084+ user_id = "test_user" ,
1085+ user_attributes = {}
1086+ )
1087+
1088+ # Create a CMAB experiment
1089+ cmab_experiment = entities .Experiment (
1090+ id = '111150' ,
1091+ key = 'cmab_experiment' ,
1092+ status = 'Running' ,
1093+ audienceIds = [],
1094+ variations = [entities .Variation ('111151' , 'variation_1' )],
1095+ forcedVariations = {},
1096+ trafficAllocation = [{'entityId' : '111151' , 'endOfRange' : 10000 }],
1097+ layerId = '111150' ,
1098+ cmab = True
1099+ )
1100+
1101+ # Create a mock user profile service
1102+ mock_ups = mock .Mock ()
1103+ mock_ups .lookup .return_value = {
1104+ 'user_id' : 'test_user' ,
1105+ 'experiment_bucket_map' : {
1106+ '111150' : {'variation_id' : '111152' } # Different variation in profile
1107+ }
1108+ }
1109+
1110+ # Create decision service with user profile service
1111+ decision_service_with_ups = decision_service .DecisionService (
1112+ mock .MagicMock (),
1113+ mock_ups ,
1114+ mock .MagicMock ()
1115+ )
1116+
1117+ # Mock the CMAB decision to return variation_1
1118+ cmab_decision_result = {
1119+ 'error' : False ,
1120+ 'result' : {'variation_id' : '111151' , 'cmab_uuid' : 'test-uuid' },
1121+ 'reasons' : ['CMAB decision made' ]
1122+ }
1123+
1124+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' ,
1125+ return_value = True ), \
1126+ mock .patch .object (self .project_config , 'get_variation_from_id' ,
1127+ return_value = entities .Variation ('111151' , 'variation_1' )), \
1128+ mock .patch ('optimizely.bucketer.Bucketer.bucket_to_entity_id' ,
1129+ return_value = ('111151' , [])), \
1130+ mock .patch .object (decision_service_with_ups , '_get_decision_for_cmab_experiment' ,
1131+ return_value = cmab_decision_result ):
1132+
1133+ # Create user profile tracker
1134+ from optimizely .user_profile import UserProfileTracker
1135+ user_profile_tracker = UserProfileTracker ('test_user' , mock_ups , mock .MagicMock ())
1136+
1137+ # Call get_variation with user profile tracker
1138+ variation_result = decision_service_with_ups .get_variation (
1139+ self .project_config ,
1140+ cmab_experiment ,
1141+ user ,
1142+ user_profile_tracker
1143+ )
1144+
1145+ variation = variation_result ['variation' ]
1146+ cmab_uuid = variation_result ['cmab_uuid' ]
1147+ reasons = variation_result ['reasons' ]
1148+
1149+ # Verify that UPS was NOT used to load the saved variation (111152)
1150+ # Instead, CMAB decision returned variation_1 (111151)
1151+ self .assertEqual ('variation_1' , variation .key )
1152+ self .assertEqual ('111151' , variation .id )
1153+ self .assertEqual ('test-uuid' , cmab_uuid )
1154+
1155+ # Verify the exclusion reason is in the decision reasons
1156+ self .assertIn ('User profile service excluded for CMAB experiment to allow dynamic decisions.' , reasons )
1157+
1158+ # Verify UPS lookup was NOT called (CMAB should bypass UPS load)
1159+ mock_ups .lookup .assert_not_called ()
1160+
1161+ # Verify UPS save was NOT called (CMAB should bypass UPS save)
1162+ mock_ups .save .assert_not_called ()
1163+
10771164
10781165class FeatureFlagDecisionTests (base .BaseTest ):
10791166 def setUp (self ):
0 commit comments