@@ -1074,6 +1074,184 @@ 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_user_profile_lookup (self ):
1078+ """Test that get_variation skips UPS lookup for CMAB experiments even when
1079+ user_profile_tracker is provided, and adds appropriate decision reason."""
1080+
1081+ user = optimizely_user_context .OptimizelyUserContext (
1082+ optimizely_client = None ,
1083+ logger = None ,
1084+ user_id = "test_user" ,
1085+ user_attributes = {}
1086+ )
1087+
1088+ cmab_experiment = entities .Experiment (
1089+ '111150' ,
1090+ 'cmab_experiment' ,
1091+ 'Running' ,
1092+ '111150' ,
1093+ [],
1094+ {},
1095+ [
1096+ entities .Variation ('111151' , 'variation_1' ),
1097+ entities .Variation ('111152' , 'variation_2' )
1098+ ],
1099+ [
1100+ {'entityId' : '111151' , 'endOfRange' : 5000 },
1101+ {'entityId' : '111152' , 'endOfRange' : 10000 }
1102+ ],
1103+ cmab = {'trafficAllocation' : 5000 }
1104+ )
1105+
1106+ user_profile_service = user_profile .UserProfileService ()
1107+ user_profile_tracker = user_profile .UserProfileTracker (user .user_id , user_profile_service )
1108+
1109+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' , return_value = True ), \
1110+ mock .patch ('optimizely.helpers.audience.does_user_meet_audience_conditions' , return_value = [True , []]), \
1111+ mock .patch .object (self .decision_service .bucketer , 'bucket_to_entity_id' ,
1112+ return_value = ['$' , []]), \
1113+ mock .patch .object (self .decision_service , 'cmab_service' ) as mock_cmab_service , \
1114+ mock .patch .object (self .project_config , 'get_variation_from_id' ,
1115+ return_value = entities .Variation ('111151' , 'variation_1' )), \
1116+ mock .patch (
1117+ 'optimizely.decision_service.DecisionService.get_stored_variation'
1118+ ) as mock_get_stored_variation , \
1119+ mock .patch .object (self .decision_service , 'logger' ) as mock_logger :
1120+
1121+ mock_cmab_service .get_decision .return_value = (
1122+ {
1123+ 'variation_id' : '111151' ,
1124+ 'cmab_uuid' : 'test-cmab-uuid-123'
1125+ },
1126+ []
1127+ )
1128+
1129+ variation_result = self .decision_service .get_variation (
1130+ self .project_config ,
1131+ cmab_experiment ,
1132+ user ,
1133+ user_profile_tracker
1134+ )
1135+
1136+ # Verify get_stored_variation was NOT called for CMAB experiment
1137+ mock_get_stored_variation .assert_not_called ()
1138+
1139+ # Verify the decision reason includes UPS exclusion message
1140+ reasons = variation_result ['reasons' ]
1141+ self .assertTrue (
1142+ any ('Skipping user profile service lookup for CMAB experiment' in r for r in reasons ),
1143+ 'Expected UPS exclusion reason in decide_reasons'
1144+ )
1145+
1146+ # Verify variation is still returned correctly
1147+ self .assertEqual (entities .Variation ('111151' , 'variation_1' ), variation_result ['variation' ])
1148+ self .assertEqual ('test-cmab-uuid-123' , variation_result ['cmab_uuid' ])
1149+ self .assertStrictFalse (variation_result ['error' ])
1150+
1151+ def test_get_variation_cmab_experiment_skips_user_profile_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+ {
1194+ 'variation_id' : '111151' ,
1195+ 'cmab_uuid' : 'test-cmab-uuid-123'
1196+ },
1197+ []
1198+ )
1199+
1200+ variation_result = self .decision_service .get_variation (
1201+ self .project_config ,
1202+ cmab_experiment ,
1203+ user ,
1204+ user_profile_tracker
1205+ )
1206+
1207+ # Verify update_user_profile was NOT called for CMAB experiment
1208+ mock_update_profile .assert_not_called ()
1209+
1210+ # Verify variation is still returned correctly
1211+ self .assertEqual (entities .Variation ('111151' , 'variation_1' ), variation_result ['variation' ])
1212+ self .assertEqual ('test-cmab-uuid-123' , variation_result ['cmab_uuid' ])
1213+
1214+ def test_get_variation_non_cmab_experiment_still_uses_user_profile (self ):
1215+ """Test that non-CMAB experiments still use UPS for lookup and save."""
1216+
1217+ user = optimizely_user_context .OptimizelyUserContext (
1218+ optimizely_client = None ,
1219+ logger = None ,
1220+ user_id = "test_user" ,
1221+ user_attributes = {}
1222+ )
1223+ user_profile_service = user_profile .UserProfileService ()
1224+ user_profile_tracker = user_profile .UserProfileTracker (user .user_id , user_profile_service )
1225+ experiment = self .project_config .get_experiment_from_key ("test_experiment" )
1226+
1227+ stored_variation = entities .Variation ("111129" , "variation" )
1228+
1229+ with mock .patch .object (self .decision_service , 'logger' ), \
1230+ mock .patch (
1231+ 'optimizely.decision_service.DecisionService.get_forced_variation' ,
1232+ return_value = [None , []]
1233+ ), \
1234+ mock .patch (
1235+ 'optimizely.decision_service.DecisionService.get_whitelisted_variation' ,
1236+ return_value = [None , []]
1237+ ), \
1238+ mock .patch (
1239+ 'optimizely.decision_service.DecisionService.get_stored_variation' ,
1240+ return_value = stored_variation
1241+ ) as mock_get_stored_variation :
1242+
1243+ variation_result = self .decision_service .get_variation (
1244+ self .project_config , experiment , user , user_profile_tracker
1245+ )
1246+
1247+ # Verify get_stored_variation WAS called for non-CMAB experiment
1248+ mock_get_stored_variation .assert_called_once_with (
1249+ self .project_config , experiment , user_profile_tracker .get_user_profile ()
1250+ )
1251+
1252+ # Verify stored variation is returned
1253+ self .assertEqual (stored_variation , variation_result ['variation' ])
1254+
10771255
10781256class FeatureFlagDecisionTests (base .BaseTest ):
10791257 def setUp (self ):
0 commit comments