diff options
Diffstat (limited to 'spec/requests')
-rw-r--r-- | spec/requests/api/admin/instance_clusters_spec.rb | 13 | ||||
-rw-r--r-- | spec/requests/api/ci/runner/jobs_artifacts_spec.rb | 2 | ||||
-rw-r--r-- | spec/requests/api/features_spec.rb | 166 | ||||
-rw-r--r-- | spec/requests/api/go_proxy_spec.rb | 4 | ||||
-rw-r--r-- | spec/requests/api/graphql/mutations/award_emojis/add_spec.rb | 4 | ||||
-rw-r--r-- | spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb | 2 | ||||
-rw-r--r-- | spec/requests/api/graphql/mutations/releases/update_spec.rb | 255 | ||||
-rw-r--r-- | spec/requests/api/group_clusters_spec.rb | 12 | ||||
-rw-r--r-- | spec/requests/api/project_clusters_spec.rb | 12 | ||||
-rw-r--r-- | spec/requests/api/usage_data_spec.rb | 81 | ||||
-rw-r--r-- | spec/requests/api/users_spec.rb | 92 | ||||
-rw-r--r-- | spec/requests/lfs_http_spec.rb | 31 | ||||
-rw-r--r-- | spec/requests/rack_attack_global_spec.rb | 33 |
13 files changed, 635 insertions, 72 deletions
diff --git a/spec/requests/api/admin/instance_clusters_spec.rb b/spec/requests/api/admin/instance_clusters_spec.rb index 1052080aad4..ab3b6b718e1 100644 --- a/spec/requests/api/admin/instance_clusters_spec.rb +++ b/spec/requests/api/admin/instance_clusters_spec.rb @@ -90,6 +90,8 @@ RSpec.describe ::API::Admin::InstanceClusters do expect(json_response['environment_scope']).to eq('*') expect(json_response['cluster_type']).to eq('instance_type') expect(json_response['domain']).to eq('example.com') + expect(json_response['enabled']).to be_truthy + expect(json_response['managed']).to be_truthy end it 'returns kubernetes platform information' do @@ -163,6 +165,7 @@ RSpec.describe ::API::Admin::InstanceClusters do name: 'test-instance-cluster', domain: 'domain.example.com', managed: false, + enabled: false, namespace_per_environment: false, platform_kubernetes_attributes: platform_kubernetes_attributes, clusterable: clusterable @@ -205,9 +208,9 @@ RSpec.describe ::API::Admin::InstanceClusters do expect(cluster_result.name).to eq('test-instance-cluster') expect(cluster_result.domain).to eq('domain.example.com') expect(cluster_result.environment_scope).to eq('*') - expect(cluster_result.enabled).to eq(true) - expect(platform_kubernetes.authorization_type).to eq('rbac') expect(cluster_result.managed).to be_falsy + expect(cluster_result.enabled).to be_falsy + expect(platform_kubernetes.authorization_type).to eq('rbac') expect(cluster_result.namespace_per_environment).to eq(false) expect(platform_kubernetes.api_url).to eq("https://example.com") expect(platform_kubernetes.token).to eq('sample-token') @@ -301,6 +304,8 @@ RSpec.describe ::API::Admin::InstanceClusters do let(:update_params) do { domain: domain, + managed: false, + enabled: false, platform_kubernetes_attributes: platform_kubernetes_attributes } end @@ -326,6 +331,8 @@ RSpec.describe ::API::Admin::InstanceClusters do it 'updates cluster attributes' do expect(cluster.domain).to eq('new-domain.com') + expect(cluster.managed).to be_falsy + expect(cluster.enabled).to be_falsy end end @@ -338,6 +345,8 @@ RSpec.describe ::API::Admin::InstanceClusters do it 'does not update cluster attributes' do expect(cluster.domain).to eq('old-domain.com') + expect(cluster.managed).to be_truthy + expect(cluster.enabled).to be_truthy end it 'returns validation errors' do diff --git a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb index 71be0c30f5a..4d8da50f8f0 100644 --- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb +++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb @@ -242,7 +242,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do } expect { authorize_artifacts_with_token_in_headers(artifact_type: :lsif) } - .to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(tracking_params) } + .to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(**tracking_params) } .by(1) end end diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb index 3f443b4f92b..acc49768545 100644 --- a/spec/requests/api/features_spec.rb +++ b/spec/requests/api/features_spec.rb @@ -6,6 +6,18 @@ RSpec.describe API::Features, stub_feature_flags: false do let_it_be(:user) { create(:user) } let_it_be(:admin) { create(:admin) } + # Find any `development` feature flag name + let(:known_feature_flag) do + Feature::Definition.definitions + .values.find(&:development?) + end + + let(:known_feature_flag_definition_hash) do + a_hash_including( + 'type' => 'development' + ) + end + before do Feature.reset Flipper.unregister_groups @@ -22,12 +34,14 @@ RSpec.describe API::Features, stub_feature_flags: false do { 'name' => 'feature_1', 'state' => 'on', - 'gates' => [{ 'key' => 'boolean', 'value' => true }] + 'gates' => [{ 'key' => 'boolean', 'value' => true }], + 'definition' => nil }, { 'name' => 'feature_2', 'state' => 'off', - 'gates' => [{ 'key' => 'boolean', 'value' => false }] + 'gates' => [{ 'key' => 'boolean', 'value' => false }], + 'definition' => nil }, { 'name' => 'feature_3', @@ -35,7 +49,14 @@ RSpec.describe API::Features, stub_feature_flags: false do 'gates' => [ { 'key' => 'boolean', 'value' => false }, { 'key' => 'groups', 'value' => ['perf_team'] } - ] + ], + 'definition' => nil + }, + { + 'name' => known_feature_flag.name, + 'state' => 'on', + 'gates' => [{ 'key' => 'boolean', 'value' => true }], + 'definition' => known_feature_flag_definition_hash } ] end @@ -44,6 +65,7 @@ RSpec.describe API::Features, stub_feature_flags: false do Feature.enable('feature_1') Feature.disable('feature_2') Feature.enable('feature_3', Feature.group(:perf_team)) + Feature.enable(known_feature_flag.name) end it 'returns a 401 for anonymous users' do @@ -67,7 +89,7 @@ RSpec.describe API::Features, stub_feature_flags: false do end describe 'POST /feature' do - let(:feature_name) { 'my_feature' } + let(:feature_name) { known_feature_flag.name } context 'when the feature does not exist' do it 'returns a 401 for anonymous users' do @@ -87,43 +109,49 @@ RSpec.describe API::Features, stub_feature_flags: false do post api("/features/#{feature_name}", admin), params: { value: 'true' } expect(response).to have_gitlab_http_status(:created) - expect(json_response).to eq( - 'name' => 'my_feature', + expect(json_response).to match( + 'name' => feature_name, 'state' => 'on', - 'gates' => [{ 'key' => 'boolean', 'value' => true }]) + 'gates' => [{ 'key' => 'boolean', 'value' => true }], + 'definition' => known_feature_flag_definition_hash + ) 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' } expect(response).to have_gitlab_http_status(:created) - expect(json_response).to eq( - 'name' => 'my_feature', + 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 eq( - 'name' => 'my_feature', + 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 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('my_feature') + 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 }, @@ -141,13 +169,15 @@ RSpec.describe API::Features, stub_feature_flags: false do post api("/features/#{feature_name}", admin), params: { value: 'true', project: project.full_path } expect(response).to have_gitlab_http_status(:created) - expect(json_response).to eq( - 'name' => 'my_feature', + expect(json_response).to match( + 'name' => feature_name, 'state' => 'conditional', 'gates' => [ { 'key' => 'boolean', 'value' => false }, { 'key' => 'actors', 'value' => ["Project:#{project.id}"] } - ]) + ], + 'definition' => known_feature_flag_definition_hash + ) end end @@ -156,12 +186,13 @@ RSpec.describe API::Features, stub_feature_flags: false do post api("/features/#{feature_name}", admin), params: { value: 'true', project: 'mep/to/the/mep/mep' } expect(response).to have_gitlab_http_status(:created) - expect(json_response).to eq( - "name" => "my_feature", + expect(json_response).to match( + "name" => feature_name, "state" => "off", "gates" => [ { "key" => "boolean", "value" => false } - ] + ], + 'definition' => known_feature_flag_definition_hash ) end end @@ -175,13 +206,15 @@ RSpec.describe API::Features, stub_feature_flags: false do post api("/features/#{feature_name}", admin), params: { value: 'true', group: group.full_path } expect(response).to have_gitlab_http_status(:created) - expect(json_response).to eq( - 'name' => 'my_feature', + expect(json_response).to match( + 'name' => feature_name, 'state' => 'conditional', 'gates' => [ { 'key' => 'boolean', 'value' => false }, { 'key' => 'actors', 'value' => ["Group:#{group.id}"] } - ]) + ], + 'definition' => known_feature_flag_definition_hash + ) end end @@ -190,12 +223,13 @@ RSpec.describe API::Features, stub_feature_flags: false do post api("/features/#{feature_name}", admin), params: { value: 'true', group: 'not/a/group' } expect(response).to have_gitlab_http_status(:created) - expect(json_response).to eq( - "name" => "my_feature", + expect(json_response).to match( + "name" => feature_name, "state" => "off", "gates" => [ { "key" => "boolean", "value" => false } - ] + ], + 'definition' => known_feature_flag_definition_hash ) end end @@ -205,26 +239,30 @@ RSpec.describe API::Features, stub_feature_flags: false do post api("/features/#{feature_name}", admin), params: { value: '50' } expect(response).to have_gitlab_http_status(:created) - expect(json_response).to eq( - 'name' => 'my_feature', + 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 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 eq( - 'name' => 'my_feature', + 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 end @@ -238,36 +276,42 @@ RSpec.describe API::Features, stub_feature_flags: false do post api("/features/#{feature_name}", admin), params: { value: 'true' } expect(response).to have_gitlab_http_status(:created) - expect(json_response).to eq( - 'name' => 'my_feature', + expect(json_response).to match( + 'name' => feature_name, 'state' => 'on', - 'gates' => [{ 'key' => 'boolean', 'value' => true }]) + '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 eq( - 'name' => 'my_feature', + 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 eq( - 'name' => 'my_feature', + 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 @@ -279,10 +323,12 @@ RSpec.describe API::Features, stub_feature_flags: false do post api("/features/#{feature_name}", admin), params: { value: 'false' } expect(response).to have_gitlab_http_status(:created) - expect(json_response).to eq( - 'name' => 'my_feature', + expect(json_response).to match( + 'name' => feature_name, 'state' => 'off', - 'gates' => [{ 'key' => 'boolean', 'value' => false }]) + '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 @@ -292,10 +338,12 @@ RSpec.describe API::Features, stub_feature_flags: false do 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 eq( - 'name' => 'my_feature', + expect(json_response).to match( + 'name' => feature_name, 'state' => 'off', - 'gates' => [{ 'key' => 'boolean', 'value' => false }]) + '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 @@ -305,10 +353,12 @@ RSpec.describe API::Features, stub_feature_flags: false do post api("/features/#{feature_name}", admin), params: { value: 'false', user: user.username } expect(response).to have_gitlab_http_status(:created) - expect(json_response).to eq( - 'name' => 'my_feature', + expect(json_response).to match( + 'name' => feature_name, 'state' => 'off', - 'gates' => [{ 'key' => 'boolean', 'value' => false }]) + 'gates' => [{ 'key' => 'boolean', 'value' => false }], + 'definition' => known_feature_flag_definition_hash + ) end end @@ -321,13 +371,15 @@ RSpec.describe API::Features, stub_feature_flags: false do post api("/features/#{feature_name}", admin), params: { value: '30' } expect(response).to have_gitlab_http_status(:created) - expect(json_response).to eq( - 'name' => 'my_feature', + 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 @@ -340,13 +392,15 @@ RSpec.describe API::Features, stub_feature_flags: false 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 eq( - 'name' => 'my_feature', + 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 diff --git a/spec/requests/api/go_proxy_spec.rb b/spec/requests/api/go_proxy_spec.rb index 9d422ebbce2..d45e24241b2 100644 --- a/spec/requests/api/go_proxy_spec.rb +++ b/spec/requests/api/go_proxy_spec.rb @@ -428,7 +428,7 @@ RSpec.describe API::GoProxy do context 'with a non-existent project' do def get_resource(user = nil, **params) - get api("/projects/not%2fa%2fproject/packages/go/#{base}/@v/list", user, params) + get api("/projects/not%2fa%2fproject/packages/go/#{base}/@v/list", user, **params) end describe 'GET /projects/:id/packages/go/*module_name/@v/list' do @@ -465,7 +465,7 @@ RSpec.describe API::GoProxy do end def get_resource(user = nil, headers: {}, **params) - get api("/projects/#{project.id}/packages/go/#{module_name}/@v/#{resource}", user, params), headers: headers + get api("/projects/#{project.id}/packages/go/#{module_name}/@v/#{resource}", user, **params), headers: headers end def fmt_pseudo_version(prefix, commit) diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb index 3aaebb5095a..b39062f2e71 100644 --- a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb +++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb @@ -56,10 +56,10 @@ RSpec.describe 'Adding an AwardEmoji' do it_behaves_like 'a mutation that does not create an AwardEmoji' it_behaves_like 'a mutation that returns top-level errors', - errors: ['Cannot award emoji to this resource'] + errors: ['You cannot award emoji to this resource.'] end - context 'when the given awardable an Awardable' do + context 'when the given awardable is an Awardable' do it 'creates an emoji' do expect do post_graphql_mutation(mutation, current_user: current_user) diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb index 6910ad80a11..170e7ff3b44 100644 --- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb +++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb @@ -55,7 +55,7 @@ RSpec.describe 'Toggling an AwardEmoji' do it_behaves_like 'a mutation that does not create or destroy an AwardEmoji' it_behaves_like 'a mutation that returns top-level errors', - errors: ['Cannot award emoji to this resource'] + errors: ['You cannot award emoji to this resource.'] end context 'when the given awardable is an Awardable' do diff --git a/spec/requests/api/graphql/mutations/releases/update_spec.rb b/spec/requests/api/graphql/mutations/releases/update_spec.rb new file mode 100644 index 00000000000..19320c3393c --- /dev/null +++ b/spec/requests/api/graphql/mutations/releases/update_spec.rb @@ -0,0 +1,255 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Updating an existing release' do + include GraphqlHelpers + include Presentable + + let_it_be(:public_user) { create(:user) } + let_it_be(:guest) { create(:user) } + let_it_be(:reporter) { create(:user) } + let_it_be(:developer) { create(:user) } + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:milestone_12_3) { create(:milestone, project: project, title: '12.3') } + let_it_be(:milestone_12_4) { create(:milestone, project: project, title: '12.4') } + + let_it_be(:tag_name) { 'v1.1.0' } + let_it_be(:name) { 'Version 7.12.5'} + let_it_be(:description) { 'Release 7.12.5 :rocket:' } + let_it_be(:released_at) { '2018-12-10' } + let_it_be(:created_at) { '2018-11-05' } + let_it_be(:milestones) { [milestone_12_3, milestone_12_4] } + + let_it_be(:release) do + create(:release, project: project, tag: tag_name, name: name, + description: description, released_at: Time.parse(released_at).utc, + created_at: Time.parse(created_at).utc, milestones: milestones) + end + + let(:mutation_name) { :release_update } + + let(:mutation_arguments) do + { + projectPath: project.full_path, + tagName: tag_name + } + end + + let(:mutation) do + graphql_mutation(mutation_name, mutation_arguments, <<~FIELDS) + release { + tagName + name + description + releasedAt + createdAt + milestones { + nodes { + title + } + } + } + errors + FIELDS + end + + let(:update_release) { post_graphql_mutation(mutation, current_user: current_user) } + let(:mutation_response) { graphql_mutation_response(mutation_name)&.with_indifferent_access } + + let(:expected_attributes) do + { + tagName: tag_name, + name: name, + description: description, + releasedAt: Time.parse(released_at).utc.iso8601, + createdAt: Time.parse(created_at).utc.iso8601, + milestones: { + nodes: milestones.map { |m| { title: m.title } } + } + }.with_indifferent_access + end + + around do |example| + freeze_time { example.run } + end + + before do + project.add_guest(guest) + project.add_reporter(reporter) + project.add_developer(developer) + + stub_default_url_options(host: 'www.example.com') + end + + shared_examples 'no errors' do + it 'returns no errors' do + update_release + + expect(graphql_errors).not_to be_present + end + end + + shared_examples 'top-level error with message' do |error_message| + it 'returns a top-level error with message' do + update_release + + expect(mutation_response).to be_nil + expect(graphql_errors.count).to eq(1) + expect(graphql_errors.first['message']).to eq(error_message) + end + end + + shared_examples 'errors-as-data with message' do |error_message| + it 'returns an error-as-data with message' do + update_release + + expect(mutation_response[:release]).to be_nil + expect(mutation_response[:errors].count).to eq(1) + expect(mutation_response[:errors].first).to match(error_message) + end + end + + shared_examples 'updates release fields' do |updates| + it_behaves_like 'no errors' + + it 'updates the correct field and returns the release' do + update_release + + expect(mutation_response[:release]).to include(expected_attributes.merge(updates).except(:milestones)) + + # Right now the milestones are returned in a non-deterministic order. + # Because of this, we need to test milestones separately to allow + # for them to be returned in any order. + # Once https://gitlab.com/gitlab-org/gitlab/-/issues/259012 has been + # fixed, this special milestone handling can be removed. + expected_milestones = expected_attributes.merge(updates)[:milestones] + expect(mutation_response[:release][:milestones][:nodes]).to match_array(expected_milestones[:nodes]) + end + end + + context 'when the current user has access to update releases' do + let(:current_user) { developer } + + context 'name' do + context 'when a new name is provided' do + let(:mutation_arguments) { super().merge(name: 'Updated name') } + + it_behaves_like 'updates release fields', name: 'Updated name' + end + + context 'when null is provided' do + let(:mutation_arguments) { super().merge(name: nil) } + + it_behaves_like 'updates release fields', name: 'v1.1.0' + end + end + + context 'description' do + context 'when a new description is provided' do + let(:mutation_arguments) { super().merge(description: 'Updated description') } + + it_behaves_like 'updates release fields', description: 'Updated description' + end + + context 'when null is provided' do + let(:mutation_arguments) { super().merge(description: nil) } + + it_behaves_like 'updates release fields', description: nil + end + end + + context 'releasedAt' do + context 'when no time zone is provided' do + let(:mutation_arguments) { super().merge(releasedAt: '2015-05-05') } + + it_behaves_like 'updates release fields', releasedAt: Time.parse('2015-05-05').utc.iso8601 + end + + context 'when a local time zone is provided' do + let(:mutation_arguments) { super().merge(releasedAt: Time.parse('2015-05-05').in_time_zone('Hawaii').iso8601) } + + it_behaves_like 'updates release fields', releasedAt: Time.parse('2015-05-05').utc.iso8601 + end + + context 'when null is provided' do + let(:mutation_arguments) { super().merge(releasedAt: nil) } + + it_behaves_like 'top-level error with message', 'if the releasedAt argument is provided, it cannot be null' + end + end + + context 'milestones' do + context 'when a new set of milestones is provided provided' do + let(:mutation_arguments) { super().merge(milestones: ['12.3']) } + + it_behaves_like 'updates release fields', milestones: { nodes: [{ title: '12.3' }] } + end + + context 'when an empty array is provided' do + let(:mutation_arguments) { super().merge(milestones: []) } + + it_behaves_like 'updates release fields', milestones: { nodes: [] } + end + + context 'when null is provided' do + let(:mutation_arguments) { super().merge(milestones: nil) } + + it_behaves_like 'top-level error with message', 'if the milestones argument is provided, it cannot be null' + end + + context 'when a non-existent milestone title is provided' do + let(:mutation_arguments) { super().merge(milestones: ['not real']) } + + it_behaves_like 'errors-as-data with message', 'Milestone(s) not found: not real' + end + + context 'when a milestone title from a different project is provided' do + let(:milestone_in_different_project) { create(:milestone, title: 'milestone in different project') } + let(:mutation_arguments) { super().merge(milestones: [milestone_in_different_project.title]) } + + it_behaves_like 'errors-as-data with message', 'Milestone(s) not found: milestone in different project' + end + end + + context 'validation' do + context 'when no updated fields are provided' do + it_behaves_like 'errors-as-data with message', 'params is empty' + end + + context 'when the tag does not exist' do + let(:mutation_arguments) { super().merge(tagName: 'not-a-real-tag') } + + it_behaves_like 'errors-as-data with message', 'Tag does not exist' + end + + context 'when the project does not exist' do + let(:mutation_arguments) { super().merge(projectPath: 'not/a/real/path') } + + it_behaves_like 'top-level error with message', "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + end + end + end + + context "when the current user doesn't have access to update releases" do + expected_error_message = "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + + context 'when the current user is a Reporter' do + let(:current_user) { reporter } + + it_behaves_like 'top-level error with message', expected_error_message + end + + context 'when the current user is a Guest' do + let(:current_user) { guest } + + it_behaves_like 'top-level error with message', expected_error_message + end + + context 'when the current user is a public user' do + let(:current_user) { public_user } + + it_behaves_like 'top-level error with message', expected_error_message + end + end +end diff --git a/spec/requests/api/group_clusters_spec.rb b/spec/requests/api/group_clusters_spec.rb index eb21ae9468c..f65f9384efa 100644 --- a/spec/requests/api/group_clusters_spec.rb +++ b/spec/requests/api/group_clusters_spec.rb @@ -89,6 +89,8 @@ RSpec.describe API::GroupClusters do expect(json_response['environment_scope']).to eq('*') expect(json_response['cluster_type']).to eq('group_type') expect(json_response['domain']).to eq('example.com') + expect(json_response['enabled']).to be_truthy + expect(json_response['managed']).to be_truthy end it 'returns group information' do @@ -172,6 +174,7 @@ RSpec.describe API::GroupClusters do name: 'test-cluster', domain: 'domain.example.com', managed: false, + enabled: false, namespace_per_environment: false, platform_kubernetes_attributes: platform_kubernetes_attributes, management_project_id: management_project_id @@ -206,6 +209,7 @@ RSpec.describe API::GroupClusters do expect(cluster_result.name).to eq('test-cluster') expect(cluster_result.domain).to eq('domain.example.com') expect(cluster_result.managed).to be_falsy + expect(cluster_result.enabled).to be_falsy expect(cluster_result.management_project_id).to eq management_project_id expect(cluster_result.namespace_per_environment).to eq(false) expect(platform_kubernetes.rbac?).to be_truthy @@ -342,7 +346,9 @@ RSpec.describe API::GroupClusters do { domain: domain, platform_kubernetes_attributes: platform_kubernetes_attributes, - management_project_id: management_project_id + management_project_id: management_project_id, + managed: false, + enabled: false } end @@ -381,6 +387,8 @@ RSpec.describe API::GroupClusters do it 'updates cluster attributes' do expect(cluster.domain).to eq('new-domain.com') expect(cluster.management_project).to eq(management_project) + expect(cluster.managed).to be_falsy + expect(cluster.enabled).to be_falsy end end @@ -394,6 +402,8 @@ RSpec.describe API::GroupClusters do it 'does not update cluster attributes' do expect(cluster.domain).to eq('old-domain.com') expect(cluster.management_project).to be_nil + expect(cluster.managed).to be_truthy + expect(cluster.enabled).to be_truthy end it 'returns validation errors' do diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index 7b37862af74..f784f677c25 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -88,6 +88,8 @@ RSpec.describe API::ProjectClusters do expect(json_response['environment_scope']).to eq('*') expect(json_response['cluster_type']).to eq('project_type') expect(json_response['domain']).to eq('example.com') + expect(json_response['enabled']).to be_truthy + expect(json_response['managed']).to be_truthy end it 'returns project information' do @@ -171,6 +173,7 @@ RSpec.describe API::ProjectClusters do name: 'test-cluster', domain: 'domain.example.com', managed: false, + enabled: false, namespace_per_environment: false, platform_kubernetes_attributes: platform_kubernetes_attributes, management_project_id: management_project_id @@ -202,6 +205,7 @@ RSpec.describe API::ProjectClusters do expect(cluster_result.name).to eq('test-cluster') expect(cluster_result.domain).to eq('domain.example.com') expect(cluster_result.managed).to be_falsy + expect(cluster_result.enabled).to be_falsy expect(cluster_result.management_project_id).to eq management_project_id expect(cluster_result.namespace_per_environment).to eq(false) expect(platform_kubernetes.rbac?).to be_truthy @@ -337,7 +341,9 @@ RSpec.describe API::ProjectClusters do { domain: 'new-domain.com', platform_kubernetes_attributes: platform_kubernetes_attributes, - management_project_id: management_project_id + management_project_id: management_project_id, + managed: false, + enabled: false } end @@ -373,6 +379,8 @@ RSpec.describe API::ProjectClusters do it 'updates cluster attributes' do expect(response).to have_gitlab_http_status(:ok) expect(cluster.domain).to eq('new-domain.com') + expect(cluster.managed).to be_falsy + expect(cluster.enabled).to be_falsy expect(cluster.platform_kubernetes.namespace).to eq('new-namespace') expect(cluster.management_project).to eq(management_project) end @@ -384,6 +392,8 @@ RSpec.describe API::ProjectClusters do it 'does not update cluster attributes' do expect(response).to have_gitlab_http_status(:bad_request) expect(cluster.domain).not_to eq('new_domain.com') + expect(cluster.managed).to be_truthy + expect(cluster.enabled).to be_truthy expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace') expect(cluster.management_project).not_to eq(management_project) end diff --git a/spec/requests/api/usage_data_spec.rb b/spec/requests/api/usage_data_spec.rb index 4f4f386e9db..d44f179eed8 100644 --- a/spec/requests/api/usage_data_spec.rb +++ b/spec/requests/api/usage_data_spec.rb @@ -5,6 +5,87 @@ require 'spec_helper' RSpec.describe API::UsageData do let_it_be(:user) { create(:user) } + describe 'POST /usage_data/increment_counter' do + let(:endpoint) { '/usage_data/increment_counter' } + let(:known_event) { "#{known_event_prefix}_#{known_event_postfix}" } + let(:known_event_prefix) { "static_site_editor" } + let(:known_event_postfix) { 'commits' } + let(:unknown_event) { 'unknown' } + + context 'without CSRF token' do + it 'returns forbidden' do + stub_feature_flags(usage_data_api: true) + allow(Gitlab::RequestForgeryProtection).to receive(:verified?).and_return(false) + + post api(endpoint, user), params: { event: known_event } + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'usage_data_api feature not enabled' do + it 'returns not_found' do + stub_feature_flags(usage_data_api: false) + + post api(endpoint, user), params: { event: known_event } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'without authentication' do + it 'returns 401 response' do + post api(endpoint), params: { event: known_event } + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + + context 'with authentication' do + before do + stub_feature_flags(usage_data_api: true) + stub_feature_flags("usage_data_#{known_event}" => true) + stub_application_setting(usage_ping_enabled: true) + allow(Gitlab::RequestForgeryProtection).to receive(:verified?).and_return(true) + end + + context 'when event is missing from params' do + it 'returns bad request' do + post api(endpoint, user), params: {} + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + %w[merge_requests commits].each do |postfix| + context 'with correct params' do + let(:known_event_postfix) { postfix } + + it 'returns status ok' do + expect(Gitlab::UsageDataCounters::BaseCounter).to receive(:count).with(known_event_postfix) + post api(endpoint, user), params: { event: known_event } + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + context 'with unknown event' do + before do + skip_feature_flags_yaml_validation + end + + it 'returns status ok' do + expect(Gitlab::UsageDataCounters::BaseCounter).not_to receive(:count) + + post api(endpoint, user), params: { event: unknown_event } + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end + describe 'POST /usage_data/increment_unique_users' do let(:endpoint) { '/usage_data/increment_unique_users' } let(:known_event) { 'g_compliance_dashboard' } diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 98840d6238a..381e0b03589 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -2510,6 +2510,98 @@ RSpec.describe API::Users, :do_not_mock_admin_mode do end end + context 'approve pending user' do + shared_examples '404' do + it 'returns 404' do + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('404 User Not Found') + end + end + + describe 'POST /users/:id/approve' do + subject(:approve) { post api("/users/#{user_id}/approve", api_user) } + + let_it_be(:pending_user) { create(:user, :blocked_pending_approval) } + let_it_be(:deactivated_user) { create(:user, :deactivated) } + let_it_be(:blocked_user) { create(:user, :blocked) } + + context 'performed by a non-admin user' do + let(:api_user) { user } + let(:user_id) { pending_user.id } + + it 'is not authorized to perform the action' do + expect { approve }.not_to change { pending_user.reload.state } + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to eq('You are not allowed to approve a user') + end + end + + context 'performed by an admin user' do + let(:api_user) { admin } + + context 'for a deactivated user' do + let(:user_id) { deactivated_user.id } + + it 'does not approve a deactivated user' do + expect { approve }.not_to change { deactivated_user.reload.state } + expect(response).to have_gitlab_http_status(:conflict) + expect(json_response['message']).to eq('The user you are trying to approve is not pending an approval') + end + end + + context 'for an pending approval user' do + let(:user_id) { pending_user.id } + + it 'returns 201' do + expect { approve }.to change { pending_user.reload.state }.to('active') + expect(response).to have_gitlab_http_status(:created) + expect(json_response['message']).to eq('Success') + end + end + + context 'for an active user' do + let(:user_id) { user.id } + + it 'returns 201' do + expect { approve }.not_to change { user.reload.state } + expect(response).to have_gitlab_http_status(:conflict) + expect(json_response['message']).to eq('The user you are trying to approve is not pending an approval') + end + end + + context 'for a blocked user' do + let(:user_id) { blocked_user.id } + + it 'returns 403' do + expect { approve }.not_to change { blocked_user.reload.state } + expect(response).to have_gitlab_http_status(:conflict) + expect(json_response['message']).to eq('The user you are trying to approve is not pending an approval') + end + end + + context 'for a ldap blocked user' do + let(:user_id) { ldap_blocked_user.id } + + it 'returns 403' do + expect { approve }.not_to change { ldap_blocked_user.reload.state } + expect(response).to have_gitlab_http_status(:conflict) + expect(json_response['message']).to eq('The user you are trying to approve is not pending an approval') + end + end + + context 'for a user that does not exist' do + let(:user_id) { non_existing_record_id } + + before do + approve + end + + it_behaves_like '404' + end + end + end + end + describe 'POST /users/:id/block' do let(:blocked_user) { create(:user, state: 'blocked') } diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 48d125a37c3..36bad1a66cb 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -219,10 +219,12 @@ RSpec.describe 'Git LFS API and storage' do describe 'when handling LFS batch request' do let(:update_lfs_permissions) { } let(:update_user_permissions) { } + let(:lfs_chunked_encoding) { true } before do update_lfs_permissions update_user_permissions + stub_feature_flags(lfs_chunked_encoding: lfs_chunked_encoding) post_lfs_json batch_url(project), body, headers end @@ -524,7 +526,24 @@ RSpec.describe 'Git LFS API and storage' do expect(json_response['objects']).to be_kind_of(Array) expect(json_response['objects'].first).to include(sample_object) expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size)) - expect(json_response['objects'].first['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream') + + headers = json_response['objects'].first['actions']['upload']['header'] + expect(headers['Content-Type']).to eq('application/octet-stream') + expect(headers['Transfer-Encoding']).to eq('chunked') + end + + context 'when lfs_chunked_encoding feature is disabled' do + let(:lfs_chunked_encoding) { false } + + it 'responds with upload hypermedia link' do + expect(json_response['objects']).to be_kind_of(Array) + expect(json_response['objects'].first).to include(sample_object) + expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size)) + + headers = json_response['objects'].first['actions']['upload']['header'] + expect(headers['Content-Type']).to eq('application/octet-stream') + expect(headers['Transfer-Encoding']).to be_nil + end end it_behaves_like 'process authorization header', renew_authorization: renew_authorization @@ -548,7 +567,10 @@ RSpec.describe 'Git LFS API and storage' do expect(lfs_object.projects.pluck(:id)).not_to include(project.id) expect(lfs_object.projects.pluck(:id)).to include(other_project.id) expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size)) - expect(json_response['objects'].first['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream') + + headers = json_response['objects'].first['actions']['upload']['header'] + expect(headers['Content-Type']).to eq('application/octet-stream') + expect(headers['Transfer-Encoding']).to eq('chunked') end it_behaves_like 'process authorization header', renew_authorization: true @@ -589,7 +611,10 @@ RSpec.describe 'Git LFS API and storage' do expect(json_response['objects'].last).to include(non_existing_object) expect(json_response['objects'].last['actions']['upload']['href']).to eq(objects_url(project, non_existing_object_oid, non_existing_object_size)) - expect(json_response['objects'].last['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream') + + headers = json_response['objects'].last['actions']['upload']['header'] + expect(headers['Content-Type']).to eq('application/octet-stream') + expect(headers['Transfer-Encoding']).to eq('chunked') end it_behaves_like 'process authorization header', renew_authorization: true diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index 805ac5a9118..c2e68df2c40 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -106,7 +106,7 @@ RSpec.describe 'Rack Attack global throttles' do let(:request_jobs_url) { '/api/v4/jobs/request' } let(:runner) { create(:ci_runner) } - it 'does not cont as unauthenticated' do + it 'does not count as unauthenticated' do (1 + requests_per_period).times do post request_jobs_url, params: { token: runner.token } expect(response).to have_gitlab_http_status(:no_content) @@ -114,6 +114,17 @@ RSpec.describe 'Rack Attack global throttles' do end end + context 'when the request is to a health endpoint' do + let(:health_endpoint) { '/-/metrics' } + + it 'does not throttle the requests' do + (1 + requests_per_period).times do + get health_endpoint + expect(response).to have_gitlab_http_status(:ok) + end + end + end + it 'logs RackAttack info into structured logs' do requests_per_period.times do get url_that_does_not_require_authentication @@ -133,6 +144,14 @@ RSpec.describe 'Rack Attack global throttles' do get url_that_does_not_require_authentication end + + it_behaves_like 'tracking when dry-run mode is set' do + let(:throttle_name) { 'throttle_unauthenticated' } + + def do_request + get url_that_does_not_require_authentication + end + end end context 'when the throttle is disabled' do @@ -231,6 +250,10 @@ RSpec.describe 'Rack Attack global throttles' do let(:post_params) { { user: { login: 'username', password: 'password' } } } + def do_request + post protected_path_that_does_not_require_authentication, params: post_params + end + before do settings_to_set[:throttle_protected_paths_requests_per_period] = requests_per_period # 1 settings_to_set[:throttle_protected_paths_period_in_seconds] = period_in_seconds # 10_000 @@ -244,7 +267,7 @@ RSpec.describe 'Rack Attack global throttles' do it 'allows requests over the rate limit' do (1 + requests_per_period).times do - post protected_path_that_does_not_require_authentication, params: post_params + do_request expect(response).to have_gitlab_http_status(:ok) end end @@ -258,12 +281,16 @@ RSpec.describe 'Rack Attack global throttles' do it 'rejects requests over the rate limit' do requests_per_period.times do - post protected_path_that_does_not_require_authentication, params: post_params + do_request expect(response).to have_gitlab_http_status(:ok) end expect_rejection { post protected_path_that_does_not_require_authentication, params: post_params } end + + it_behaves_like 'tracking when dry-run mode is set' do + let(:throttle_name) { 'throttle_unauthenticated_protected_paths' } + end end end |