Skip to content

Commit a9c1950

Browse files
authored
Fix broken tests resulting from conflicts. (#66)
1 parent d9f9803 commit a9c1950

File tree

5 files changed

+64
-40
lines changed

5 files changed

+64
-40
lines changed

lib/optimizely/bucketer.rb

+8-12
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def bucket(experiment, user_id)
4141
# experiment - Experiment for which visitor is to be bucketed.
4242
# user_id - String ID for user.
4343
#
44-
# Returns String variation ID in which visitor with ID user_id has been placed. Nil if no variation.
44+
# Returns variation in which visitor with ID user_id has been placed. Nil if no variation.
4545

4646
# check if experiment is in a group; if so, check if user is bucketed into specified experiment
4747
experiment_id = experiment['id']
@@ -50,12 +50,8 @@ def bucket(experiment, user_id)
5050
if group_id
5151
group = @config.group_key_map.fetch(group_id)
5252
if Helpers::Group.random_policy?(group)
53-
bucketing_id = sprintf(BUCKETING_ID_TEMPLATE, user_id: user_id, entity_id: group_id)
5453
traffic_allocations = group.fetch('trafficAllocation')
55-
bucket_value = generate_bucket_value(bucketing_id)
56-
@config.logger.log(Logger::DEBUG, "Assigned experiment bucket #{bucket_value} to user '#{user_id}'.")
57-
bucketed_experiment_id = find_bucket(bucket_value, traffic_allocations)
58-
54+
bucketed_experiment_id = find_bucket(user_id, group_id, traffic_allocations)
5955
# return if the user is not bucketed into any experiment
6056
unless bucketed_experiment_id
6157
@config.logger.log(Logger::INFO, "User '#{user_id}' is in no experiment.")
@@ -81,14 +77,14 @@ def bucket(experiment, user_id)
8177

8278
traffic_allocations = experiment['trafficAllocation']
8379
variation_id = find_bucket(user_id, experiment_id, traffic_allocations)
84-
8580
if variation_id && variation_id != ''
86-
variation_key = @config.get_variation_key_from_id(experiment_key, variation_id)
81+
variation = @config.get_variation_from_id(experiment_key, variation_id)
82+
variation_key = variation ? variation['key'] : nil
8783
@config.logger.log(
8884
Logger::INFO,
8985
"User '#{user_id}' is in variation '#{variation_key}' of experiment '#{experiment_key}'."
9086
)
91-
return variation_id
87+
return variation
9288
end
9389

9490
# Handle the case when the traffic range is empty due to sticky bucketing
@@ -100,8 +96,6 @@ def bucket(experiment, user_id)
10096
nil
10197
end
10298

103-
private
104-
10599
def find_bucket(user_id, parent_id, traffic_allocations)
106100
# Helper function to find the matching entity ID for a given bucketing value in a list of traffic allocations.
107101
#
@@ -126,6 +120,8 @@ def find_bucket(user_id, parent_id, traffic_allocations)
126120
nil
127121
end
128122

123+
private
124+
129125
def generate_bucket_value(bucketing_id)
130126
# Helper function to generate bucket value in half-closed interval [0, MAX_TRAFFIC_VALUE).
131127
#
@@ -147,4 +143,4 @@ def generate_unsigned_hash_code_32_bit(bucketing_id)
147143
MurmurHash3::V32.str_hash(bucketing_id, @bucket_seed) & UNSIGNED_MAX_32_BIT_VALUE
148144
end
149145
end
150-
end
146+
end

lib/optimizely/decision_service.rb

+3-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ def get_variation(experiment_key, user_id, attributes = nil)
8888
end
8989

9090
# Bucket normally
91-
variation_id = @bucketer.bucket(experiment, user_id)
91+
variation = @bucketer.bucket(experiment, user_id)
92+
variation_id = variation ? variation['id'] : nil
9293

9394
# Persist bucketing decision
9495
save_user_profile(user_profile, experiment_id, variation_id)
@@ -156,7 +157,7 @@ def get_user_profile(user_id)
156157
#
157158
# user_id - String ID for the user
158159
#
159-
# Returns Hash stored user profile (or a default one if lookup fails or user profile service not provided)
160+
# Returns Hash stored user profile (or a default one if lookup fails or user profile service not provided)
160161

161162
user_profile = {
162163
:user_id => user_id,

lib/optimizely/project_config.rb

+24-2
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,28 @@ def get_variation_key_from_id(experiment_key, variation_id)
196196
nil
197197
end
198198

199+
def get_variation_from_id(experiment_key, variation_id)
200+
# Get variation given experiment key and variation ID
201+
#
202+
# experiment_key - Key representing parent experiment of variation
203+
# variation_id - ID of the variation
204+
#
205+
# Returns the variation or nil if not found
206+
207+
variation_id_map = @variation_id_map[experiment_key]
208+
if variation_id_map
209+
variation = variation_id_map[variation_id]
210+
return variation if variation
211+
@logger.log Logger::ERROR, "Variation id '#{variation_id}' is not in datafile."
212+
@error_handler.handle_error InvalidVariationError
213+
return nil
214+
end
215+
216+
@logger.log Logger::ERROR, "Experiment key '#{experiment_key}' is not in datafile."
217+
@error_handler.handle_error InvalidExperimentError
218+
nil
219+
end
220+
199221
def get_variation_id_from_key(experiment_key, variation_key)
200222
# Get variation ID given experiment key and variation key
201223
#
@@ -276,7 +298,7 @@ def get_forced_variation(experiment_key, user_id)
276298
end
277299

278300
@logger.log(Logger::DEBUG, "Variation '#{variation_key}' is mapped to experiment '#{experiment_key}' and user '#{user_id}' in the forced variation map")
279-
301+
280302
variation
281303
end
282304

@@ -317,7 +339,7 @@ def set_forced_variation(experiment_key, user_id, variation_key)
317339
return false
318340
end
319341

320-
unless @forced_variation_map.has_key? user_id
342+
unless @forced_variation_map.has_key? user_id
321343
@forced_variation_map[user_id] = {}
322344
end
323345
@forced_variation_map[user_id][experiment_id] = variation_id

spec/bucketing_spec.rb

+17-16
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@ def get_bucketing_id(user_id, entity_id=nil)
3636
experiment = config.get_experiment_from_key('test_experiment')
3737

3838
# Variation 1
39-
expect(bucketer.bucket(experiment, 'test_user')).to eq('111128')
39+
expected_variation_1 = config.get_variation_from_id('test_experiment', '111128')
40+
expect(bucketer.bucket(experiment, 'test_user')).to eq(expected_variation_1)
4041

4142
# Variation 2
42-
expect(bucketer.bucket(experiment, 'test_user')).to eq('111129')
43+
expected_variation_2 = config.get_variation_from_id('test_experiment','111129')
44+
expect(bucketer.bucket(experiment, 'test_user')).to eq(expected_variation_2)
4345

4446
# No matching variation
4547
expect(bucketer.bucket(experiment, 'test_user')).to be_nil
@@ -58,14 +60,13 @@ def get_bucketing_id(user_id, entity_id=nil)
5860
expect(bucketer).to receive(:generate_bucket_value).twice.and_return(3000)
5961

6062
experiment = config.get_experiment_from_key('group1_exp1')
61-
expect(bucketer.bucket(experiment, 'test_user')).to eq('130001')
63+
expected_variation = config.get_variation_from_id('group1_exp1','130001')
64+
expect(bucketer.bucket(experiment, 'test_user')).to eq(expected_variation)
6265
expect(spy_logger).to have_received(:log).exactly(4).times
63-
expect(spy_logger).to have_received(:log)
64-
.with(Logger::DEBUG, "Assigned experiment bucket 3000 to user 'test_user'.")
66+
expect(spy_logger).to have_received(:log).twice
67+
.with(Logger::DEBUG, "Assigned bucket 3000 to user 'test_user'.")
6568
expect(spy_logger).to have_received(:log)
6669
.with(Logger::INFO, "User 'test_user' is in experiment 'group1_exp1' of group 101.")
67-
expect(spy_logger).to have_received(:log)
68-
.with(Logger::DEBUG, "Assigned variation bucket 3000 to user 'test_user'.")
6970
expect(spy_logger).to have_received(:log)
7071
.with(Logger::INFO, "User 'test_user' is in variation 'g1_e1_v1' of experiment 'group1_exp1'.")
7172
end
@@ -76,13 +77,12 @@ def get_bucketing_id(user_id, entity_id=nil)
7677
experiment = config.get_experiment_from_key('group1_exp2')
7778
expect(bucketer.bucket(experiment, 'test_user')).to be_nil
7879
expect(spy_logger).to have_received(:log)
79-
.with(Logger::DEBUG, "Assigned experiment bucket 3000 to user 'test_user'.")
80+
.with(Logger::DEBUG, "Assigned bucket 3000 to user 'test_user'.")
8081
expect(spy_logger).to have_received(:log)
8182
.with(Logger::INFO, "User 'test_user' is not in experiment 'group1_exp2' of group 101.")
8283
end
8384

8485
it 'should return nil when user is not bucketed into any bucket' do
85-
expect(bucketer).to receive(:generate_bucket_value).once.and_return(3000)
8686
expect(bucketer).to receive(:find_bucket).once.and_return(nil)
8787

8888
experiment = config.get_experiment_from_key('group1_exp2')
@@ -95,10 +95,11 @@ def get_bucketing_id(user_id, entity_id=nil)
9595
expect(bucketer).to receive(:generate_bucket_value).once.and_return(3000)
9696

9797
experiment = config.get_experiment_from_key('group2_exp1')
98-
expect(bucketer.bucket(experiment, 'test_user')).to eq('144443')
98+
expected_variation = config.get_variation_from_id('group2_exp1','144443')
99+
expect(bucketer.bucket(experiment, 'test_user')).to eq(expected_variation)
99100
expect(spy_logger).to have_received(:log).twice
100101
expect(spy_logger).to have_received(:log)
101-
.with(Logger::DEBUG, "Assigned variation bucket 3000 to user 'test_user'.")
102+
.with(Logger::DEBUG, "Assigned bucket 3000 to user 'test_user'.")
102103
expect(spy_logger).to have_received(:log)
103104
.with(Logger::INFO, "User 'test_user' is in variation 'g2_e1_v1' of experiment 'group2_exp1'.")
104105
end
@@ -110,7 +111,7 @@ def get_bucketing_id(user_id, entity_id=nil)
110111
expect(bucketer.bucket(experiment, 'test_user')).to be_nil
111112
expect(spy_logger).to have_received(:log).twice
112113
expect(spy_logger).to have_received(:log)
113-
.with(Logger::DEBUG, "Assigned variation bucket 50000 to user 'test_user'.")
114+
.with(Logger::DEBUG, "Assigned bucket 50000 to user 'test_user'.")
114115
expect(spy_logger).to have_received(:log)
115116
.with(Logger::INFO, "User 'test_user' is in no variation.")
116117
end
@@ -147,7 +148,7 @@ def get_bucketing_id(user_id, entity_id=nil)
147148
experiment = config.get_experiment_from_key('test_experiment')
148149
bucketer.bucket(experiment, 'test_user')
149150
expect(spy_logger).to have_received(:log).twice
150-
expect(spy_logger).to have_received(:log).with(Logger::DEBUG, "Assigned variation bucket 50 to user 'test_user'.")
151+
expect(spy_logger).to have_received(:log).with(Logger::DEBUG, "Assigned bucket 50 to user 'test_user'.")
151152
expect(spy_logger).to have_received(:log).with(
152153
Logger::INFO,
153154
"User 'test_user' is in variation 'control' of experiment 'test_experiment'."
@@ -161,7 +162,7 @@ def get_bucketing_id(user_id, entity_id=nil)
161162
bucketer.bucket(experiment, 'test_user')
162163
expect(spy_logger).to have_received(:log).twice
163164
expect(spy_logger).to have_received(:log)
164-
.with(Logger::DEBUG, "Assigned variation bucket 5050 to user 'test_user'.")
165+
.with(Logger::DEBUG, "Assigned bucket 5050 to user 'test_user'.")
165166
expect(spy_logger).to have_received(:log).with(
166167
Logger::INFO,
167168
"User 'test_user' is in variation 'variation' of experiment 'test_experiment'."
@@ -175,9 +176,9 @@ def get_bucketing_id(user_id, entity_id=nil)
175176
bucketer.bucket(experiment, 'test_user')
176177
expect(spy_logger).to have_received(:log).twice
177178
expect(spy_logger).to have_received(:log)
178-
.with(Logger::DEBUG, "Assigned variation bucket 50000 to user 'test_user'.")
179+
.with(Logger::DEBUG, "Assigned bucket 50000 to user 'test_user'.")
179180
expect(spy_logger).to have_received(:log)
180181
.with(Logger::INFO, "User 'test_user' is in no variation.")
181182
end
182183
end
183-
end
184+
end

spec/project_spec.rb

+12-8
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ class InvalidErrorHandler; end
121121
before(:example) do
122122
allow(Time).to receive(:now).and_return(time_now)
123123
allow(SecureRandom).to receive(:uuid).and_return('a68cf1ad-0393-4e18-af87-efe8f01a7c9c');
124-
124+
125125
@expected_activate_params = {
126126
account_id: '12001',
127127
project_id: '111001',
@@ -151,7 +151,8 @@ class InvalidErrorHandler; end
151151
it 'should properly activate a user, invoke Event object with right params, and return variation' do
152152
params = @expected_activate_params
153153

154-
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return('111128')
154+
variation_to_return = project_instance.config.get_variation_from_id('test_experiment', '111128')
155+
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return(variation_to_return)
155156
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
156157
allow(project_instance.config).to receive(:get_audience_ids_for_experiment)
157158
.with('test_experiment')
@@ -196,7 +197,8 @@ class InvalidErrorHandler; end
196197
}]
197198
params[:visitors][0][:snapshots][0][:events][0][:entity_id] = '3'
198199

199-
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return('122228')
200+
variation_to_return = project_instance.config.get_variation_from_id('test_experiment_with_audience', '122228')
201+
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return(variation_to_return)
200202
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
201203

202204
expect(project_instance.activate('test_experiment_with_audience', 'test_user', 'browser_type' => 'firefox'))
@@ -257,7 +259,8 @@ class InvalidErrorHandler; end
257259
it 'should log when an impression event is dispatched' do
258260
params = @expected_activate_params
259261

260-
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return('111128')
262+
variation_to_return = project_instance.config.get_variation_from_id('test_experiment', '111128')
263+
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return(variation_to_return)
261264
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
262265
allow(project_instance.config).to receive(:get_audience_ids_for_experiment)
263266
.with('test_experiment')
@@ -268,7 +271,8 @@ class InvalidErrorHandler; end
268271
end
269272

270273
it 'should log when an exception has occurred during dispatching the impression event' do
271-
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return('111128')
274+
variation_to_return = project_instance.config.get_variation_from_id('test_experiment', '111128')
275+
allow(project_instance.decision_service.bucketer).to receive(:bucket).and_return(variation_to_return)
272276
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(any_args).and_raise(RuntimeError)
273277
project_instance.activate('test_experiment', 'test_user')
274278
expect(spy_logger).to have_received(:log).once.with(Logger::ERROR, "Unable to dispatch impression event. Error: RuntimeError")
@@ -320,7 +324,7 @@ class InvalidErrorHandler; end
320324
before(:example) do
321325
allow(Time).to receive(:now).and_return(time_now)
322326
allow(SecureRandom).to receive(:uuid).and_return('a68cf1ad-0393-4e18-af87-efe8f01a7c9c');
323-
327+
324328
@expected_track_event_params = {
325329
account_id: '12001',
326330
project_id: '111001',
@@ -596,7 +600,7 @@ class InvalidErrorHandler; end
596600
expect(project_instance.get_variation('test_experiment','test_user')). to eq('variation')
597601
end
598602

599-
# setForcedVariation on a running experiment with audience enabled and then call getVariation on that same experiment with invalid attributes.
603+
# setForcedVariation on a running experiment with audience enabled and then call getVariation on that same experiment with invalid attributes.
600604
it 'should return nil when getVariation called on audience enabled running experiment with invalid attributes' do
601605
project_instance.set_forced_variation('test_experiment_with_audience','test_user','control_with_audience')
602606
expect { project_instance.get_variation('test_experiment_with_audience', 'test_user', 'invalid') }
@@ -606,7 +610,7 @@ class InvalidErrorHandler; end
606610
# Adding this test case to cover this in code coverage. All test cases for getForceVariation are present in
607611
# project_config_spec.rb which test the get_force_variation method in project_config. The one in optimizely.rb
608612
# only calls the other one
609-
613+
610614
# getForceVariation on a running experiment after setforcevariation
611615
it 'should return expected variation id when get_forced_variation is called on a running experiment after setForcedVariation' do
612616
project_instance.set_forced_variation('test_experiment','test_user','variation')

0 commit comments

Comments
 (0)