|
22 | 22 | let(:config_body_JSON) { OptimizelySpec::V2_CONFIG_BODY_JSON }
|
23 | 23 | let(:error_handler) { Optimizely::NoOpErrorHandler.new }
|
24 | 24 | let(:spy_logger) { spy('logger') }
|
| 25 | + let(:spy_user_profile_service) { spy('user_profile_service') } |
25 | 26 | let(:config) { Optimizely::ProjectConfig.new(config_body_JSON, spy_logger, error_handler) }
|
26 |
| - let(:decision_service) { Optimizely::DecisionService.new(config) } |
| 27 | + let(:decision_service) { Optimizely::DecisionService.new(config, spy_user_profile_service) } |
27 | 28 |
|
28 | 29 | describe '#get_variation' do
|
29 | 30 | before(:example) do
|
30 | 31 | # stub out bucketer and audience evaluator so we can make sure they are / aren't called
|
31 | 32 | allow(decision_service.bucketer).to receive(:bucket).and_call_original
|
32 | 33 | allow(decision_service).to receive(:get_forced_variation_id).and_call_original
|
33 | 34 | allow(Optimizely::Audience).to receive(:user_in_experiment?).and_call_original
|
| 35 | + |
| 36 | + # by default, spy user profile service should no-op. we override this behavior in specific tests |
| 37 | + allow(spy_user_profile_service).to receive(:lookup).and_return(nil) |
34 | 38 | end
|
35 | 39 |
|
36 | 40 | it 'should return the correct variation ID for a given user ID and key of a running experiment' do
|
|
118 | 122 | # bucketing should have occured
|
119 | 123 | expect(decision_service.bucketer).to have_received(:bucket).once.with('test_experiment', 'forced_user_with_invalid_variation')
|
120 | 124 | end
|
| 125 | + |
| 126 | + describe 'when a UserProfile service is provided' do |
| 127 | + it 'should look up the UserProfile, bucket normally, and save the result if no saved profile is found' do |
| 128 | + expected_user_profile = { |
| 129 | + :user_id => 'test_user', |
| 130 | + :experiment_bucket_map => { |
| 131 | + '111127' => { |
| 132 | + :variation_id => '111128' |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | + expect(spy_user_profile_service).to receive(:lookup).once.and_return(nil) |
| 137 | + |
| 138 | + expect(decision_service.get_variation('test_experiment', 'test_user')).to eq('111128') |
| 139 | + |
| 140 | + # bucketing should have occurred |
| 141 | + expect(decision_service.bucketer).to have_received(:bucket).once |
| 142 | + # bucketing decision should have been saved |
| 143 | + expect(spy_user_profile_service).to have_received(:save).once.with(expected_user_profile) |
| 144 | + expect(spy_logger).to have_received(:log).once |
| 145 | + .with(Logger::INFO, "Saved variation ID 111128 of experiment ID 111127 for user 'test_user'.") |
| 146 | + end |
| 147 | + |
| 148 | + it 'should look up the user profile and skip normal bucketing if a profile with a saved decision is found' do |
| 149 | + saved_user_profile = { |
| 150 | + :user_id => 'test_user', |
| 151 | + :experiment_bucket_map => { |
| 152 | + '111127' => { |
| 153 | + :variation_id => '111129' |
| 154 | + } |
| 155 | + } |
| 156 | + } |
| 157 | + expect(spy_user_profile_service).to receive(:lookup) |
| 158 | + .with('test_user').once.and_return(saved_user_profile) |
| 159 | + |
| 160 | + expect(decision_service.get_variation('test_experiment', 'test_user')).to eq('111129') |
| 161 | + expect(spy_logger).to have_received(:log).once |
| 162 | + .with(Logger::INFO, "Returning previously activated variation ID 111129 of experiment 'test_experiment' for user 'test_user' from user profile.") |
| 163 | + |
| 164 | + # saved user profiles should short circuit bucketing |
| 165 | + expect(decision_service.bucketer).not_to have_received(:bucket) |
| 166 | + # saved user profiles should short circuit audience evaluation |
| 167 | + expect(Optimizely::Audience).not_to have_received(:user_in_experiment?) |
| 168 | + # the user profile should not be updated if bucketing did not take place |
| 169 | + expect(spy_user_profile_service).not_to have_received(:save) |
| 170 | + end |
| 171 | + |
| 172 | + it 'should look up the user profile and bucket normally if a profile without a saved decision is found' do |
| 173 | + saved_user_profile = { |
| 174 | + :user_id => 'test_user', |
| 175 | + :experiment_bucket_map => { |
| 176 | + # saved decision, but not for this experiment |
| 177 | + '122227' => { |
| 178 | + :variation_id => '122228' |
| 179 | + } |
| 180 | + } |
| 181 | + } |
| 182 | + expect(spy_user_profile_service).to receive(:lookup) |
| 183 | + .once.with('test_user').and_return(saved_user_profile) |
| 184 | + |
| 185 | + expect(decision_service.get_variation('test_experiment', 'test_user')).to eq('111128') |
| 186 | + |
| 187 | + # bucketing should have occurred |
| 188 | + expect(decision_service.bucketer).to have_received(:bucket).once |
| 189 | + |
| 190 | + # user profile should have been updated with bucketing decision |
| 191 | + expected_user_profile = { |
| 192 | + :user_id => 'test_user', |
| 193 | + :experiment_bucket_map => { |
| 194 | + '111127' => { |
| 195 | + :variation_id => '111128' |
| 196 | + }, |
| 197 | + '122227' => { |
| 198 | + :variation_id => '122228' |
| 199 | + } |
| 200 | + } |
| 201 | + } |
| 202 | + expect(spy_user_profile_service).to have_received(:save).once.with(expected_user_profile) |
| 203 | + end |
| 204 | + |
| 205 | + it 'should bucket normally if the user profile contains a variation ID not in the datafile' do |
| 206 | + saved_user_profile = { |
| 207 | + :user_id => 'test_user', |
| 208 | + :experiment_bucket_map => { |
| 209 | + # saved decision, but with invalid variation ID |
| 210 | + '111127' => { |
| 211 | + :variation_id => '111111' |
| 212 | + } |
| 213 | + } |
| 214 | + } |
| 215 | + expect(spy_user_profile_service).to receive(:lookup) |
| 216 | + .once.with('test_user').and_return(saved_user_profile) |
| 217 | + |
| 218 | + expect(decision_service.get_variation('test_experiment', 'test_user')).to eq('111128') |
| 219 | + |
| 220 | + # bucketing should have occurred |
| 221 | + expect(decision_service.bucketer).to have_received(:bucket).once |
| 222 | + |
| 223 | + # user profile should have been updated with bucketing decision |
| 224 | + expected_user_profile = { |
| 225 | + :user_id => 'test_user', |
| 226 | + :experiment_bucket_map => { |
| 227 | + '111127' => { |
| 228 | + :variation_id => '111128' |
| 229 | + } |
| 230 | + } |
| 231 | + } |
| 232 | + expect(spy_user_profile_service).to have_received(:save).with(expected_user_profile) |
| 233 | + end |
| 234 | + |
| 235 | + it 'should bucket normally if the user profile service throws an error during lookup' do |
| 236 | + expect(spy_user_profile_service).to receive(:lookup).once.with('test_user').and_throw(:LookupError) |
| 237 | + |
| 238 | + expect(decision_service.get_variation('test_experiment', 'test_user')).to eq('111128') |
| 239 | + |
| 240 | + expect(spy_logger).to have_received(:log).once |
| 241 | + .with(Logger::ERROR, "Error while looking up user profile for user ID 'test_user': uncaught throw :LookupError.") |
| 242 | + # bucketing should have occurred |
| 243 | + expect(decision_service.bucketer).to have_received(:bucket).once |
| 244 | + end |
| 245 | + |
| 246 | + it 'should log an error if the user profile service throws an error during save' do |
| 247 | + expect(spy_user_profile_service).to receive(:save).once.and_throw(:SaveError) |
| 248 | + |
| 249 | + expect(decision_service.get_variation('test_experiment', 'test_user')).to eq('111128') |
| 250 | + |
| 251 | + expect(spy_logger).to have_received(:log).once |
| 252 | + .with(Logger::ERROR, "Error while saving user profile for user ID 'test_user': uncaught throw :SaveError.") |
| 253 | + end |
| 254 | + end |
121 | 255 | end
|
122 | 256 | end
|
0 commit comments