@@ -1074,6 +1074,189 @@ 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_excludes_ups_lookup (self ):
1078+ """Test that CMAB experiments skip User Profile Service lookup."""
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+ '111150' ,
1091+ 'cmab_experiment' ,
1092+ 'Running' ,
1093+ '111150' ,
1094+ [], # No audience IDs
1095+ {},
1096+ [
1097+ entities .Variation ('111151' , 'variation_1' ),
1098+ entities .Variation ('111152' , 'variation_2' )
1099+ ],
1100+ [
1101+ {'entityId' : '111151' , 'endOfRange' : 5000 },
1102+ {'entityId' : '111152' , 'endOfRange' : 10000 }
1103+ ],
1104+ cmab = {'trafficAllocation' : 5000 }
1105+ )
1106+
1107+ # Create user profile service and tracker
1108+ user_profile_service = user_profile .UserProfileService ()
1109+ user_profile_tracker = user_profile .UserProfileTracker (user .user_id , user_profile_service )
1110+
1111+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' , return_value = True ), \
1112+ mock .patch ('optimizely.helpers.audience.does_user_meet_audience_conditions' , return_value = [True , []]), \
1113+ mock .patch .object (self .decision_service .bucketer , 'bucket_to_entity_id' ,
1114+ return_value = ['$' , []]), \
1115+ mock .patch .object (self .decision_service , 'cmab_service' ) as mock_cmab_service , \
1116+ mock .patch .object (self .decision_service , 'get_stored_variation' ) as mock_get_stored_variation , \
1117+ mock .patch .object (self .decision_service , 'logger' ) as mock_logger , \
1118+ mock .patch .object (self .project_config , 'get_variation_from_id' ,
1119+ return_value = entities .Variation ('111151' , 'variation_1' )):
1120+
1121+ # Setup CMAB service to return a decision
1122+ mock_cmab_service .get_decision .return_value = (
1123+ {'variation_id' : '111151' , 'cmab_uuid' : 'test-cmab-uuid-123' },
1124+ []
1125+ )
1126+
1127+ # Call get_variation with the CMAB experiment and UPS tracker
1128+ variation_result = self .decision_service .get_variation (
1129+ self .project_config ,
1130+ cmab_experiment ,
1131+ user ,
1132+ user_profile_tracker
1133+ )
1134+ variation = variation_result ['variation' ]
1135+ reasons = variation_result ['reasons' ]
1136+
1137+ # Verify we get a variation
1138+ self .assertEqual (entities .Variation ('111151' , 'variation_1' ), variation )
1139+
1140+ # Verify UPS lookup was NOT called for CMAB
1141+ mock_get_stored_variation .assert_not_called ()
1142+
1143+ # Verify decision reason includes UPS exclusion message
1144+ self .assertIn ('Skipping User Profile Service for CMAB experiment "cmab_experiment".' , reasons )
1145+
1146+ # Verify logger was called with UPS exclusion message
1147+ mock_logger .debug .assert_any_call ('Skipping User Profile Service for CMAB experiment "cmab_experiment".' )
1148+
1149+ def test_get_variation_cmab_excludes_ups_update (self ):
1150+ """Test that CMAB experiments skip User Profile Service updates."""
1151+
1152+ # Create a user context
1153+ user = optimizely_user_context .OptimizelyUserContext (
1154+ optimizely_client = None ,
1155+ logger = None ,
1156+ user_id = "test_user" ,
1157+ user_attributes = {}
1158+ )
1159+
1160+ # Create a CMAB experiment
1161+ cmab_experiment = entities .Experiment (
1162+ '111150' ,
1163+ 'cmab_experiment' ,
1164+ 'Running' ,
1165+ '111150' ,
1166+ [], # No audience IDs
1167+ {},
1168+ [
1169+ entities .Variation ('111151' , 'variation_1' ),
1170+ entities .Variation ('111152' , 'variation_2' )
1171+ ],
1172+ [
1173+ {'entityId' : '111151' , 'endOfRange' : 5000 },
1174+ {'entityId' : '111152' , 'endOfRange' : 10000 }
1175+ ],
1176+ cmab = {'trafficAllocation' : 5000 }
1177+ )
1178+
1179+ # Create user profile service and tracker
1180+ user_profile_service = user_profile .UserProfileService ()
1181+ user_profile_tracker = user_profile .UserProfileTracker (user .user_id , user_profile_service )
1182+
1183+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' , return_value = True ), \
1184+ mock .patch ('optimizely.helpers.audience.does_user_meet_audience_conditions' , return_value = [True , []]), \
1185+ mock .patch .object (self .decision_service .bucketer , 'bucket_to_entity_id' ,
1186+ return_value = ['$' , []]), \
1187+ mock .patch .object (self .decision_service , 'cmab_service' ) as mock_cmab_service , \
1188+ mock .patch .object (user_profile_tracker , 'update_user_profile' ) as mock_update_profile , \
1189+ mock .patch .object (self .project_config , 'get_variation_from_id' ,
1190+ return_value = entities .Variation ('111151' , 'variation_1' )):
1191+
1192+ # Setup CMAB service to return a decision
1193+ mock_cmab_service .get_decision .return_value = (
1194+ {'variation_id' : '111151' , 'cmab_uuid' : 'test-cmab-uuid-123' },
1195+ []
1196+ )
1197+
1198+ # Call get_variation with the CMAB experiment and UPS tracker
1199+ variation_result = self .decision_service .get_variation (
1200+ self .project_config ,
1201+ cmab_experiment ,
1202+ user ,
1203+ user_profile_tracker
1204+ )
1205+ variation = variation_result ['variation' ]
1206+
1207+ # Verify we get a variation
1208+ self .assertEqual (entities .Variation ('111151' , 'variation_1' ), variation )
1209+
1210+ # Verify UPS update was NOT called for CMAB
1211+ mock_update_profile .assert_not_called ()
1212+
1213+ def test_get_variation_non_cmab_uses_ups (self ):
1214+ """Test that non-CMAB experiments still use User Profile Service."""
1215+
1216+ # Create a user context
1217+ user = optimizely_user_context .OptimizelyUserContext (
1218+ optimizely_client = None ,
1219+ logger = None ,
1220+ user_id = "test_user" ,
1221+ user_attributes = {}
1222+ )
1223+
1224+ # Create a regular (non-CMAB) experiment
1225+ regular_experiment = self .project_config .get_experiment_from_key ("test_experiment" )
1226+
1227+ # Create user profile service and tracker
1228+ user_profile_service = user_profile .UserProfileService ()
1229+ user_profile_tracker = user_profile .UserProfileTracker (user .user_id , user_profile_service )
1230+
1231+ with mock .patch .object (self .decision_service , 'get_forced_variation' ,
1232+ return_value = [None , []]), \
1233+ mock .patch .object (self .decision_service , 'get_whitelisted_variation' ,
1234+ return_value = [None , []]), \
1235+ mock .patch .object (self .decision_service , 'get_stored_variation' ,
1236+ return_value = None ) as mock_get_stored_variation , \
1237+ mock .patch ('optimizely.helpers.audience.does_user_meet_audience_conditions' , return_value = [True , []]), \
1238+ mock .patch .object (self .decision_service .bucketer , 'bucket' ,
1239+ return_value = [entities .Variation ('211129' , 'variation' ), []]), \
1240+ mock .patch .object (user_profile_tracker , 'update_user_profile' ) as mock_update_profile :
1241+
1242+ # Call get_variation with regular experiment and UPS tracker
1243+ variation_result = self .decision_service .get_variation (
1244+ self .project_config ,
1245+ regular_experiment ,
1246+ user ,
1247+ user_profile_tracker
1248+ )
1249+ variation = variation_result ['variation' ]
1250+
1251+ # Verify we get a variation
1252+ self .assertIsNotNone (variation )
1253+
1254+ # Verify UPS lookup WAS called for non-CMAB
1255+ mock_get_stored_variation .assert_called_once ()
1256+
1257+ # Verify UPS update WAS called for non-CMAB
1258+ mock_update_profile .assert_called_once_with (regular_experiment , variation )
1259+
10771260
10781261class FeatureFlagDecisionTests (base .BaseTest ):
10791262 def setUp (self ):
0 commit comments