diff options
Diffstat (limited to 'spec/requests/api')
55 files changed, 1462 insertions, 247 deletions
diff --git a/spec/requests/api/bulk_imports_spec.rb b/spec/requests/api/bulk_imports_spec.rb index f0edfa6f227..1a28687c830 100644 --- a/spec/requests/api/bulk_imports_spec.rb +++ b/spec/requests/api/bulk_imports_spec.rb @@ -20,6 +20,48 @@ RSpec.describe API::BulkImports do end end + describe 'POST /bulk_imports' do + it 'starts a new migration' do + post api('/bulk_imports', user), params: { + configuration: { + url: 'http://gitlab.example', + access_token: 'access_token' + }, + entities: [ + source_type: 'group_entity', + source_full_path: 'full_path', + destination_name: 'destination_name', + destination_namespace: 'destination_namespace' + ] + } + + expect(response).to have_gitlab_http_status(:created) + + expect(json_response['status']).to eq('created') + end + + context 'when provided url is blocked' do + it 'returns blocked url error' do + post api('/bulk_imports', user), params: { + configuration: { + url: 'url', + access_token: 'access_token' + }, + entities: [ + source_type: 'group_entity', + source_full_path: 'full_path', + destination_name: 'destination_name', + destination_namespace: 'destination_namespace' + ] + } + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + + expect(json_response['message']).to eq('Validation failed: Url is blocked: Only allowed schemes are http, https') + end + end + end + describe 'GET /bulk_imports/entities' do it 'returns a list of all import entities authored by the user' do get api('/bulk_imports/entities', user) diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/ci/jobs_spec.rb index cff006bed94..b6ab9310471 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/ci/jobs_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Jobs do +RSpec.describe API::Ci::Jobs do include HttpBasicAuthHelpers include DependencyProxyHelpers @@ -114,7 +114,7 @@ RSpec.describe API::Jobs do context 'with job token authentication header' do include_context 'with auth headers' do - let(:header) { { API::Helpers::Runner::JOB_TOKEN_HEADER => running_job.token } } + let(:header) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => running_job.token } } end it_behaves_like 'returns common job data' do @@ -150,7 +150,7 @@ RSpec.describe API::Jobs do context 'with non running job' do include_context 'with auth headers' do - let(:header) { { API::Helpers::Runner::JOB_TOKEN_HEADER => job.token } } + let(:header) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token } } end it_behaves_like 'returns unauthorized' @@ -523,15 +523,13 @@ RSpec.describe API::Jobs do context 'when artifacts are stored remotely' do let(:proxy_download) { false } + let(:job) { create(:ci_build, pipeline: pipeline) } + let(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) } before do stub_artifacts_object_storage(proxy_download: proxy_download) - end - - let(:job) { create(:ci_build, pipeline: pipeline) } - let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) } - before do + artifact job.reload get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user) @@ -708,11 +706,7 @@ RSpec.describe API::Jobs do context 'with branch name containing slash' do before do pipeline.reload - pipeline.update!(ref: 'improve/awesome', - sha: project.commit('improve/awesome').sha) - end - - before do + pipeline.update!(ref: 'improve/awesome', sha: project.commit('improve/awesome').sha) get_for_ref('improve/awesome') end diff --git a/spec/requests/api/ci/pipelines_spec.rb b/spec/requests/api/ci/pipelines_spec.rb index eb6c0861844..640e1ee6422 100644 --- a/spec/requests/api/ci/pipelines_spec.rb +++ b/spec/requests/api/ci/pipelines_spec.rb @@ -34,7 +34,28 @@ RSpec.describe API::Ci::Pipelines do expect(json_response.first['sha']).to match(/\A\h{40}\z/) expect(json_response.first['id']).to eq pipeline.id expect(json_response.first['web_url']).to be_present - expect(json_response.first.keys).to contain_exactly(*%w[id project_id sha ref status web_url created_at updated_at]) + end + + describe 'keys in the response' do + context 'when `pipeline_source_filter` feature flag is disabled' do + before do + stub_feature_flags(pipeline_source_filter: false) + end + + it 'does not includes pipeline source' do + get api("/projects/#{project.id}/pipelines", user) + + expect(json_response.first.keys).to contain_exactly(*%w[id project_id sha ref status web_url created_at updated_at]) + end + end + + context 'when `pipeline_source_filter` feature flag is disabled' do + it 'includes pipeline source' do + get api("/projects/#{project.id}/pipelines", user) + + expect(json_response.first.keys).to contain_exactly(*%w[id project_id sha ref status web_url created_at updated_at source]) + end + end end context 'when parameter is passed' do @@ -294,6 +315,48 @@ RSpec.describe API::Ci::Pipelines do end end end + + context 'when a source is specified' do + before do + create(:ci_pipeline, project: project, source: :push) + create(:ci_pipeline, project: project, source: :web) + create(:ci_pipeline, project: project, source: :api) + end + + context 'when `pipeline_source_filter` feature flag is disabled' do + before do + stub_feature_flags(pipeline_source_filter: false) + end + + it 'returns all pipelines' do + get api("/projects/#{project.id}/pipelines", user), params: { source: 'web' } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).not_to be_empty + expect(json_response.length).to be >= 3 + end + end + + context 'when `pipeline_source_filter` feature flag is enabled' do + it 'returns matched pipelines' do + get api("/projects/#{project.id}/pipelines", user), params: { source: 'web' } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).not_to be_empty + json_response.each { |r| expect(r['source']).to eq('web') } + end + + context 'when source is invalid' do + it 'returns bad_request' do + get api("/projects/#{project.id}/pipelines", user), params: { source: 'invalid-source' } + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + end + end end end @@ -1150,4 +1213,43 @@ RSpec.describe API::Ci::Pipelines do end end end + + describe 'GET /projects/:id/pipelines/:pipeline_id/test_report_summary' do + subject { get api("/projects/#{project.id}/pipelines/#{pipeline.id}/test_report_summary", current_user) } + + context 'authorized user' do + let(:current_user) { user } + + let(:pipeline) { create(:ci_pipeline, project: project) } + + context 'when pipeline does not have a test report summary' do + it 'returns an empty test report summary' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['total']['count']).to eq(0) + end + end + + context 'when pipeline has a test report summary' do + let(:pipeline) { create(:ci_pipeline, :with_report_results, project: project) } + + it 'returns the test report summary' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['total']['count']).to eq(2) + end + end + end + + context 'unauthorized user' do + it 'does not return project pipelines' do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/test_report_summary", non_member) + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq '404 Project Not Found' + end + end + end end diff --git a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb index 017a12a4a40..195aac2e5f0 100644 --- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb +++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb @@ -32,7 +32,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do let(:job) { create(:ci_build, :pending, user: user, project: project, pipeline: pipeline, runner_id: runner.id) } let(:jwt) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } let(:headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt } } - let(:headers_with_token) { headers.merge(API::Helpers::Runner::JOB_TOKEN_HEADER => job.token) } + let(:headers_with_token) { headers.merge(API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token) } let(:file_upload) { fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') } let(:file_upload2) { fixture_file_upload('spec/fixtures/dk.png', 'image/gif') } @@ -398,7 +398,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do context 'when using runners token' do it 'responds with forbidden' do - upload_artifacts(file_upload, headers.merge(API::Helpers::Runner::JOB_TOKEN_HEADER => job.project.runners_token)) + upload_artifacts(file_upload, headers.merge(API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.project.runners_token)) expect(response).to have_gitlab_http_status(:forbidden) end diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb index 00c3a0a31af..adac81ff6f4 100644 --- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb +++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb @@ -506,32 +506,12 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end describe 'preloading job_artifacts_archive' do - context 'when the feature flag is disabled' do - before do - stub_feature_flags(preload_associations_jobs_request_api_endpoint: false) - end - - it 'queries the ci_job_artifacts table multiple times' do - expect { request_job }.to exceed_all_query_limit(1).for_model(::Ci::JobArtifact) - end - - it 'queries the ci_builds table more than three times' do - expect { request_job }.to exceed_all_query_limit(3).for_model(::Ci::Build) - end + it 'queries the ci_job_artifacts table once only' do + expect { request_job }.not_to exceed_all_query_limit(1).for_model(::Ci::JobArtifact) end - context 'when the feature flag is enabled' do - before do - stub_feature_flags(preload_associations_jobs_request_api_endpoint: true) - end - - it 'queries the ci_job_artifacts table once only' do - expect { request_job }.not_to exceed_all_query_limit(1).for_model(::Ci::JobArtifact) - end - - it 'queries the ci_builds table five times' do - expect { request_job }.not_to exceed_all_query_limit(5).for_model(::Ci::Build) - end + it 'queries the ci_builds table five times' do + expect { request_job }.not_to exceed_all_query_limit(5).for_model(::Ci::Build) end end end diff --git a/spec/requests/api/ci/runner/jobs_trace_spec.rb b/spec/requests/api/ci/runner/jobs_trace_spec.rb index e20c7e36096..2760e306693 100644 --- a/spec/requests/api/ci/runner/jobs_trace_spec.rb +++ b/spec/requests/api/ci/runner/jobs_trace_spec.rb @@ -33,7 +33,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_trace_chunks do project: project, user: user, runner_id: runner.id, pipeline: pipeline) end - let(:headers) { { API::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' } } + let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' } } let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) } let(:update_interval) { 10.seconds.to_i } diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb index 6d222046998..17b988a60c5 100644 --- a/spec/requests/api/ci/runner/runners_post_spec.rb +++ b/spec/requests/api/ci/runner/runners_post_spec.rb @@ -98,14 +98,33 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do before do create(:ci_runner, runner_type: :project_type, projects: [project], contacted_at: 1.second.ago) create(:plan_limits, :default_plan, ci_registered_project_runners: 1) + + skip_default_enabled_yaml_check + stub_feature_flags(ci_runner_limits_override: ci_runner_limits_override) end - it 'does not create runner' do - request + context 'with ci_runner_limits_override FF disabled' do + let(:ci_runner_limits_override) { false } + + it 'does not create runner' do + request - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to include('runner_projects.base' => ['Maximum number of ci registered project runners (1) exceeded']) - expect(project.runners.reload.size).to eq(1) + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to include('runner_projects.base' => ['Maximum number of ci registered project runners (1) exceeded']) + expect(project.runners.reload.size).to eq(1) + end + end + + context 'with ci_runner_limits_override FF enabled' do + let(:ci_runner_limits_override) { true } + + it 'creates runner' do + request + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['message']).to be_nil + expect(project.runners.reload.size).to eq(2) + end end end @@ -113,6 +132,9 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do before do create(:ci_runner, runner_type: :project_type, projects: [project], created_at: 14.months.ago, contacted_at: 13.months.ago) create(:plan_limits, :default_plan, ci_registered_project_runners: 1) + + skip_default_enabled_yaml_check + stub_feature_flags(ci_runner_limits_override: false) end it 'creates runner' do @@ -182,14 +204,33 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do before do create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 1.month.ago) create(:plan_limits, :default_plan, ci_registered_group_runners: 1) + + skip_default_enabled_yaml_check + stub_feature_flags(ci_runner_limits_override: ci_runner_limits_override) end - it 'does not create runner' do - request + context 'with ci_runner_limits_override FF disabled' do + let(:ci_runner_limits_override) { false } + + it 'does not create runner' do + request - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to include('runner_namespaces.base' => ['Maximum number of ci registered group runners (1) exceeded']) - expect(group.runners.reload.size).to eq(1) + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to include('runner_namespaces.base' => ['Maximum number of ci registered group runners (1) exceeded']) + expect(group.runners.reload.size).to eq(1) + end + end + + context 'with ci_runner_limits_override FF enabled' do + let(:ci_runner_limits_override) { true } + + it 'creates runner' do + request + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['message']).to be_nil + expect(group.runners.reload.size).to eq(2) + end end end @@ -198,6 +239,9 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do create(:ci_runner, runner_type: :group_type, groups: [group], created_at: 4.months.ago, contacted_at: 3.months.ago) create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 4.months.ago) create(:plan_limits, :default_plan, ci_registered_group_runners: 1) + + skip_default_enabled_yaml_check + stub_feature_flags(ci_runner_limits_override: false) end it 'creates runner' do diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb index 82fb4440429..902938d7d02 100644 --- a/spec/requests/api/ci/runners_spec.rb +++ b/spec/requests/api/ci/runners_spec.rb @@ -1003,13 +1003,31 @@ RSpec.describe API::Ci::Runners do context 'when it exceeds the application limits' do before do create(:plan_limits, :default_plan, ci_registered_project_runners: 1) + + skip_default_enabled_yaml_check + stub_feature_flags(ci_runner_limits_override: ci_runner_limits_override) end - it 'does not enable specific runner' do - expect do - post api("/projects/#{project.id}/runners", admin), params: { runner_id: new_project_runner.id } - end.not_to change { project.runners.count } - expect(response).to have_gitlab_http_status(:bad_request) + context 'with ci_runner_limits_override FF disabled' do + let(:ci_runner_limits_override) { false } + + it 'does not enable specific runner' do + expect do + post api("/projects/#{project.id}/runners", admin), params: { runner_id: new_project_runner.id } + end.not_to change { project.runners.count } + expect(response).to have_gitlab_http_status(:bad_request) + end + end + + context 'with ci_runner_limits_override FF enabled' do + let(:ci_runner_limits_override) { true } + + it 'enables specific runner' do + expect do + post api("/projects/#{project.id}/runners", admin), params: { runner_id: new_project_runner.id } + end.to change { project.runners.count } + expect(response).to have_gitlab_http_status(:created) + end end end end diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/ci/triggers_spec.rb index 4318f106996..410e2ae405e 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/ci/triggers_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Triggers do +RSpec.describe API::Ci::Triggers do let_it_be(:user) { create(:user) } let_it_be(:user2) { create(:user) } diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/ci/variables_spec.rb index 1ae9b0d548d..dc524e121d5 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/ci/variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Variables do +RSpec.describe API::Ci::Variables do let(:user) { create(:user) } let(:user2) { create(:user) } let!(:project) { create(:project, creator_id: user.id) } diff --git a/spec/requests/api/debian_group_packages_spec.rb b/spec/requests/api/debian_group_packages_spec.rb index 931eaf41891..3e11b480860 100644 --- a/spec/requests/api/debian_group_packages_spec.rb +++ b/spec/requests/api/debian_group_packages_spec.rb @@ -6,10 +6,16 @@ RSpec.describe API::DebianGroupPackages do include WorkhorseHelpers include_context 'Debian repository shared context', :group, false do + context 'with invalid parameter' do + let(:url) { "/groups/1/-/packages/debian/dists/with+space/InRelease" } + + it_behaves_like 'Debian repository GET request', :bad_request, /^distribution is invalid$/ + end + describe 'GET groups/:id/-/packages/debian/dists/*distribution/Release.gpg' do let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/Release.gpg" } - it_behaves_like 'Debian repository read endpoint', 'GET request', :not_found + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^-----BEGIN PGP SIGNATURE-----/ end describe 'GET groups/:id/-/packages/debian/dists/*distribution/Release' do @@ -21,7 +27,7 @@ RSpec.describe API::DebianGroupPackages do describe 'GET groups/:id/-/packages/debian/dists/*distribution/InRelease' do let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/InRelease" } - it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^Codename: fixture-distribution\n$/ + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^-----BEGIN PGP SIGNED MESSAGE-----/ end describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/binary-:architecture/Packages' do @@ -30,10 +36,25 @@ RSpec.describe API::DebianGroupPackages do it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /Description: This is an incomplete Packages file/ end - describe 'GET groups/:id/-/packages/debian/pool/:component/:letter/:source_package/:file_name' do - let(:url) { "/groups/#{container.id}/-/packages/debian/pool/#{component.name}/#{letter}/#{source_package}/#{package_name}_#{package_version}_#{architecture.name}.deb" } + describe 'GET groups/:id/-/packages/debian/pool/:codename/:project_id/:letter/:package_name/:package_version/:file_name' do + let(:url) { "/groups/#{container.id}/-/packages/debian/pool/#{package.debian_distribution.codename}/#{project.id}/#{letter}/#{package.name}/#{package.version}/#{file_name}" } + + using RSpec::Parameterized::TableSyntax + + where(:file_name, :success_body) do + 'sample_1.2.3~alpha2.tar.xz' | /^.7zXZ/ + 'sample_1.2.3~alpha2.dsc' | /^Format: 3.0 \(native\)/ + 'libsample0_1.2.3~alpha2_amd64.deb' | /^!<arch>/ + 'sample-udeb_1.2.3~alpha2_amd64.udeb' | /^!<arch>/ + 'sample_1.2.3~alpha2_amd64.buildinfo' | /Build-Tainted-By/ + 'sample_1.2.3~alpha2_amd64.changes' | /urgency=medium/ + end + + with_them do + include_context 'with file_name', params[:file_name] - it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^TODO File$/ + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, params[:success_body] + end end end end diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb index fb7da467322..d0b0debaf13 100644 --- a/spec/requests/api/debian_project_packages_spec.rb +++ b/spec/requests/api/debian_project_packages_spec.rb @@ -6,10 +6,16 @@ RSpec.describe API::DebianProjectPackages do include WorkhorseHelpers include_context 'Debian repository shared context', :project, true do + context 'with invalid parameter' do + let(:url) { "/projects/1/packages/debian/dists/with+space/InRelease" } + + it_behaves_like 'Debian repository GET request', :bad_request, /^distribution is invalid$/ + end + describe 'GET projects/:id/packages/debian/dists/*distribution/Release.gpg' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release.gpg" } - it_behaves_like 'Debian repository read endpoint', 'GET request', :not_found + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^-----BEGIN PGP SIGNATURE-----/ end describe 'GET projects/:id/packages/debian/dists/*distribution/Release' do @@ -21,7 +27,7 @@ RSpec.describe API::DebianProjectPackages do describe 'GET projects/:id/packages/debian/dists/*distribution/InRelease' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/InRelease" } - it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^Codename: fixture-distribution\n$/ + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^-----BEGIN PGP SIGNED MESSAGE-----/ end describe 'GET projects/:id/packages/debian/dists/*distribution/:component/binary-:architecture/Packages' do @@ -30,10 +36,25 @@ RSpec.describe API::DebianProjectPackages do it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /Description: This is an incomplete Packages file/ end - describe 'GET projects/:id/packages/debian/pool/:component/:letter/:source_package/:file_name' do - let(:url) { "/projects/#{container.id}/packages/debian/pool/#{component.name}/#{letter}/#{source_package}/#{package_name}_#{package_version}_#{architecture.name}.deb" } + describe 'GET projects/:id/packages/debian/pool/:codename/:letter/:package_name/:package_version/:file_name' do + let(:url) { "/projects/#{container.id}/packages/debian/pool/#{package.debian_distribution.codename}/#{letter}/#{package.name}/#{package.version}/#{file_name}" } + + using RSpec::Parameterized::TableSyntax - it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^TODO File$/ + where(:file_name, :success_body) do + 'sample_1.2.3~alpha2.tar.xz' | /^.7zXZ/ + 'sample_1.2.3~alpha2.dsc' | /^Format: 3.0 \(native\)/ + 'libsample0_1.2.3~alpha2_amd64.deb' | /^!<arch>/ + 'sample-udeb_1.2.3~alpha2_amd64.udeb' | /^!<arch>/ + 'sample_1.2.3~alpha2_amd64.buildinfo' | /Build-Tainted-By/ + 'sample_1.2.3~alpha2_amd64.changes' | /urgency=medium/ + end + + with_them do + include_context 'with file_name', params[:file_name] + + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, params[:success_body] + end end describe 'PUT projects/:id/packages/debian/:file_name' do diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index 5d40e8c529a..bc7bb7523c9 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -360,6 +360,8 @@ RSpec.describe API::Environments do expect(json_response["scheduled_entries"].size).to eq(1) expect(json_response["scheduled_entries"].first["id"]).to eq(old_stopped_review_env.id) expect(json_response["unprocessable_entries"].size).to eq(0) + expect(json_response["scheduled_entries"]).to match_schema('public_api/v4/environments') + expect(json_response["unprocessable_entries"]).to match_schema('public_api/v4/environments') expect(old_stopped_review_env.reload.auto_delete_at).to eq(1.week.from_now) expect(new_stopped_review_env.reload.auto_delete_at).to be_nil diff --git a/spec/requests/api/error_tracking_collector_spec.rb b/spec/requests/api/error_tracking_collector_spec.rb index 52d63410e7a..4b186657c4a 100644 --- a/spec/requests/api/error_tracking_collector_spec.rb +++ b/spec/requests/api/error_tracking_collector_spec.rb @@ -4,15 +4,17 @@ require 'spec_helper' RSpec.describe API::ErrorTrackingCollector do let_it_be(:project) { create(:project, :private) } - let_it_be(:setting) { create(:project_error_tracking_setting, project: project) } + let_it_be(:setting) { create(:project_error_tracking_setting, :integrated, project: project) } + let_it_be(:client_key) { create(:error_tracking_client_key, project: project) } describe "POST /error_tracking/collector/api/:id/envelope" do let_it_be(:raw_event) { fixture_file('error_tracking/event.txt') } let_it_be(:url) { "/error_tracking/collector/api/#{project.id}/envelope" } let(:params) { raw_event } + let(:headers) { { 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}" } } - subject { post api(url), params: params } + subject { post api(url), params: params, headers: headers } RSpec.shared_examples 'not found' do it 'reponds with 404' do @@ -38,6 +40,14 @@ RSpec.describe API::ErrorTrackingCollector do it_behaves_like 'not found' end + context 'integrated error tracking is disabled' do + before do + setting.update!(integrated: false) + end + + it_behaves_like 'not found' + end + context 'feature flag is disabled' do before do stub_feature_flags(integrated_error_tracking: false) @@ -46,6 +56,24 @@ RSpec.describe API::ErrorTrackingCollector do it_behaves_like 'not found' end + context 'auth headers are missing' do + let(:headers) { {} } + + it_behaves_like 'bad request' + end + + context 'public key is wrong' do + let(:headers) { { 'X-Sentry-Auth' => "Sentry sentry_key=glet_1fedb514e17f4b958435093deb02048c" } } + + it_behaves_like 'not found' + end + + context 'public key is inactive' do + let(:client_key) { create(:error_tracking_client_key, :disabled, project: project) } + + it_behaves_like 'not found' + end + context 'empty body' do let(:params) { '' } diff --git a/spec/requests/api/error_tracking_spec.rb b/spec/requests/api/error_tracking_spec.rb index 39121af7bc3..ec9a3378acc 100644 --- a/spec/requests/api/error_tracking_spec.rb +++ b/spec/requests/api/error_tracking_spec.rb @@ -17,7 +17,8 @@ RSpec.describe API::ErrorTracking do 'active' => setting.reload.enabled, 'project_name' => setting.project_name, 'sentry_external_url' => setting.sentry_external_url, - 'api_url' => setting.api_url + 'api_url' => setting.api_url, + 'integrated' => setting.integrated ) end end @@ -79,6 +80,19 @@ RSpec.describe API::ErrorTracking do .to eq('active is empty') end end + + context 'with integrated param' do + let(:params) { { active: true, integrated: true } } + + it 'updates the integrated flag' do + expect(setting.integrated).to be_falsey + + make_request + + expect(json_response).to include('integrated' => true) + expect(setting.reload.integrated).to be_truthy + end + end end context 'without a project setting' do diff --git a/spec/requests/api/feature_flags_spec.rb b/spec/requests/api/feature_flags_spec.rb index 8edf8825fb2..8c8c6803a38 100644 --- a/spec/requests/api/feature_flags_spec.rb +++ b/spec/requests/api/feature_flags_spec.rb @@ -417,7 +417,7 @@ RSpec.describe API::FeatureFlags do version: 'new_version_flag', strategies: [{ name: 'flexibleRollout', - parameters: { groupId: 'default', rollout: '50', stickiness: 'DEFAULT' }, + parameters: { groupId: 'default', rollout: '50', stickiness: 'default' }, scopes: [{ environment_scope: 'staging' }] @@ -434,7 +434,7 @@ RSpec.describe API::FeatureFlags do expect(feature_flag.version).to eq('new_version_flag') expect(feature_flag.strategies.map { |s| s.slice(:name, :parameters).deep_symbolize_keys }).to eq([{ name: 'flexibleRollout', - parameters: { groupId: 'default', rollout: '50', stickiness: 'DEFAULT' } + parameters: { groupId: 'default', rollout: '50', stickiness: 'default' } }]) expect(feature_flag.strategies.first.scopes.map { |s| s.slice(:environment_scope).deep_symbolize_keys }).to eq([{ environment_scope: 'staging' @@ -630,7 +630,7 @@ RSpec.describe API::FeatureFlags do strategies: [{ id: strategy.id, name: 'flexibleRollout', - parameters: { groupId: 'default', rollout: '10', stickiness: 'DEFAULT' } + parameters: { groupId: 'default', rollout: '10', stickiness: 'default' } }] } @@ -642,7 +642,7 @@ RSpec.describe API::FeatureFlags do expect(result).to eq([{ id: strategy.id, name: 'flexibleRollout', - parameters: { groupId: 'default', rollout: '10', stickiness: 'DEFAULT' } + parameters: { groupId: 'default', rollout: '10', stickiness: 'default' } }]) end @@ -677,7 +677,7 @@ RSpec.describe API::FeatureFlags do params = { strategies: [{ name: 'flexibleRollout', - parameters: { groupId: 'default', rollout: '10', stickiness: 'DEFAULT' } + parameters: { groupId: 'default', rollout: '10', stickiness: 'default' } }] } @@ -694,7 +694,7 @@ RSpec.describe API::FeatureFlags do parameters: {} }, { name: 'flexibleRollout', - parameters: { groupId: 'default', rollout: '10', stickiness: 'DEFAULT' } + parameters: { groupId: 'default', rollout: '10', stickiness: 'default' } }]) end diff --git a/spec/requests/api/generic_packages_spec.rb b/spec/requests/api/generic_packages_spec.rb index 378ee2f3e7c..4091253fb54 100644 --- a/spec/requests/api/generic_packages_spec.rb +++ b/spec/requests/api/generic_packages_spec.rb @@ -18,7 +18,7 @@ RSpec.describe API::GenericPackages do let_it_be(:project_deploy_token_wo) { create(:project_deploy_token, deploy_token: deploy_token_wo, project: project) } let(:user) { personal_access_token.user } - let(:ci_build) { create(:ci_build, :running, user: user, project: project) } + let(:ci_build) { create(:ci_build, :running, user: user) } let(:snowplow_standard_context_params) { { user: user, project: project, namespace: project.namespace } } def auth_header diff --git a/spec/requests/api/go_proxy_spec.rb b/spec/requests/api/go_proxy_spec.rb index 0143340de11..e678b6cf1c8 100644 --- a/spec/requests/api/go_proxy_spec.rb +++ b/spec/requests/api/go_proxy_spec.rb @@ -11,7 +11,7 @@ RSpec.describe API::GoProxy do let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" } let_it_be(:oauth) { create :oauth_access_token, scopes: 'api', resource_owner: user } - let_it_be(:job) { create :ci_build, user: user, status: :running, project: project } + let_it_be(:job) { create :ci_build, user: user, status: :running } let_it_be(:pa_token) { create :personal_access_token, user: user } let_it_be(:modules) do diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb index 10f05efa1b8..e6362fdde88 100644 --- a/spec/requests/api/graphql/ci/jobs_spec.rb +++ b/spec/requests/api/graphql/ci/jobs_spec.rb @@ -117,14 +117,19 @@ RSpec.describe 'Query.project.pipeline' do ) end - it 'avoids N+1 queries' do - control_count = ActiveRecord::QueryRecorder.new do - post_graphql(query, current_user: user, variables: first_n.with(1)) + it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do + post_graphql(query, current_user: user) + + control = ActiveRecord::QueryRecorder.new(skip_cached: false) do + post_graphql(query, current_user: user) end + create(:ci_build, name: 'test-a', pipeline: pipeline) + create(:ci_build, name: 'test-b', pipeline: pipeline) + expect do - post_graphql(query, current_user: user, variables: first_n.with(3)) - end.not_to exceed_query_limit(control_count) + post_graphql(query, current_user: user) + end.not_to exceed_all_query_limit(control) end end end @@ -137,11 +142,19 @@ RSpec.describe 'Query.project.pipeline' do query { project(fullPath: "#{project.full_path}") { pipeline(iid: "#{pipeline.iid}") { - jobs { + stages { nodes { - artifacts { + groups{ nodes { - downloadPath + jobs { + nodes { + artifacts { + nodes { + downloadPath + } + } + } + } } } } @@ -158,7 +171,7 @@ RSpec.describe 'Query.project.pipeline' do post_graphql(query, current_user: user) - job_data = graphql_data.dig('project', 'pipeline', 'jobs', 'nodes').first + job_data = graphql_data_at(:project, :pipeline, :stages, :nodes, :groups, :nodes, :jobs, :nodes).first expect(job_data.dig('artifacts', 'nodes').count).to be(2) end end @@ -169,7 +182,7 @@ RSpec.describe 'Query.project.pipeline' do post_graphql(query, current_user: user) - job_data = graphql_data.dig('project', 'pipeline', 'jobs', 'nodes').first + job_data = graphql_data_at(:project, :pipeline, :stages, :nodes, :groups, :nodes, :jobs, :nodes).first expect(job_data['artifacts']).to be_nil end end diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb index cdd46ca4ecc..74547196445 100644 --- a/spec/requests/api/graphql/ci/runner_spec.rb +++ b/spec/requests/api/graphql/ci/runner_spec.rb @@ -52,14 +52,14 @@ RSpec.describe 'Query.runner(id)' do 'version' => runner.version, 'shortSha' => runner.short_sha, 'revision' => runner.revision, - 'locked' => runner.locked, + 'locked' => false, 'active' => runner.active, 'status' => runner.status.to_s.upcase, 'maximumTimeout' => runner.maximum_timeout, 'accessLevel' => runner.access_level.to_s.upcase, 'runUntagged' => runner.run_untagged, 'ipAddress' => runner.ip_address, - 'runnerType' => 'INSTANCE_TYPE', + 'runnerType' => runner.instance_type? ? 'INSTANCE_TYPE' : 'PROJECT_TYPE', 'jobCount' => 0, 'projectCount' => nil ) @@ -109,6 +109,40 @@ RSpec.describe 'Query.runner(id)' do end end + describe 'for project runner' do + using RSpec::Parameterized::TableSyntax + + where(is_locked: [true, false]) + + with_them do + let(:project_runner) do + create(:ci_runner, :project, description: 'Runner 3', contacted_at: 1.day.ago, active: false, locked: is_locked, + version: 'adfe157', revision: 'b', ip_address: '10.10.10.10', access_level: 1, run_untagged: true) + end + + let(:query) do + wrap_fields(query_graphql_path(query_path, all_graphql_fields_for('CiRunner'))) + end + + let(:query_path) do + [ + [:runner, { id: project_runner.to_global_id.to_s }] + ] + end + + it 'retrieves correct locked value' do + post_graphql(query, current_user: user) + + runner_data = graphql_data_at(:runner) + + expect(runner_data).to match a_hash_including( + 'id' => "gid://gitlab/Ci::Runner/#{project_runner.id}", + 'locked' => is_locked + ) + end + end + end + describe 'for inactive runner' do it_behaves_like 'runner details fetch', :inactive_instance_runner end diff --git a/spec/requests/api/graphql/current_user_query_spec.rb b/spec/requests/api/graphql/current_user_query_spec.rb index dc832b42fa5..086a57094ca 100644 --- a/spec/requests/api/graphql/current_user_query_spec.rb +++ b/spec/requests/api/graphql/current_user_query_spec.rb @@ -5,8 +5,15 @@ require 'spec_helper' RSpec.describe 'getting project information' do include GraphqlHelpers + let(:fields) do + <<~GRAPHQL + name + namespace { id } + GRAPHQL + end + let(:query) do - graphql_query_for('currentUser', {}, 'name') + graphql_query_for('currentUser', {}, fields) end subject { graphql_data['currentUser'] } @@ -20,7 +27,7 @@ RSpec.describe 'getting project information' do it_behaves_like 'a working graphql query' - it { is_expected.to include('name' => current_user.name) } + it { is_expected.to include('name' => current_user.name, 'namespace' => { 'id' => current_user.namespace.to_global_id.to_s }) } end context 'when there is no current_user' do diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb index b6bbf8d5dd2..fd0ee5d52b9 100644 --- a/spec/requests/api/graphql/group_query_spec.rb +++ b/spec/requests/api/graphql/group_query_spec.rb @@ -8,11 +8,11 @@ RSpec.describe 'getting group information' do include GraphqlHelpers include UploadHelpers - let(:user1) { create(:user, can_create_group: false) } - let(:user2) { create(:user) } - let(:admin) { create(:admin) } - let(:public_group) { create(:group, :public) } - let(:private_group) { create(:group, :private) } + let_it_be(:user1) { create(:user, can_create_group: false) } + let_it_be(:user2) { create(:user) } + let_it_be(:admin) { create(:admin) } + let_it_be(:private_group) { create(:group, :private) } + let_it_be(:public_group) { create(:group, :public) } # similar to the API "GET /groups/:id" describe "Query group(fullPath)" do @@ -78,6 +78,7 @@ RSpec.describe 'getting group information' do expect(graphql_data['group']['parentId']).to eq(group1.parent_id) expect(graphql_data['group']['issues']['nodes'].count).to eq(1) expect(graphql_data['group']['issues']['nodes'][0]['iid']).to eq(issue.iid.to_s) + expect(graphql_data['group']['sharedRunnersSetting']).to eq(group1.shared_runners_setting.upcase) end it "does not return a non existing group" do @@ -105,6 +106,20 @@ RSpec.describe 'getting group information' do expect { post_multiplex(queries, current_user: admin) } .to issue_same_number_of_queries_as { post_graphql(group_query(group1), current_user: admin) } end + + context "when querying group's descendant groups" do + let_it_be(:subgroup1) { create(:group, parent: public_group) } + let_it_be(:subgroup2) { create(:group, parent: subgroup1) } + + let(:descendants) { [subgroup1, subgroup2] } + + it 'returns all descendant groups user has access to' do + post_graphql(group_query(public_group), current_user: admin) + + names = graphql_data['group']['descendantGroups']['nodes'].map { |n| n['name'] } + expect(names).to match_array(descendants.map(&:name)) + end + end end context "when authenticated as admin" do diff --git a/spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb b/spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb new file mode 100644 index 00000000000..ee0f0a9bccb --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "JobCancel" do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) } + let_it_be(:job) { create(:ci_build, pipeline: pipeline, name: 'build') } + + let(:mutation) do + variables = { + id: job.to_global_id.to_s + } + graphql_mutation(:job_cancel, variables, + <<-QL + errors + job { + id + } + QL + ) + end + + let(:mutation_response) { graphql_mutation_response(:job_cancel) } + + it 'returns an error if the user is not allowed to cancel the job' do + project.add_developer(user) + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_errors).not_to be_empty + end + + it 'cancels a job' do + job_id = ::Gitlab::GlobalId.build(job, id: job.id).to_s + project.add_maintainer(user) + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['job']['id']).to eq(job_id) + expect(job.reload.status).to eq('canceled') + end +end diff --git a/spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb b/spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb new file mode 100644 index 00000000000..4ddc019a2b5 --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'JobUnschedule' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) } + let_it_be(:job) { create(:ci_build, :scheduled, pipeline: pipeline, name: 'build') } + + let(:mutation) do + variables = { + id: job.to_global_id.to_s + } + graphql_mutation(:job_unschedule, variables, + <<-QL + errors + job { + id + } + QL + ) + end + + let(:mutation_response) { graphql_mutation_response(:job_unschedule) } + + it 'returns an error if the user is not allowed to unschedule the job' do + project.add_developer(user) + + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_errors).not_to be_empty + expect(job.reload.status).to eq('scheduled') + end + + it 'unschedules a job' do + project.add_maintainer(user) + + job_id = ::Gitlab::GlobalId.build(job, id: job.id).to_s + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['job']['id']).to eq(job_id) + expect(job.reload.status).to eq('manual') + end +end diff --git a/spec/requests/api/graphql/mutations/groups/update_spec.rb b/spec/requests/api/graphql/mutations/groups/update_spec.rb new file mode 100644 index 00000000000..b9dfb8e37ab --- /dev/null +++ b/spec/requests/api/graphql/mutations/groups/update_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'GroupUpdate' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be_with_reload(:group) { create(:group) } + + let(:variables) do + { + full_path: group.full_path, + shared_runners_setting: 'DISABLED_WITH_OVERRIDE' + } + end + + let(:mutation) { graphql_mutation(:group_update, variables) } + + context 'when unauthorized' do + shared_examples 'unauthorized' do + it 'returns an error' do + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_errors).not_to be_empty + end + end + + context 'when not a group member' do + it_behaves_like 'unauthorized' + end + + context 'when a non-admin group member' do + before do + group.add_developer(user) + end + + it_behaves_like 'unauthorized' + end + end + + context 'when authorized' do + before do + group.add_owner(user) + end + + it 'updates shared runners settings' do + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(graphql_errors).to be_nil + expect(group.reload.shared_runners_setting).to eq(variables[:shared_runners_setting].downcase) + end + + context 'when bad arguments are provided' do + let(:variables) { { full_path: '', shared_runners_setting: 'INVALID' } } + + it 'returns the errors' do + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_errors).not_to be_empty + expect(group.reload.shared_runners_setting).to eq('enabled') + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb index ea5be9f9852..72e47a98373 100644 --- a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb @@ -68,7 +68,7 @@ RSpec.describe 'Setting Due Date of an issue' do it 'returns an error' do post_graphql_mutation(mutation, current_user: current_user) - expect(graphql_errors).to include(a_hash_including('message' => /Argument dueDate must be provided/)) + expect(graphql_errors).to include(a_hash_including('message' => /Arguments must be provided: dueDate/)) end end diff --git a/spec/requests/api/graphql/mutations/issues/update_spec.rb b/spec/requests/api/graphql/mutations/issues/update_spec.rb index b3e1ab62e54..c3aaf090703 100644 --- a/spec/requests/api/graphql/mutations/issues/update_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/update_spec.rb @@ -8,6 +8,8 @@ RSpec.describe 'Update of an existing issue' do let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project, :public) } let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:label1) { create(:label, project: project) } + let_it_be(:label2) { create(:label, project: project) } let(:input) do { @@ -20,7 +22,9 @@ RSpec.describe 'Update of an existing issue' do } end - let(:mutation) { graphql_mutation(:update_issue, input.merge(project_path: project.full_path, locked: true)) } + let(:extra_params) { { project_path: project.full_path, locked: true } } + let(:input_params) { input.merge(extra_params) } + let(:mutation) { graphql_mutation(:update_issue, input_params) } let(:mutation_response) { graphql_mutation_response(:update_issue) } context 'the user is not allowed to update issue' do @@ -39,5 +43,82 @@ RSpec.describe 'Update of an existing issue' do expect(mutation_response['issue']).to include(input) expect(mutation_response['issue']).to include('discussionLocked' => true) end + + context 'setting labels' do + let(:mutation) do + graphql_mutation(:update_issue, input_params) do + <<~QL + issue { + labels { + nodes { + id + } + } + } + errors + QL + end + end + + context 'reset labels' do + let(:input_params) { input.merge(extra_params).merge({ labelIds: [label1.id, label2.id] }) } + + it 'resets labels' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(json_response['errors']).to be_nil + expect(mutation_response['issue']['labels']).to include({ "nodes" => [{ "id" => label1.to_global_id.to_s }, { "id" => label2.to_global_id.to_s }] }) + end + + context 'reset labels and add labels' do + let(:input_params) { input.merge(extra_params).merge({ labelIds: [label1.id], addLabelIds: [label2.id] }) } + + it 'returns error for mutually exclusive arguments' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(json_response['errors'].first['message']).to eq('labelIds is mutually exclusive with any of addLabelIds or removeLabelIds') + expect(mutation_response).to be_nil + end + end + + context 'reset labels and remove labels' do + let(:input_params) { input.merge(extra_params).merge({ labelIds: [label1.id], removeLabelIds: [label2.id] }) } + + it 'returns error for mutually exclusive arguments' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(json_response['errors'].first['message']).to eq('labelIds is mutually exclusive with any of addLabelIds or removeLabelIds') + expect(mutation_response).to be_nil + end + end + + context 'with global label ids' do + let(:input_params) { input.merge(extra_params).merge({ labelIds: [label1.to_global_id.to_s, label2.to_global_id.to_s] }) } + + it 'resets labels' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(json_response['errors']).to be_nil + expect(mutation_response['issue']['labels']).to include({ "nodes" => [{ "id" => label1.to_global_id.to_s }, { "id" => label2.to_global_id.to_s }] }) + end + end + end + + context 'add and remove labels' do + let(:input_params) { input.merge(extra_params).merge({ addLabelIds: [label1.id], removeLabelIds: [label2.id] }) } + + it 'returns error for mutually exclusive arguments' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(json_response['errors']).to be_nil + expect(mutation_response['issue']['labels']).to include({ "nodes" => [{ "id" => label1.to_global_id.to_s }] }) + end + end + end end end diff --git a/spec/requests/api/graphql/mutations/packages/destroy_file_spec.rb b/spec/requests/api/graphql/mutations/packages/destroy_file_spec.rb new file mode 100644 index 00000000000..7be629f8f4b --- /dev/null +++ b/spec/requests/api/graphql/mutations/packages/destroy_file_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Destroying a package file' do + using RSpec::Parameterized::TableSyntax + + include GraphqlHelpers + + let_it_be_with_reload(:package) { create(:maven_package) } + let_it_be(:user) { create(:user) } + + let(:project) { package.project } + let(:id) { package.package_files.first.to_global_id.to_s } + + let(:query) do + <<~GQL + errors + GQL + end + + let(:params) { { id: id } } + let(:mutation) { graphql_mutation(:destroy_package_file, params, query) } + let(:mutation_response) { graphql_mutation_response(:destroyPackageFile) } + + shared_examples 'destroying the package file' do + it 'destroy the package file' do + expect { mutation_request }.to change { ::Packages::PackageFile.count }.by(-1) + end + + it_behaves_like 'returning response status', :success + end + + shared_examples 'denying the mutation request' do + it 'does not destroy the package file' do + expect(::Packages::PackageFile) + .not_to receive(:destroy) + + expect { mutation_request }.not_to change { ::Packages::PackageFile.count } + + expect(mutation_response).to be_nil + end + + it_behaves_like 'returning response status', :success + end + + describe 'post graphql mutation' do + subject(:mutation_request) { post_graphql_mutation(mutation, current_user: user) } + + context 'with valid id' do + where(:user_role, :shared_examples_name) do + :maintainer | 'destroying the package file' + :developer | 'denying the mutation request' + :reporter | 'denying the mutation request' + :guest | 'denying the mutation request' + :anonymous | 'denying the mutation request' + end + + with_them do + before do + project.send("add_#{user_role}", user) unless user_role == :anonymous + end + + it_behaves_like params[:shared_examples_name] + end + end + + context 'with invalid id' do + let(:params) { { id: 'gid://gitlab/Packages::PackageFile/5555' } } + + it_behaves_like 'denying the mutation request' + end + + context 'when an error occures' do + let(:error_messages) { ['some error'] } + + before do + project.add_maintainer(user) + end + + it 'returns the errors in the response' do + allow_next_found_instance_of(::Packages::PackageFile) do |package_file| + allow(package_file).to receive(:destroy).and_return(false) + allow(package_file).to receive_message_chain(:errors, :full_messages).and_return(error_messages) + end + + mutation_request + + expect(mutation_response['errors']).to eq(error_messages) + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb index 43d846cb297..77fd6cddc09 100644 --- a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb @@ -58,7 +58,7 @@ RSpec.describe 'Mark snippet as spam' do end it 'marks snippet as spam' do - expect_next(Spam::MarkAsSpamService, target: snippet) + expect_next(Spam::AkismetMarkAsSpamService, target: snippet) .to receive(:execute).and_return(true) post_graphql_mutation(mutation, current_user: current_user) diff --git a/spec/requests/api/graphql/packages/nuget_spec.rb b/spec/requests/api/graphql/packages/nuget_spec.rb index 1de16009684..ba8d2ca42d2 100644 --- a/spec/requests/api/graphql/packages/nuget_spec.rb +++ b/spec/requests/api/graphql/packages/nuget_spec.rb @@ -6,8 +6,11 @@ RSpec.describe 'nuget package details' do include_context 'package details setup' let_it_be(:package) { create(:nuget_package, :with_metadatum, project: project) } + let_it_be(:dependency_link) { create(:packages_dependency_link, :with_nuget_metadatum, package: package) } let(:metadata) { query_graphql_fragment('NugetMetadata') } + let(:dependency_link_response) { graphql_data_at(:package, :dependency_links, :nodes, 0) } + let(:dependency_response) { graphql_data_at(:package, :dependency_links, :nodes, 0, :dependency) } subject { post_graphql(query, current_user: user) } @@ -26,4 +29,34 @@ RSpec.describe 'nuget package details' do 'iconUrl' => package.nuget_metadatum.icon_url ) end + + it 'has dependency links' do + expect(dependency_link_response).to include( + 'id' => global_id_of(dependency_link), + 'dependencyType' => dependency_link.dependency_type.upcase + ) + + expect(dependency_response).to include( + 'id' => global_id_of(dependency_link.dependency), + 'name' => dependency_link.dependency.name, + 'versionPattern' => dependency_link.dependency.version_pattern + ) + end + + it 'avoids N+1 queries' do + first_user = create(:user) + second_user = create(:user) + + control_count = ActiveRecord::QueryRecorder.new do + post_graphql(query, current_user: first_user) + end + + create_list(:packages_dependency_link, 10, :with_nuget_metadatum, package: package) + + expect do + post_graphql(query, current_user: second_user) + end.not_to exceed_query_limit(control_count) + + expect(response).to have_gitlab_http_status(:ok) + end end diff --git a/spec/requests/api/graphql/project/alert_management/alert/issue_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/issue_spec.rb index 05a98a9dd9c..29896c16f5b 100644 --- a/spec/requests/api/graphql/project/alert_management/alert/issue_spec.rb +++ b/spec/requests/api/graphql/project/alert_management/alert/issue_spec.rb @@ -40,7 +40,7 @@ RSpec.describe 'getting Alert Management Alert Issue' do context 'with gitlab alert' do before do - create(:alert_management_alert, :with_issue, project: project, payload: payload) + create(:alert_management_alert, :with_incident, project: project, payload: payload) end it 'includes the correct alert issue payload data' do @@ -57,7 +57,7 @@ RSpec.describe 'getting Alert Management Alert Issue' do context 'with gitlab alert' do before do - create(:alert_management_alert, :with_issue, project: project, payload: payload) + create(:alert_management_alert, :with_incident, project: project, payload: payload) end it 'avoids N+1 queries' do diff --git a/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb b/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb index 14fabaaf032..40a3281d3b7 100644 --- a/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb +++ b/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'getting a detailed sentry error' do let_it_be(:project) { create(:project, :repository) } let_it_be(:project_setting) { create(:project_error_tracking_setting, project: project) } let_it_be(:current_user) { project.owner } - let_it_be(:sentry_detailed_error) { build(:detailed_error_tracking_error) } + let_it_be(:sentry_detailed_error) { build(:error_tracking_sentry_detailed_error) } let(:sentry_gid) { sentry_detailed_error.to_global_id.to_s } let(:fields) do diff --git a/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb index e71e5a48ddc..80376f56ee8 100644 --- a/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb +++ b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb @@ -16,7 +16,7 @@ RSpec.describe 'sentry errors requests' do end describe 'getting a detailed sentry error' do - let_it_be(:sentry_detailed_error) { build(:detailed_error_tracking_error) } + let_it_be(:sentry_detailed_error) { build(:error_tracking_sentry_detailed_error) } let(:sentry_gid) { sentry_detailed_error.to_global_id.to_s } @@ -97,7 +97,7 @@ RSpec.describe 'sentry errors requests' do end describe 'getting an errors list' do - let_it_be(:sentry_error) { build(:error_tracking_error) } + let_it_be(:sentry_error) { build(:error_tracking_sentry_error) } let_it_be(:pagination) do { 'next' => { 'cursor' => '2222' }, @@ -193,7 +193,7 @@ RSpec.describe 'sentry errors requests' do end describe 'getting a stack trace' do - let_it_be(:sentry_stack_trace) { build(:error_tracking_error_event) } + let_it_be(:sentry_stack_trace) { build(:error_tracking_sentry_error_event) } let(:sentry_gid) { global_id_of(Gitlab::ErrorTracking::DetailedError.new(id: 1)) } diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb index dd9d44136e5..ff0d7ecceb5 100644 --- a/spec/requests/api/graphql/project/issues_spec.rb +++ b/spec/requests/api/graphql/project/issues_spec.rb @@ -323,7 +323,7 @@ RSpec.describe 'getting an issue list for a project' do it 'avoids N+1 queries' do control = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) } - create(:alert_management_alert, :with_issue, project: project) + create(:alert_management_alert, :with_incident, project: project) expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(control) end @@ -476,6 +476,17 @@ RSpec.describe 'getting an issue list for a project' do include_examples 'N+1 query check' end + context 'when requesting `merge_requests_count`' do + let(:requested_fields) { [:merge_requests_count] } + + before do + create_list(:merge_requests_closing_issues, 2, issue: issue_a) + create_list(:merge_requests_closing_issues, 3, issue: issue_b) + end + + include_examples 'N+1 query check' + end + context 'when requesting `timelogs`' do let(:requested_fields) { 'timelogs { nodes { timeSpent } }' } diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb index 7fc1ef05fa7..1b0405be09c 100644 --- a/spec/requests/api/graphql/project/merge_requests_spec.rb +++ b/spec/requests/api/graphql/project/merge_requests_spec.rb @@ -422,6 +422,46 @@ RSpec.describe 'getting merge request listings nested in a project' do end end end + + context 'when sorting by closed_at DESC' do + let(:sort_param) { :CLOSED_AT_DESC } + let(:expected_results) do + [ + merge_request_b, + merge_request_d, + merge_request_c, + merge_request_e, + merge_request_a + ].map { |mr| global_id_of(mr) } + end + + before do + five_days_ago = 5.days.ago + + merge_request_d.metrics.update!(latest_closed_at: five_days_ago) + + # same merged_at, the second order column will decide (merge_request.id) + merge_request_c.metrics.update!(latest_closed_at: five_days_ago) + + merge_request_b.metrics.update!(latest_closed_at: 1.day.ago) + end + + it_behaves_like 'sorted paginated query' do + let(:first_param) { 2 } + end + + context 'when last parameter is given' do + let(:params) { graphql_args(sort: sort_param, last: 2) } + let(:page_info) { nil } + + it 'takes the last 2 records' do + query = pagination_query(params) + post_graphql(query, current_user: current_user) + + expect(results.map { |item| item["id"] }).to eq(expected_results.last(2)) + end + end + end end context 'when only the count is requested' do diff --git a/spec/requests/api/graphql/project/repository_spec.rb b/spec/requests/api/graphql/project/repository_spec.rb index bddd300e27f..8810f2fa3d5 100644 --- a/spec/requests/api/graphql/project/repository_spec.rb +++ b/spec/requests/api/graphql/project/repository_spec.rb @@ -83,4 +83,26 @@ RSpec.describe 'getting a repository in a project' do expect(graphql_data['project']['repository']).to be_nil end end + + context 'when paginated tree requested' do + let(:fields) do + %( + paginatedTree { + nodes { + trees { + nodes { + path + } + } + } + } + ) + end + + it 'returns paginated tree' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['repository']['paginatedTree']).to be_present + end + end end diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb index 7b081bb7568..7d182a3414b 100644 --- a/spec/requests/api/graphql_spec.rb +++ b/spec/requests/api/graphql_spec.rb @@ -385,7 +385,7 @@ RSpec.describe 'GraphQL' do context 'authenticated user' do subject { post_graphql(query, current_user: user) } - it 'does not raise an error as it uses the `AUTHENTICATED_COMPLEXITY`' do + it 'does not raise an error as it uses the `AUTHENTICATED_MAX_COMPLEXITY`' do subject expect(graphql_errors).to be_nil diff --git a/spec/requests/api/group_debian_distributions_spec.rb b/spec/requests/api/group_debian_distributions_spec.rb new file mode 100644 index 00000000000..ec1912b72bf --- /dev/null +++ b/spec/requests/api/group_debian_distributions_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe API::GroupDebianDistributions do + include HttpBasicAuthHelpers + include WorkhorseHelpers + + include_context 'Debian repository shared context', :group, false do + describe 'POST groups/:id/-/debian_distributions' do + let(:method) { :post } + let(:url) { "/groups/#{container.id}/-/debian_distributions" } + let(:api_params) { { 'codename': 'my-codename' } } + + it_behaves_like 'Debian repository write endpoint', 'POST distribution request', :created, /^{.*"codename":"my-codename",.*"components":\["main"\],.*"architectures":\["all","amd64"\]/, authenticate_non_public: false + end + + describe 'GET groups/:id/-/debian_distributions' do + let(:url) { "/groups/#{container.id}/-/debian_distributions" } + + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^\[{.*"codename":"existing-codename",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/, authenticate_non_public: false + end + + describe 'GET groups/:id/-/debian_distributions/:codename' do + let(:url) { "/groups/#{container.id}/-/debian_distributions/#{distribution.codename}" } + + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^{.*"codename":"existing-codename",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/, authenticate_non_public: false + end + + describe 'PUT groups/:id/-/debian_distributions/:codename' do + let(:method) { :put } + let(:url) { "/groups/#{container.id}/-/debian_distributions/#{distribution.codename}" } + let(:api_params) { { suite: 'my-suite' } } + + it_behaves_like 'Debian repository write endpoint', 'PUT distribution request', :success, /^{.*"codename":"existing-codename",.*"suite":"my-suite",/, authenticate_non_public: false + end + + describe 'DELETE groups/:id/-/debian_distributions/:codename' do + let(:method) { :delete } + let(:url) { "/groups/#{container.id}/-/debian_distributions/#{distribution.codename}" } + + it_behaves_like 'Debian repository maintainer write endpoint', 'DELETE distribution request', :success, /^{"message":"202 Accepted"}$/, authenticate_non_public: false + end + end +end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index ad7a2e3b1fb..30df47ccc41 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -10,7 +10,7 @@ RSpec.describe API::Groups do let_it_be(:user2) { create(:user) } let_it_be(:user3) { create(:user) } let_it_be(:admin) { create(:admin) } - let_it_be(:group1) { create(:group, avatar: File.open(uploaded_image_temp_path)) } + let_it_be(:group1) { create(:group, path: 'some_path', avatar: File.open(uploaded_image_temp_path)) } let_it_be(:group2) { create(:group, :private) } let_it_be(:project1) { create(:project, namespace: group1) } let_it_be(:project2) { create(:project, namespace: group2) } @@ -63,6 +63,19 @@ RSpec.describe API::Groups do end end + shared_examples 'skips searching in full path' do + it 'does not find groups by full path' do + subgroup = create(:group, parent: parent, path: "#{parent.path}-subgroup") + create(:group, parent: parent, path: 'not_matching_path') + + get endpoint, params: { search: parent.path } + + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(subgroup.id) + end + end + describe "GET /groups" do context "when unauthenticated" do it "returns public groups" do @@ -406,6 +419,22 @@ RSpec.describe API::Groups do expect(response_groups).to contain_exactly(group2.id, group3.id) end end + + context 'when searching' do + let_it_be(:subgroup1) { create(:group, parent: group1, path: 'some_path') } + + let(:response_groups) { json_response.map { |group| group['id'] } } + + subject { get api('/groups', user1), params: { search: group1.path } } + + it 'finds also groups with full path matching search param' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + expect(response_groups).to match_array([group1.id, subgroup1.id]) + end + end end describe "GET /groups/:id" do @@ -936,23 +965,6 @@ RSpec.describe API::Groups do expect(project_names).to eq(['Project', 'Test', 'Test Project']) end end - - context 'when `similarity_search` feature flag is off' do - before do - stub_feature_flags(similarity_search: false) - end - - it 'returns items ordered by name' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to include_pagination_headers - expect(json_response.length).to eq(2) - - project_names = json_response.map { |proj| proj['name'] } - expect(project_names).to eq(['Test', 'Test Project']) - end - end end it "returns the group's projects with simple representation" do @@ -1424,6 +1436,11 @@ RSpec.describe API::Groups do expect(json_response.first).to include('statistics') end end + + it_behaves_like 'skips searching in full path' do + let(:parent) { group1 } + let(:endpoint) { api("/groups/#{group1.id}/subgroups", user1) } + end end describe 'GET /groups/:id/descendant_groups' do @@ -1558,6 +1575,11 @@ RSpec.describe API::Groups do expect(json_response.first).to include('statistics') end end + + it_behaves_like 'skips searching in full path' do + let(:parent) { group1 } + let(:endpoint) { api("/groups/#{group1.id}/descendant_groups", user1) } + end end describe "POST /groups" do diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb index f9f03c9e55c..76a4548df8a 100644 --- a/spec/requests/api/invitations_spec.rb +++ b/spec/requests/api/invitations_spec.rb @@ -152,6 +152,20 @@ RSpec.describe API::Invitations do end end + context 'with areas_of_focus', :snowplow do + it 'tracks the areas_of_focus from params' do + post invitations_url(source, maintainer), + params: { email: email, access_level: Member::DEVELOPER, areas_of_focus: 'Other' } + + expect_snowplow_event( + category: 'Members::InviteService', + action: 'area_of_focus', + label: 'Other', + property: source.members.last.id.to_s + ) + end + end + context 'with invite_source considerations', :snowplow do let(:params) { { email: email, access_level: Member::DEVELOPER } } diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb index d9f11b19e6e..c3fd02dad51 100644 --- a/spec/requests/api/maven_packages_spec.rb +++ b/spec/requests/api/maven_packages_spec.rb @@ -15,7 +15,7 @@ RSpec.describe API::MavenPackages do let_it_be(:package_file) { package.package_files.with_file_name_like('%.xml').first } let_it_be(:jar_file) { package.package_files.with_file_name_like('%.jar').first } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } - let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running, project: project) } + let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running) } let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } let_it_be(:deploy_token_for_group) { create(:deploy_token, :group, read_package_registry: true, write_package_registry: true) } @@ -217,6 +217,15 @@ RSpec.describe API::MavenPackages do end end + shared_examples 'successfully returning the file' do + it 'returns the file', :aggregate_failures do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response.media_type).to eq('application/octet-stream') + end + end + describe 'GET /api/v4/packages/maven/*path/:file_name' do context 'a public project' do subject { download_file(file_name: package_file.file_name) } @@ -224,12 +233,7 @@ RSpec.describe API::MavenPackages do shared_examples 'getting a file' do it_behaves_like 'tracking the file download event' - it 'returns the file' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') - end + it_behaves_like 'successfully returning the file' it 'returns sha1 of the file' do download_file(file_name: package_file.file_name + '.sha1') @@ -260,12 +264,7 @@ RSpec.describe API::MavenPackages do shared_examples 'getting a file' do it_behaves_like 'tracking the file download event' - it 'returns the file' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') - end + it_behaves_like 'successfully returning the file' it 'denies download when no private token' do download_file(file_name: package_file.file_name) @@ -297,12 +296,7 @@ RSpec.describe API::MavenPackages do shared_examples 'getting a file' do it_behaves_like 'tracking the file download event' - it 'returns the file' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') - end + it_behaves_like 'successfully returning the file' it 'denies download when not enough permissions' do unless project.root_namespace == user.namespace @@ -409,12 +403,7 @@ RSpec.describe API::MavenPackages do shared_examples 'getting a file for a group' do it_behaves_like 'tracking the file download event' - it 'returns the file' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') - end + it_behaves_like 'successfully returning the file' it 'returns sha1 of the file' do download_file(file_name: package_file.file_name + '.sha1') @@ -445,12 +434,7 @@ RSpec.describe API::MavenPackages do shared_examples 'getting a file for a group' do it_behaves_like 'tracking the file download event' - it 'returns the file' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') - end + it_behaves_like 'successfully returning the file' it 'denies download when no private token' do download_file(file_name: package_file.file_name) @@ -482,12 +466,7 @@ RSpec.describe API::MavenPackages do shared_examples 'getting a file for a group' do it_behaves_like 'tracking the file download event' - it 'returns the file' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') - end + it_behaves_like 'successfully returning the file' it 'denies download when not enough permissions' do group.add_guest(user) @@ -516,12 +495,7 @@ RSpec.describe API::MavenPackages do context 'with group deploy token' do subject { download_file_with_token(file_name: package_file.file_name, request_headers: group_deploy_token_headers) } - it 'returns the file' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') - end + it_behaves_like 'successfully returning the file' it 'returns the file with only write_package_registry scope' do deploy_token_for_group.update!(read_package_registry: false) @@ -553,12 +527,7 @@ RSpec.describe API::MavenPackages do group.add_reporter(user) end - it 'returns the file' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') - end + it_behaves_like 'successfully returning the file' context 'with a non existing maven path' do subject { download_file_with_token(file_name: package_file.file_name, path: 'foo/bar/1.2.3', request_headers: headers_with_token, group_id: root_group.id) } @@ -657,12 +626,7 @@ RSpec.describe API::MavenPackages do it_behaves_like 'tracking the file download event' - it 'returns the file' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') - end + it_behaves_like 'successfully returning the file' it 'returns sha1 of the file' do download_file(file_name: package_file.file_name + '.sha1') @@ -672,6 +636,19 @@ RSpec.describe API::MavenPackages do expect(response.body).to eq(package_file.file_sha1) end + context 'when the repository is disabled' do + before do + project.project_feature.update!( + # Disable merge_requests and builds as well, since merge_requests and + # builds cannot have higher visibility than repository. + merge_requests_access_level: ProjectFeature::DISABLED, + builds_access_level: ProjectFeature::DISABLED, + repository_access_level: ProjectFeature::DISABLED) + end + + it_behaves_like 'successfully returning the file' + end + context 'with a non existing maven path' do subject { download_file(file_name: package_file.file_name, path: 'foo/bar/1.2.3') } @@ -688,12 +665,7 @@ RSpec.describe API::MavenPackages do it_behaves_like 'tracking the file download event' - it 'returns the file' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') - end + it_behaves_like 'successfully returning the file' it 'denies download when not enough permissions' do project.add_guest(user) diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index cac1b95e854..48ded93d85f 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -409,6 +409,53 @@ RSpec.describe API::Members do end end + context 'with areas_of_focus considerations', :snowplow do + context 'when there is 1 user to add' do + let(:user_id) { stranger.id } + + context 'when areas_of_focus is present in params' do + it 'tracks the areas_of_focus' do + post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), + params: { user_id: user_id, access_level: Member::DEVELOPER, areas_of_focus: 'Other' } + + expect_snowplow_event( + category: 'Members::CreateService', + action: 'area_of_focus', + label: 'Other', + property: source.members.last.id.to_s + ) + end + end + + context 'when areas_of_focus is not present in params' do + it 'does not track the areas_of_focus' do + post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), + params: { user_id: user_id, access_level: Member::DEVELOPER } + + expect_no_snowplow_event(category: 'Members::CreateService', action: 'area_of_focus') + end + end + end + + context 'when there are multiple users to add' do + let(:user_id) { [developer.id, stranger.id].join(',') } + + context 'when areas_of_focus is present in params' do + it 'tracks the areas_of_focus' do + post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), + params: { user_id: user_id, access_level: Member::DEVELOPER, areas_of_focus: 'Other' } + + expect_snowplow_event( + category: 'Members::CreateService', + action: 'area_of_focus', + label: 'Other', + property: source.members.last.id.to_s + ) + end + end + end + end + it "returns 409 if member already exists" do post api("/#{source_type.pluralize}/#{source.id}/members", maintainer), params: { user_id: maintainer.id, access_level: Member::MAINTAINER } diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 1ed06a40f16..222d8992d1b 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -91,6 +91,19 @@ RSpec.describe API::Namespaces do expect(json_response).to be_an Array expect(json_response.length).to eq(1) end + + context 'with owned_only param' do + it 'returns only owned groups' do + group1.add_developer(user) + group2.add_owner(user) + + get api("/namespaces?owned_only=true", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response.map { |resource| resource['id'] }).to match_array([user.namespace_id, group2.id]) + end + end end end diff --git a/spec/requests/api/npm_project_packages_spec.rb b/spec/requests/api/npm_project_packages_spec.rb index ab74da4bda4..8c35a1642e2 100644 --- a/spec/requests/api/npm_project_packages_spec.rb +++ b/spec/requests/api/npm_project_packages_spec.rb @@ -78,7 +78,7 @@ RSpec.describe API::NpmProjectPackages do context 'with a job token for a different user' do let_it_be(:other_user) { create(:user) } - let_it_be_with_reload(:other_job) { create(:ci_build, :running, user: other_user, project: project) } + let_it_be_with_reload(:other_job) { create(:ci_build, :running, user: other_user) } let(:headers) { build_token_auth_header(other_job.token) } @@ -161,8 +161,10 @@ RSpec.describe API::NpmProjectPackages do end end - context 'valid package record' do - let(:params) { upload_params(package_name: package_name) } + context 'valid package params' do + let_it_be(:version) { '1.2.3' } + + let(:params) { upload_params(package_name: package_name, package_version: version) } let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } } shared_examples 'handling upload with different authentications' do @@ -211,6 +213,15 @@ RSpec.describe API::NpmProjectPackages do end end + shared_examples 'uploading the package' do + it 'uploads the package' do + expect { upload_package_with_token(package_name, params) } + .to change { project.packages.count }.by(1) + + expect(response).to have_gitlab_http_status(:ok) + end + end + context 'with a scoped name' do let(:package_name) { "@#{group.path}/my_package_name" } @@ -228,6 +239,32 @@ RSpec.describe API::NpmProjectPackages do it_behaves_like 'handling upload with different authentications' end + + context 'with an existing package' do + let_it_be(:second_project) { create(:project, namespace: namespace) } + + context 'following the naming convention' do + let_it_be(:second_package) { create(:npm_package, project: second_project, name: "@#{group.path}/test", version: version) } + + let(:package_name) { "@#{group.path}/test" } + + it_behaves_like 'handling invalid record with 400 error' + + context 'with a new version' do + let_it_be(:version) { '4.5.6' } + + it_behaves_like 'uploading the package' + end + end + + context 'not following the naming convention' do + let_it_be(:second_package) { create(:npm_package, project: second_project, name: "@any_scope/test", version: version) } + + let(:package_name) { "@any_scope/test" } + + it_behaves_like 'uploading the package' + end + end end context 'package creation fails' do diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml index 8341fac3191..c5bcedd491a 100644 --- a/spec/requests/api/project_attributes.yml +++ b/spec/requests/api/project_attributes.yml @@ -89,6 +89,8 @@ ci_cd_settings: - group_runners_enabled - merge_pipelines_enabled - merge_trains_enabled + - merge_pipelines_enabled + - merge_trains_enabled - auto_rollback_enabled remapped_attributes: default_git_depth: ci_default_git_depth @@ -119,7 +121,6 @@ project_feature: - project_id - requirements_access_level - security_and_compliance_access_level - - container_registry_access_level - updated_at computed_attributes: - issues_enabled @@ -132,7 +133,6 @@ project_feature: project_setting: unexposed_attributes: - - allow_editing_commit_messages - created_at - has_confluence - has_vulnerabilities diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb index 606279ec20a..8c9a93cf9fa 100644 --- a/spec/requests/api/project_milestones_spec.rb +++ b/spec/requests/api/project_milestones_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe API::ProjectMilestones do let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, namespace: user.namespace ) } + let_it_be_with_reload(:project) { create(:project, namespace: user.namespace ) } let_it_be(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') } let_it_be(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') } let_it_be(:route) { "/projects/#{project.id}/milestones" } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index a869866c698..3622eedfed5 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -207,6 +207,18 @@ RSpec.describe API::Projects do let(:current_user) { user } end + it 'includes container_registry_access_level', :aggregate_failures do + project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED) + + get api('/projects', user) + project_response = json_response.find { |p| p['id'] == project.id } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + expect(project_response['container_registry_access_level']).to eq('disabled') + expect(project_response['container_registry_enabled']).to eq(false) + end + context 'when some projects are in a group' do before do create(:project, :public, group: create(:group)) @@ -219,7 +231,6 @@ RSpec.describe API::Projects do end it 'includes correct value of container_registry_enabled', :aggregate_failures do - project.update_column(:container_registry_enabled, true) project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED) get api('/projects', user) @@ -700,52 +711,112 @@ RSpec.describe API::Projects do end end - context 'sorting by project statistics' do - %w(repository_size storage_size wiki_size packages_size).each do |order_by| - context "sorting by #{order_by}" do - before do - ProjectStatistics.update_all(order_by => 100) - project4.statistics.update_columns(order_by => 10) - project.statistics.update_columns(order_by => 200) - end + context 'sorting' do + context 'by project statistics' do + %w(repository_size storage_size wiki_size packages_size).each do |order_by| + context "sorting by #{order_by}" do + before do + ProjectStatistics.update_all(order_by => 100) + project4.statistics.update_columns(order_by => 10) + project.statistics.update_columns(order_by => 200) + end - context 'admin user' do - let(:current_user) { admin } + context 'admin user' do + let(:current_user) { admin } - context "when sorting by #{order_by} ascendingly" do - it 'returns a properly sorted list of projects' do - get api('/projects', current_user), params: { order_by: order_by, sort: :asc } + context "when sorting by #{order_by} ascendingly" do + it 'returns a properly sorted list of projects' do + get api('/projects', current_user), params: { order_by: order_by, sort: :asc } - expect(response).to have_gitlab_http_status(:ok) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['id']).to eq(project4.id) + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['id']).to eq(project4.id) + end + end + + context "when sorting by #{order_by} descendingly" do + it 'returns a properly sorted list of projects' do + get api('/projects', current_user), params: { order_by: order_by, sort: :desc } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['id']).to eq(project.id) + end end end - context "when sorting by #{order_by} descendingly" do - it 'returns a properly sorted list of projects' do - get api('/projects', current_user), params: { order_by: order_by, sort: :desc } + context 'non-admin user' do + let(:current_user) { user } + + it 'returns projects ordered normally' do + get api('/projects', current_user), params: { order_by: order_by } expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.first['id']).to eq(project.id) + expect(json_response.map { |project| project['id'] }).to eq(user_projects.map(&:id).sort.reverse) end end end + end + end - context 'non-admin user' do - let(:current_user) { user } + context 'by similarity', :aggregate_failures do + let_it_be(:group_with_projects) { create(:group) } + let_it_be(:project_1) { create(:project, name: 'Project', path: 'project', group: group_with_projects) } + let_it_be(:project_2) { create(:project, name: 'Test Project', path: 'test-project', group: group_with_projects) } + let_it_be(:project_3) { create(:project, name: 'Test', path: 'test', group: group_with_projects) } + let_it_be(:project_4) { create(:project, :public, name: 'Test Public Project') } - it 'returns projects ordered normally' do - get api('/projects', current_user), params: { order_by: order_by } + let(:current_user) { user } + let(:params) { { order_by: 'similarity', search: 'test' } } - expect(response).to have_gitlab_http_status(:ok) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.map { |project| project['id'] }).to eq(user_projects.map(&:id).sort.reverse) - end + subject { get api('/projects', current_user), params: params } + + before do + group_with_projects.add_owner(current_user) + end + + it 'returns non-public items based ordered by similarity' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response.length).to eq(2) + + project_names = json_response.map { |proj| proj['name'] } + expect(project_names).to contain_exactly('Test', 'Test Project') + end + + context 'when `search` parameter is not given' do + let(:params) { { order_by: 'similarity' } } + + it 'returns items ordered by created_at descending' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response.length).to eq(8) + + project_names = json_response.map { |proj| proj['name'] } + expect(project_names).to contain_exactly(project.name, project2.name, 'second_project', 'public_project', 'Project', 'Test Project', 'Test Public Project', 'Test') + end + end + + context 'when called anonymously' do + let(:current_user) { nil } + + it 'returns items ordered by created_at descending' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response.length).to eq(1) + + project_names = json_response.map { |proj| proj['name'] } + expect(project_names).to contain_exactly('Test Public Project') end end end @@ -982,7 +1053,7 @@ RSpec.describe API::Projects do expect(response).to have_gitlab_http_status(:bad_request) end - it "assigns attributes to project" do + it "assigns attributes to project", :aggregate_failures do project = attributes_for(:project, { path: 'camelCasePath', issues_enabled: false, @@ -1004,6 +1075,7 @@ RSpec.describe API::Projects do }).tap do |attrs| attrs[:operations_access_level] = 'disabled' attrs[:analytics_access_level] = 'disabled' + attrs[:container_registry_access_level] = 'private' end post api('/projects', user), params: project @@ -1011,7 +1083,10 @@ RSpec.describe API::Projects do expect(response).to have_gitlab_http_status(:created) project.each_pair do |k, v| - next if %i[has_external_issue_tracker has_external_wiki issues_enabled merge_requests_enabled wiki_enabled storage_version].include?(k) + next if %i[ + has_external_issue_tracker has_external_wiki issues_enabled merge_requests_enabled wiki_enabled storage_version + container_registry_access_level + ].include?(k) expect(json_response[k.to_s]).to eq(v) end @@ -1023,6 +1098,28 @@ RSpec.describe API::Projects do expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED) expect(project.operations_access_level).to eq(ProjectFeature::DISABLED) expect(project.project_feature.analytics_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.container_registry_access_level).to eq(ProjectFeature::PRIVATE) + end + + it 'assigns container_registry_enabled to project', :aggregate_failures do + project = attributes_for(:project, { container_registry_enabled: true }) + + post api('/projects', user), params: project + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['container_registry_enabled']).to eq(true) + expect(json_response['container_registry_access_level']).to eq('enabled') + expect(Project.find_by(path: project[:path]).container_registry_access_level).to eq(ProjectFeature::ENABLED) + end + + it 'assigns container_registry_enabled to project' do + project = attributes_for(:project, { container_registry_enabled: true }) + + post api('/projects', user), params: project + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['container_registry_enabled']).to eq(true) + expect(Project.find_by(path: project[:path]).container_registry_access_level).to eq(ProjectFeature::ENABLED) end it 'creates a project using a template' do @@ -1280,6 +1377,14 @@ RSpec.describe API::Projects do expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id) end + it 'includes container_registry_access_level', :aggregate_failures do + get api("/users/#{user4.id}/projects/", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + expect(json_response.first.keys).to include('container_registry_access_level') + end + context 'and using id_after' do let!(:another_public_project) { create(:project, :public, name: 'another_public_project', creator_id: user4.id, namespace: user4.namespace) } @@ -1464,6 +1569,18 @@ RSpec.describe API::Projects do expect(json_response['error']).to eq('name is missing') end + it 'sets container_registry_enabled' do + project = attributes_for(:project).tap do |attrs| + attrs[:container_registry_enabled] = true + end + + post api("/projects/user/#{user.id}", admin), params: project + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['container_registry_enabled']).to eq(true) + expect(Project.find_by(path: project[:path]).container_registry_access_level).to eq(ProjectFeature::ENABLED) + end + it 'assigns attributes to project' do project = attributes_for(:project, { issues_enabled: false, @@ -1589,6 +1706,59 @@ RSpec.describe API::Projects do expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy end + + context 'container_registry_enabled' do + using RSpec::Parameterized::TableSyntax + + where(:container_registry_enabled, :container_registry_access_level) do + true | ProjectFeature::ENABLED + false | ProjectFeature::DISABLED + end + + with_them do + it 'setting container_registry_enabled also sets container_registry_access_level', :aggregate_failures do + project_attributes = attributes_for(:project).tap do |attrs| + attrs[:container_registry_enabled] = container_registry_enabled + end + + post api("/projects/user/#{user.id}", admin), params: project_attributes + + project = Project.find_by(path: project_attributes[:path]) + expect(response).to have_gitlab_http_status(:created) + expect(json_response['container_registry_access_level']).to eq(ProjectFeature.str_from_access_level(container_registry_access_level)) + expect(json_response['container_registry_enabled']).to eq(container_registry_enabled) + expect(project.container_registry_access_level).to eq(container_registry_access_level) + expect(project.container_registry_enabled).to eq(container_registry_enabled) + end + end + end + + context 'container_registry_access_level' do + using RSpec::Parameterized::TableSyntax + + where(:container_registry_access_level, :container_registry_enabled) do + 'enabled' | true + 'private' | true + 'disabled' | false + end + + with_them do + it 'setting container_registry_access_level also sets container_registry_enabled', :aggregate_failures do + project_attributes = attributes_for(:project).tap do |attrs| + attrs[:container_registry_access_level] = container_registry_access_level + end + + post api("/projects/user/#{user.id}", admin), params: project_attributes + + project = Project.find_by(path: project_attributes[:path]) + expect(response).to have_gitlab_http_status(:created) + expect(json_response['container_registry_access_level']).to eq(container_registry_access_level) + expect(json_response['container_registry_enabled']).to eq(container_registry_enabled) + expect(project.container_registry_access_level).to eq(ProjectFeature.access_level_from_str(container_registry_access_level)) + expect(project.container_registry_enabled).to eq(container_registry_enabled) + end + end + end end describe "POST /projects/:id/uploads/authorize" do @@ -1974,6 +2144,7 @@ RSpec.describe API::Projects do expect(json_response['jobs_enabled']).to be_present expect(json_response['snippets_enabled']).to be_present expect(json_response['container_registry_enabled']).to be_present + expect(json_response['container_registry_access_level']).to be_present expect(json_response['created_at']).to be_present expect(json_response['last_activity_at']).to be_present expect(json_response['shared_runners_enabled']).to be_present @@ -2065,6 +2236,7 @@ RSpec.describe API::Projects do expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions) expect(json_response['remove_source_branch_after_merge']).to be_truthy expect(json_response['container_registry_enabled']).to be_present + expect(json_response['container_registry_access_level']).to be_present expect(json_response['created_at']).to be_present expect(json_response['last_activity_at']).to be_present expect(json_response['shared_runners_enabled']).to be_present @@ -2865,6 +3037,59 @@ RSpec.describe API::Projects do end end + describe 'POST /projects/:id/import_project_members/:project_id' do + let_it_be(:project2) { create(:project) } + let_it_be(:project2_user) { create(:user) } + + before_all do + project.add_maintainer(user) + project2.add_maintainer(user) + project2.add_developer(project2_user) + end + + it 'returns 200 when it successfully imports members from another project' do + expect do + post api("/projects/#{project.id}/import_project_members/#{project2.id}", user) + end.to change { project.members.count }.by(2) + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['message']).to eq('Successfully imported') + end + + it 'returns 404 if the source project does not exist' do + expect do + post api("/projects/#{project.id}/import_project_members/#{non_existing_record_id}", user) + end.not_to change { project.members.count } + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'returns 404 if the target project members cannot be administered by the requester' do + private_project = create(:project, :private) + + expect do + post api("/projects/#{private_project.id}/import_project_members/#{project2.id}", user) + end.not_to change { project.members.count } + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'returns 422 if the import failed for valid projects' do + allow_next_instance_of(::ProjectTeam) do |project_team| + allow(project_team).to receive(:import).and_return(false) + end + + expect do + post api("/projects/#{project.id}/import_project_members/#{project2.id}", user) + end.not_to change { project.members.count } + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + expect(json_response['message']).to eq('Import failed') + end + end + describe 'PUT /projects/:id' do before do expect(project).to be_persisted @@ -2891,6 +3116,24 @@ RSpec.describe API::Projects do end end + it 'sets container_registry_access_level', :aggregate_failures do + put api("/projects/#{project.id}", user), params: { container_registry_access_level: 'private' } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['container_registry_access_level']).to eq('private') + expect(Project.find_by(path: project[:path]).container_registry_access_level).to eq(ProjectFeature::PRIVATE) + end + + it 'sets container_registry_enabled' do + project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED) + + put(api("/projects/#{project.id}", user), params: { container_registry_enabled: true }) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['container_registry_enabled']).to eq(true) + expect(project.reload.container_registry_access_level).to eq(ProjectFeature::ENABLED) + end + it 'returns 400 when nothing sent' do project_param = {} diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb index e66326db2a2..8df2460a2b6 100644 --- a/spec/requests/api/pypi_packages_spec.rb +++ b/spec/requests/api/pypi_packages_spec.rb @@ -13,7 +13,7 @@ RSpec.describe API::PypiPackages do let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } - let_it_be(:job) { create(:ci_build, :running, user: user, project: project) } + let_it_be(:job) { create(:ci_build, :running, user: user) } let(:headers) { {} } @@ -23,7 +23,8 @@ RSpec.describe API::PypiPackages do subject { get api(url), headers: headers } describe 'GET /api/v4/groups/:id/-/packages/pypi/simple/:package_name' do - let(:url) { "/groups/#{group.id}/-/packages/pypi/simple/#{package.name}" } + let(:package_name) { package.name } + let(:url) { "/groups/#{group.id}/-/packages/pypi/simple/#{package_name}" } let(:snowplow_gitlab_standard_context) { {} } it_behaves_like 'pypi simple API endpoint' @@ -38,6 +39,12 @@ RSpec.describe API::PypiPackages do end it_behaves_like 'deploy token for package GET requests' + + context 'with group path as id' do + let(:url) { "/groups/#{CGI.escape(group.full_path)}/-/packages/pypi/simple/#{package_name}"} + + it_behaves_like 'deploy token for package GET requests' + end end context 'job token' do @@ -54,13 +61,20 @@ RSpec.describe API::PypiPackages do end describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do - let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package.name}" } + let(:package_name) { package.name } + let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package_name}" } let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } it_behaves_like 'pypi simple API endpoint' it_behaves_like 'rejects PyPI access with unknown project id' it_behaves_like 'deploy token for package GET requests' it_behaves_like 'job token for package GET requests' + + context 'with project path as id' do + let(:url) { "/projects/#{CGI.escape(project.full_path)}/packages/pypi/simple/#{package.name}" } + + it_behaves_like 'deploy token for package GET requests' + end end end diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index 03e0954e5ab..87b08587904 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -811,7 +811,7 @@ RSpec.describe API::Releases do end context 'when using JOB-TOKEN auth' do - let(:job) { create(:ci_build, user: maintainer, project: project) } + let(:job) { create(:ci_build, user: maintainer) } let(:params) do { name: 'Another release', diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index d019e89e0b4..d3262b8056b 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -107,13 +107,18 @@ RSpec.describe API::Repositories do shared_examples_for 'repository blob' do it 'returns blob attributes as json' do + stub_const("Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE", 5) + get api(route, current_user) expect(response).to have_gitlab_http_status(:ok) expect(json_response['size']).to eq(111) expect(json_response['encoding']).to eq("base64") - expect(Base64.decode64(json_response['content']).lines.first).to eq("class Commit\n") expect(json_response['sha']).to eq(sample_blob.oid) + + content = Base64.decode64(json_response['content']) + expect(content.lines.first).to eq("class Commit\n") + expect(content).to eq(project.repository.gitaly_blob_client.get_blob(oid: sample_blob.oid, limit: -1).data) end context 'when sha does not exist' do @@ -164,7 +169,10 @@ RSpec.describe API::Repositories do shared_examples_for 'repository raw blob' do it 'returns the repository raw blob' do - expect(Gitlab::Workhorse).to receive(:send_git_blob) + expect(Gitlab::Workhorse).to receive(:send_git_blob) do |_, blob| + expect(blob.id).to eq(sample_blob.oid) + expect(blob.loaded_size).to eq(0) + end get api(route, current_user) diff --git a/spec/requests/api/rubygem_packages_spec.rb b/spec/requests/api/rubygem_packages_spec.rb index 9b104520b52..afa7adad80c 100644 --- a/spec/requests/api/rubygem_packages_spec.rb +++ b/spec/requests/api/rubygem_packages_spec.rb @@ -10,7 +10,7 @@ RSpec.describe API::RubygemPackages do let_it_be_with_reload(:project) { create(:project) } let_it_be(:personal_access_token) { create(:personal_access_token) } let_it_be(:user) { personal_access_token.user } - let_it_be(:job) { create(:ci_build, :running, user: user, project: project) } + let_it_be(:job) { create(:ci_build, :running, user: user) } let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } let_it_be(:headers) { {} } diff --git a/spec/requests/api/statistics_spec.rb b/spec/requests/api/statistics_spec.rb index eab97b6916e..baffb2792e9 100644 --- a/spec/requests/api/statistics_spec.rb +++ b/spec/requests/api/statistics_spec.rb @@ -63,7 +63,7 @@ RSpec.describe API::Statistics, 'Statistics' do # Make sure the reltuples have been updated # to get a correct count on postgresql tables_to_analyze.each do |table| - ActiveRecord::Base.connection.execute("ANALYZE #{table}") + ApplicationRecord.connection.execute("ANALYZE #{table}") end get api(path, admin) diff --git a/spec/requests/api/terraform/modules/v1/packages_spec.rb b/spec/requests/api/terraform/modules/v1/packages_spec.rb index b04f5ad9a94..6803c09b8c2 100644 --- a/spec/requests/api/terraform/modules/v1/packages_spec.rb +++ b/spec/requests/api/terraform/modules/v1/packages_spec.rb @@ -12,7 +12,7 @@ RSpec.describe API::Terraform::Modules::V1::Packages do let_it_be(:package) { create(:terraform_module_package, project: project) } let_it_be(:personal_access_token) { create(:personal_access_token) } let_it_be(:user) { personal_access_token.user } - let_it_be(:job) { create(:ci_build, :running, user: user, project: project) } + let_it_be(:job) { create(:ci_build, :running, user: user) } let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } diff --git a/spec/requests/api/user_counts_spec.rb b/spec/requests/api/user_counts_spec.rb index 94e25d647fc..ab2aa87d1b7 100644 --- a/spec/requests/api/user_counts_spec.rb +++ b/spec/requests/api/user_counts_spec.rb @@ -3,8 +3,10 @@ require 'spec_helper' RSpec.describe API::UserCounts do - let(:user) { create(:user) } - let(:project) { create(:project, :public) } + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:issue) { create(:issue, project: project, author: user, assignees: [user]) } + let_it_be(:todo) { create(:todo, :pending, user: user, project: project) } let!(:merge_request) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project, title: "Test") } @@ -18,22 +20,36 @@ RSpec.describe API::UserCounts do end context 'when authenticated' do - it 'returns open counts for current user' do + it 'returns assigned issue counts for current_user' do get api('/user_counts', user) - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to be_a Hash - expect(json_response['merge_requests']).to eq(1) + expect(json_response['assigned_issues']).to eq(1) end - it 'updates the mr count when a new mr is assigned' do - create(:merge_request, source_project: project, author: user, assignees: [user]) + context 'merge requests' do + it 'returns assigned MR counts for current user' do + get api('/user_counts', user) + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_a Hash + expect(json_response['merge_requests']).to eq(1) + end + + it 'updates the mr count when a new mr is assigned' do + create(:merge_request, source_project: project, author: user, assignees: [user]) + + get api('/user_counts', user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_a Hash + expect(json_response['merge_requests']).to eq(2) + end + end + + it 'returns pending todo counts for current_user' do get api('/user_counts', user) - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to be_a Hash - expect(json_response['merge_requests']).to eq(2) + expect(json_response['todos']).to eq(1) end end end diff --git a/spec/requests/api/v3/github_spec.rb b/spec/requests/api/v3/github_spec.rb index 4100b246218..255f53e4c7c 100644 --- a/spec/requests/api/v3/github_spec.rb +++ b/spec/requests/api/v3/github_spec.rb @@ -472,6 +472,17 @@ RSpec.describe API::V3::Github do expect(response).to have_gitlab_http_status(:ok) end + + context 'when the project has no repository', :aggregate_failures do + let_it_be(:project) { create(:project, creator: user) } + + it 'returns an empty collection response' do + jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/branches", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_empty + end + end end context 'unauthenticated' do |