@@ -1074,6 +1074,87 @@ 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_ups (self ):
1078+ """Test that CMAB experiments exclude User Profile Service for both reading and saving."""
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 a mock user profile tracker
1108+ mock_user_profile_tracker = mock .MagicMock ()
1109+ mock_user_profile = user_profile .UserProfile ('test_user' )
1110+ # Add a stored variation for the CMAB experiment (should be ignored)
1111+ mock_user_profile .experiment_bucket_map ['111150' ] = {'variation_id' : '111152' }
1112+ mock_user_profile_tracker .get_user_profile .return_value = mock_user_profile
1113+
1114+ with mock .patch ('optimizely.helpers.experiment.is_experiment_running' , return_value = True ), \
1115+ mock .patch ('optimizely.helpers.audience.does_user_meet_audience_conditions' , return_value = [True , []]), \
1116+ mock .patch .object (self .decision_service .bucketer , 'bucket_to_entity_id' ,
1117+ return_value = ['$' , []]) as mock_bucket , \
1118+ mock .patch .object (self .decision_service , 'cmab_service' ) as mock_cmab_service , \
1119+ mock .patch .object (self .project_config , 'get_variation_from_id' ,
1120+ return_value = entities .Variation ('111151' , 'variation_1' )), \
1121+ mock .patch .object (self .decision_service , 'logger' ) as mock_logger :
1122+
1123+ # Configure CMAB service to return a decision
1124+ mock_cmab_service .get_decision .return_value = (
1125+ {
1126+ 'variation_id' : '111151' ,
1127+ 'cmab_uuid' : 'test-cmab-uuid-123'
1128+ },
1129+ [] # reasons list
1130+ )
1131+
1132+ # Call get_variation with the CMAB experiment and user profile tracker
1133+ variation_result = self .decision_service .get_variation (
1134+ self .project_config ,
1135+ cmab_experiment ,
1136+ user ,
1137+ mock_user_profile_tracker
1138+ )
1139+ variation = variation_result ['variation' ]
1140+ cmab_uuid = variation_result ['cmab_uuid' ]
1141+ reasons = variation_result ['reasons' ]
1142+ error = variation_result ['error' ]
1143+
1144+ # Verify the variation returned is from CMAB, not from stored profile
1145+ self .assertEqual (entities .Variation ('111151' , 'variation_1' ), variation )
1146+ self .assertEqual ('test-cmab-uuid-123' , cmab_uuid )
1147+ self .assertStrictFalse (error )
1148+
1149+ # Verify UPS exclusion reason is in decision reasons
1150+ self .assertIn ('Skipping User Profile Service for CMAB experiment "cmab_experiment".' , reasons )
1151+
1152+ # Verify get_stored_variation was not called (implicitly, since we got CMAB decision)
1153+ mock_logger .debug .assert_any_call ('Skipping User Profile Service for CMAB experiment "cmab_experiment".' )
1154+
1155+ # Verify update_user_profile was NOT called (CMAB shouldn't save to UPS)
1156+ mock_user_profile_tracker .update_user_profile .assert_not_called ()
1157+
10771158
10781159class FeatureFlagDecisionTests (base .BaseTest ):
10791160 def setUp (self ):
0 commit comments