Commit 3ae7e96f authored by Stan Hu's avatar Stan Hu

Merge branch 'nicolasdular/extract-experimentation-concern' into 'master'

Extract experimentation concern

See merge request gitlab-org/gitlab!47020
parents fc0d6e93 9f0f75d2
# frozen_string_literal: true # frozen_string_literal: true
require 'zlib'
# == Experimentation # == Experimentation
# #
# Utility module for A/B testing experimental features. Define your experiments in the `EXPERIMENTS` constant. # Utility module for A/B testing experimental features. Define your experiments in the `EXPERIMENTS` constant.
...@@ -87,126 +85,6 @@ module Gitlab ...@@ -87,126 +85,6 @@ module Gitlab
} }
}.freeze }.freeze
# Controller concern that checks if an `experimentation_subject_id cookie` is present and sets it if absent.
# Used for A/B testing of experimental features. Exposes the `experiment_enabled?(experiment_name)` method
# to controllers and views. It returns true when the experiment is enabled and the user is selected as part
# of the experimental group.
#
module ControllerConcern
include ::Gitlab::Experimentation::GroupTypes
extend ActiveSupport::Concern
included do
before_action :set_experimentation_subject_id_cookie, unless: :dnt_enabled?
helper_method :experiment_enabled?, :experiment_tracking_category_and_group
end
def set_experimentation_subject_id_cookie
return if cookies[:experimentation_subject_id].present?
cookies.permanent.signed[:experimentation_subject_id] = {
value: SecureRandom.uuid,
secure: ::Gitlab.config.gitlab.https,
httponly: true
}
end
def push_frontend_experiment(experiment_key)
var_name = experiment_key.to_s.camelize(:lower)
enabled = experiment_enabled?(experiment_key)
gon.push({ experiments: { var_name => enabled } }, true)
end
def experiment_enabled?(experiment_key)
return false if dnt_enabled?
return true if Experimentation.enabled_for_value?(experiment_key, experimentation_subject_index(experiment_key))
return true if forced_enabled?(experiment_key)
false
end
def track_experiment_event(experiment_key, action, value = nil)
return if dnt_enabled?
track_experiment_event_for(experiment_key, action, value) do |tracking_data|
::Gitlab::Tracking.event(tracking_data.delete(:category), tracking_data.delete(:action), **tracking_data)
end
end
def frontend_experimentation_tracking_data(experiment_key, action, value = nil)
return if dnt_enabled?
track_experiment_event_for(experiment_key, action, value) do |tracking_data|
gon.push(tracking_data: tracking_data)
end
end
def record_experiment_user(experiment_key)
return if dnt_enabled?
return unless Experimentation.enabled?(experiment_key) && current_user
::Experiment.add_user(experiment_key, tracking_group(experiment_key), current_user)
end
def experiment_tracking_category_and_group(experiment_key)
"#{tracking_category(experiment_key)}:#{tracking_group(experiment_key, '_group')}"
end
private
def dnt_enabled?
Gitlab::Utils.to_boolean(request.headers['DNT'])
end
def experimentation_subject_id
cookies.signed[:experimentation_subject_id]
end
def experimentation_subject_index(experiment_key)
return if experimentation_subject_id.blank?
if Experimentation.experiment(experiment_key).use_backwards_compatible_subject_index
experimentation_subject_id.delete('-').hex % 100
else
Zlib.crc32("#{experiment_key}#{experimentation_subject_id}") % 100
end
end
def track_experiment_event_for(experiment_key, action, value)
return unless Experimentation.enabled?(experiment_key)
yield experimentation_tracking_data(experiment_key, action, value)
end
def experimentation_tracking_data(experiment_key, action, value)
{
category: tracking_category(experiment_key),
action: action,
property: tracking_group(experiment_key, "_group"),
label: experimentation_subject_id,
value: value
}.compact
end
def tracking_category(experiment_key)
Experimentation.experiment(experiment_key).tracking_category
end
def tracking_group(experiment_key, suffix = nil)
return unless Experimentation.enabled?(experiment_key)
group = experiment_enabled?(experiment_key) ? GROUP_EXPERIMENTAL : GROUP_CONTROL
suffix ? "#{group}#{suffix}" : group
end
def forced_enabled?(experiment_key)
params.has_key?(:force_experiment) && params[:force_experiment] == experiment_key.to_s
end
end
class << self class << self
def experiment(key) def experiment(key)
Experiment.new(EXPERIMENTS[key].merge(key: key)) Experiment.new(EXPERIMENTS[key].merge(key: key))
......
# frozen_string_literal: true
require 'zlib'
# Controller concern that checks if an `experimentation_subject_id cookie` is present and sets it if absent.
# Used for A/B testing of experimental features. Exposes the `experiment_enabled?(experiment_name)` method
# to controllers and views. It returns true when the experiment is enabled and the user is selected as part
# of the experimental group.
#
module Gitlab
module Experimentation
module ControllerConcern
include ::Gitlab::Experimentation::GroupTypes
extend ActiveSupport::Concern
included do
before_action :set_experimentation_subject_id_cookie, unless: :dnt_enabled?
helper_method :experiment_enabled?, :experiment_tracking_category_and_group
end
def set_experimentation_subject_id_cookie
return if cookies[:experimentation_subject_id].present?
cookies.permanent.signed[:experimentation_subject_id] = {
value: SecureRandom.uuid,
secure: ::Gitlab.config.gitlab.https,
httponly: true
}
end
def push_frontend_experiment(experiment_key)
var_name = experiment_key.to_s.camelize(:lower)
enabled = experiment_enabled?(experiment_key)
gon.push({ experiments: { var_name => enabled } }, true)
end
def experiment_enabled?(experiment_key)
return false if dnt_enabled?
return true if Experimentation.enabled_for_value?(experiment_key, experimentation_subject_index(experiment_key))
return true if forced_enabled?(experiment_key)
false
end
def track_experiment_event(experiment_key, action, value = nil)
return if dnt_enabled?
track_experiment_event_for(experiment_key, action, value) do |tracking_data|
::Gitlab::Tracking.event(tracking_data.delete(:category), tracking_data.delete(:action), **tracking_data)
end
end
def frontend_experimentation_tracking_data(experiment_key, action, value = nil)
return if dnt_enabled?
track_experiment_event_for(experiment_key, action, value) do |tracking_data|
gon.push(tracking_data: tracking_data)
end
end
def record_experiment_user(experiment_key)
return if dnt_enabled?
return unless Experimentation.enabled?(experiment_key) && current_user
::Experiment.add_user(experiment_key, tracking_group(experiment_key), current_user)
end
def experiment_tracking_category_and_group(experiment_key)
"#{tracking_category(experiment_key)}:#{tracking_group(experiment_key, '_group')}"
end
private
def dnt_enabled?
Gitlab::Utils.to_boolean(request.headers['DNT'])
end
def experimentation_subject_id
cookies.signed[:experimentation_subject_id]
end
def experimentation_subject_index(experiment_key)
return if experimentation_subject_id.blank?
if Experimentation.experiment(experiment_key).use_backwards_compatible_subject_index
experimentation_subject_id.delete('-').hex % 100
else
Zlib.crc32("#{experiment_key}#{experimentation_subject_id}") % 100
end
end
def track_experiment_event_for(experiment_key, action, value)
return unless Experimentation.enabled?(experiment_key)
yield experimentation_tracking_data(experiment_key, action, value)
end
def experimentation_tracking_data(experiment_key, action, value)
{
category: tracking_category(experiment_key),
action: action,
property: tracking_group(experiment_key, "_group"),
label: experimentation_subject_id,
value: value
}.compact
end
def tracking_category(experiment_key)
Experimentation.experiment(experiment_key).tracking_category
end
def tracking_group(experiment_key, suffix = nil)
return unless Experimentation.enabled?(experiment_key)
group = experiment_enabled?(experiment_key) ? GROUP_EXPERIMENTAL : GROUP_CONTROL
suffix ? "#{group}#{suffix}" : group
end
def forced_enabled?(experiment_key)
params.has_key?(:force_experiment) && params[:force_experiment] == experiment_key.to_s
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
before do
stub_const('Gitlab::Experimentation::EXPERIMENTS', {
backwards_compatible_test_experiment: {
environment: environment,
tracking_category: 'Team',
use_backwards_compatible_subject_index: true
},
test_experiment: {
environment: environment,
tracking_category: 'Team'
}
}
)
Feature.enable_percentage_of_time(:backwards_compatible_test_experiment_experiment_percentage, enabled_percentage)
Feature.enable_percentage_of_time(:test_experiment_experiment_percentage, enabled_percentage)
end
let(:environment) { Rails.env.test? }
let(:enabled_percentage) { 10 }
controller(ApplicationController) do
include Gitlab::Experimentation::ControllerConcern
def index
head :ok
end
end
describe '#set_experimentation_subject_id_cookie' do
let(:do_not_track) { nil }
let(:cookie) { cookies.permanent.signed[:experimentation_subject_id] }
before do
request.headers['DNT'] = do_not_track if do_not_track.present?
get :index
end
context 'cookie is present' do
before do
cookies[:experimentation_subject_id] = 'test'
end
it 'does not change the cookie' do
expect(cookies[:experimentation_subject_id]).to eq 'test'
end
end
context 'cookie is not present' do
it 'sets a permanent signed cookie' do
expect(cookie).to be_present
end
context 'DNT: 0' do
let(:do_not_track) { '0' }
it 'sets a permanent signed cookie' do
expect(cookie).to be_present
end
end
context 'DNT: 1' do
let(:do_not_track) { '1' }
it 'does nothing' do
expect(cookie).not_to be_present
end
end
end
end
describe '#push_frontend_experiment' do
it 'pushes an experiment to the frontend' do
gon = instance_double('gon')
experiments = { experiments: { 'myExperiment' => true } }
stub_experiment_for_user(my_experiment: true)
allow(controller).to receive(:gon).and_return(gon)
expect(gon).to receive(:push).with(experiments, true)
controller.push_frontend_experiment(:my_experiment)
end
end
describe '#experiment_enabled?' do
def check_experiment(exp_key = :test_experiment)
controller.experiment_enabled?(exp_key)
end
subject { check_experiment }
context 'cookie is not present' do
it 'calls Gitlab::Experimentation.enabled_for_value? with the name of the experiment and an experimentation_subject_index of nil' do
expect(Gitlab::Experimentation).to receive(:enabled_for_value?).with(:test_experiment, nil)
check_experiment
end
end
context 'cookie is present' do
using RSpec::Parameterized::TableSyntax
before do
cookies.permanent.signed[:experimentation_subject_id] = 'abcd-1234'
get :index
end
where(:experiment_key, :index_value) do
:test_experiment | 40 # Zlib.crc32('test_experimentabcd-1234') % 100 = 40
:backwards_compatible_test_experiment | 76 # 'abcd1234'.hex % 100 = 76
end
with_them do
it 'calls Gitlab::Experimentation.enabled_for_value? with the name of the experiment and the calculated experimentation_subject_index based on the uuid' do
expect(Gitlab::Experimentation).to receive(:enabled_for_value?).with(experiment_key, index_value)
check_experiment(experiment_key)
end
end
end
it 'returns true when DNT: 0 is set in the request' do
allow(Gitlab::Experimentation).to receive(:enabled_for_value?) { true }
controller.request.headers['DNT'] = '0'
is_expected.to be_truthy
end
it 'returns false when DNT: 1 is set in the request' do
allow(Gitlab::Experimentation).to receive(:enabled_for_value?) { true }
controller.request.headers['DNT'] = '1'
is_expected.to be_falsy
end
describe 'URL parameter to force enable experiment' do
it 'returns true unconditionally' do
get :index, params: { force_experiment: :test_experiment }
is_expected.to be_truthy
end
end
end
describe '#track_experiment_event', :snowplow do
context 'when the experiment is enabled' do
before do
stub_experiment(test_experiment: true)
end
context 'the user is part of the experimental group' do
before do
stub_experiment_for_user(test_experiment: true)
end
it 'tracks the event with the right parameters' do
controller.track_experiment_event(:test_experiment, 'start', 1)
expect_snowplow_event(
category: 'Team',
action: 'start',
property: 'experimental_group',
value: 1
)
end
end
context 'the user is part of the control group' do
before do
stub_experiment_for_user(test_experiment: false)
end
it 'tracks the event with the right parameters' do
controller.track_experiment_event(:test_experiment, 'start', 1)
expect_snowplow_event(
category: 'Team',
action: 'start',
property: 'control_group',
value: 1
)
end
end
context 'do not track is disabled' do
before do
request.headers['DNT'] = '0'
end
it 'does track the event' do
controller.track_experiment_event(:test_experiment, 'start', 1)
expect_snowplow_event(
category: 'Team',
action: 'start',
property: 'control_group',
value: 1
)
end
end
context 'do not track enabled' do
before do
request.headers['DNT'] = '1'
end
it 'does not track the event' do
controller.track_experiment_event(:test_experiment, 'start', 1)
expect_no_snowplow_event
end
end
end
context 'when the experiment is disabled' do
before do
stub_experiment(test_experiment: false)
end
it 'does not track the event' do
controller.track_experiment_event(:test_experiment, 'start')
expect_no_snowplow_event
end
end
end
describe '#frontend_experimentation_tracking_data' do
context 'when the experiment is enabled' do
before do
stub_experiment(test_experiment: true)
end
context 'the user is part of the experimental group' do
before do
stub_experiment_for_user(test_experiment: true)
end
it 'pushes the right parameters to gon' do
controller.frontend_experimentation_tracking_data(:test_experiment, 'start', 'team_id')
expect(Gon.tracking_data).to eq(
{
category: 'Team',
action: 'start',
property: 'experimental_group',
value: 'team_id'
}
)
end
end
context 'the user is part of the control group' do
before do
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:experiment_enabled?).with(:test_experiment).and_return(false)
end
end
it 'pushes the right parameters to gon' do
controller.frontend_experimentation_tracking_data(:test_experiment, 'start', 'team_id')
expect(Gon.tracking_data).to eq(
{
category: 'Team',
action: 'start',
property: 'control_group',
value: 'team_id'
}
)
end
it 'does not send nil value to gon' do
controller.frontend_experimentation_tracking_data(:test_experiment, 'start')
expect(Gon.tracking_data).to eq(
{
category: 'Team',
action: 'start',
property: 'control_group'
}
)
end
end
context 'do not track disabled' do
before do
request.headers['DNT'] = '0'
end
it 'pushes the right parameters to gon' do
controller.frontend_experimentation_tracking_data(:test_experiment, 'start')
expect(Gon.tracking_data).to eq(
{
category: 'Team',
action: 'start',
property: 'control_group'
}
)
end
end
context 'do not track enabled' do
before do
request.headers['DNT'] = '1'
end
it 'does not push data to gon' do
controller.frontend_experimentation_tracking_data(:test_experiment, 'start')
expect(Gon.method_defined?(:tracking_data)).to be_falsey
end
end
end
context 'when the experiment is disabled' do
before do
stub_experiment(test_experiment: false)
end
it 'does not push data to gon' do
expect(Gon.method_defined?(:tracking_data)).to be_falsey
controller.track_experiment_event(:test_experiment, 'start')
end
end
end
describe '#record_experiment_user' do
let(:user) { build(:user) }
context 'when the experiment is enabled' do
before do
stub_experiment(test_experiment: true)
allow(controller).to receive(:current_user).and_return(user)
end
context 'the user is part of the experimental group' do
before do
stub_experiment_for_user(test_experiment: true)
end
it 'calls add_user on the Experiment model' do
expect(::Experiment).to receive(:add_user).with(:test_experiment, :experimental, user)
controller.record_experiment_user(:test_experiment)
end
end
context 'the user is part of the control group' do
before do
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:experiment_enabled?).with(:test_experiment).and_return(false)
end
end
it 'calls add_user on the Experiment model' do
expect(::Experiment).to receive(:add_user).with(:test_experiment, :control, user)
controller.record_experiment_user(:test_experiment)
end
end
end
context 'when the experiment is disabled' do
before do
stub_experiment(test_experiment: false)
allow(controller).to receive(:current_user).and_return(user)
end
it 'does not call add_user on the Experiment model' do
expect(::Experiment).not_to receive(:add_user)
controller.record_experiment_user(:test_experiment)
end
end
context 'when there is no current_user' do
before do
stub_experiment(test_experiment: true)
end
it 'does not call add_user on the Experiment model' do
expect(::Experiment).not_to receive(:add_user)
controller.record_experiment_user(:test_experiment)
end
end
context 'do not track' do
before do
allow(controller).to receive(:current_user).and_return(user)
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:experiment_enabled?).with(:test_experiment).and_return(false)
end
end
context 'is disabled' do
before do
request.headers['DNT'] = '0'
end
it 'calls add_user on the Experiment model' do
expect(::Experiment).to receive(:add_user).with(:test_experiment, :control, user)
controller.record_experiment_user(:test_experiment)
end
end
context 'is enabled' do
before do
request.headers['DNT'] = '1'
end
it 'does not call add_user on the Experiment model' do
expect(::Experiment).not_to receive(:add_user)
controller.record_experiment_user(:test_experiment)
end
end
end
end
describe '#experiment_tracking_category_and_group' do
let_it_be(:experiment_key) { :test_something }
subject { controller.experiment_tracking_category_and_group(experiment_key) }
it 'returns a string with the experiment tracking category & group joined with a ":"' do
expect(controller).to receive(:tracking_category).with(experiment_key).and_return('Experiment::Category')
expect(controller).to receive(:tracking_group).with(experiment_key, '_group').and_return('experimental_group')
expect(subject).to eq('Experiment::Category:experimental_group')
end
end
end
...@@ -50,420 +50,6 @@ RSpec.describe Gitlab::Experimentation, :snowplow do ...@@ -50,420 +50,6 @@ RSpec.describe Gitlab::Experimentation, :snowplow do
let(:environment) { Rails.env.test? } let(:environment) { Rails.env.test? }
let(:enabled_percentage) { 10 } let(:enabled_percentage) { 10 }
describe Gitlab::Experimentation::ControllerConcern, type: :controller do
controller(ApplicationController) do
include Gitlab::Experimentation::ControllerConcern
def index
head :ok
end
end
describe '#set_experimentation_subject_id_cookie' do
let(:do_not_track) { nil }
let(:cookie) { cookies.permanent.signed[:experimentation_subject_id] }
before do
request.headers['DNT'] = do_not_track if do_not_track.present?
get :index
end
context 'cookie is present' do
before do
cookies[:experimentation_subject_id] = 'test'
end
it 'does not change the cookie' do
expect(cookies[:experimentation_subject_id]).to eq 'test'
end
end
context 'cookie is not present' do
it 'sets a permanent signed cookie' do
expect(cookie).to be_present
end
context 'DNT: 0' do
let(:do_not_Track) { '0' }
it 'sets a permanent signed cookie' do
expect(cookie).to be_present
end
end
context 'DNT: 1' do
let(:do_not_track) { '1' }
it 'does nothing' do
expect(cookie).not_to be_present
end
end
end
end
describe '#push_frontend_experiment' do
it 'pushes an experiment to the frontend' do
gon = instance_double('gon')
experiments = { experiments: { 'myExperiment' => true } }
stub_experiment_for_user(my_experiment: true)
allow(controller).to receive(:gon).and_return(gon)
expect(gon).to receive(:push).with(experiments, true)
controller.push_frontend_experiment(:my_experiment)
end
end
describe '#experiment_enabled?' do
def check_experiment(exp_key = :test_experiment)
controller.experiment_enabled?(exp_key)
end
subject { check_experiment }
context 'cookie is not present' do
it 'calls Gitlab::Experimentation.enabled_for_value? with the name of the experiment and an experimentation_subject_index of nil' do
expect(Gitlab::Experimentation).to receive(:enabled_for_value?).with(:test_experiment, nil)
check_experiment
end
end
context 'cookie is present' do
using RSpec::Parameterized::TableSyntax
before do
cookies.permanent.signed[:experimentation_subject_id] = 'abcd-1234'
get :index
end
where(:experiment_key, :index_value) do
:test_experiment | 40 # Zlib.crc32('test_experimentabcd-1234') % 100 = 40
:backwards_compatible_test_experiment | 76 # 'abcd1234'.hex % 100 = 76
end
with_them do
it 'calls Gitlab::Experimentation.enabled_for_value? with the name of the experiment and the calculated experimentation_subject_index based on the uuid' do
expect(Gitlab::Experimentation).to receive(:enabled_for_value?).with(experiment_key, index_value)
check_experiment(experiment_key)
end
end
end
it 'returns true when DNT: 0 is set in the request' do
allow(Gitlab::Experimentation).to receive(:enabled_for_value?) { true }
controller.request.headers['DNT'] = '0'
is_expected.to be_truthy
end
it 'returns false when DNT: 1 is set in the request' do
allow(Gitlab::Experimentation).to receive(:enabled_for_value?) { true }
controller.request.headers['DNT'] = '1'
is_expected.to be_falsy
end
describe 'URL parameter to force enable experiment' do
it 'returns true unconditionally' do
get :index, params: { force_experiment: :test_experiment }
is_expected.to be_truthy
end
end
end
describe '#track_experiment_event' do
context 'when the experiment is enabled' do
before do
stub_experiment(test_experiment: true)
end
context 'the user is part of the experimental group' do
before do
stub_experiment_for_user(test_experiment: true)
end
it 'tracks the event with the right parameters' do
controller.track_experiment_event(:test_experiment, 'start', 1)
expect_snowplow_event(
category: 'Team',
action: 'start',
property: 'experimental_group',
value: 1
)
end
end
context 'the user is part of the control group' do
before do
stub_experiment_for_user(test_experiment: false)
end
it 'tracks the event with the right parameters' do
controller.track_experiment_event(:test_experiment, 'start', 1)
expect_snowplow_event(
category: 'Team',
action: 'start',
property: 'control_group',
value: 1
)
end
end
context 'do not track is disabled' do
before do
request.headers['DNT'] = '0'
end
it 'does track the event' do
controller.track_experiment_event(:test_experiment, 'start', 1)
expect_snowplow_event(
category: 'Team',
action: 'start',
property: 'control_group',
value: 1
)
end
end
context 'do not track enabled' do
before do
request.headers['DNT'] = '1'
end
it 'does not track the event' do
controller.track_experiment_event(:test_experiment, 'start', 1)
expect_no_snowplow_event
end
end
end
context 'when the experiment is disabled' do
before do
stub_experiment(test_experiment: false)
end
it 'does not track the event' do
controller.track_experiment_event(:test_experiment, 'start')
expect_no_snowplow_event
end
end
end
describe '#frontend_experimentation_tracking_data' do
context 'when the experiment is enabled' do
before do
stub_experiment(test_experiment: true)
end
context 'the user is part of the experimental group' do
before do
stub_experiment_for_user(test_experiment: true)
end
it 'pushes the right parameters to gon' do
controller.frontend_experimentation_tracking_data(:test_experiment, 'start', 'team_id')
expect(Gon.tracking_data).to eq(
{
category: 'Team',
action: 'start',
property: 'experimental_group',
value: 'team_id'
}
)
end
end
context 'the user is part of the control group' do
before do
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:experiment_enabled?).with(:test_experiment).and_return(false)
end
end
it 'pushes the right parameters to gon' do
controller.frontend_experimentation_tracking_data(:test_experiment, 'start', 'team_id')
expect(Gon.tracking_data).to eq(
{
category: 'Team',
action: 'start',
property: 'control_group',
value: 'team_id'
}
)
end
it 'does not send nil value to gon' do
controller.frontend_experimentation_tracking_data(:test_experiment, 'start')
expect(Gon.tracking_data).to eq(
{
category: 'Team',
action: 'start',
property: 'control_group'
}
)
end
end
context 'do not track disabled' do
before do
request.headers['DNT'] = '0'
end
it 'pushes the right parameters to gon' do
controller.frontend_experimentation_tracking_data(:test_experiment, 'start')
expect(Gon.tracking_data).to eq(
{
category: 'Team',
action: 'start',
property: 'control_group'
}
)
end
end
context 'do not track enabled' do
before do
request.headers['DNT'] = '1'
end
it 'does not push data to gon' do
controller.frontend_experimentation_tracking_data(:test_experiment, 'start')
expect(Gon.method_defined?(:tracking_data)).to be_falsey
end
end
end
context 'when the experiment is disabled' do
before do
stub_experiment(test_experiment: false)
end
it 'does not push data to gon' do
expect(Gon.method_defined?(:tracking_data)).to be_falsey
controller.track_experiment_event(:test_experiment, 'start')
end
end
end
describe '#record_experiment_user' do
let(:user) { build(:user) }
context 'when the experiment is enabled' do
before do
stub_experiment(test_experiment: true)
allow(controller).to receive(:current_user).and_return(user)
end
context 'the user is part of the experimental group' do
before do
stub_experiment_for_user(test_experiment: true)
end
it 'calls add_user on the Experiment model' do
expect(::Experiment).to receive(:add_user).with(:test_experiment, :experimental, user)
controller.record_experiment_user(:test_experiment)
end
end
context 'the user is part of the control group' do
before do
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:experiment_enabled?).with(:test_experiment).and_return(false)
end
end
it 'calls add_user on the Experiment model' do
expect(::Experiment).to receive(:add_user).with(:test_experiment, :control, user)
controller.record_experiment_user(:test_experiment)
end
end
end
context 'when the experiment is disabled' do
before do
stub_experiment(test_experiment: false)
allow(controller).to receive(:current_user).and_return(user)
end
it 'does not call add_user on the Experiment model' do
expect(::Experiment).not_to receive(:add_user)
controller.record_experiment_user(:test_experiment)
end
end
context 'when there is no current_user' do
before do
stub_experiment(test_experiment: true)
end
it 'does not call add_user on the Experiment model' do
expect(::Experiment).not_to receive(:add_user)
controller.record_experiment_user(:test_experiment)
end
end
context 'do not track' do
before do
allow(controller).to receive(:current_user).and_return(user)
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:experiment_enabled?).with(:test_experiment).and_return(false)
end
end
context 'is disabled' do
before do
request.headers['DNT'] = '0'
end
it 'calls add_user on the Experiment model' do
expect(::Experiment).to receive(:add_user).with(:test_experiment, :control, user)
controller.record_experiment_user(:test_experiment)
end
end
context 'is enabled' do
before do
request.headers['DNT'] = '1'
end
it 'does not call add_user on the Experiment model' do
expect(::Experiment).not_to receive(:add_user)
controller.record_experiment_user(:test_experiment)
end
end
end
end
describe '#experiment_tracking_category_and_group' do
let_it_be(:experiment_key) { :test_something }
subject { controller.experiment_tracking_category_and_group(experiment_key) }
it 'returns a string with the experiment tracking category & group joined with a ":"' do
expect(controller).to receive(:tracking_category).with(experiment_key).and_return('Experiment::Category')
expect(controller).to receive(:tracking_group).with(experiment_key, '_group').and_return('experimental_group')
expect(subject).to eq('Experiment::Category:experimental_group')
end
end
end
describe '.enabled?' do describe '.enabled?' do
subject { described_class.enabled?(:test_experiment) } subject { described_class.enabled?(:test_experiment) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment