# frozen_string_literal: true require 'spec_helper' RSpec.describe Gitlab::Experimentation do using RSpec::Parameterized::TableSyntax before do stub_const('Gitlab::Experimentation::EXPERIMENTS', { test_experiment: { tracking_category: 'Team' }, tabular_experiment: { tracking_category: 'Team', rollout_strategy: rollout_strategy } }) skip_feature_flags_yaml_validation skip_default_enabled_yaml_check Feature.enable_percentage_of_time(:test_experiment_experiment_percentage, enabled_percentage) allow(Gitlab).to receive(:com?).and_return(true) end let(:enabled_percentage) { 10 } let(:rollout_strategy) { nil } describe '.get_experiment' do subject { described_class.get_experiment(:test_experiment) } context 'returns experiment' do it { is_expected.to be_instance_of(Gitlab::Experimentation::Experiment) } end context 'experiment is not defined' do subject { described_class.get_experiment(:missing_experiment) } it { is_expected.to be_nil } end end describe '.active?' do subject { described_class.active?(:test_experiment) } context 'feature toggle is enabled' do it { is_expected.to eq(true) } end describe 'experiment is not defined' do it 'returns false' do expect(described_class.active?(:missing_experiment)).to eq(false) end end describe 'experiment is disabled' do let(:enabled_percentage) { 0 } it { is_expected.to eq(false) } end end describe '.in_experiment_group?' do let(:enabled_percentage) { 50 } let(:experiment_subject) { 'z' } # Zlib.crc32('test_experimentz') % 100 = 33 subject { described_class.in_experiment_group?(:test_experiment, subject: experiment_subject) } context 'when experiment is active' do context 'when subject is part of the experiment' do it { is_expected.to eq(true) } end context 'when subject is not part of the experiment' do let(:experiment_subject) { 'a' } # Zlib.crc32('test_experimenta') % 100 = 61 it { is_expected.to eq(false) } end context 'when subject has a global_id' do let(:experiment_subject) { double(:subject, to_global_id: 'z') } it { is_expected.to eq(true) } end context 'when subject is nil' do let(:experiment_subject) { nil } it { is_expected.to eq(false) } end context 'when subject is an empty string' do let(:experiment_subject) { '' } it { is_expected.to eq(false) } end end context 'when experiment is not active' do before do allow(described_class).to receive(:active?).and_return(false) end it { is_expected.to eq(false) } end end describe '.log_invalid_rollout' do subject { described_class.log_invalid_rollout(:test_experiment, 1) } before do allow(described_class).to receive(:valid_subject_for_rollout_strategy?).and_return(valid) end context 'subject is not valid for experiment' do let(:valid) { false } it 'logs a warning message' do expect_next_instance_of(Gitlab::ExperimentationLogger) do |logger| expect(logger) .to receive(:warn) .with( message: 'Subject must conform to the rollout strategy', experiment_key: :test_experiment, subject: 'Integer', rollout_strategy: :cookie ) end subject end end context 'subject is valid for experiment' do let(:valid) { true } it 'does not log a warning message' do expect(Gitlab::ExperimentationLogger).not_to receive(:build) subject end end end describe '.valid_subject_for_rollout_strategy?' do subject { described_class.valid_subject_for_rollout_strategy?(:tabular_experiment, experiment_subject) } where(:rollout_strategy, :experiment_subject, :result) do :cookie | nil | true nil | nil | true :cookie | 'string' | true nil | User.new | false :user | User.new | true :group | User.new | false :group | Group.new | true end with_them do it { is_expected.to be(result) } end end end