summaryrefslogtreecommitdiff
path: root/spec/requests/api
diff options
context:
space:
mode:
Diffstat (limited to 'spec/requests/api')
-rw-r--r--spec/requests/api/bulk_imports_spec.rb42
-rw-r--r--spec/requests/api/ci/jobs_spec.rb (renamed from spec/requests/api/jobs_spec.rb)20
-rw-r--r--spec/requests/api/ci/pipelines_spec.rb104
-rw-r--r--spec/requests/api/ci/runner/jobs_artifacts_spec.rb4
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb28
-rw-r--r--spec/requests/api/ci/runner/jobs_trace_spec.rb2
-rw-r--r--spec/requests/api/ci/runner/runners_post_spec.rb64
-rw-r--r--spec/requests/api/ci/runners_spec.rb28
-rw-r--r--spec/requests/api/ci/triggers_spec.rb (renamed from spec/requests/api/triggers_spec.rb)2
-rw-r--r--spec/requests/api/ci/variables_spec.rb (renamed from spec/requests/api/variables_spec.rb)2
-rw-r--r--spec/requests/api/debian_group_packages_spec.rb31
-rw-r--r--spec/requests/api/debian_project_packages_spec.rb31
-rw-r--r--spec/requests/api/environments_spec.rb2
-rw-r--r--spec/requests/api/error_tracking_collector_spec.rb32
-rw-r--r--spec/requests/api/error_tracking_spec.rb16
-rw-r--r--spec/requests/api/feature_flags_spec.rb12
-rw-r--r--spec/requests/api/generic_packages_spec.rb2
-rw-r--r--spec/requests/api/go_proxy_spec.rb2
-rw-r--r--spec/requests/api/graphql/ci/jobs_spec.rb33
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb38
-rw-r--r--spec/requests/api/graphql/current_user_query_spec.rb11
-rw-r--r--spec/requests/api/graphql/group_query_spec.rb25
-rw-r--r--spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb45
-rw-r--r--spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb48
-rw-r--r--spec/requests/api/graphql/mutations/groups/update_spec.rb66
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/update_spec.rb83
-rw-r--r--spec/requests/api/graphql/mutations/packages/destroy_file_spec.rb93
-rw-r--r--spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb2
-rw-r--r--spec/requests/api/graphql/packages/nuget_spec.rb33
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert/issue_spec.rb4
-rw-r--r--spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb6
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb13
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb40
-rw-r--r--spec/requests/api/graphql/project/repository_spec.rb22
-rw-r--r--spec/requests/api/graphql_spec.rb2
-rw-r--r--spec/requests/api/group_debian_distributions_spec.rb44
-rw-r--r--spec/requests/api/groups_spec.rb58
-rw-r--r--spec/requests/api/invitations_spec.rb14
-rw-r--r--spec/requests/api/maven_packages_spec.rb94
-rw-r--r--spec/requests/api/members_spec.rb47
-rw-r--r--spec/requests/api/namespaces_spec.rb13
-rw-r--r--spec/requests/api/npm_project_packages_spec.rb43
-rw-r--r--spec/requests/api/project_attributes.yml4
-rw-r--r--spec/requests/api/project_milestones_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb309
-rw-r--r--spec/requests/api/pypi_packages_spec.rb20
-rw-r--r--spec/requests/api/releases_spec.rb2
-rw-r--r--spec/requests/api/repositories_spec.rb12
-rw-r--r--spec/requests/api/rubygem_packages_spec.rb2
-rw-r--r--spec/requests/api/statistics_spec.rb2
-rw-r--r--spec/requests/api/terraform/modules/v1/packages_spec.rb2
-rw-r--r--spec/requests/api/user_counts_spec.rb38
-rw-r--r--spec/requests/api/v3/github_spec.rb11
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