@@ -1074,6 +1074,211 @@ 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+ # 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+ [],
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+ # Set up a user profile tracker with a stored variation for this experiment
1108+ ups = user_profile .UserProfileService ()
1109+ tracker = user_profile .UserProfileTracker ("test_user" , ups , self .decision_service .logger )
1110+ stored_profile = user_profile .UserProfile (
1111+ "test_user" ,
1112+ {"111150" : {"variation_id" : "111152" }}
1113+ )
1114+ tracker .user_profile = stored_profile
1115+
1116+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' , return_value = True ), \
1117+ mock .patch ('optimizely.helpers.audience.does_user_meet_audience_conditions' , return_value = [True , []]), \
1118+ mock .patch .object (self .decision_service .bucketer , 'bucket_to_entity_id' ,
1119+ return_value = ['$' , []]), \
1120+ mock .patch .object (self .decision_service , 'cmab_service' ) as mock_cmab_service , \
1121+ mock .patch .object (self .project_config , 'get_variation_from_id' ,
1122+ return_value = entities .Variation ('111151' , 'variation_1' )), \
1123+ mock .patch .object (self .decision_service , 'get_stored_variation' ) as mock_stored_variation , \
1124+ mock .patch .object (self .decision_service , 'logger' ) as mock_logger :
1125+
1126+ # Configure CMAB service to return a decision
1127+ mock_cmab_service .get_decision .return_value = (
1128+ {
1129+ 'variation_id' : '111151' ,
1130+ 'cmab_uuid' : 'test-cmab-uuid-456'
1131+ },
1132+ []
1133+ )
1134+
1135+ # Call get_variation with the CMAB experiment and a user profile tracker
1136+ variation_result = self .decision_service .get_variation (
1137+ self .project_config ,
1138+ cmab_experiment ,
1139+ user ,
1140+ tracker
1141+ )
1142+
1143+ # Verify UPS lookup was NOT called for CMAB experiment
1144+ mock_stored_variation .assert_not_called ()
1145+
1146+ # Verify the CMAB decision was used instead
1147+ self .assertEqual (entities .Variation ('111151' , 'variation_1' ), variation_result ['variation' ])
1148+ self .assertEqual ('test-cmab-uuid-456' , variation_result ['cmab_uuid' ])
1149+ self .assertStrictFalse (variation_result ['error' ])
1150+
1151+ # Verify the UPS exclusion reason is in the decision reasons
1152+ ups_skip_reasons = [r for r in variation_result ['reasons' ]
1153+ if 'Skipping user profile service lookup for CMAB experiment' in r ]
1154+ self .assertEqual (len (ups_skip_reasons ), 1 )
1155+
1156+ def test_get_variation_cmab_experiment_skips_ups_save (self ):
1157+ """Test that get_variation skips UPS save for CMAB experiments."""
1158+
1159+ # Create a user context
1160+ user = optimizely_user_context .OptimizelyUserContext (
1161+ optimizely_client = None ,
1162+ logger = None ,
1163+ user_id = "test_user" ,
1164+ user_attributes = {}
1165+ )
1166+
1167+ # Create a CMAB experiment
1168+ cmab_experiment = entities .Experiment (
1169+ '111150' ,
1170+ 'cmab_experiment' ,
1171+ 'Running' ,
1172+ '111150' ,
1173+ [],
1174+ {},
1175+ [
1176+ entities .Variation ('111151' , 'variation_1' ),
1177+ entities .Variation ('111152' , 'variation_2' )
1178+ ],
1179+ [
1180+ {'entityId' : '111151' , 'endOfRange' : 5000 },
1181+ {'entityId' : '111152' , 'endOfRange' : 10000 }
1182+ ],
1183+ cmab = {'trafficAllocation' : 5000 }
1184+ )
1185+
1186+ # Set up a user profile tracker
1187+ ups = user_profile .UserProfileService ()
1188+ tracker = user_profile .UserProfileTracker ("test_user" , ups , self .decision_service .logger )
1189+
1190+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' , return_value = True ), \
1191+ mock .patch ('optimizely.helpers.audience.does_user_meet_audience_conditions' , return_value = [True , []]), \
1192+ mock .patch .object (self .decision_service .bucketer , 'bucket_to_entity_id' ,
1193+ return_value = ['$' , []]), \
1194+ mock .patch .object (self .decision_service , 'cmab_service' ) as mock_cmab_service , \
1195+ mock .patch .object (self .project_config , 'get_variation_from_id' ,
1196+ return_value = entities .Variation ('111151' , 'variation_1' )), \
1197+ mock .patch .object (tracker , 'update_user_profile' ) as mock_update_profile , \
1198+ mock .patch .object (self .decision_service , 'logger' ):
1199+
1200+ # Configure CMAB service to return a decision
1201+ mock_cmab_service .get_decision .return_value = (
1202+ {
1203+ 'variation_id' : '111151' ,
1204+ 'cmab_uuid' : 'test-cmab-uuid-789'
1205+ },
1206+ []
1207+ )
1208+
1209+ # Call get_variation with the CMAB experiment and a user profile tracker
1210+ variation_result = self .decision_service .get_variation (
1211+ self .project_config ,
1212+ cmab_experiment ,
1213+ user ,
1214+ tracker
1215+ )
1216+
1217+ # Verify the variation was assigned
1218+ self .assertEqual (entities .Variation ('111151' , 'variation_1' ), variation_result ['variation' ])
1219+
1220+ # Verify UPS save was NOT called for CMAB experiment
1221+ mock_update_profile .assert_not_called ()
1222+
1223+ def test_get_variation_non_cmab_experiment_still_uses_ups (self ):
1224+ """Test that get_variation still uses UPS for non-CMAB experiments."""
1225+
1226+ # Create a user context
1227+ user = optimizely_user_context .OptimizelyUserContext (
1228+ optimizely_client = None ,
1229+ logger = None ,
1230+ user_id = "test_user" ,
1231+ user_attributes = {}
1232+ )
1233+
1234+ # Create a non-CMAB experiment (no cmab attribute)
1235+ experiment = entities .Experiment (
1236+ '111150' ,
1237+ 'regular_experiment' ,
1238+ 'Running' ,
1239+ '111150' ,
1240+ [],
1241+ {},
1242+ [
1243+ entities .Variation ('111151' , 'variation_1' ),
1244+ ],
1245+ [
1246+ {'entityId' : '111151' , 'endOfRange' : 10000 },
1247+ ],
1248+ )
1249+
1250+ # Set up a user profile tracker with a stored variation
1251+ ups = user_profile .UserProfileService ()
1252+ tracker = user_profile .UserProfileTracker ("test_user" , ups , self .decision_service .logger )
1253+
1254+ stored_variation = entities .Variation ('111151' , 'variation_1' )
1255+
1256+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' , return_value = True ), \
1257+ mock .patch .object (self .decision_service , 'get_forced_variation' , return_value = [None , []]), \
1258+ mock .patch .object (self .decision_service , 'get_whitelisted_variation' , return_value = [None , []]), \
1259+ mock .patch .object (self .decision_service , 'get_stored_variation' ,
1260+ return_value = stored_variation ) as mock_stored_variation , \
1261+ mock .patch .object (self .decision_service , 'logger' ) as mock_logger :
1262+
1263+ # Call get_variation with a non-CMAB experiment and a user profile tracker
1264+ variation_result = self .decision_service .get_variation (
1265+ self .project_config ,
1266+ experiment ,
1267+ user ,
1268+ tracker
1269+ )
1270+
1271+ # Verify UPS lookup WAS called for non-CMAB experiment
1272+ mock_stored_variation .assert_called_once ()
1273+
1274+ # Verify the stored variation was returned
1275+ self .assertEqual (stored_variation , variation_result ['variation' ])
1276+
1277+ # Verify there is no UPS exclusion reason
1278+ ups_skip_reasons = [r for r in variation_result ['reasons' ]
1279+ if 'Skipping user profile service lookup for CMAB experiment' in r ]
1280+ self .assertEqual (len (ups_skip_reasons ), 0 )
1281+
10771282
10781283class FeatureFlagDecisionTests (base .BaseTest ):
10791284 def setUp (self ):
0 commit comments