Skip to content

Commit 0198eac

Browse files
authored
Updating whitelisting check to override audience check (#8)
* Forced variation precedence * Tests * Respond to feedback, update ProjectConfig spec to point to V2 datafile
1 parent 6f2b5fe commit 0198eac

File tree

5 files changed

+127
-7
lines changed

5 files changed

+127
-7
lines changed

lib/optimizely.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ def preconditions_valid?(experiment_key, user_id, attributes)
160160
return false
161161
end
162162

163+
if @config.user_in_forced_variation?(experiment_key, user_id)
164+
return true
165+
end
166+
163167
unless Audience.user_in_experiment?(@config, experiment_key, attributes)
164168
@logger.log(Logger::INFO,
165169
"User '#{user_id} does not meet the conditions to be in experiment '#{experiment_key}.")

lib/optimizely/project_config.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,19 @@ def get_attribute_id(attribute_key)
257257
nil
258258
end
259259

260+
def user_in_forced_variation?(experiment_key, user_id)
261+
# Determines if a given user is in a forced variation
262+
#
263+
# experiment_key - String experiment key
264+
# user_id - String user ID
265+
#
266+
# Returns true if user is in a forced variation
267+
268+
forced_variations = get_forced_variations(experiment_key)
269+
return forced_variations.include?(user_id) if forced_variations
270+
false
271+
end
272+
260273
private
261274

262275
def generate_key_map(array, key)

spec/project_config_spec.rb

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,40 @@
33
require 'optimizely/exceptions'
44

55
describe Optimizely::ProjectConfig do
6-
let(:config_body) { OptimizelySpec::V1_CONFIG_BODY }
7-
let(:config_body_JSON) { OptimizelySpec::V1_CONFIG_BODY_JSON }
6+
let(:config_body) { OptimizelySpec::V2_CONFIG_BODY }
7+
let(:config_body_JSON) { OptimizelySpec::V2_CONFIG_BODY_JSON }
88
let(:error_handler) { Optimizely::NoOpErrorHandler.new }
99
let(:logger) { Optimizely::NoOpLogger.new }
10+
let(:config) { Optimizely::ProjectConfig.new(config_body_JSON, logger, error_handler)}
11+
12+
describe '#user_in_forced_variations' do
13+
it 'should return false when the experiment has no forced variations' do
14+
expect(config.user_in_forced_variation?('group1_exp1', 'test_user')).to be(false)
15+
end
16+
17+
it 'should return false when the user is not in a forced variation' do
18+
expect(config.user_in_forced_variation?('test_experiment', 'test_user')).to be(false)
19+
end
20+
21+
it 'should return true when the user is in a forced variation' do
22+
expect(config.user_in_forced_variation?('test_experiment', 'forced_user1')).to be(true)
23+
end
24+
end
1025

1126
describe '.initialize' do
1227
it 'should initialize properties correctly upon creating project' do
1328
project_config = Optimizely::ProjectConfig.new(config_body_JSON, logger, error_handler)
1429

1530
expect(project_config.account_id).to eq(config_body['accountId'])
16-
expect(project_config.attributes).to eq(config_body['dimensions'])
31+
expect(project_config.attributes).to eq(config_body['attributes'])
1732
expect(project_config.audiences).to eq(config_body['audiences'])
1833
expect(project_config.events).to eq(config_body['events'])
1934
expect(project_config.groups).to eq(config_body['groups'])
2035
expect(project_config.project_id).to eq(config_body['projectId'])
2136
expect(project_config.revision).to eq(config_body['revision'])
2237

2338
expected_attribute_key_map = {
24-
'browser_type' => config_body['dimensions'][0]
39+
'browser_type' => config_body['attributes'][0]
2540
}
2641

2742
expected_audience_id_map = {

spec/project_spec.rb

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'spec_helper'
22
require 'optimizely'
3+
require 'optimizely/audience'
34
require 'optimizely/helpers/validator'
45
require 'optimizely/exceptions'
56
require 'optimizely/version'
@@ -98,8 +99,8 @@ class InvalidErrorHandler; end
9899
allow(project_instance.bucketer).to receive(:bucket).and_return('111128')
99100
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
100101
allow(project_instance.config).to receive(:get_audience_ids_for_experiment)
101-
.with('test_experiment')
102-
.and_return([])
102+
.with('test_experiment')
103+
.and_return([])
103104

104105
stub_request(:get, log_url).with(:query => params)
105106

@@ -506,6 +507,41 @@ class InvalidErrorHandler; end
506507
expect { project_instance.activate('test_experiment', 'test_user', 'invalid') }
507508
.to raise_error(Optimizely::InvalidAttributeFormatError)
508509
end
510+
511+
it 'should override the audience check if the user is whitelisted to a specific variation' do
512+
params = {
513+
'projectId' => '111001',
514+
'accountId' => '12001',
515+
'visitorId' => 'forced_audience_user',
516+
'userFeatures' => [
517+
{
518+
'id' => '111094',
519+
'name' => 'browser_type',
520+
'type' => 'custom',
521+
'value' => 'wrong_browser',
522+
'shouldIndex' => true,
523+
},
524+
],
525+
'clientEngine' => 'ruby-sdk',
526+
'clientVersion' => version,
527+
'timestamp' => (time_now.to_f * 1000).to_i,
528+
'isGlobalHoldback' => false,
529+
'layerId' => '3',
530+
'decision' => {
531+
'variationId' => '122229',
532+
'experimentId' => '122227',
533+
'isLayerHoldback' => false,
534+
}
535+
}
536+
537+
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
538+
allow(Optimizely::Audience).to receive(:user_in_experiment?)
539+
540+
expect(project_instance.activate('test_experiment_with_audience', 'forced_audience_user', 'browser_type' => 'wrong_browser'))
541+
.to eq('variation_with_audience')
542+
expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, impression_log_url, params, post_headers)).once
543+
expect(Optimizely::Audience).to_not have_received(:user_in_experiment?)
544+
end
509545
end
510546

