@@ -1074,6 +1074,175 @@ 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_skips_ups_lookup (self ):
1078+ """Test that get_variation skips UPS lookup for CMAB experiments."""
1079+
1080+ user = optimizely_user_context .OptimizelyUserContext (
1081+ optimizely_client = None ,
1082+ logger = None ,
1083+ user_id = "test_user" ,
1084+ user_attributes = {}
1085+ )
1086+
1087+ cmab_experiment = entities .Experiment (
1088+ '111150' ,
1089+ 'cmab_experiment' ,
1090+ 'Running' ,
1091+ '111150' ,
1092+ [],
1093+ {},
1094+ [
1095+ entities .Variation ('111151' , 'variation_1' ),
1096+ entities .Variation ('111152' , 'variation_2' )
1097+ ],
1098+ [
1099+ {'entityId' : '111151' , 'endOfRange' : 5000 },
1100+ {'entityId' : '111152' , 'endOfRange' : 10000 }
1101+ ],
1102+ cmab = {'trafficAllocation' : 5000 }
1103+ )
1104+
1105+ user_profile_service = user_profile .UserProfileService ()
1106+ user_profile_tracker = user_profile .UserProfileTracker (user .user_id , user_profile_service )
1107+
1108+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' , return_value = True ), \
1109+ mock .patch ('optimizely.helpers.audience.does_user_meet_audience_conditions' , return_value = [True , []]), \
1110+ mock .patch .object (self .decision_service .bucketer , 'bucket_to_entity_id' ,
1111+ return_value = ['$' , []]), \
1112+ mock .patch .object (self .decision_service , 'cmab_service' ) as mock_cmab_service , \
1113+ mock .patch .object (self .project_config , 'get_variation_from_id' ,
1114+ return_value = entities .Variation ('111151' , 'variation_1' )), \
1115+ mock .patch .object (self .decision_service , 'get_stored_variation' ) as mock_get_stored , \
1116+ mock .patch .object (self .decision_service , 'logger' ) as mock_logger :
1117+
1118+ mock_cmab_service .get_decision .return_value = (
1119+ {'variation_id' : '111151' , 'cmab_uuid' : 'test-cmab-uuid-123' },
1120+ []
1121+ )
1122+
1123+ variation_result = self .decision_service .get_variation (
1124+ self .project_config ,
1125+ cmab_experiment ,
1126+ user ,
1127+ user_profile_tracker
1128+ )
1129+
1130+ # UPS lookup should NOT be called for CMAB experiments
1131+ mock_get_stored .assert_not_called ()
1132+
1133+ # Should still return valid variation from CMAB
1134+ self .assertEqual (entities .Variation ('111151' , 'variation_1' ), variation_result ['variation' ])
1135+ self .assertEqual ('test-cmab-uuid-123' , variation_result ['cmab_uuid' ])
1136+ self .assertStrictFalse (variation_result ['error' ])
1137+
1138+ # Should log the UPS exclusion reason
1139+ mock_logger .info .assert_any_call (
1140+ 'Skipping user profile service for CMAB experiment "cmab_experiment". '
1141+ 'CMAB decisions are excluded from UPS.'
1142+ )
1143+
1144+ # Decision reasons should include UPS exclusion message
1145+ self .assertIn (
1146+ 'Skipping user profile service for CMAB experiment "cmab_experiment". '
1147+ 'CMAB decisions are excluded from UPS.' ,
1148+ variation_result ['reasons' ]
1149+ )
1150+
1151+ def test_get_variation_cmab_experiment_skips_ups_save (self ):
1152+ """Test that get_variation does not save to UPS for CMAB experiments."""
1153+
1154+ user = optimizely_user_context .OptimizelyUserContext (
1155+ optimizely_client = None ,
1156+ logger = None ,
1157+ user_id = "test_user" ,
1158+ user_attributes = {}
1159+ )
1160+
1161+ cmab_experiment = entities .Experiment (
1162+ '111150' ,
1163+ 'cmab_experiment' ,
1164+ 'Running' ,
1165+ '111150' ,
1166+ [],
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+ user_profile_service = user_profile .UserProfileService ()
1180+ user_profile_tracker = user_profile .UserProfileTracker (user .user_id , user_profile_service )
1181+
1182+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' , return_value = True ), \
1183+ mock .patch ('optimizely.helpers.audience.does_user_meet_audience_conditions' , return_value = [True , []]), \
1184+ mock .patch .object (self .decision_service .bucketer , 'bucket_to_entity_id' ,
1185+ return_value = ['$' , []]), \
1186+ mock .patch .object (self .decision_service , 'cmab_service' ) as mock_cmab_service , \
1187+ mock .patch .object (self .project_config , 'get_variation_from_id' ,
1188+ return_value = entities .Variation ('111151' , 'variation_1' )), \
1189+ mock .patch .object (user_profile_tracker , 'update_user_profile' ) as mock_update_profile , \
1190+ mock .patch .object (self .decision_service , 'logger' ):
1191+
1192+ mock_cmab_service .get_decision .return_value = (
1193+ {'variation_id' : '111151' , 'cmab_uuid' : 'test-cmab-uuid-123' },
1194+ []
1195+ )
1196+
1197+ variation_result = self .decision_service .get_variation (
1198+ self .project_config ,
1199+ cmab_experiment ,
1200+ user ,
1201+ user_profile_tracker
1202+ )
1203+
1204+ # UPS save should NOT be called for CMAB experiments
1205+ mock_update_profile .assert_not_called ()
1206+
1207+ # Should still return valid variation
1208+ self .assertEqual (entities .Variation ('111151' , 'variation_1' ), variation_result ['variation' ])
1209+ self .assertStrictFalse (variation_result ['error' ])
1210+
1211+ def test_get_variation_non_cmab_experiment_still_uses_ups (self ):
1212+ """Test that non-CMAB experiments still use UPS normally."""
1213+
1214+ user = optimizely_user_context .OptimizelyUserContext (
1215+ optimizely_client = None ,
1216+ logger = None ,
1217+ user_id = "test_user" ,
1218+ user_attributes = {}
1219+ )
1220+
1221+ experiment = self .project_config .get_experiment_from_key ('test_experiment' )
1222+
1223+ user_profile_service = user_profile .UserProfileService ()
1224+ user_profile_tracker_instance = user_profile .UserProfileTracker (user .user_id , user_profile_service )
1225+
1226+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' , return_value = True ), \
1227+ mock .patch ('optimizely.helpers.audience.does_user_meet_audience_conditions' , return_value = [True , []]), \
1228+ mock .patch .object (self .decision_service , 'get_stored_variation' ,
1229+ return_value = entities .Variation ('111128' , 'control' )) as mock_get_stored , \
1230+ mock .patch .object (self .decision_service , 'logger' ):
1231+
1232+ variation_result = self .decision_service .get_variation (
1233+ self .project_config ,
1234+ experiment ,
1235+ user ,
1236+ user_profile_tracker_instance
1237+ )
1238+
1239+ # UPS lookup SHOULD be called for non-CMAB experiments
1240+ mock_get_stored .assert_called_once ()
1241+
1242+ # Should return stored variation
1243+ self .assertEqual (entities .Variation ('111128' , 'control' ), variation_result ['variation' ])
1244+ self .assertStrictFalse (variation_result ['error' ])
1245+
10771246
10781247class FeatureFlagDecisionTests (base .BaseTest ):
10791248 def setUp (self ):
0 commit comments