2021-01-29 00:20:46 +05:30
# 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 : {
tracking_category : 'Team' ,
use_backwards_compatible_subject_index : true
} ,
test_experiment : {
2021-03-11 19:13:27 +05:30
tracking_category : 'Team' ,
rollout_strategy : rollout_strategy
} ,
my_experiment : {
2021-01-29 00:20:46 +05:30
tracking_category : 'Team'
}
}
)
2021-06-08 01:23:25 +05:30
allow ( Gitlab ) . to receive ( :dev_env_or_com? ) . and_return ( is_gitlab_com )
2021-01-29 00:20:46 +05:30
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 ( :enabled_percentage ) { 10 }
2021-03-11 19:13:27 +05:30
let ( :rollout_strategy ) { nil }
2021-06-08 01:23:25 +05:30
let ( :is_gitlab_com ) { true }
2021-01-29 00:20:46 +05:30
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 ] }
2021-06-08 01:23:25 +05:30
let ( :cookie_value ) { nil }
2021-01-29 00:20:46 +05:30
before do
request . headers [ 'DNT' ] = do_not_track if do_not_track . present?
2021-06-08 01:23:25 +05:30
request . cookies [ :experimentation_subject_id ] = cookie_value if cookie_value
2021-01-29 00:20:46 +05:30
get :index
end
context 'cookie is present' do
2021-06-08 01:23:25 +05:30
let ( :cookie_value ) { 'test' }
2021-01-29 00:20:46 +05:30
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
2021-06-08 01:23:25 +05:30
context 'when not on gitlab.com' do
let ( :is_gitlab_com ) { false }
context 'when cookie was set' do
let ( :cookie_value ) { 'test' }
it 'cookie gets deleted' do
expect ( cookie ) . not_to be_present
end
end
context 'when no cookie was set before' do
it 'does nothing' do
expect ( cookie ) . not_to be_present
end
end
end
2021-01-29 00:20:46 +05:30
end
describe '#push_frontend_experiment' do
it 'pushes an experiment to the frontend' do
gon = instance_double ( 'gon' )
2021-02-22 17:27:13 +05:30
stub_experiment_for_subject ( my_experiment : true )
2021-01-29 00:20:46 +05:30
allow ( controller ) . to receive ( :gon ) . and_return ( gon )
2021-02-22 17:27:13 +05:30
expect ( gon ) . to receive ( :push ) . with ( { experiments : { 'myExperiment' = > true } } , true )
2021-01-29 00:20:46 +05:30
controller . push_frontend_experiment ( :my_experiment )
end
end
describe '#experiment_enabled?' do
2021-02-22 17:27:13 +05:30
def check_experiment ( exp_key = :test_experiment , subject = nil )
controller . experiment_enabled? ( exp_key , subject : subject )
2021-01-29 00:20:46 +05:30
end
subject { check_experiment }
context 'cookie is not present' do
2021-02-22 17:27:13 +05:30
it { is_expected . to eq ( false ) }
2021-01-29 00:20:46 +05:30
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
2021-02-22 17:27:13 +05:30
:test_experiment | 'abcd-1234'
:backwards_compatible_test_experiment | 'abcd1234'
2021-01-29 00:20:46 +05:30
end
with_them do
2021-02-22 17:27:13 +05:30
it 'calls Gitlab::Experimentation.in_experiment_group?? with the name of the experiment and the calculated experimentation_subject_index based on the uuid' do
expect ( Gitlab :: Experimentation ) . to receive ( :in_experiment_group? ) . with ( experiment_key , subject : index_value )
2021-01-29 00:20:46 +05:30
check_experiment ( experiment_key )
end
end
2021-02-22 17:27:13 +05:30
context 'when subject is given' do
2021-03-11 19:13:27 +05:30
let ( :rollout_strategy ) { :user }
2021-02-22 17:27:13 +05:30
let ( :user ) { build ( :user ) }
it 'uses the subject' do
expect ( Gitlab :: Experimentation ) . to receive ( :in_experiment_group? ) . with ( :test_experiment , subject : user )
2021-01-29 00:20:46 +05:30
2021-02-22 17:27:13 +05:30
check_experiment ( :test_experiment , user )
end
end
2021-01-29 00:20:46 +05:30
end
2021-02-22 17:27:13 +05:30
context 'do not track' do
before do
allow ( Gitlab :: Experimentation ) . to receive ( :in_experiment_group? ) { true }
end
context 'when do not track is disabled' do
before do
controller . request . headers [ 'DNT' ] = '0'
end
it { is_expected . to eq ( true ) }
end
2021-01-29 00:20:46 +05:30
2021-02-22 17:27:13 +05:30
context 'when do not track is enabled' do
before do
controller . request . headers [ 'DNT' ] = '1'
end
it { is_expected . to eq ( false ) }
end
2021-01-29 00:20:46 +05:30
end
2021-02-22 17:27:13 +05:30
context 'URL parameter to force enable experiment' do
2021-01-29 00:20:46 +05:30
it 'returns true unconditionally' do
get :index , params : { force_experiment : :test_experiment }
2021-02-22 17:27:13 +05:30
is_expected . to eq ( true )
2021-01-29 00:20:46 +05:30
end
end
2021-03-08 18:12:59 +05:30
context 'Cookie parameter to force enable experiment' do
it 'returns true unconditionally' do
cookies [ :force_experiment ] = 'test_experiment,another_experiment'
get :index
expect ( check_experiment ( :test_experiment ) ) . to eq ( true )
expect ( check_experiment ( :another_experiment ) ) . to eq ( true )
end
end
2021-01-29 00:20:46 +05:30
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
2021-02-22 17:27:13 +05:30
stub_experiment_for_subject ( test_experiment : true )
2021-01-29 00:20:46 +05:30
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
2021-02-22 17:27:13 +05:30
stub_experiment_for_subject ( test_experiment : false )
2021-01-29 00:20:46 +05:30
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
2021-02-22 17:27:13 +05:30
context 'subject is provided' do
before do
stub_experiment_for_subject ( test_experiment : false )
end
it " provides the subject's hashed global_id as label " do
experiment_subject = double ( :subject , to_global_id : 'abc' )
2021-03-11 19:13:27 +05:30
allow ( Gitlab :: Experimentation ) . to receive ( :valid_subject_for_rollout_strategy? ) . and_return ( true )
2021-02-22 17:27:13 +05:30
controller . track_experiment_event ( :test_experiment , 'start' , 1 , subject : experiment_subject )
expect_snowplow_event (
category : 'Team' ,
action : 'start' ,
property : 'control_group' ,
value : 1 ,
label : Digest :: MD5 . hexdigest ( 'abc' )
)
end
it " provides the subject's hashed string representation as label " do
experiment_subject = 'somestring'
controller . track_experiment_event ( :test_experiment , 'start' , 1 , subject : experiment_subject )
expect_snowplow_event (
category : 'Team' ,
action : 'start' ,
property : 'control_group' ,
value : 1 ,
label : Digest :: MD5 . hexdigest ( 'somestring' )
)
end
end
context 'no subject is provided but cookie is set' do
before do
get :index
stub_experiment_for_subject ( test_experiment : false )
end
it 'uses the experimentation_subject_id as fallback' do
controller . track_experiment_event ( :test_experiment , 'start' , 1 )
expect_snowplow_event (
category : 'Team' ,
action : 'start' ,
property : 'control_group' ,
value : 1 ,
label : cookies . permanent . signed [ :experimentation_subject_id ]
)
end
end
2021-01-29 00:20:46 +05:30
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
2021-02-22 17:27:13 +05:30
stub_experiment_for_subject ( test_experiment : true )
2021-01-29 00:20:46 +05:30
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
2021-02-22 17:27:13 +05:30
stub_experiment_for_subject ( test_experiment : false )
2021-01-29 00:20:46 +05:30
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' )
2021-02-22 17:27:13 +05:30
expect ( Gon . method_defined? ( :tracking_data ) ) . to eq ( false )
2021-01-29 00:20:46 +05:30
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
2021-02-22 17:27:13 +05:30
expect ( Gon . method_defined? ( :tracking_data ) ) . to eq ( false )
2021-01-29 00:20:46 +05:30
controller . track_experiment_event ( :test_experiment , 'start' )
end
end
end
describe '#record_experiment_user' do
let ( :user ) { build ( :user ) }
2021-02-22 17:27:13 +05:30
let ( :context ) { { a : 42 } }
2021-01-29 00:20:46 +05:30
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
2021-02-22 17:27:13 +05:30
stub_experiment_for_subject ( test_experiment : true )
2021-01-29 00:20:46 +05:30
end
it 'calls add_user on the Experiment model' do
2021-02-22 17:27:13 +05:30
expect ( :: Experiment ) . to receive ( :add_user ) . with ( :test_experiment , :experimental , user , context )
2021-01-29 00:20:46 +05:30
2021-02-22 17:27:13 +05:30
controller . record_experiment_user ( :test_experiment , context )
2021-01-29 00:20:46 +05:30
end
2021-03-11 19:13:27 +05:30
context 'with a cookie based rollout strategy' do
it 'calls tracking_group with a nil subject' do
expect ( controller ) . to receive ( :tracking_group ) . with ( :test_experiment , nil , subject : nil ) . and_return ( :experimental )
allow ( :: Experiment ) . to receive ( :add_user ) . with ( :test_experiment , :experimental , user , context )
controller . record_experiment_user ( :test_experiment , context )
end
end
context 'with a user based rollout strategy' do
let ( :rollout_strategy ) { :user }
it 'calls tracking_group with a user subject' do
expect ( controller ) . to receive ( :tracking_group ) . with ( :test_experiment , nil , subject : user ) . and_return ( :experimental )
allow ( :: Experiment ) . to receive ( :add_user ) . with ( :test_experiment , :experimental , user , context )
controller . record_experiment_user ( :test_experiment , context )
end
end
2021-01-29 00:20:46 +05:30
end
context 'the user is part of the control group' do
before do
2021-02-22 17:27:13 +05:30
stub_experiment_for_subject ( test_experiment : false )
2021-01-29 00:20:46 +05:30
end
it 'calls add_user on the Experiment model' do
2021-02-22 17:27:13 +05:30
expect ( :: Experiment ) . to receive ( :add_user ) . with ( :test_experiment , :control , user , context )
2021-01-29 00:20:46 +05:30
2021-02-22 17:27:13 +05:30
controller . record_experiment_user ( :test_experiment , context )
2021-01-29 00:20:46 +05:30
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 )
2021-02-22 17:27:13 +05:30
controller . record_experiment_user ( :test_experiment , context )
2021-01-29 00:20:46 +05:30
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 )
2021-02-22 17:27:13 +05:30
controller . record_experiment_user ( :test_experiment , context )
2021-01-29 00:20:46 +05:30
end
end
context 'do not track' do
before do
2021-02-22 17:27:13 +05:30
stub_experiment ( test_experiment : true )
2021-01-29 00:20:46 +05:30
allow ( controller ) . to receive ( :current_user ) . and_return ( user )
end
context 'is disabled' do
before do
request . headers [ 'DNT' ] = '0'
2021-02-22 17:27:13 +05:30
stub_experiment_for_subject ( test_experiment : false )
2021-01-29 00:20:46 +05:30
end
it 'calls add_user on the Experiment model' do
2021-02-22 17:27:13 +05:30
expect ( :: Experiment ) . to receive ( :add_user ) . with ( :test_experiment , :control , user , context )
2021-01-29 00:20:46 +05:30
2021-02-22 17:27:13 +05:30
controller . record_experiment_user ( :test_experiment , context )
2021-01-29 00:20:46 +05:30
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 )
2021-02-22 17:27:13 +05:30
controller . record_experiment_user ( :test_experiment , context )
2021-01-29 00:20:46 +05:30
end
end
end
end
2021-04-17 20:07:23 +05:30
describe '#record_experiment_group' do
let ( :group ) { 'a group object' }
let ( :experiment_key ) { :some_experiment_key }
let ( :dnt_enabled ) { false }
let ( :experiment_active ) { true }
let ( :rollout_strategy ) { :whatever }
let ( :variant ) { 'variant' }
before do
allow ( controller ) . to receive ( :dnt_enabled? ) . and_return ( dnt_enabled )
allow ( :: Gitlab :: Experimentation ) . to receive ( :active? ) . and_return ( experiment_active )
allow ( :: Gitlab :: Experimentation ) . to receive ( :rollout_strategy ) . and_return ( rollout_strategy )
allow ( controller ) . to receive ( :tracking_group ) . and_return ( variant )
allow ( :: Experiment ) . to receive ( :add_group )
end
subject ( :record_experiment_group ) { controller . record_experiment_group ( experiment_key , group ) }
shared_examples 'exits early without recording' do
it 'returns early without recording the group as an ExperimentSubject' do
expect ( :: Experiment ) . not_to receive ( :add_group )
record_experiment_group
end
end
shared_examples 'calls tracking_group' do | using_cookie_rollout |
it " calls tracking_group with #{ using_cookie_rollout ? 'a nil' : 'the group as the' } subject " do
expect ( controller ) . to receive ( :tracking_group ) . with ( experiment_key , nil , subject : using_cookie_rollout ? nil : group ) . and_return ( variant )
record_experiment_group
end
end
shared_examples 'records the group' do
it 'records the group' do
expect ( :: Experiment ) . to receive ( :add_group ) . with ( experiment_key , group : group , variant : variant )
record_experiment_group
end
end
context 'when DNT is enabled' do
let ( :dnt_enabled ) { true }
include_examples 'exits early without recording'
end
context 'when the experiment is not active' do
let ( :experiment_active ) { false }
include_examples 'exits early without recording'
end
context 'when a nil group is given' do
let ( :group ) { nil }
include_examples 'exits early without recording'
end
context 'when the experiment uses a cookie-based rollout strategy' do
let ( :rollout_strategy ) { :cookie }
include_examples 'calls tracking_group' , true
include_examples 'records the group'
end
context 'when the experiment uses a non-cookie-based rollout strategy' do
let ( :rollout_strategy ) { :group }
include_examples 'calls tracking_group' , false
include_examples 'records the group'
end
end
2021-02-22 17:27:13 +05:30
describe '#record_experiment_conversion_event' do
let ( :user ) { build ( :user ) }
before do
allow ( controller ) . to receive ( :dnt_enabled? ) . and_return ( false )
allow ( controller ) . to receive ( :current_user ) . and_return ( user )
stub_experiment ( test_experiment : true )
end
subject ( :record_conversion_event ) do
controller . record_experiment_conversion_event ( :test_experiment )
end
it 'records the conversion event for the experiment & user' do
2021-04-17 20:07:23 +05:30
expect ( :: Experiment ) . to receive ( :record_conversion_event ) . with ( :test_experiment , user , { } )
2021-02-22 17:27:13 +05:30
record_conversion_event
end
shared_examples 'does not record the conversion event' do
it 'does not record the conversion event' do
expect ( :: Experiment ) . not_to receive ( :record_conversion_event )
record_conversion_event
end
end
context 'when DNT is enabled' do
before do
allow ( controller ) . to receive ( :dnt_enabled? ) . and_return ( true )
end
include_examples 'does not record the conversion event'
end
context 'when there is no current user' do
before do
allow ( controller ) . to receive ( :current_user ) . and_return ( nil )
end
include_examples 'does not record the conversion event'
end
context 'when the experiment is not enabled' do
before do
stub_experiment ( test_experiment : false )
end
include_examples 'does not record the conversion event'
end
end
2021-01-29 00:20:46 +05:30
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' )
2021-02-22 17:27:13 +05:30
expect ( controller ) . to receive ( :tracking_group ) . with ( experiment_key , '_group' , subject : nil ) . and_return ( 'experimental_group' )
2021-01-29 00:20:46 +05:30
expect ( subject ) . to eq ( 'Experiment::Category:experimental_group' )
end
end
end