511547
describe '#track' do
@@ -681,6 +717,48 @@ class InvalidErrorHandler; end
681717
expect { project_instance.track('invalid_event', 'test_user') }.to raise_error(Optimizely::InvalidGoalError)
682718
expect(project_instance.event_dispatcher).to_not have_received(:dispatch_event)
683719
end
720+
721+
it 'should override the audience check if the user is whitelisted to a specific variation' do
722+
params = {
723+
'projectId' => '111001',
724+
'accountId' => '12001',
725+
'visitorId' => 'forced_audience_user',
726+
'userFeatures' => [
727+
{
728+
'id' => '111094',
729+
'name' => 'browser_type',
730+
'type' => 'custom',
731+
'value' => 'wrong_browser',
732+
'shouldIndex' => true,
733+
}
734+
],
735+
'clientEngine' => 'ruby-sdk',
736+
'clientVersion' => version,
737+
'timestamp' => (time_now.to_f * 1000).to_i,
738+
'isGlobalHoldback' => false,
739+
'eventEntityId' => '111097',
740+
'eventFeatures' => [],
741+
'eventName' => 'test_event_with_audience',
742+
'eventMetrics' => [],
743+
'layerStates' => [
744+
{
745+
'layerId' => '3',
746+
'decision' => {
747+
'variationId' => '122229',
748+
'experimentId' => '122227',
749+
'isLayerHoldback' => false,
750+
},
751+
'actionTriggered' => true,
752+
}
753+
]
754+
}
755+
allow(project_instance.event_dispatcher).to receive(:dispatch_event).with(instance_of(Optimizely::Event))
756+
allow(Optimizely::Audience).to receive(:user_in_experiment?)
757+
758+
project_instance.track('test_event_with_audience', 'forced_audience_user', 'browser_type' => 'wrong_browser')
759+
expect(Optimizely::Audience).to_not have_received(:user_in_experiment?)
760+
expect(project_instance.event_dispatcher).to have_received(:dispatch_event).with(Optimizely::Event.new(:post, conversion_log_url, params, post_headers)).once
761+
end
684762
end
685763

686764
describe '#get_variation' do
@@ -712,5 +790,13 @@ class InvalidErrorHandler; end
712790
expect { project_instance.get_variation('test_experiment', 'test_user', 'invalid') }
713791
.to raise_error(Optimizely::InvalidAttributeFormatError)
714792
end
793+
794+
it 'should override the audience check if the user is whitelisted to a specific variation' do
795+
allow(Optimizely::Audience).to receive(:user_in_experiment?)
796+
797+
expect(project_instance.get_variation('test_experiment_with_audience', 'forced_audience_user', 'browser_type' => 'wrong_browser'))
798+
.to eq('variation_with_audience')
799+
expect(Optimizely::Audience).to_not have_received(:user_in_experiment?)
800+
end
715801
end
716802
end

spec/spec_params.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ module OptimizelySpec
8383
'entityId' => '122229',
8484
'endOfRange' => 10000
8585
}],
86-
'forcedVariations' => {},
86+
'forcedVariations' => {
87+
'forced_audience_user' => 'variation_with_audience',
88+
},
8789
'id' => '122227',
8890
'percentageIncluded' => 10000,
8991
'variations' => [{

0 commit comments

Comments
 (0)