diff options
Diffstat (limited to 'spec/lib/feature_spec.rb')
-rw-r--r-- | spec/lib/feature_spec.rb | 226 |
1 files changed, 216 insertions, 10 deletions
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb index ad324406450..c087931d36a 100644 --- a/spec/lib/feature_spec.rb +++ b/spec/lib/feature_spec.rb @@ -162,6 +162,13 @@ RSpec.describe Feature, stub_feature_flags: false do stub_feature_flag_definition(:enabled_feature_flag, default_enabled: true) end + context 'when using redis cache', :use_clean_rails_redis_caching do + it 'does not make recursive feature-flag calls' do + expect(described_class).to receive(:enabled?).once.and_call_original + described_class.enabled?(:disabled_feature_flag) + end + end + context 'when self-recursive' do before do allow(Feature).to receive(:with_feature).and_wrap_original do |original, name, &block| @@ -318,6 +325,31 @@ RSpec.describe Feature, stub_feature_flags: false do end end + context 'with a group member' do + let(:key) { :awesome_feature } + let(:guinea_pigs) { create_list(:user, 3) } + + before do + described_class.reset + stub_feature_flag_definition(key) + Flipper.unregister_groups + Flipper.register(:guinea_pigs) do |actor| + guinea_pigs.include?(actor.thing) + end + described_class.enable(key, described_class.group(:guinea_pigs)) + end + + it 'is true for all group members' do + expect(described_class.enabled?(key, guinea_pigs[0])).to be_truthy + expect(described_class.enabled?(key, guinea_pigs[1])).to be_truthy + expect(described_class.enabled?(key, guinea_pigs[2])).to be_truthy + end + + it 'is false for any other actor' do + expect(described_class.enabled?(key, create(:user))).to be_falsey + end + end + context 'with an individual actor' do let(:actor) { stub_feature_flag_gate('CustomActor:5') } let(:another_actor) { stub_feature_flag_gate('CustomActor:10') } @@ -495,7 +527,7 @@ RSpec.describe Feature, stub_feature_flags: false do let(:expected_extra) {} it 'logs the event' do - expect(Feature.logger).to receive(:info).with(key: key, action: expected_action, **expected_extra) + expect(Feature.logger).to receive(:info).at_least(:once).with(key: key, action: expected_action, **expected_extra) subject end @@ -518,10 +550,10 @@ RSpec.describe Feature, stub_feature_flags: false do end context 'when thing is an actor' do - let(:thing) { create(:project) } + let(:thing) { create(:user) } it_behaves_like 'logging' do - let(:expected_action) { :enable } + let(:expected_action) { eq(:enable) | eq(:remove_opt_out) } let(:expected_extra) { { "extra.thing" => thing.flipper_id.to_s } } end end @@ -544,12 +576,160 @@ RSpec.describe Feature, stub_feature_flags: false do end context 'when thing is an actor' do - let(:thing) { create(:project) } + let(:thing) { create(:user) } + let(:flag_opts) { {} } it_behaves_like 'logging' do let(:expected_action) { :disable } let(:expected_extra) { { "extra.thing" => thing.flipper_id.to_s } } end + + before do + stub_feature_flag_definition(key, flag_opts) + end + + context 'when the feature flag was enabled for this actor' do + before do + described_class.enable(key, thing) + end + + it 'marks this thing as disabled' do + expect { subject }.to change { thing_enabled? }.from(true).to(false) + end + + it 'does not change the global value' do + expect { subject }.not_to change { described_class.enabled?(key) }.from(false) + end + + it 'is possible to re-enable the feature' do + subject + + expect { described_class.enable(key, thing) } + .to change { thing_enabled? }.from(false).to(true) + end + end + + context 'when the feature flag is enabled globally' do + before do + described_class.enable(key) + end + + it 'does not mark this thing as disabled' do + expect { subject }.not_to change { thing_enabled? }.from(true) + end + + it 'does not change the global value' do + expect { subject }.not_to change { described_class.enabled?(key) }.from(true) + end + end + end + end + + describe 'opt_out' do + subject { described_class.opt_out(key, thing) } + + let(:key) { :awesome_feature } + + before do + stub_feature_flag_definition(key) + described_class.enable(key) + end + + context 'when thing is an actor' do + let_it_be(:thing) { create(:project) } + + it 'marks this thing as disabled' do + expect { subject }.to change { thing_enabled? }.from(true).to(false) + end + + it 'does not change the global value' do + expect { subject }.not_to change { described_class.enabled?(key) }.from(true) + end + + it_behaves_like 'logging' do + let(:expected_action) { eq(:opt_out) } + let(:expected_extra) { { "extra.thing" => thing.flipper_id.to_s } } + end + + it 'stores the opt-out information as a gate' do + subject + + flag = described_class.get(key) + + expect(flag.actors_value).to include(include(thing.flipper_id)) + expect(flag.actors_value).not_to include(thing.flipper_id) + end + end + + context 'when thing is a group' do + let(:thing) { Feature.group(:guinea_pigs) } + let(:guinea_pigs) { create_list(:user, 3) } + + before do + Feature.reset + Flipper.unregister_groups + Flipper.register(:guinea_pigs) do |actor| + guinea_pigs.include?(actor.thing) + end + end + + it 'has no effect' do + expect { subject }.not_to change { described_class.enabled?(key, guinea_pigs.first) }.from(true) + end + end + end + + describe 'remove_opt_out' do + subject { described_class.remove_opt_out(key, thing) } + + let(:key) { :awesome_feature } + + before do + stub_feature_flag_definition(key) + described_class.enable(key) + described_class.opt_out(key, thing) + end + + context 'when thing is an actor' do + let_it_be(:thing) { create(:project) } + + it 're-enables this thing' do + expect { subject }.to change { thing_enabled? }.from(false).to(true) + end + + it 'does not change the global value' do + expect { subject }.not_to change { described_class.enabled?(key) }.from(true) + end + + it_behaves_like 'logging' do + let(:expected_action) { eq(:remove_opt_out) } + let(:expected_extra) { { "extra.thing" => thing.flipper_id.to_s } } + end + + it 'removes the opt-out information' do + subject + + flag = described_class.get(key) + + expect(flag.actors_value).to be_empty + end + end + + context 'when thing is a group' do + let(:thing) { Feature.group(:guinea_pigs) } + let(:guinea_pigs) { create_list(:user, 3) } + + before do + Feature.reset + Flipper.unregister_groups + Flipper.register(:guinea_pigs) do |actor| + guinea_pigs.include?(actor.thing) + end + end + + it 'has no effect' do + expect { subject }.not_to change { described_class.enabled?(key, guinea_pigs.first) }.from(true) + end end end @@ -563,6 +743,16 @@ RSpec.describe Feature, stub_feature_flags: false do let(:expected_action) { :enable_percentage_of_time } let(:expected_extra) { { "extra.percentage" => percentage.to_s } } end + + context 'when the flag is on' do + before do + described_class.enable(key) + end + + it 'fails with InvalidOperation' do + expect { subject }.to raise_error(described_class::InvalidOperation) + end + end end describe '.disable_percentage_of_time' do @@ -586,6 +776,16 @@ RSpec.describe Feature, stub_feature_flags: false do let(:expected_action) { :enable_percentage_of_actors } let(:expected_extra) { { "extra.percentage" => percentage.to_s } } end + + context 'when the flag is on' do + before do + described_class.enable(key) + end + + it 'fails with InvalidOperation' do + expect { subject }.to raise_error(described_class::InvalidOperation) + end + end end describe '.disable_percentage_of_actors' do @@ -603,6 +803,7 @@ RSpec.describe Feature, stub_feature_flags: false do subject { described_class.remove(key) } let(:key) { :awesome_feature } + let(:actor) { create(:user) } before do described_class.enable(key) @@ -617,13 +818,10 @@ RSpec.describe Feature, stub_feature_flags: false do it 'returns nil' do expect(described_class.remove(:non_persisted_feature_flag)).to be_nil end - end - context 'for a persisted feature' do - it 'returns true' do - described_class.enable(:persisted_feature_flag) - - expect(described_class.remove(:persisted_feature_flag)).to be_truthy + it 'returns true, and cleans up' do + expect(subject).to be_truthy + expect(described_class.persisted_names).not_to include(key) end end end @@ -712,6 +910,10 @@ RSpec.describe Feature, stub_feature_flags: false do end end + before do + stub_feature_flag_definition(:enabled_feature_flag) + end + it 'gives the correct value when enabling for an additional actor' do described_class.enable(:enabled_feature_flag, actor) initial_gate_values = active_record_adapter.get(described_class.get(:enabled_feature_flag)) @@ -834,4 +1036,8 @@ RSpec.describe Feature, stub_feature_flags: false do end end end + + def thing_enabled? + described_class.enabled?(key, thing) + end end |