diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
commit | aee0a117a889461ce8ced6fcf73207fe017f1d99 (patch) | |
tree | 891d9ef189227a8445d83f35c1b0fc99573f4380 /spec/experiments | |
parent | 8d46af3258650d305f53b819eabf7ab18d22f59e (diff) |
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'spec/experiments')
-rw-r--r-- | spec/experiments/application_experiment_spec.rb | 217 | ||||
-rw-r--r-- | spec/experiments/new_project_sast_enabled_experiment_spec.rb | 2 |
2 files changed, 157 insertions, 62 deletions
diff --git a/spec/experiments/application_experiment_spec.rb b/spec/experiments/application_experiment_spec.rb index b0788eec808..5146fe3e752 100644 --- a/spec/experiments/application_experiment_spec.rb +++ b/spec/experiments/application_experiment_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe ApplicationExperiment, :experiment do - subject { described_class.new('namespaced/stub', **context) } + subject(:application_experiment) { described_class.new('namespaced/stub', **context) } let(:context) { {} } let(:feature_definition) { { name: 'namespaced_stub', type: 'experiment', default_enabled: false } } @@ -15,7 +15,7 @@ RSpec.describe ApplicationExperiment, :experiment do end before do - allow(subject).to receive(:enabled?).and_return(true) + allow(application_experiment).to receive(:enabled?).and_return(true) end it "doesn't raise an exception without a defined control" do @@ -26,7 +26,7 @@ RSpec.describe ApplicationExperiment, :experiment do describe "#enabled?" do before do - allow(subject).to receive(:enabled?).and_call_original + allow(application_experiment).to receive(:enabled?).and_call_original allow(Feature::Definition).to receive(:get).and_return('_instance_') allow(Gitlab).to receive(:dev_env_or_com?).and_return(true) @@ -34,25 +34,25 @@ RSpec.describe ApplicationExperiment, :experiment do end it "is enabled when all criteria are met" do - expect(subject).to be_enabled + expect(application_experiment).to be_enabled end it "isn't enabled if the feature definition doesn't exist" do expect(Feature::Definition).to receive(:get).with('namespaced_stub').and_return(nil) - expect(subject).not_to be_enabled + expect(application_experiment).not_to be_enabled end it "isn't enabled if we're not in dev or dotcom environments" do expect(Gitlab).to receive(:dev_env_or_com?).and_return(false) - expect(subject).not_to be_enabled + expect(application_experiment).not_to be_enabled end it "isn't enabled if the feature flag state is :off" do expect(Feature).to receive(:get).with('namespaced_stub').and_return(double(state: :off)) - expect(subject).not_to be_enabled + expect(application_experiment).not_to be_enabled end end @@ -60,11 +60,11 @@ RSpec.describe ApplicationExperiment, :experiment do let(:should_track) { true } before do - allow(subject).to receive(:should_track?).and_return(should_track) + allow(application_experiment).to receive(:should_track?).and_return(should_track) end it "tracks the assignment", :snowplow do - subject.publish + application_experiment.publish expect_snowplow_event( category: 'namespaced/stub', @@ -74,24 +74,16 @@ RSpec.describe ApplicationExperiment, :experiment do end it "publishes to the client" do - expect(subject).to receive(:publish_to_client) + expect(application_experiment).to receive(:publish_to_client) - subject.publish - end - - it "publishes to the database if we've opted for that" do - subject.record! - - expect(subject).to receive(:publish_to_database) - - subject.publish + application_experiment.publish end context 'when we should not track' do let(:should_track) { false } it 'does not track an event to Snowplow', :snowplow do - subject.publish + application_experiment.publish expect_no_snowplow_event end @@ -102,13 +94,13 @@ RSpec.describe ApplicationExperiment, :experiment do signature = { key: '86208ac54ca798e11f127e8b23ec396a', variant: 'control' } expect(Gon).to receive(:push).with({ experiment: { 'namespaced/stub' => hash_including(signature) } }, true) - subject.publish_to_client + application_experiment.publish_to_client end it "handles when Gon raises exceptions (like when it can't be pushed into)" do expect(Gon).to receive(:push).and_raise(NoMethodError) - expect { subject.publish_to_client }.not_to raise_error + expect { application_experiment.publish_to_client }.not_to raise_error end context 'when we should not track' do @@ -117,7 +109,7 @@ RSpec.describe ApplicationExperiment, :experiment do it 'returns early' do expect(Gon).not_to receive(:push) - subject.publish_to_client + application_experiment.publish_to_client end end end @@ -125,13 +117,15 @@ RSpec.describe ApplicationExperiment, :experiment do describe '#publish_to_database' do using RSpec::Parameterized::TableSyntax + let(:publish_to_database) { application_experiment.publish_to_database } + shared_examples 'does not record to the database' do it 'does not create an experiment record' do - expect { subject.publish_to_database }.not_to change(Experiment, :count) + expect { publish_to_database }.not_to change(Experiment, :count) end it 'does not create an experiment subject record' do - expect { subject.publish_to_database }.not_to change(ExperimentSubject, :count) + expect { publish_to_database }.not_to change(ExperimentSubject, :count) end end @@ -139,16 +133,16 @@ RSpec.describe ApplicationExperiment, :experiment do let(:context) { { context_key => context_value } } where(:context_key, :context_value, :object_type) do - :namespace | build(:namespace) | :namespace - :group | build(:namespace) | :namespace - :project | build(:project) | :project - :user | build(:user) | :user - :actor | build(:user) | :user + :namespace | build(:namespace, id: non_existing_record_id) | :namespace + :group | build(:namespace, id: non_existing_record_id) | :namespace + :project | build(:project, id: non_existing_record_id) | :project + :user | build(:user, id: non_existing_record_id) | :user + :actor | build(:user, id: non_existing_record_id) | :user end with_them do it 'creates an experiment and experiment subject record' do - expect { subject.publish_to_database }.to change(Experiment, :count).by(1) + expect { publish_to_database }.to change(Experiment, :count).by(1) expect(Experiment.last.name).to eq('namespaced/stub') expect(ExperimentSubject.last.send(object_type)).to eq(context[context_key]) @@ -156,6 +150,16 @@ RSpec.describe ApplicationExperiment, :experiment do end end + context "when experiment hasn't ran" do + let(:context) { { user: create(:user) } } + + it 'sets a variant on the experiment subject' do + publish_to_database + + expect(ExperimentSubject.last.variant).to eq('control') + end + end + context 'when there is not a usable subject' do let(:context) { { context_key => context_value } } @@ -183,15 +187,15 @@ RSpec.describe ApplicationExperiment, :experiment do end it "doesn't track if we shouldn't track" do - allow(subject).to receive(:should_track?).and_return(false) + allow(application_experiment).to receive(:should_track?).and_return(false) - subject.track(:action) + application_experiment.track(:action) expect_no_snowplow_event end it "tracks the event with the expected arguments and merged contexts" do - subject.track(:action, property: '_property_', context: [fake_context]) + application_experiment.track(:action, property: '_property_', context: [fake_context]) expect_snowplow_event( category: 'namespaced/stub', @@ -229,11 +233,80 @@ RSpec.describe ApplicationExperiment, :experiment do ] ) end + + context "when using known context resources" do + let(:user) { build(:user, id: non_existing_record_id) } + let(:project) { build(:project, id: non_existing_record_id) } + let(:namespace) { build(:namespace, id: non_existing_record_id) } + let(:group) { build(:group, id: non_existing_record_id) } + let(:actor) { user } + + let(:context) { { user: user, project: project, namespace: namespace } } + + it "includes those using the gitlab standard context" do + subject.track(:action) + + expect_snowplow_event( + category: 'namespaced/stub', + action: 'action', + user: user, + project: project, + namespace: namespace, + context: an_instance_of(Array) + ) + end + + it "falls back to using the group key" do + subject.context(namespace: nil, group: group) + + subject.track(:action) + + expect_snowplow_event( + category: 'namespaced/stub', + action: 'action', + user: user, + project: project, + namespace: group, + context: an_instance_of(Array) + ) + end + + context "with the actor key" do + it "provides it to the tracking call as the user" do + subject.context(user: nil, actor: actor) + + subject.track(:action) + + expect_snowplow_event( + category: 'namespaced/stub', + action: 'action', + user: actor, + project: project, + namespace: namespace, + context: an_instance_of(Array) + ) + end + + it "handles when it's not a user record" do + subject.context(user: nil, actor: nil) + + subject.track(:action) + + expect_snowplow_event( + category: 'namespaced/stub', + action: 'action', + project: project, + namespace: namespace, + context: an_instance_of(Array) + ) + end + end + end end describe "#key_for" do it "generates MD5 hashes" do - expect(subject.key_for(foo: :bar)).to eq('6f9ac12afdb9b58c2f19a136d09f9153') + expect(application_experiment.key_for(foo: :bar)).to eq('6f9ac12afdb9b58c2f19a136d09f9153') end end @@ -251,31 +324,33 @@ RSpec.describe ApplicationExperiment, :experiment do "https://badplace.com\nhttps://gitlab.com" | nil 'https://gitlabbcom' | nil 'https://gitlabbcom/' | nil + 'http://gdk.test/foo/bar' | 'http://gdk.test/foo/bar' + 'http://localhost:3000/foo/bar' | 'http://localhost:3000/foo/bar' end with_them do it "returns the url or nil if invalid" do allow(Gitlab).to receive(:dev_env_or_com?).and_return(true) - expect(subject.process_redirect_url(url)).to eq(processed_url) + expect(application_experiment.process_redirect_url(url)).to eq(processed_url) end it "considers all urls invalid when not on dev or com" do allow(Gitlab).to receive(:dev_env_or_com?).and_return(false) - expect(subject.process_redirect_url(url)).to be_nil + expect(application_experiment.process_redirect_url(url)).to be_nil end end it "generates the correct urls based on where the engine was mounted" do - url = Rails.application.routes.url_helpers.experiment_redirect_url(subject, url: 'https://docs.gitlab.com') - expect(url).to include("/-/experiment/namespaced%2Fstub:#{subject.context.key}?https://docs.gitlab.com") + url = Rails.application.routes.url_helpers.experiment_redirect_url(application_experiment, url: 'https://docs.gitlab.com') + expect(url).to include("/-/experiment/namespaced%2Fstub:#{application_experiment.context.key}?https://docs.gitlab.com") end end context "when resolving variants" do it "uses the default value as specified in the yaml" do - expect(Feature).to receive(:enabled?).with('namespaced_stub', subject, type: :experiment, default_enabled: :yaml) + expect(Feature).to receive(:enabled?).with('namespaced_stub', application_experiment, type: :experiment, default_enabled: :yaml) - expect(subject.variant.name).to eq('control') + expect(application_experiment.variant.name).to eq('control') end context "when rolled out to 100%" do @@ -284,32 +359,52 @@ RSpec.describe ApplicationExperiment, :experiment do end it "returns the first variant name" do - subject.try(:variant1) {} - subject.try(:variant2) {} + application_experiment.try(:variant1) {} + application_experiment.try(:variant2) {} - expect(subject.variant.name).to eq('variant1') + expect(application_experiment.variant.name).to eq('variant1') end end end + context "when nesting experiments" do + before do + stub_experiments(top: :control, nested: :control) + end + + it "doesn't raise an exception" do + expect { experiment(:top) { |e| e.control { experiment(:nested) { } } } }.not_to raise_error + end + + it "tracks an event", :snowplow do + experiment(:top) { |e| e.control { experiment(:nested) { } } } + + expect(Gitlab::Tracking).to have_received(:event).with( # rubocop:disable RSpec/ExpectGitlabTracking + 'top', + 'nested', + hash_including(label: 'nested') + ) + end + end + context "when caching" do let(:cache) { Gitlab::Experiment::Configuration.cache } before do allow(Gitlab::Experiment::Configuration).to receive(:cache).and_call_original - cache.clear(key: subject.name) + cache.clear(key: application_experiment.name) - subject.use { } # setup the control - subject.try { } # setup the candidate + application_experiment.use { } # setup the control + application_experiment.try { } # setup the candidate end it "caches the variant determined by the variant resolver" do - expect(subject.variant.name).to eq('candidate') # we should be in the experiment + expect(application_experiment.variant.name).to eq('candidate') # we should be in the experiment - subject.run + application_experiment.run - expect(subject.cache.read).to eq('candidate') + expect(application_experiment.cache.read).to eq('candidate') end it "doesn't cache a variant if we don't explicitly provide one" do @@ -320,11 +415,11 @@ RSpec.describe ApplicationExperiment, :experiment do # the control. stub_feature_flags(namespaced_stub: false) # simulate being not rolled out - expect(subject.variant.name).to eq('control') # if we ask, it should be control + expect(application_experiment.variant.name).to eq('control') # if we ask, it should be control - subject.run + application_experiment.run - expect(subject.cache.read).to be_nil + expect(application_experiment.cache.read).to be_nil end it "caches a control variant if we assign it specifically" do @@ -332,27 +427,27 @@ RSpec.describe ApplicationExperiment, :experiment do # that this context will always get the control variant unless we delete # the field from the cache (or clear the entire experiment cache) -- or # write code that would specify a different variant. - subject.run(:control) + application_experiment.run(:control) - expect(subject.cache.read).to eq('control') + expect(application_experiment.cache.read).to eq('control') end context "arbitrary attributes" do before do - subject.cache.store.clear(key: subject.name + '_attrs') + application_experiment.cache.store.clear(key: application_experiment.name + '_attrs') end it "sets and gets attributes about an experiment" do - subject.cache.attr_set(:foo, :bar) + application_experiment.cache.attr_set(:foo, :bar) - expect(subject.cache.attr_get(:foo)).to eq('bar') + expect(application_experiment.cache.attr_get(:foo)).to eq('bar') end it "increments a value for an experiment" do - expect(subject.cache.attr_get(:foo)).to be_nil + expect(application_experiment.cache.attr_get(:foo)).to be_nil - expect(subject.cache.attr_inc(:foo)).to eq(1) - expect(subject.cache.attr_inc(:foo)).to eq(2) + expect(application_experiment.cache.attr_inc(:foo)).to eq(1) + expect(application_experiment.cache.attr_inc(:foo)).to eq(2) end end end diff --git a/spec/experiments/new_project_sast_enabled_experiment_spec.rb b/spec/experiments/new_project_sast_enabled_experiment_spec.rb index dcf71bfffd7..38f58c01973 100644 --- a/spec/experiments/new_project_sast_enabled_experiment_spec.rb +++ b/spec/experiments/new_project_sast_enabled_experiment_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe NewProjectSastEnabledExperiment do it "defines the expected behaviors and variants" do - expect(subject.behaviors.keys).to match_array(%w[control candidate free_indicator]) + expect(subject.behaviors.keys).to match_array(%w[control candidate free_indicator unchecked_candidate]) end it "publishes to the database" do |