Skip to content

Commit 15c3d59

Browse files
[FSSDK-12034] Update: Exclude CMAB from UserProfileService (#384)
* feat: add user profile service behavior for CMAB experiments * rubocop fix
1 parent f986c13 commit 15c3d59

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed

lib/optimizely/decision_service.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ def get_variation(project_config, experiment_id, user_context, user_profile_trac
132132
return VariationResult.new(nil, true, decide_reasons, nil)
133133
end
134134

135+
@logger.log(Logger::DEBUG, "Skipping user profile service for CMAB experiment '#{experiment_key}'. CMAB decisions are dynamic and not stored for sticky bucketing.")
136+
should_ignore_user_profile_service = true
135137
cmab_decision = cmab_decision_result.result
136138
variation_id = cmab_decision&.variation_id
137139
cmab_uuid = cmab_decision&.cmab_uuid

spec/decision_service_spec.rb

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,5 +1166,101 @@
11661166
expect(spy_cmab_service).not_to have_received(:get_decision)
11671167
end
11681168
end
1169+
1170+
describe 'user profile service behavior' do
1171+
it 'should not save user profile for CMAB experiments' do
1172+
# Create a CMAB experiment configuration
1173+
cmab_experiment = {
1174+
'id' => '111150',
1175+
'key' => 'cmab_experiment',
1176+
'status' => 'Running',
1177+
'layerId' => '111150',
1178+
'audienceIds' => [],
1179+
'forcedVariations' => {},
1180+
'variations' => [
1181+
{'id' => '111151', 'key' => 'variation_1'},
1182+
{'id' => '111152', 'key' => 'variation_2'}
1183+
],
1184+
'trafficAllocation' => [
1185+
{'entityId' => '111151', 'endOfRange' => 5000},
1186+
{'entityId' => '111152', 'endOfRange' => 10_000}
1187+
],
1188+
'cmab' => {'trafficAllocation' => 5000}
1189+
}
1190+
user_context = project_instance.create_user_context('test_user', {})
1191+
1192+
# Create a user profile tracker
1193+
user_profile_tracker = Optimizely::UserProfileTracker.new(user_context.user_id, spy_user_profile_service, spy_logger)
1194+
1195+
# Mock experiment lookup to return our CMAB experiment
1196+
allow(config).to receive(:get_experiment_from_id).with('111150').and_return(cmab_experiment)
1197+
allow(config).to receive(:experiment_running?).with(cmab_experiment).and_return(true)
1198+
1199+
# Mock audience evaluation to pass
1200+
allow(Optimizely::Audience).to receive(:user_meets_audience_conditions?).and_return([true, []])
1201+
1202+
# Mock bucketer to return a valid entity ID (user is in traffic allocation)
1203+
allow(decision_service.bucketer).to receive(:bucket_to_entity_id)
1204+
.with(config, cmab_experiment, 'test_user', 'test_user')
1205+
.and_return(['$', []])
1206+
1207+
# Mock CMAB service to return a decision
1208+
allow(spy_cmab_service).to receive(:get_decision)
1209+
.with(config, user_context, '111150', [])
1210+
.and_return(Optimizely::CmabDecision.new(variation_id: '111151', cmab_uuid: 'test-cmab-uuid-123'))
1211+
1212+
# Mock variation lookup
1213+
allow(config).to receive(:get_variation_from_id_by_experiment_id)
1214+
.with('111150', '111151')
1215+
.and_return({'id' => '111151', 'key' => 'variation_1'})
1216+
1217+
# Spy on update_user_profile method
1218+
allow(user_profile_tracker).to receive(:update_user_profile).and_call_original
1219+
1220+
# Call get_variation with the CMAB experiment and user profile tracker
1221+
variation_result = decision_service.get_variation(config, '111150', user_context, user_profile_tracker)
1222+
1223+
# Verify the variation and cmab_uuid are returned
1224+
expect(variation_result.variation_id).to eq('111151')
1225+
expect(variation_result.cmab_uuid).to eq('test-cmab-uuid-123')
1226+
1227+
# Verify user profile was NOT updated for CMAB experiment
1228+
expect(user_profile_tracker).not_to have_received(:update_user_profile)
1229+
1230+
# Verify debug log was called to explain CMAB exclusion
1231+
expect(spy_logger).to have_received(:log).with(
1232+
Logger::DEBUG,
1233+
"Skipping user profile service for CMAB experiment 'cmab_experiment'. CMAB decisions are dynamic and not stored for sticky bucketing."
1234+
)
1235+
end
1236+
1237+
it 'should save user profile for standard (non-CMAB) experiments' do
1238+
# Use a standard (non-CMAB) experiment
1239+
config.get_experiment_from_key('test_experiment')
1240+
user_context = project_instance.create_user_context('test_user', {})
1241+
1242+
# Create a user profile tracker
1243+
user_profile_tracker = Optimizely::UserProfileTracker.new(user_context.user_id, spy_user_profile_service, spy_logger)
1244+
1245+
# Mock audience evaluation to pass
1246+
allow(Optimizely::Audience).to receive(:user_meets_audience_conditions?).and_return([true, []])
1247+
1248+
# Mock bucketer to return a variation
1249+
allow(decision_service.bucketer).to receive(:bucket)
1250+
.and_return([{'id' => '111129', 'key' => 'variation'}, []])
1251+
1252+
# Spy on update_user_profile method
1253+
allow(user_profile_tracker).to receive(:update_user_profile).and_call_original
1254+
1255+
# Call get_variation with standard experiment and user profile tracker
1256+
variation_result = decision_service.get_variation(config, '111127', user_context, user_profile_tracker)
1257+
1258+
# Verify variation was returned
1259+
expect(variation_result.variation_id).to eq('111129')
1260+
1261+
# Verify user profile WAS updated for standard experiment
1262+
expect(user_profile_tracker).to have_received(:update_user_profile).with('111127', '111129')
1263+
end
1264+
end
11691265
end
11701266
end

0 commit comments

Comments
 (0)