diff options
Diffstat (limited to 'spec/requests/api/features_spec.rb')
-rw-r--r-- | spec/requests/api/features_spec.rb | 772 |
1 files changed, 395 insertions, 377 deletions
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb index b54be4f5258..d0334cf6dd2 100644 --- a/spec/requests/api/features_spec.rb +++ b/spec/requests/api/features_spec.rb @@ -92,402 +92,292 @@ RSpec.describe API::Features, stub_feature_flags: false do describe 'POST /feature' do let(:feature_name) { known_feature_flag.name } - context 'when the feature does not exist' do - it 'returns a 401 for anonymous users' do - post api("/features/#{feature_name}") + # TODO: remove this shared examples block when set_feature_flag_service feature flag + # is removed. Then remove also any duplicate specs covered by the service class. + shared_examples 'sets the feature flag status' do + context 'when the feature does not exist' do + it 'returns a 401 for anonymous users' do + post api("/features/#{feature_name}") - expect(response).to have_gitlab_http_status(:unauthorized) - end + expect(response).to have_gitlab_http_status(:unauthorized) + end - it 'returns a 403 for users' do - post api("/features/#{feature_name}", user) + it 'returns a 403 for users' do + post api("/features/#{feature_name}", user) - expect(response).to have_gitlab_http_status(:forbidden) - end + expect(response).to have_gitlab_http_status(:forbidden) + end - context 'when passed value=true' do - it 'creates an enabled feature' do - post api("/features/#{feature_name}", admin), params: { value: 'true' } + context 'when passed value=true' do + it 'creates an enabled feature' do + post api("/features/#{feature_name}", admin), params: { value: 'true' } - expect(response).to have_gitlab_http_status(:created) - expect(json_response).to match( - 'name' => feature_name, - 'state' => 'on', - 'gates' => [{ 'key' => 'boolean', 'value' => true }], - 'definition' => known_feature_flag_definition_hash - ) - end + expect(response).to have_gitlab_http_status(:created) + expect(json_response).to match( + 'name' => feature_name, + 'state' => 'on', + 'gates' => [{ 'key' => 'boolean', 'value' => true }], + 'definition' => known_feature_flag_definition_hash + ) + end - it 'logs the event' do - expect(Feature.logger).to receive(:info).once + it 'logs the event' do + expect(Feature.logger).to receive(:info).once - post api("/features/#{feature_name}", admin), params: { value: 'true' } - end + post api("/features/#{feature_name}", admin), params: { value: 'true' } + end - it 'creates an enabled feature for the given Flipper group when passed feature_group=perf_team' do - post api("/features/#{feature_name}", admin), params: { value: 'true', feature_group: 'perf_team' } + it 'creates an enabled feature for the given Flipper group when passed feature_group=perf_team' do + post api("/features/#{feature_name}", admin), params: { value: 'true', feature_group: 'perf_team' } + + expect(response).to have_gitlab_http_status(:created) + expect(json_response).to match( + 'name' => feature_name, + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'groups', 'value' => ['perf_team'] } + ], + 'definition' => known_feature_flag_definition_hash + ) + end - expect(response).to have_gitlab_http_status(:created) - expect(json_response).to match( - 'name' => feature_name, - 'state' => 'conditional', - 'gates' => [ - { 'key' => 'boolean', 'value' => false }, - { 'key' => 'groups', 'value' => ['perf_team'] } - ], - 'definition' => known_feature_flag_definition_hash - ) - end + it 'creates an enabled feature for the given user when passed user=username' do + post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username } + + expect(response).to have_gitlab_http_status(:created) + expect(json_response).to match( + 'name' => feature_name, + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'actors', 'value' => ["User:#{user.id}"] } + ], + 'definition' => known_feature_flag_definition_hash + ) + end - it 'creates an enabled feature for the given user when passed user=username' do - post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username } + it 'creates an enabled feature for the given user and feature group when passed user=username and feature_group=perf_team' do + post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username, feature_group: 'perf_team' } - expect(response).to have_gitlab_http_status(:created) - expect(json_response).to match( - 'name' => feature_name, - 'state' => 'conditional', - 'gates' => [ + expect(response).to have_gitlab_http_status(:created) + expect(json_response['name']).to eq(feature_name) + expect(json_response['state']).to eq('conditional') + expect(json_response['gates']).to contain_exactly( { 'key' => 'boolean', 'value' => false }, + { 'key' => 'groups', 'value' => ['perf_team'] }, { 'key' => 'actors', 'value' => ["User:#{user.id}"] } - ], - 'definition' => known_feature_flag_definition_hash - ) - end - - it 'creates an enabled feature for the given user and feature group when passed user=username and feature_group=perf_team' do - post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username, feature_group: 'perf_team' } - - expect(response).to have_gitlab_http_status(:created) - expect(json_response['name']).to eq(feature_name) - expect(json_response['state']).to eq('conditional') - expect(json_response['gates']).to contain_exactly( - { 'key' => 'boolean', 'value' => false }, - { 'key' => 'groups', 'value' => ['perf_team'] }, - { 'key' => 'actors', 'value' => ["User:#{user.id}"] } - ) + ) + end end - end - shared_examples 'does not enable the flag' do |actor_type| - let(:actor_path) { raise NotImplementedError } - let(:expected_inexistent_path) { actor_path } + shared_examples 'does not enable the flag' do |actor_type| + let(:actor_path) { raise NotImplementedError } + let(:expected_inexistent_path) { actor_path } - it 'returns the current state of the flag without changes' do - post api("/features/#{feature_name}", admin), params: { value: 'true', actor_type => actor_path } + it 'returns the current state of the flag without changes' do + post api("/features/#{feature_name}", admin), params: { value: 'true', actor_type => actor_path } - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to eq("400 Bad request - #{expected_inexistent_path} is not found!") + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq("400 Bad request - #{expected_inexistent_path} is not found!") + end end - end - shared_examples 'enables the flag for the actor' do |actor_type| - it 'sets the feature gate' do - post api("/features/#{feature_name}", admin), params: { value: 'true', actor_type => actor.full_path } - - expect(response).to have_gitlab_http_status(:created) - expect(json_response).to match( - 'name' => feature_name, - 'state' => 'conditional', - 'gates' => [ - { 'key' => 'boolean', 'value' => false }, - { 'key' => 'actors', 'value' => ["#{actor.class}:#{actor.id}"] } - ], - 'definition' => known_feature_flag_definition_hash - ) + shared_examples 'enables the flag for the actor' do |actor_type| + it 'sets the feature gate' do + post api("/features/#{feature_name}", admin), params: { value: 'true', actor_type => actor.full_path } + + expect(response).to have_gitlab_http_status(:created) + expect(json_response).to match( + 'name' => feature_name, + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'actors', 'value' => ["#{actor.class}:#{actor.id}"] } + ], + 'definition' => known_feature_flag_definition_hash + ) + end end - end - shared_examples 'creates an enabled feature for the specified entries' do - it do - post api("/features/#{feature_name}", admin), params: { value: 'true', **gate_params } + shared_examples 'creates an enabled feature for the specified entries' do + it do + post api("/features/#{feature_name}", admin), params: { value: 'true', **gate_params } - expect(response).to have_gitlab_http_status(:created) - expect(json_response['name']).to eq(feature_name) - expect(json_response['gates']).to contain_exactly( - { 'key' => 'boolean', 'value' => false }, - { 'key' => 'actors', 'value' => array_including(expected_gate_params) } - ) + expect(response).to have_gitlab_http_status(:created) + expect(json_response['name']).to eq(feature_name) + expect(json_response['gates']).to contain_exactly( + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'actors', 'value' => array_including(expected_gate_params) } + ) + end end - end - context 'when enabling for a project by path' do - context 'when the project exists' do - it_behaves_like 'enables the flag for the actor', :project do - let(:actor) { create(:project) } + context 'when enabling for a project by path' do + context 'when the project exists' do + it_behaves_like 'enables the flag for the actor', :project do + let(:actor) { create(:project) } + end end - end - context 'when the project does not exist' do - it_behaves_like 'does not enable the flag', :project do - let(:actor_path) { 'mep/to/the/mep/mep' } + context 'when the project does not exist' do + it_behaves_like 'does not enable the flag', :project do + let(:actor_path) { 'mep/to/the/mep/mep' } + end end end - end - context 'when enabling for a group by path' do - context 'when the group exists' do - it_behaves_like 'enables the flag for the actor', :group do - let(:actor) { create(:group) } + context 'when enabling for a group by path' do + context 'when the group exists' do + it_behaves_like 'enables the flag for the actor', :group do + let(:actor) { create(:group) } + end end - end - context 'when the group does not exist' do - it_behaves_like 'does not enable the flag', :group do - let(:actor_path) { 'not/a/group' } + context 'when the group does not exist' do + it_behaves_like 'does not enable the flag', :group do + let(:actor_path) { 'not/a/group' } + end end end - end - context 'when enabling for a namespace by path' do - context 'when the user namespace exists' do - it_behaves_like 'enables the flag for the actor', :namespace do - let(:actor) { create(:namespace) } + context 'when enabling for a namespace by path' do + context 'when the user namespace exists' do + it_behaves_like 'enables the flag for the actor', :namespace do + let(:actor) { create(:namespace) } + end end - end - context 'when the group namespace exists' do - it_behaves_like 'enables the flag for the actor', :namespace do - let(:actor) { create(:group) } + context 'when the group namespace exists' do + it_behaves_like 'enables the flag for the actor', :namespace do + let(:actor) { create(:group) } + end end - end - context 'when the user namespace does not exist' do - it_behaves_like 'does not enable the flag', :namespace do - let(:actor_path) { 'not/a/group' } + context 'when the user namespace does not exist' do + it_behaves_like 'does not enable the flag', :namespace do + let(:actor_path) { 'not/a/group' } + end end - end - context 'when a project namespace exists' do - let(:project_namespace) { create(:project_namespace) } + context 'when a project namespace exists' do + let(:project_namespace) { create(:project_namespace) } - it_behaves_like 'does not enable the flag', :namespace do - let(:actor_path) { project_namespace.full_path } + it_behaves_like 'does not enable the flag', :namespace do + let(:actor_path) { project_namespace.full_path } + end end end - end - context 'with multiple users' do - let_it_be(:users) { create_list(:user, 3) } + context 'with multiple users' do + let_it_be(:users) { create_list(:user, 3) } - it_behaves_like 'creates an enabled feature for the specified entries' do - let(:gate_params) { { user: users.map(&:username).join(',') } } - let(:expected_gate_params) { users.map(&:flipper_id) } - end - - context 'when empty value exists between comma' do it_behaves_like 'creates an enabled feature for the specified entries' do - let(:gate_params) { { user: "#{users.first.username},,,," } } - let(:expected_gate_params) { users.first.flipper_id } + let(:gate_params) { { user: users.map(&:username).join(',') } } + let(:expected_gate_params) { users.map(&:flipper_id) } end - end - context 'when one of the users does not exist' do - it_behaves_like 'does not enable the flag', :user do - let(:actor_path) { "#{users.first.username},inexistent-entry" } - let(:expected_inexistent_path) { "inexistent-entry" } + context 'when empty value exists between comma' do + it_behaves_like 'creates an enabled feature for the specified entries' do + let(:gate_params) { { user: "#{users.first.username},,,," } } + let(:expected_gate_params) { users.first.flipper_id } + end end - end - end - context 'with multiple projects' do - let_it_be(:projects) { create_list(:project, 3) } - - it_behaves_like 'creates an enabled feature for the specified entries' do - let(:gate_params) { { project: projects.map(&:full_path).join(',') } } - let(:expected_gate_params) { projects.map(&:flipper_id) } - end - - context 'when empty value exists between comma' do - it_behaves_like 'creates an enabled feature for the specified entries' do - let(:gate_params) { { project: "#{projects.first.full_path},,,," } } - let(:expected_gate_params) { projects.first.flipper_id } + context 'when one of the users does not exist' do + it_behaves_like 'does not enable the flag', :user do + let(:actor_path) { "#{users.first.username},inexistent-entry" } + let(:expected_inexistent_path) { "inexistent-entry" } + end end end - context 'when one of the projects does not exist' do - it_behaves_like 'does not enable the flag', :project do - let(:actor_path) { "#{projects.first.full_path},inexistent-entry" } - let(:expected_inexistent_path) { "inexistent-entry" } - end - end - end - - context 'with multiple groups' do - let_it_be(:groups) { create_list(:group, 3) } + context 'with multiple projects' do + let_it_be(:projects) { create_list(:project, 3) } - it_behaves_like 'creates an enabled feature for the specified entries' do - let(:gate_params) { { group: groups.map(&:full_path).join(',') } } - let(:expected_gate_params) { groups.map(&:flipper_id) } - end - - context 'when empty value exists between comma' do it_behaves_like 'creates an enabled feature for the specified entries' do - let(:gate_params) { { group: "#{groups.first.full_path},,,," } } - let(:expected_gate_params) { groups.first.flipper_id } + let(:gate_params) { { project: projects.map(&:full_path).join(',') } } + let(:expected_gate_params) { projects.map(&:flipper_id) } end - end - context 'when one of the groups does not exist' do - it_behaves_like 'does not enable the flag', :group do - let(:actor_path) { "#{groups.first.full_path},inexistent-entry" } - let(:expected_inexistent_path) { "inexistent-entry" } + context 'when empty value exists between comma' do + it_behaves_like 'creates an enabled feature for the specified entries' do + let(:gate_params) { { project: "#{projects.first.full_path},,,," } } + let(:expected_gate_params) { projects.first.flipper_id } + end end - end - end - - context 'with multiple namespaces' do - let_it_be(:namespaces) { create_list(:namespace, 3) } - - it_behaves_like 'creates an enabled feature for the specified entries' do - let(:gate_params) { { namespace: namespaces.map(&:full_path).join(',') } } - let(:expected_gate_params) { namespaces.map(&:flipper_id) } - end - context 'when empty value exists between comma' do - it_behaves_like 'creates an enabled feature for the specified entries' do - let(:gate_params) { { namespace: "#{namespaces.first.full_path},,,," } } - let(:expected_gate_params) { namespaces.first.flipper_id } + context 'when one of the projects does not exist' do + it_behaves_like 'does not enable the flag', :project do + let(:actor_path) { "#{projects.first.full_path},inexistent-entry" } + let(:expected_inexistent_path) { "inexistent-entry" } + end end end - context 'when one of the namespaces does not exist' do - it_behaves_like 'does not enable the flag', :namespace do - let(:actor_path) { "#{namespaces.first.full_path},inexistent-entry" } - let(:expected_inexistent_path) { "inexistent-entry" } - end - end - end - - it 'creates a feature with the given percentage of time if passed an integer' do - post api("/features/#{feature_name}", admin), params: { value: '50' } - - expect(response).to have_gitlab_http_status(:created) - expect(json_response).to match( - 'name' => feature_name, - 'state' => 'conditional', - 'gates' => [ - { 'key' => 'boolean', 'value' => false }, - { 'key' => 'percentage_of_time', 'value' => 50 } - ], - 'definition' => known_feature_flag_definition_hash - ) - end - - it 'creates a feature with the given percentage of time if passed a float' do - post api("/features/#{feature_name}", admin), params: { value: '0.01' } - - expect(response).to have_gitlab_http_status(:created) - expect(json_response).to match( - 'name' => feature_name, - 'state' => 'conditional', - 'gates' => [ - { 'key' => 'boolean', 'value' => false }, - { 'key' => 'percentage_of_time', 'value' => 0.01 } - ], - 'definition' => known_feature_flag_definition_hash - ) - end - - it 'creates a feature with the given percentage of actors if passed an integer' do - post api("/features/#{feature_name}", admin), params: { value: '50', key: 'percentage_of_actors' } - - expect(response).to have_gitlab_http_status(:created) - expect(json_response).to match( - 'name' => feature_name, - 'state' => 'conditional', - 'gates' => [ - { 'key' => 'boolean', 'value' => false }, - { 'key' => 'percentage_of_actors', 'value' => 50 } - ], - 'definition' => known_feature_flag_definition_hash - ) - end - - it 'creates a feature with the given percentage of actors if passed a float' do - post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors' } - - expect(response).to have_gitlab_http_status(:created) - expect(json_response).to match( - 'name' => feature_name, - 'state' => 'conditional', - 'gates' => [ - { 'key' => 'boolean', 'value' => false }, - { 'key' => 'percentage_of_actors', 'value' => 0.01 } - ], - 'definition' => known_feature_flag_definition_hash - ) - end + context 'with multiple groups' do + let_it_be(:groups) { create_list(:group, 3) } - describe 'mutually exclusive parameters' do - shared_examples 'fails to set the feature flag' do - it 'returns an error' do - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['error']).to match(/key, \w+ are mutually exclusive/) + it_behaves_like 'creates an enabled feature for the specified entries' do + let(:gate_params) { { group: groups.map(&:full_path).join(',') } } + let(:expected_gate_params) { groups.map(&:flipper_id) } end - end - context 'when key and feature_group are provided' do - before do - post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', feature_group: 'some-value' } + context 'when empty value exists between comma' do + it_behaves_like 'creates an enabled feature for the specified entries' do + let(:gate_params) { { group: "#{groups.first.full_path},,,," } } + let(:expected_gate_params) { groups.first.flipper_id } + end end - it_behaves_like 'fails to set the feature flag' - end - - context 'when key and user are provided' do - before do - post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', user: 'some-user' } + context 'when one of the groups does not exist' do + it_behaves_like 'does not enable the flag', :group do + let(:actor_path) { "#{groups.first.full_path},inexistent-entry" } + let(:expected_inexistent_path) { "inexistent-entry" } + end end - - it_behaves_like 'fails to set the feature flag' end - context 'when key and group are provided' do - before do - post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', group: 'somepath' } - end - - it_behaves_like 'fails to set the feature flag' - end + context 'with multiple namespaces' do + let_it_be(:namespaces) { create_list(:namespace, 3) } - context 'when key and namespace are provided' do - before do - post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', namespace: 'somepath' } + it_behaves_like 'creates an enabled feature for the specified entries' do + let(:gate_params) { { namespace: namespaces.map(&:full_path).join(',') } } + let(:expected_gate_params) { namespaces.map(&:flipper_id) } end - it_behaves_like 'fails to set the feature flag' - end - - context 'when key and project are provided' do - before do - post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', project: 'somepath' } + context 'when empty value exists between comma' do + it_behaves_like 'creates an enabled feature for the specified entries' do + let(:gate_params) { { namespace: "#{namespaces.first.full_path},,,," } } + let(:expected_gate_params) { namespaces.first.flipper_id } + end end - it_behaves_like 'fails to set the feature flag' + context 'when one of the namespaces does not exist' do + it_behaves_like 'does not enable the flag', :namespace do + let(:actor_path) { "#{namespaces.first.full_path},inexistent-entry" } + let(:expected_inexistent_path) { "inexistent-entry" } + end + end end - end - end - context 'when the feature exists' do - before do - Feature.disable(feature_name) # This also persists the feature on the DB - end - - context 'when passed value=true' do - it 'enables the feature' do - post api("/features/#{feature_name}", admin), params: { value: 'true' } + it 'creates a feature with the given percentage of time if passed an integer' do + post api("/features/#{feature_name}", admin), params: { value: '50' } expect(response).to have_gitlab_http_status(:created) expect(json_response).to match( 'name' => feature_name, - 'state' => 'on', - 'gates' => [{ 'key' => 'boolean', 'value' => true }], + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'percentage_of_time', 'value' => 50 } + ], 'definition' => known_feature_flag_definition_hash ) end - it 'enables the feature for the given Flipper group when passed feature_group=perf_team' do - post api("/features/#{feature_name}", admin), params: { value: 'true', feature_group: 'perf_team' } + it 'creates a feature with the given percentage of time if passed a float' do + post api("/features/#{feature_name}", admin), params: { value: '0.01' } expect(response).to have_gitlab_http_status(:created) expect(json_response).to match( @@ -495,14 +385,14 @@ RSpec.describe API::Features, stub_feature_flags: false do 'state' => 'conditional', 'gates' => [ { 'key' => 'boolean', 'value' => false }, - { 'key' => 'groups', 'value' => ['perf_team'] } + { 'key' => 'percentage_of_time', 'value' => 0.01 } ], 'definition' => known_feature_flag_definition_hash ) end - it 'enables the feature for the given user when passed user=username' do - post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username } + it 'creates a feature with the given percentage of actors if passed an integer' do + post api("/features/#{feature_name}", admin), params: { value: '50', key: 'percentage_of_actors' } expect(response).to have_gitlab_http_status(:created) expect(json_response).to match( @@ -510,102 +400,230 @@ RSpec.describe API::Features, stub_feature_flags: false do 'state' => 'conditional', 'gates' => [ { 'key' => 'boolean', 'value' => false }, - { 'key' => 'actors', 'value' => ["User:#{user.id}"] } + { 'key' => 'percentage_of_actors', 'value' => 50 } ], 'definition' => known_feature_flag_definition_hash ) end - end - - context 'when feature is enabled and value=false is passed' do - it 'disables the feature' do - Feature.enable(feature_name) - expect(Feature.enabled?(feature_name)).to eq(true) - post api("/features/#{feature_name}", admin), params: { value: 'false' } + it 'creates a feature with the given percentage of actors if passed a float' do + post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors' } expect(response).to have_gitlab_http_status(:created) expect(json_response).to match( 'name' => feature_name, - 'state' => 'off', - 'gates' => [{ 'key' => 'boolean', 'value' => false }], + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'percentage_of_actors', 'value' => 0.01 } + ], 'definition' => known_feature_flag_definition_hash ) end - it 'disables the feature for the given Flipper group when passed feature_group=perf_team' do - Feature.enable(feature_name, Feature.group(:perf_team)) - expect(Feature.enabled?(feature_name, admin)).to be_truthy + describe 'mutually exclusive parameters' do + shared_examples 'fails to set the feature flag' do + it 'returns an error' do + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to match(/key, \w+ are mutually exclusive/) + end + end - post api("/features/#{feature_name}", admin), params: { value: 'false', feature_group: 'perf_team' } + context 'when key and feature_group are provided' do + before do + post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', feature_group: 'some-value' } + end - expect(response).to have_gitlab_http_status(:created) - expect(json_response).to match( - 'name' => feature_name, - 'state' => 'off', - 'gates' => [{ 'key' => 'boolean', 'value' => false }], - 'definition' => known_feature_flag_definition_hash - ) - end + it_behaves_like 'fails to set the feature flag' + end - it 'disables the feature for the given user when passed user=username' do - Feature.enable(feature_name, user) - expect(Feature.enabled?(feature_name, user)).to be_truthy + context 'when key and user are provided' do + before do + post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', user: 'some-user' } + end - post api("/features/#{feature_name}", admin), params: { value: 'false', user: user.username } + it_behaves_like 'fails to set the feature flag' + end - expect(response).to have_gitlab_http_status(:created) - expect(json_response).to match( - 'name' => feature_name, - 'state' => 'off', - 'gates' => [{ 'key' => 'boolean', 'value' => false }], - 'definition' => known_feature_flag_definition_hash - ) + context 'when key and group are provided' do + before do + post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', group: 'somepath' } + end + + it_behaves_like 'fails to set the feature flag' + end + + context 'when key and namespace are provided' do + before do + post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', namespace: 'somepath' } + end + + it_behaves_like 'fails to set the feature flag' + end + + context 'when key and project are provided' do + before do + post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', project: 'somepath' } + end + + it_behaves_like 'fails to set the feature flag' + end end end - context 'with a pre-existing percentage of time value' do + context 'when the feature exists' do before do - Feature.enable_percentage_of_time(feature_name, 50) + Feature.disable(feature_name) # This also persists the feature on the DB end - it 'updates the percentage of time if passed an integer' do - post api("/features/#{feature_name}", admin), params: { value: '30' } + context 'when passed value=true' do + it 'enables the feature' do + post api("/features/#{feature_name}", admin), params: { value: 'true' } - expect(response).to have_gitlab_http_status(:created) - expect(json_response).to match( - 'name' => feature_name, - 'state' => 'conditional', - 'gates' => [ - { 'key' => 'boolean', 'value' => false }, - { 'key' => 'percentage_of_time', 'value' => 30 } - ], - 'definition' => known_feature_flag_definition_hash - ) + expect(response).to have_gitlab_http_status(:created) + expect(json_response).to match( + 'name' => feature_name, + 'state' => 'on', + 'gates' => [{ 'key' => 'boolean', 'value' => true }], + 'definition' => known_feature_flag_definition_hash + ) + end + + it 'enables the feature for the given Flipper group when passed feature_group=perf_team' do + post api("/features/#{feature_name}", admin), params: { value: 'true', feature_group: 'perf_team' } + + expect(response).to have_gitlab_http_status(:created) + expect(json_response).to match( + 'name' => feature_name, + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'groups', 'value' => ['perf_team'] } + ], + 'definition' => known_feature_flag_definition_hash + ) + end + + it 'enables the feature for the given user when passed user=username' do + post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username } + + expect(response).to have_gitlab_http_status(:created) + expect(json_response).to match( + 'name' => feature_name, + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'actors', 'value' => ["User:#{user.id}"] } + ], + 'definition' => known_feature_flag_definition_hash + ) + end end - end - context 'with a pre-existing percentage of actors value' do - before do - Feature.enable_percentage_of_actors(feature_name, 42) + context 'when feature is enabled and value=false is passed' do + it 'disables the feature' do + Feature.enable(feature_name) + expect(Feature.enabled?(feature_name)).to eq(true) + + post api("/features/#{feature_name}", admin), params: { value: 'false' } + + expect(response).to have_gitlab_http_status(:created) + expect(json_response).to match( + 'name' => feature_name, + 'state' => 'off', + 'gates' => [{ 'key' => 'boolean', 'value' => false }], + 'definition' => known_feature_flag_definition_hash + ) + end + + it 'disables the feature for the given Flipper group when passed feature_group=perf_team' do + Feature.enable(feature_name, Feature.group(:perf_team)) + expect(Feature.enabled?(feature_name, admin)).to be_truthy + + post api("/features/#{feature_name}", admin), params: { value: 'false', feature_group: 'perf_team' } + + expect(response).to have_gitlab_http_status(:created) + expect(json_response).to match( + 'name' => feature_name, + 'state' => 'off', + 'gates' => [{ 'key' => 'boolean', 'value' => false }], + 'definition' => known_feature_flag_definition_hash + ) + end + + it 'disables the feature for the given user when passed user=username' do + Feature.enable(feature_name, user) + expect(Feature.enabled?(feature_name, user)).to be_truthy + + post api("/features/#{feature_name}", admin), params: { value: 'false', user: user.username } + + expect(response).to have_gitlab_http_status(:created) + expect(json_response).to match( + 'name' => feature_name, + 'state' => 'off', + 'gates' => [{ 'key' => 'boolean', 'value' => false }], + 'definition' => known_feature_flag_definition_hash + ) + end end - it 'updates the percentage of actors if passed an integer' do - post api("/features/#{feature_name}", admin), params: { value: '74', key: 'percentage_of_actors' } + context 'with a pre-existing percentage of time value' do + before do + Feature.enable_percentage_of_time(feature_name, 50) + end - expect(response).to have_gitlab_http_status(:created) - expect(json_response).to match( - 'name' => feature_name, - 'state' => 'conditional', - 'gates' => [ - { 'key' => 'boolean', 'value' => false }, - { 'key' => 'percentage_of_actors', 'value' => 74 } - ], - 'definition' => known_feature_flag_definition_hash - ) + it 'updates the percentage of time if passed an integer' do + post api("/features/#{feature_name}", admin), params: { value: '30' } + + expect(response).to have_gitlab_http_status(:created) + expect(json_response).to match( + 'name' => feature_name, + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'percentage_of_time', 'value' => 30 } + ], + 'definition' => known_feature_flag_definition_hash + ) + end + end + + context 'with a pre-existing percentage of actors value' do + before do + Feature.enable_percentage_of_actors(feature_name, 42) + end + + it 'updates the percentage of actors if passed an integer' do + post api("/features/#{feature_name}", admin), params: { value: '74', key: 'percentage_of_actors' } + + expect(response).to have_gitlab_http_status(:created) + expect(json_response).to match( + 'name' => feature_name, + 'state' => 'conditional', + 'gates' => [ + { 'key' => 'boolean', 'value' => false }, + { 'key' => 'percentage_of_actors', 'value' => 74 } + ], + 'definition' => known_feature_flag_definition_hash + ) + end end end end + + before do + stub_feature_flags(set_feature_flag_service: true) + end + + it_behaves_like 'sets the feature flag status' + + context 'when feature flag set_feature_flag_service is disabled' do + before do + stub_feature_flags(set_feature_flag_service: false) + end + + it_behaves_like 'sets the feature flag status' + end end describe 'DELETE /feature/:name' do |