Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 16:37:47 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 16:37:47 +0300
commitaee0a117a889461ce8ced6fcf73207fe017f1d99 (patch)
tree891d9ef189227a8445d83f35c1b0fc99573f4380 /spec/requests/api
parent8d46af3258650d305f53b819eabf7ab18d22f59e (diff)
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'spec/requests/api')
-rw-r--r--spec/requests/api/admin/plan_limits_spec.rb7
-rw-r--r--spec/requests/api/ci/job_artifacts_spec.rb661
-rw-r--r--spec/requests/api/ci/jobs_spec.rb605
-rw-r--r--spec/requests/api/ci/pipelines_spec.rb5
-rw-r--r--spec/requests/api/ci/runner/jobs_artifacts_spec.rb12
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb4
-rw-r--r--spec/requests/api/ci/runner/runners_post_spec.rb64
-rw-r--r--spec/requests/api/ci/runners_spec.rb29
-rw-r--r--spec/requests/api/commit_statuses_spec.rb34
-rw-r--r--spec/requests/api/commits_spec.rb4
-rw-r--r--spec/requests/api/composer_packages_spec.rb14
-rw-r--r--spec/requests/api/conan_project_packages_spec.rb5
-rw-r--r--spec/requests/api/dependency_proxy_spec.rb22
-rw-r--r--spec/requests/api/error_tracking/collector_spec.rb12
-rw-r--r--spec/requests/api/graphql/boards/board_list_issues_query_spec.rb46
-rw-r--r--spec/requests/api/graphql/boards/board_list_query_spec.rb33
-rw-r--r--spec/requests/api/graphql/boards/board_lists_query_spec.rb8
-rw-r--r--spec/requests/api/graphql/ci/jobs_spec.rb95
-rw-r--r--spec/requests/api/graphql/ci/pipelines_spec.rb12
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb50
-rw-r--r--spec/requests/api/graphql/ci/runners_spec.rb9
-rw-r--r--spec/requests/api/graphql/container_repository/container_repository_details_spec.rb82
-rw-r--r--spec/requests/api/graphql/current_user/todos_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/design_management/delete_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb16
-rw-r--r--spec/requests/api/graphql/mutations/user_callouts/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/packages/package_spec.rb87
-rw-r--r--spec/requests/api/graphql/project/cluster_agents_spec.rb35
-rw-r--r--spec/requests/api/graphql/project/jobs_spec.rb56
-rw-r--r--spec/requests/api/graphql/project/pipeline_spec.rb42
-rw-r--r--spec/requests/api/graphql/project_query_spec.rb34
-rw-r--r--spec/requests/api/groups_spec.rb140
-rw-r--r--spec/requests/api/import_github_spec.rb6
-rw-r--r--spec/requests/api/invitations_spec.rb18
-rw-r--r--spec/requests/api/issues/get_project_issues_spec.rb2
-rw-r--r--spec/requests/api/labels_spec.rb16
-rw-r--r--spec/requests/api/markdown_golden_master_spec.rb9
-rw-r--r--spec/requests/api/members_spec.rb31
-rw-r--r--spec/requests/api/project_import_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb23
-rw-r--r--spec/requests/api/repositories_spec.rb69
-rw-r--r--spec/requests/api/search_spec.rb17
-rw-r--r--spec/requests/api/settings_spec.rb9
-rw-r--r--spec/requests/api/terraform/state_spec.rb10
-rw-r--r--spec/requests/api/todos_spec.rb2
-rw-r--r--spec/requests/api/topics_spec.rb40
-rw-r--r--spec/requests/api/v3/github_spec.rb12
47 files changed, 1587 insertions, 908 deletions
diff --git a/spec/requests/api/admin/plan_limits_spec.rb b/spec/requests/api/admin/plan_limits_spec.rb
index f497227789a..03642ad617e 100644
--- a/spec/requests/api/admin/plan_limits_spec.rb
+++ b/spec/requests/api/admin/plan_limits_spec.rb
@@ -25,6 +25,7 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
expect(json_response).to be_an Hash
expect(json_response['conan_max_file_size']).to eq(Plan.default.actual_limits.conan_max_file_size)
expect(json_response['generic_packages_max_file_size']).to eq(Plan.default.actual_limits.generic_packages_max_file_size)
+ expect(json_response['helm_max_file_size']).to eq(Plan.default.actual_limits.helm_max_file_size)
expect(json_response['maven_max_file_size']).to eq(Plan.default.actual_limits.maven_max_file_size)
expect(json_response['npm_max_file_size']).to eq(Plan.default.actual_limits.npm_max_file_size)
expect(json_response['nuget_max_file_size']).to eq(Plan.default.actual_limits.nuget_max_file_size)
@@ -45,6 +46,7 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
expect(json_response).to be_an Hash
expect(json_response['conan_max_file_size']).to eq(Plan.default.actual_limits.conan_max_file_size)
expect(json_response['generic_packages_max_file_size']).to eq(Plan.default.actual_limits.generic_packages_max_file_size)
+ expect(json_response['helm_max_file_size']).to eq(Plan.default.actual_limits.helm_max_file_size)
expect(json_response['maven_max_file_size']).to eq(Plan.default.actual_limits.maven_max_file_size)
expect(json_response['npm_max_file_size']).to eq(Plan.default.actual_limits.npm_max_file_size)
expect(json_response['nuget_max_file_size']).to eq(Plan.default.actual_limits.nuget_max_file_size)
@@ -84,6 +86,7 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
'plan_name': 'default',
'conan_max_file_size': 10,
'generic_packages_max_file_size': 20,
+ 'helm_max_file_size': 25,
'maven_max_file_size': 30,
'npm_max_file_size': 40,
'nuget_max_file_size': 50,
@@ -95,6 +98,7 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
expect(json_response).to be_an Hash
expect(json_response['conan_max_file_size']).to eq(10)
expect(json_response['generic_packages_max_file_size']).to eq(20)
+ expect(json_response['helm_max_file_size']).to eq(25)
expect(json_response['maven_max_file_size']).to eq(30)
expect(json_response['npm_max_file_size']).to eq(40)
expect(json_response['nuget_max_file_size']).to eq(50)
@@ -129,6 +133,7 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
'plan_name': 'default',
'conan_max_file_size': 'a',
'generic_packages_max_file_size': 'b',
+ 'helm_max_file_size': 'h',
'maven_max_file_size': 'c',
'npm_max_file_size': 'd',
'nuget_max_file_size': 'e',
@@ -140,8 +145,8 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
expect(json_response['error']).to include(
'conan_max_file_size is invalid',
'generic_packages_max_file_size is invalid',
+ 'helm_max_file_size is invalid',
'maven_max_file_size is invalid',
- 'generic_packages_max_file_size is invalid',
'npm_max_file_size is invalid',
'nuget_max_file_size is invalid',
'pypi_max_file_size is invalid',
diff --git a/spec/requests/api/ci/job_artifacts_spec.rb b/spec/requests/api/ci/job_artifacts_spec.rb
new file mode 100644
index 00000000000..585fab33708
--- /dev/null
+++ b/spec/requests/api/ci/job_artifacts_spec.rb
@@ -0,0 +1,661 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Ci::JobArtifacts do
+ include HttpBasicAuthHelpers
+ include DependencyProxyHelpers
+
+ include HttpIOHelpers
+
+ let_it_be(:project, reload: true) do
+ create(:project, :repository, public_builds: false)
+ end
+
+ let_it_be(:pipeline, reload: true) do
+ create(:ci_pipeline, project: project,
+ sha: project.commit.id,
+ ref: project.default_branch)
+ end
+
+ let(:user) { create(:user) }
+ let(:api_user) { user }
+ let(:reporter) { create(:project_member, :reporter, project: project).user }
+ let(:guest) { create(:project_member, :guest, project: project).user }
+
+ let!(:job) do
+ create(:ci_build, :success, :tags, pipeline: pipeline,
+ artifacts_expire_at: 1.day.since)
+ end
+
+ before do
+ project.add_developer(user)
+ end
+
+ shared_examples 'returns unauthorized' do
+ it 'returns unauthorized' do
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ describe 'DELETE /projects/:id/jobs/:job_id/artifacts' do
+ let!(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
+
+ before do
+ delete api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
+ end
+
+ context 'when user is anonymous' do
+ let(:api_user) { nil }
+
+ it 'does not delete artifacts' do
+ expect(job.job_artifacts.size).to eq 2
+ end
+
+ it 'returns status 401 (unauthorized)' do
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'with developer' do
+ it 'does not delete artifacts' do
+ expect(job.job_artifacts.size).to eq 2
+ end
+
+ it 'returns status 403 (forbidden)' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'with authorized user' do
+ let(:maintainer) { create(:project_member, :maintainer, project: project).user }
+ let!(:api_user) { maintainer }
+
+ it 'deletes artifacts' do
+ expect(job.job_artifacts.size).to eq 0
+ end
+
+ it 'returns status 204 (no content)' do
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/jobs/:job_id/artifacts/:artifact_path' do
+ context 'when job has artifacts' do
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
+
+ let(:artifact) do
+ 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif'
+ end
+
+ context 'when user is anonymous' do
+ let(:api_user) { nil }
+
+ context 'when project is public' do
+ it 'allows to access artifacts' do
+ project.update_column(:visibility_level,
+ Gitlab::VisibilityLevel::PUBLIC)
+ project.update_column(:public_builds, true)
+
+ get_artifact_file(artifact)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when project is public with artifacts that are non public' do
+ let(:job) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline) }
+
+ it 'rejects access to artifacts' do
+ project.update_column(:visibility_level,
+ Gitlab::VisibilityLevel::PUBLIC)
+ project.update_column(:public_builds, true)
+
+ get_artifact_file(artifact)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'with the non_public_artifacts feature flag disabled' do
+ before do
+ stub_feature_flags(non_public_artifacts: false)
+ end
+
+ it 'allows access to artifacts' do
+ project.update_column(:visibility_level,
+ Gitlab::VisibilityLevel::PUBLIC)
+ project.update_column(:public_builds, true)
+
+ get_artifact_file(artifact)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'when project is public with builds access disabled' do
+ it 'rejects access to artifacts' do
+ project.update_column(:visibility_level,
+ Gitlab::VisibilityLevel::PUBLIC)
+ project.update_column(:public_builds, false)
+
+ get_artifact_file(artifact)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when project is private' do
+ it 'rejects access and hides existence of artifacts' do
+ project.update_column(:visibility_level,
+ Gitlab::VisibilityLevel::PRIVATE)
+ project.update_column(:public_builds, true)
+
+ get_artifact_file(artifact)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'when user is authorized' do
+ it 'returns a specific artifact file for a valid path' do
+ expect(Gitlab::Workhorse)
+ .to receive(:send_artifacts_entry)
+ .and_call_original
+
+ get_artifact_file(artifact)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers.to_h)
+ .to include('Content-Type' => 'application/json',
+ 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ expect(response.parsed_body).to be_empty
+ end
+
+ context 'when artifacts are locked' do
+ it 'allows access to expired artifact' do
+ pipeline.artifacts_locked!
+ job.update!(artifacts_expire_at: Time.now - 7.days)
+
+ get_artifact_file(artifact)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+ end
+
+ context 'when job does not have artifacts' do
+ it 'does not return job artifact file' do
+ get_artifact_file('some/artifact')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ def get_artifact_file(artifact_path)
+ get api("/projects/#{project.id}/jobs/#{job.id}/" \
+ "artifacts/#{artifact_path}", api_user)
+ end
+ end
+
+ describe 'GET /projects/:id/jobs/:job_id/artifacts' do
+ shared_examples 'downloads artifact' do
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding' => 'binary',
+ 'Content-Disposition' => %q(attachment; filename="ci_build_artifacts.zip"; filename*=UTF-8''ci_build_artifacts.zip) }
+ end
+
+ it 'returns specific job artifacts' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers.to_h).to include(download_headers)
+ expect(response.body).to match_file(job.artifacts_file.file.file)
+ end
+ end
+
+ context 'normal authentication' do
+ context 'job with artifacts' do
+ context 'when artifacts are stored locally' do
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
+
+ subject { get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user) }
+
+ context 'authorized user' do
+ it_behaves_like 'downloads artifact'
+ end
+
+ context 'when job token is used' do
+ let(:other_job) { create(:ci_build, :running, user: user) }
+
+ subject { get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", job_token: other_job.token) }
+
+ before do
+ stub_licensed_features(cross_project_pipelines: true)
+ end
+
+ it_behaves_like 'downloads artifact'
+
+ context 'when job token scope is enabled' do
+ before do
+ other_job.project.ci_cd_settings.update!(job_token_scope_enabled: true)
+ end
+
+ it 'does not allow downloading artifacts' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'when project is added to the job token scope' do
+ let!(:link) { create(:ci_job_token_project_scope_link, source_project: other_job.project, target_project: job.project) }
+
+ it_behaves_like 'downloads artifact'
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'does not return specific job artifacts' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ 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)
+
+ artifact
+ job.reload
+
+ get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
+ end
+
+ context 'when proxy download is enabled' do
+ let(:proxy_download) { true }
+
+ it 'responds with the workhorse send-url' do
+ expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("send-url:")
+ end
+ end
+
+ context 'when proxy download is disabled' do
+ it 'returns location redirect' do
+ expect(response).to have_gitlab_http_status(:found)
+ end
+ end
+
+ context 'authorized user' do
+ it 'returns the file remote URL' do
+ expect(response).to redirect_to(artifact.file.url)
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'does not return specific job artifacts' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'when public project guest and artifacts are non public' do
+ let(:api_user) { guest }
+ let(:job) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline) }
+
+ before do
+ project.update_column(:visibility_level,
+ Gitlab::VisibilityLevel::PUBLIC)
+ project.update_column(:public_builds, true)
+ get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
+ end
+
+ it 'rejects access and hides existence of artifacts' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'with the non_public_artifacts feature flag disabled' do
+ before do
+ stub_feature_flags(non_public_artifacts: false)
+ get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
+ end
+
+ it 'allows access to artifacts' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ it 'does not return job artifacts if not uploaded' do
+ get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
+ let(:api_user) { reporter }
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
+
+ before do
+ stub_artifacts_object_storage
+ job.success
+ end
+
+ def get_for_ref(ref = pipeline.ref, job_name = job.name)
+ get api("/projects/#{project.id}/jobs/artifacts/#{ref}/download", api_user), params: { job: job_name }
+ end
+
+ context 'when not logged in' do
+ let(:api_user) { nil }
+
+ before do
+ get_for_ref
+ end
+
+ it 'does not find a resource in a private project' do
+ expect(project).to be_private
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when logging as guest' do
+ let(:api_user) { guest }
+
+ before do
+ get_for_ref
+ end
+
+ it 'gives 403' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'non-existing job' do
+ shared_examples 'not found' do
+ it { expect(response).to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'has no such ref' do
+ before do
+ get_for_ref('TAIL')
+ end
+
+ it_behaves_like 'not found'
+ end
+
+ context 'has no such job' do
+ before do
+ get_for_ref(pipeline.ref, 'NOBUILD')
+ end
+
+ it_behaves_like 'not found'
+ end
+ end
+
+ context 'find proper job' do
+ let(:job_with_artifacts) { job }
+
+ shared_examples 'a valid file' do
+ context 'when artifacts are stored locally', :sidekiq_might_not_need_inline do
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding' => 'binary',
+ 'Content-Disposition' =>
+ %Q(attachment; filename="#{job_with_artifacts.artifacts_file.filename}"; filename*=UTF-8''#{job.artifacts_file.filename}) }
+ end
+
+ it { expect(response).to have_gitlab_http_status(:ok) }
+ it { expect(response.headers.to_h).to include(download_headers) }
+ end
+
+ context 'when artifacts are stored remotely' do
+ let(:job) { create(:ci_build, pipeline: pipeline, user: api_user) }
+ let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }
+
+ before do
+ job.reload
+
+ get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
+ end
+
+ it 'returns location redirect' do
+ expect(response).to have_gitlab_http_status(:found)
+ end
+ end
+ end
+
+ context 'with regular branch' do
+ before do
+ pipeline.reload
+ pipeline.update!(ref: 'master',
+ sha: project.commit('master').sha)
+
+ get_for_ref('master')
+ end
+
+ it_behaves_like 'a valid file'
+ end
+
+ context 'with branch name containing slash' do
+ before do
+ pipeline.reload
+ pipeline.update!(ref: 'improve/awesome', sha: project.commit('improve/awesome').sha)
+ get_for_ref('improve/awesome')
+ end
+
+ it_behaves_like 'a valid file'
+ end
+
+ context 'with job name in a child pipeline' do
+ let(:child_pipeline) { create(:ci_pipeline, child_of: pipeline) }
+ let!(:child_job) { create(:ci_build, :artifacts, :success, name: 'rspec', pipeline: child_pipeline) }
+ let(:job_with_artifacts) { child_job }
+
+ before do
+ get_for_ref('master', child_job.name)
+ end
+
+ it_behaves_like 'a valid file'
+ end
+ end
+ end
+
+ describe 'GET id/jobs/artifacts/:ref_name/raw/*artifact_path?job=name' do
+ context 'when job has artifacts' do
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
+ let(:artifact) { 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' }
+ let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
+ let(:public_builds) { true }
+
+ before do
+ stub_artifacts_object_storage
+ job.success
+
+ project.update!(visibility_level: visibility_level,
+ public_builds: public_builds)
+
+ get_artifact_file(artifact)
+ end
+
+ context 'when user is anonymous' do
+ let(:api_user) { nil }
+
+ context 'when project is public' do
+ let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
+ let(:public_builds) { true }
+
+ it 'allows to access artifacts', :sidekiq_might_not_need_inline do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers.to_h)
+ .to include('Content-Type' => 'application/json',
+ 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ end
+ end
+
+ context 'when project is public with builds access disabled' do
+ let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
+ let(:public_builds) { false }
+
+ it 'rejects access to artifacts' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response).to have_key('message')
+ expect(response.headers.to_h)
+ .not_to include('Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ end
+ end
+
+ context 'when project is public with non public artifacts' do
+ let(:job) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline, user: api_user) }
+ let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
+ let(:public_builds) { true }
+
+ it 'rejects access and hides existence of artifacts', :sidekiq_might_not_need_inline do
+ get_artifact_file(artifact)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response).to have_key('message')
+ expect(response.headers.to_h)
+ .not_to include('Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ end
+
+ context 'with the non_public_artifacts feature flag disabled' do
+ before do
+ stub_feature_flags(non_public_artifacts: false)
+ end
+
+ it 'allows access to artifacts', :sidekiq_might_not_need_inline do
+ get_artifact_file(artifact)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'when project is private' do
+ let(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
+ let(:public_builds) { true }
+
+ it 'rejects access and hides existence of artifacts' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response).to have_key('message')
+ expect(response.headers.to_h)
+ .not_to include('Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ end
+ end
+ end
+
+ context 'when user is authorized' do
+ let(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
+ let(:public_builds) { true }
+
+ it 'returns a specific artifact file for a valid path', :sidekiq_might_not_need_inline do
+ expect(Gitlab::Workhorse)
+ .to receive(:send_artifacts_entry)
+ .and_call_original
+
+ get_artifact_file(artifact)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers.to_h)
+ .to include('Content-Type' => 'application/json',
+ 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ expect(response.parsed_body).to be_empty
+ end
+ end
+
+ context 'with branch name containing slash' do
+ before do
+ pipeline.reload
+ pipeline.update!(ref: 'improve/awesome',
+ sha: project.commit('improve/awesome').sha)
+ end
+
+ it 'returns a specific artifact file for a valid path', :sidekiq_might_not_need_inline do
+ get_artifact_file(artifact, 'improve/awesome')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers.to_h)
+ .to include('Content-Type' => 'application/json',
+ 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ end
+ end
+
+ context 'non-existing job' do
+ shared_examples 'not found' do
+ it { expect(response).to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'has no such ref' do
+ before do
+ get_artifact_file('some/artifact', 'wrong-ref')
+ end
+
+ it_behaves_like 'not found'
+ end
+
+ context 'has no such job' do
+ before do
+ get_artifact_file('some/artifact', pipeline.ref, 'wrong-job-name')
+ end
+
+ it_behaves_like 'not found'
+ end
+ end
+ end
+
+ context 'when job does not have artifacts' do
+ let(:job) { create(:ci_build, pipeline: pipeline, user: api_user) }
+
+ it 'does not return job artifact file' do
+ get_artifact_file('some/artifact')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ def get_artifact_file(artifact_path, ref = pipeline.ref, job_name = job.name)
+ get api("/projects/#{project.id}/jobs/artifacts/#{ref}/raw/#{artifact_path}", api_user), params: { job: job_name }
+ end
+ end
+
+ describe 'POST /projects/:id/jobs/:job_id/artifacts/keep' do
+ before do
+ post api("/projects/#{project.id}/jobs/#{job.id}/artifacts/keep", user)
+ end
+
+ context 'artifacts did not expire' do
+ let(:job) do
+ create(:ci_build, :trace_artifact, :artifacts, :success,
+ project: project, pipeline: pipeline, artifacts_expire_at: Time.now + 7.days)
+ end
+
+ it 'keeps artifacts' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(job.reload.artifacts_expire_at).to be_nil
+ end
+ end
+
+ context 'no artifacts' do
+ let(:job) { create(:ci_build, project: project, pipeline: pipeline) }
+
+ it 'responds with not found' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/ci/jobs_spec.rb b/spec/requests/api/ci/jobs_spec.rb
index 410020b68cd..7c85cbc31a5 100644
--- a/spec/requests/api/ci/jobs_spec.rb
+++ b/spec/requests/api/ci/jobs_spec.rb
@@ -428,584 +428,41 @@ RSpec.describe API::Ci::Jobs do
end
end
- context 'when trace artifact record exists with no stored file', :skip_before_request do
- before do
- create(:ci_job_artifact, :unarchived_trace_artifact, job: job, project: job.project)
- end
-
- it 'returns no artifacts nor trace data' do
+ context 'when job succeeded' do
+ it 'does not return failure_reason' do
get api("/projects/#{project.id}/jobs/#{job.id}", api_user)
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['artifacts']).to be_an Array
- expect(json_response['artifacts'].size).to eq(1)
- expect(json_response['artifacts'][0]['file_type']).to eq('trace')
- expect(json_response['artifacts'][0]['filename']).to eq('job.log')
- end
- end
- end
-
- describe 'DELETE /projects/:id/jobs/:job_id/artifacts' do
- let!(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
-
- before do
- delete api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
- end
-
- context 'when user is anonymous' do
- let(:api_user) { nil }
-
- it 'does not delete artifacts' do
- expect(job.job_artifacts.size).to eq 2
- end
-
- it 'returns status 401 (unauthorized)' do
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
-
- context 'with developer' do
- it 'does not delete artifacts' do
- expect(job.job_artifacts.size).to eq 2
- end
-
- it 'returns status 403 (forbidden)' do
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'with authorized user' do
- let(:maintainer) { create(:project_member, :maintainer, project: project).user }
- let!(:api_user) { maintainer }
-
- it 'deletes artifacts' do
- expect(job.job_artifacts.size).to eq 0
- end
-
- it 'returns status 204 (no content)' do
- expect(response).to have_gitlab_http_status(:no_content)
+ expect(json_response).not_to include('failure_reason')
end
end
- end
-
- describe 'GET /projects/:id/jobs/:job_id/artifacts/:artifact_path' do
- context 'when job has artifacts' do
- let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
-
- let(:artifact) do
- 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif'
- end
-
- context 'when user is anonymous' do
- let(:api_user) { nil }
-
- context 'when project is public' do
- it 'allows to access artifacts' do
- project.update_column(:visibility_level,
- Gitlab::VisibilityLevel::PUBLIC)
- project.update_column(:public_builds, true)
-
- get_artifact_file(artifact)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context 'when project is public with artifacts that are non public' do
- let(:job) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline) }
-
- it 'rejects access to artifacts' do
- project.update_column(:visibility_level,
- Gitlab::VisibilityLevel::PUBLIC)
- project.update_column(:public_builds, true)
-
- get_artifact_file(artifact)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- context 'with the non_public_artifacts feature flag disabled' do
- before do
- stub_feature_flags(non_public_artifacts: false)
- end
-
- it 'allows access to artifacts' do
- project.update_column(:visibility_level,
- Gitlab::VisibilityLevel::PUBLIC)
- project.update_column(:public_builds, true)
-
- get_artifact_file(artifact)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
- end
-
- context 'when project is public with builds access disabled' do
- it 'rejects access to artifacts' do
- project.update_column(:visibility_level,
- Gitlab::VisibilityLevel::PUBLIC)
- project.update_column(:public_builds, false)
-
- get_artifact_file(artifact)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'when project is private' do
- it 'rejects access and hides existence of artifacts' do
- project.update_column(:visibility_level,
- Gitlab::VisibilityLevel::PRIVATE)
- project.update_column(:public_builds, true)
-
- get_artifact_file(artifact)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
- context 'when user is authorized' do
- it 'returns a specific artifact file for a valid path' do
- expect(Gitlab::Workhorse)
- .to receive(:send_artifacts_entry)
- .and_call_original
-
- get_artifact_file(artifact)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers.to_h)
- .to include('Content-Type' => 'application/json',
- 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
- expect(response.parsed_body).to be_empty
- end
-
- context 'when artifacts are locked' do
- it 'allows access to expired artifact' do
- pipeline.artifacts_locked!
- job.update!(artifacts_expire_at: Time.now - 7.days)
-
- get_artifact_file(artifact)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
- end
- end
-
- context 'when job does not have artifacts' do
- it 'does not return job artifact file' do
- get_artifact_file('some/artifact')
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- def get_artifact_file(artifact_path)
- get api("/projects/#{project.id}/jobs/#{job.id}/" \
- "artifacts/#{artifact_path}", api_user)
- end
- end
-
- describe 'GET /projects/:id/jobs/:job_id/artifacts' do
- shared_examples 'downloads artifact' do
- let(:download_headers) do
- { 'Content-Transfer-Encoding' => 'binary',
- 'Content-Disposition' => %q(attachment; filename="ci_build_artifacts.zip"; filename*=UTF-8''ci_build_artifacts.zip) }
- end
-
- it 'returns specific job artifacts' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers.to_h).to include(download_headers)
- expect(response.body).to match_file(job.artifacts_file.file.file)
- end
- end
-
- context 'normal authentication' do
- context 'job with artifacts' do
- context 'when artifacts are stored locally' do
- let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
-
- before do
- get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
- end
-
- context 'authorized user' do
- it_behaves_like 'downloads artifact'
- end
-
- context 'unauthorized user' do
- let(:api_user) { nil }
-
- it 'does not return specific job artifacts' do
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- 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)
-
- artifact
- job.reload
-
- get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
- end
-
- context 'when proxy download is enabled' do
- let(:proxy_download) { true }
-
- it 'responds with the workhorse send-url' do
- expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("send-url:")
- end
- end
-
- context 'when proxy download is disabled' do
- it 'returns location redirect' do
- expect(response).to have_gitlab_http_status(:found)
- end
- end
-
- context 'authorized user' do
- it 'returns the file remote URL' do
- expect(response).to redirect_to(artifact.file.url)
- end
- end
-
- context 'unauthorized user' do
- let(:api_user) { nil }
-
- it 'does not return specific job artifacts' do
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- context 'when public project guest and artifacts are non public' do
- let(:api_user) { guest }
- let(:job) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline) }
-
- before do
- project.update_column(:visibility_level,
- Gitlab::VisibilityLevel::PUBLIC)
- project.update_column(:public_builds, true)
- get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
- end
-
- it 'rejects access and hides existence of artifacts' do
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- context 'with the non_public_artifacts feature flag disabled' do
- before do
- stub_feature_flags(non_public_artifacts: false)
- get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
- end
-
- it 'allows access to artifacts' do
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
- end
-
- it 'does not return job artifacts if not uploaded' do
- get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
- end
-
- describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
- let(:api_user) { reporter }
- let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
-
- before do
- stub_artifacts_object_storage
- job.success
- end
-
- def get_for_ref(ref = pipeline.ref, job_name = job.name)
- get api("/projects/#{project.id}/jobs/artifacts/#{ref}/download", api_user), params: { job: job_name }
- end
-
- context 'when not logged in' do
- let(:api_user) { nil }
-
- before do
- get_for_ref
- end
-
- it 'does not find a resource in a private project' do
- expect(project).to be_private
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'when logging as guest' do
- let(:api_user) { guest }
-
- before do
- get_for_ref
- end
-
- it 'gives 403' do
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context 'non-existing job' do
- shared_examples 'not found' do
- it { expect(response).to have_gitlab_http_status(:not_found) }
- end
-
- context 'has no such ref' do
- before do
- get_for_ref('TAIL')
- end
-
- it_behaves_like 'not found'
- end
-
- context 'has no such job' do
- before do
- get_for_ref(pipeline.ref, 'NOBUILD')
- end
-
- it_behaves_like 'not found'
- end
- end
-
- context 'find proper job' do
- let(:job_with_artifacts) { job }
-
- shared_examples 'a valid file' do
- context 'when artifacts are stored locally', :sidekiq_might_not_need_inline do
- let(:download_headers) do
- { 'Content-Transfer-Encoding' => 'binary',
- 'Content-Disposition' =>
- %Q(attachment; filename="#{job_with_artifacts.artifacts_file.filename}"; filename*=UTF-8''#{job.artifacts_file.filename}) }
- end
-
- it { expect(response).to have_gitlab_http_status(:ok) }
- it { expect(response.headers.to_h).to include(download_headers) }
- end
-
- context 'when artifacts are stored remotely' do
- let(:job) { create(:ci_build, pipeline: pipeline, user: api_user) }
- let!(:artifact) { create(:ci_job_artifact, :archive, :remote_store, job: job) }
-
- before do
- job.reload
-
- get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
- end
-
- it 'returns location redirect' do
- expect(response).to have_gitlab_http_status(:found)
- end
- end
- end
-
- context 'with regular branch' do
- before do
- pipeline.reload
- pipeline.update!(ref: 'master',
- sha: project.commit('master').sha)
-
- get_for_ref('master')
- end
-
- it_behaves_like 'a valid file'
- end
-
- context 'with branch name containing slash' do
- before do
- pipeline.reload
- pipeline.update!(ref: 'improve/awesome', sha: project.commit('improve/awesome').sha)
- get_for_ref('improve/awesome')
- end
-
- it_behaves_like 'a valid file'
+ context 'when job failed' do
+ let(:job) do
+ create(:ci_build, :failed, :tags, pipeline: pipeline)
end
- context 'with job name in a child pipeline' do
- let(:child_pipeline) { create(:ci_pipeline, child_of: pipeline) }
- let!(:child_job) { create(:ci_build, :artifacts, :success, name: 'rspec', pipeline: child_pipeline) }
- let(:job_with_artifacts) { child_job }
-
- before do
- get_for_ref('master', child_job.name)
- end
+ it 'returns failure_reason' do
+ get api("/projects/#{project.id}/jobs/#{job.id}", api_user)
- it_behaves_like 'a valid file'
+ expect(json_response).to include('failure_reason')
end
end
- end
-
- describe 'GET id/jobs/artifacts/:ref_name/raw/*artifact_path?job=name' do
- context 'when job has artifacts' do
- let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
- let(:artifact) { 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' }
- let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
- let(:public_builds) { true }
+ context 'when trace artifact record exists with no stored file', :skip_before_request do
before do
- stub_artifacts_object_storage
- job.success
-
- project.update!(visibility_level: visibility_level,
- public_builds: public_builds)
-
- get_artifact_file(artifact)
- end
-
- context 'when user is anonymous' do
- let(:api_user) { nil }
-
- context 'when project is public' do
- let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
- let(:public_builds) { true }
-
- it 'allows to access artifacts', :sidekiq_might_not_need_inline do
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers.to_h)
- .to include('Content-Type' => 'application/json',
- 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
- end
- end
-
- context 'when project is public with builds access disabled' do
- let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
- let(:public_builds) { false }
-
- it 'rejects access to artifacts' do
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response).to have_key('message')
- expect(response.headers.to_h)
- .not_to include('Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
- end
- end
-
- context 'when project is public with non public artifacts' do
- let(:job) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline, user: api_user) }
- let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
- let(:public_builds) { true }
-
- it 'rejects access and hides existence of artifacts', :sidekiq_might_not_need_inline do
- get_artifact_file(artifact)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response).to have_key('message')
- expect(response.headers.to_h)
- .not_to include('Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
- end
-
- context 'with the non_public_artifacts feature flag disabled' do
- before do
- stub_feature_flags(non_public_artifacts: false)
- end
-
- it 'allows access to artifacts', :sidekiq_might_not_need_inline do
- get_artifact_file(artifact)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
- end
-
- context 'when project is private' do
- let(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
- let(:public_builds) { true }
-
- it 'rejects access and hides existence of artifacts' do
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response).to have_key('message')
- expect(response.headers.to_h)
- .not_to include('Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
- end
- end
- end
-
- context 'when user is authorized' do
- let(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
- let(:public_builds) { true }
-
- it 'returns a specific artifact file for a valid path', :sidekiq_might_not_need_inline do
- expect(Gitlab::Workhorse)
- .to receive(:send_artifacts_entry)
- .and_call_original
-
- get_artifact_file(artifact)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers.to_h)
- .to include('Content-Type' => 'application/json',
- 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
- expect(response.parsed_body).to be_empty
- end
- end
-
- context 'with branch name containing slash' do
- before do
- pipeline.reload
- pipeline.update!(ref: 'improve/awesome',
- sha: project.commit('improve/awesome').sha)
- end
-
- it 'returns a specific artifact file for a valid path', :sidekiq_might_not_need_inline do
- get_artifact_file(artifact, 'improve/awesome')
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers.to_h)
- .to include('Content-Type' => 'application/json',
- 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
- end
- end
-
- context 'non-existing job' do
- shared_examples 'not found' do
- it { expect(response).to have_gitlab_http_status(:not_found) }
- end
-
- context 'has no such ref' do
- before do
- get_artifact_file('some/artifact', 'wrong-ref')
- end
-
- it_behaves_like 'not found'
- end
-
- context 'has no such job' do
- before do
- get_artifact_file('some/artifact', pipeline.ref, 'wrong-job-name')
- end
-
- it_behaves_like 'not found'
- end
+ create(:ci_job_artifact, :unarchived_trace_artifact, job: job, project: job.project)
end
- end
- context 'when job does not have artifacts' do
- let(:job) { create(:ci_build, pipeline: pipeline, user: api_user) }
-
- it 'does not return job artifact file' do
- get_artifact_file('some/artifact')
+ it 'returns no artifacts nor trace data' do
+ get api("/projects/#{project.id}/jobs/#{job.id}", api_user)
- expect(response).to have_gitlab_http_status(:not_found)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['artifacts']).to be_an Array
+ expect(json_response['artifacts'].size).to eq(1)
+ expect(json_response['artifacts'][0]['file_type']).to eq('trace')
+ expect(json_response['artifacts'][0]['filename']).to eq('job.log')
end
end
-
- def get_artifact_file(artifact_path, ref = pipeline.ref, job_name = job.name)
- get api("/projects/#{project.id}/jobs/artifacts/#{ref}/raw/#{artifact_path}", api_user), params: { job: job_name }
- end
end
describe 'GET /projects/:id/jobs/:job_id/trace' do
@@ -1249,32 +706,6 @@ RSpec.describe API::Ci::Jobs do
end
end
- describe 'POST /projects/:id/jobs/:job_id/artifacts/keep' do
- before do
- post api("/projects/#{project.id}/jobs/#{job.id}/artifacts/keep", user)
- end
-
- context 'artifacts did not expire' do
- let(:job) do
- create(:ci_build, :trace_artifact, :artifacts, :success,
- project: project, pipeline: pipeline, artifacts_expire_at: Time.now + 7.days)
- end
-
- it 'keeps artifacts' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(job.reload.artifacts_expire_at).to be_nil
- end
- end
-
- context 'no artifacts' do
- let(:job) { create(:ci_build, project: project, pipeline: pipeline) }
-
- it 'responds with not found' do
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
describe 'POST /projects/:id/jobs/:job_id/play' do
before do
post api("/projects/#{project.id}/jobs/#{job.id}/play", api_user)
diff --git a/spec/requests/api/ci/pipelines_spec.rb b/spec/requests/api/ci/pipelines_spec.rb
index 7ae350885f4..13838cffd76 100644
--- a/spec/requests/api/ci/pipelines_spec.rb
+++ b/spec/requests/api/ci/pipelines_spec.rb
@@ -33,6 +33,7 @@ RSpec.describe API::Ci::Pipelines do
expect(json_response).to be_an Array
expect(json_response.first['sha']).to match(/\A\h{40}\z/)
expect(json_response.first['id']).to eq pipeline.id
+ expect(json_response.first['iid']).to eq pipeline.iid
expect(json_response.first['web_url']).to be_present
end
@@ -40,7 +41,7 @@ RSpec.describe API::Ci::Pipelines 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])
+ expect(json_response.first.keys).to contain_exactly(*%w[id iid project_id sha ref status web_url created_at updated_at source])
end
end
@@ -840,7 +841,7 @@ RSpec.describe API::Ci::Pipelines do
it 'exposes the coverage' do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
- expect(json_response["coverage"].to_i).to eq(30)
+ expect(json_response["coverage"]).to eq('30.00')
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 195aac2e5f0..f627f207d98 100644
--- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
@@ -131,8 +131,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
let(:send_request) { subject }
end
- it 'updates runner info' do
- expect { subject }.to change { runner.reload.contacted_at }
+ it "doesn't update runner info" do
+ expect { subject }.not_to change { runner.reload.contacted_at }
end
shared_examples 'authorizes local file' do
@@ -280,8 +280,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
end
- it 'updates runner info' do
- expect { upload_artifacts(file_upload, headers_with_token) }.to change { runner.reload.contacted_at }
+ it "doesn't update runner info" do
+ expect { upload_artifacts(file_upload, headers_with_token) }.not_to change { runner.reload.contacted_at }
end
context 'when the artifact is too large' do
@@ -812,8 +812,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
let(:send_request) { download_artifact }
end
- it 'updates runner info' do
- expect { download_artifact }.to change { runner.reload.contacted_at }
+ it "doesn't update runner info" do
+ expect { download_artifact }.not_to change { runner.reload.contacted_at }
end
context 'when job has artifacts' do
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 fdf1a278d4c..68f7581bf06 100644
--- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
@@ -833,8 +833,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
let(:expected_params) { { project: project.full_path, client_id: "runner/#{runner.id}" } }
end
- it_behaves_like 'not executing any extra queries for the application context', 2 do
- # Extra queries: Project, Route
+ it_behaves_like 'not executing any extra queries for the application context', 3 do
+ # Extra queries: Project, Route, RunnerProject
let(:subject_proc) { proc { request_job } }
end
end
diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb
index b3a7d591c93..a51d8b458f8 100644
--- a/spec/requests/api/ci/runner/runners_post_spec.rb
+++ b/spec/requests/api/ci/runner/runners_post_spec.rb
@@ -98,33 +98,14 @@ 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
-
- 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)
- end
end
- context 'with ci_runner_limits_override FF enabled' do
- let(:ci_runner_limits_override) { true }
-
- it 'creates runner' do
- request
+ it 'does not create 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
+ 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
@@ -132,9 +113,6 @@ 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
@@ -204,33 +182,14 @@ 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
-
- 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)
- end
end
- context 'with ci_runner_limits_override FF enabled' do
- let(:ci_runner_limits_override) { true }
-
- it 'creates runner' do
- request
+ it 'does not create 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
+ 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
@@ -239,9 +198,6 @@ 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 6879dfc9572..6ca380a3cb9 100644
--- a/spec/requests/api/ci/runners_spec.rb
+++ b/spec/requests/api/ci/runners_spec.rb
@@ -254,6 +254,7 @@ RSpec.describe API::Ci::Runners do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['description']).to eq(shared_runner.description)
expect(json_response['maximum_timeout']).to be_nil
+ expect(json_response['status']).to eq("not_connected")
end
end
@@ -1101,31 +1102,13 @@ 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
- 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
+ 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
end
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 47bc3eb74a6..39be28d7427 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -14,8 +14,19 @@ RSpec.describe API::CommitStatuses do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
context 'ci commit exists' do
- let!(:master) { project.ci_pipelines.create!(source: :push, sha: commit.id, ref: 'master', protected: false) }
- let!(:develop) { project.ci_pipelines.create!(source: :push, sha: commit.id, ref: 'develop', protected: false) }
+ let!(:master) do
+ project.ci_pipelines.build(source: :push, sha: commit.id, ref: 'master', protected: false).tap do |p|
+ p.ensure_project_iid! # Necessary to avoid cross-database modification error
+ p.save!
+ end
+ end
+
+ let!(:develop) do
+ project.ci_pipelines.build(source: :push, sha: commit.id, ref: 'develop', protected: false).tap do |p|
+ p.ensure_project_iid! # Necessary to avoid cross-database modification error
+ p.save!
+ end
+ end
context "reporter user" do
let(:statuses_id) { json_response.map { |status| status['id'] } }
@@ -131,7 +142,7 @@ RSpec.describe API::CommitStatuses do
%w[pending running success failed canceled].each do |status|
context "for #{status}" do
context 'when pipeline for sha does not exists' do
- it 'creates commit status' do
+ it 'creates commit status and sets pipeline iid' do
post api(post_url, developer), params: { state: status }
expect(response).to have_gitlab_http_status(:created)
@@ -145,6 +156,8 @@ RSpec.describe API::CommitStatuses do
if status == 'failed'
expect(CommitStatus.find(json_response['id'])).to be_api_failure
end
+
+ expect(::Ci::Pipeline.last.iid).not_to be_nil
end
end
end
@@ -308,8 +321,19 @@ RSpec.describe API::CommitStatuses do
end
context 'when a pipeline id is specified' do
- let!(:first_pipeline) { project.ci_pipelines.create!(source: :push, sha: commit.id, ref: 'master', status: 'created') }
- let!(:other_pipeline) { project.ci_pipelines.create!(source: :push, sha: commit.id, ref: 'master', status: 'created') }
+ let!(:first_pipeline) do
+ project.ci_pipelines.build(source: :push, sha: commit.id, ref: 'master', status: 'created').tap do |p|
+ p.ensure_project_iid! # Necessary to avoid cross-database modification error
+ p.save!
+ end
+ end
+
+ let!(:other_pipeline) do
+ project.ci_pipelines.build(source: :push, sha: commit.id, ref: 'master', status: 'created').tap do |p|
+ p.ensure_project_iid! # Necessary to avoid cross-database modification error
+ p.save!
+ end
+ end
subject do
post api(post_url, developer), params: {
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 1d76c281dee..1e587480fd9 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -377,11 +377,11 @@ RSpec.describe API::Commits do
end
context 'when using warden' do
- it 'increments usage counters', :clean_gitlab_redis_shared_state do
+ it 'increments usage counters', :clean_gitlab_redis_sessions do
session_id = Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d')
session_hash = { 'warden.user.user.key' => [[user.id], user.encrypted_password[0, 29]] }
- Gitlab::Redis::SharedState.with do |redis|
+ Gitlab::Redis::Sessions.with do |redis|
redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash))
end
diff --git a/spec/requests/api/composer_packages_spec.rb b/spec/requests/api/composer_packages_spec.rb
index e75725cacba..21b4634ce25 100644
--- a/spec/requests/api/composer_packages_spec.rb
+++ b/spec/requests/api/composer_packages_spec.rb
@@ -9,6 +9,10 @@ RSpec.describe API::ComposerPackages do
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:package_name) { 'package-name' }
let_it_be(:project, reload: true) { create(:project, :custom_repo, files: { 'composer.json' => { name: package_name }.to_json }, group: group) }
+ let_it_be(:deploy_token_for_project) { 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_for_project, project: project) }
+ let_it_be(:deploy_token_for_group) { create(:deploy_token, :group, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token_for_group, group: group) }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
let(:headers) { {} }
@@ -92,6 +96,8 @@ RSpec.describe API::ComposerPackages do
group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
+ it_behaves_like 'Composer access with deploy tokens'
+
context 'with access to the api' do
where(:project_visibility_level, :user_role, :member, :user_token, :include_package) do
'PRIVATE' | :developer | true | true | :include_package
@@ -162,6 +168,8 @@ RSpec.describe API::ComposerPackages do
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
+
+ it_behaves_like 'Composer access with deploy tokens'
end
it_behaves_like 'rejects Composer access with unknown group id'
@@ -219,6 +227,8 @@ RSpec.describe API::ComposerPackages do
end
end
end
+
+ it_behaves_like 'Composer access with deploy tokens'
end
it_behaves_like 'rejects Composer access with unknown group id'
@@ -265,6 +275,8 @@ RSpec.describe API::ComposerPackages do
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
+
+ it_behaves_like 'Composer access with deploy tokens'
end
it_behaves_like 'rejects Composer access with unknown group id'
@@ -308,6 +320,8 @@ RSpec.describe API::ComposerPackages do
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
+
+ it_behaves_like 'Composer publish with deploy tokens'
end
it_behaves_like 'rejects Composer access with unknown project id'
diff --git a/spec/requests/api/conan_project_packages_spec.rb b/spec/requests/api/conan_project_packages_spec.rb
index da054ed2e96..c108f2efaaf 100644
--- a/spec/requests/api/conan_project_packages_spec.rb
+++ b/spec/requests/api/conan_project_packages_spec.rb
@@ -1,10 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
-RSpec.describe API::ConanProjectPackages, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/326194' do
+RSpec.describe API::ConanProjectPackages do
include_context 'conan api setup'
let(:project_id) { project.id }
+ let(:snowplow_standard_context_params) { { user: user, project: project, namespace: project.namespace } }
describe 'GET /api/v4/projects/:id/packages/conan/v1/ping' do
let(:url) { "/projects/#{project.id}/packages/conan/v1/ping" }
@@ -92,7 +93,7 @@ RSpec.describe API::ConanProjectPackages, quarantine: 'https://gitlab.com/gitlab
end
end
- context 'file download endpoints' do
+ context 'file download endpoints', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/326194' do
include_context 'conan file download endpoints'
describe 'GET /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/
diff --git a/spec/requests/api/dependency_proxy_spec.rb b/spec/requests/api/dependency_proxy_spec.rb
index 2837d1c02c4..067852ef1e9 100644
--- a/spec/requests/api/dependency_proxy_spec.rb
+++ b/spec/requests/api/dependency_proxy_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe API::DependencyProxy, api: true do
- include ExclusiveLeaseHelpers
-
let_it_be(:user) { create(:user) }
let_it_be(:blob) { create(:dependency_proxy_blob )}
let_it_be(:group, reload: true) { blob.group }
@@ -20,11 +18,8 @@ RSpec.describe API::DependencyProxy, api: true do
shared_examples 'responding to purge requests' do
context 'with feature available and enabled' do
- let_it_be(:lease_key) { "dependency_proxy:delete_group_blobs:#{group.id}" }
-
context 'an admin user' do
it 'deletes the blobs and returns no content' do
- stub_exclusive_lease(lease_key, timeout: 1.hour)
expect(PurgeDependencyProxyCacheWorker).to receive(:perform_async)
subject
@@ -32,23 +27,6 @@ RSpec.describe API::DependencyProxy, api: true do
expect(response).to have_gitlab_http_status(:accepted)
expect(response.body).to eq('202')
end
-
- context 'called multiple times in one hour', :clean_gitlab_redis_shared_state do
- it 'returns 409 with an error message' do
- stub_exclusive_lease_taken(lease_key, timeout: 1.hour)
-
- subject
-
- expect(response).to have_gitlab_http_status(:conflict)
- expect(response.body).to include('This request has already been made.')
- end
-
- it 'executes service only for the first time' do
- expect(PurgeDependencyProxyCacheWorker).to receive(:perform_async).once
-
- 2.times { subject }
- end
- end
end
context 'a non-admin' do
diff --git a/spec/requests/api/error_tracking/collector_spec.rb b/spec/requests/api/error_tracking/collector_spec.rb
index 21e2849fef0..573da862b57 100644
--- a/spec/requests/api/error_tracking/collector_spec.rb
+++ b/spec/requests/api/error_tracking/collector_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe API::ErrorTracking::Collector do
end
RSpec.shared_examples 'successful request' do
- it 'writes to the database and returns OK' do
+ it 'writes to the database and returns OK', :aggregate_failures do
expect { subject }.to change { ErrorTracking::ErrorEvent.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
@@ -40,6 +40,8 @@ RSpec.describe API::ErrorTracking::Collector do
subject { post api(url), params: params, headers: headers }
+ it_behaves_like 'successful request'
+
context 'error tracking feature is disabled' do
before do
setting.update!(enabled: false)
@@ -109,8 +111,6 @@ RSpec.describe API::ErrorTracking::Collector do
it_behaves_like 'successful request'
end
-
- it_behaves_like 'successful request'
end
describe "POST /error_tracking/collector/api/:id/store" do
@@ -165,6 +165,12 @@ RSpec.describe API::ErrorTracking::Collector do
it_behaves_like 'successful request'
end
+ context 'body contains nullbytes' do
+ let_it_be(:raw_event) { fixture_file('error_tracking/parsed_event_nullbytes.json') }
+
+ it_behaves_like 'successful request'
+ end
+
context 'sentry_key as param and empty headers' do
let(:url) { "/error_tracking/collector/api/#{project.id}/store?sentry_key=#{sentry_key}" }
let(:headers) { {} }
diff --git a/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb b/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb
index 241c658441b..6324db0be4a 100644
--- a/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb
@@ -16,6 +16,7 @@ RSpec.describe 'get board lists' do
let(:params) { '' }
let(:board) { }
+ let(:confidential) { false }
let(:board_parent_type) { board_parent.class.to_s.downcase }
let(:board_data) { graphql_data[board_parent_type]['boards']['nodes'][0] }
let(:lists_data) { board_data['lists']['nodes'][0] }
@@ -30,7 +31,7 @@ RSpec.describe 'get board lists' do
nodes {
lists {
nodes {
- issues(filters: {labelName: "#{label2.title}"}, first: 3) {
+ issues(filters: {labelName: "#{label2.title}", confidential: #{confidential}}, first: 3) {
count
nodes {
#{all_graphql_fields_for('issues'.classify)}
@@ -57,14 +58,15 @@ RSpec.describe 'get board lists' do
end
shared_examples 'group and project board list issues query' do
- let!(:board) { create(:board, resource_parent: board_parent) }
- let!(:label_list) { create(:list, board: board, label: label, position: 10) }
- let!(:issue1) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 9) }
- let!(:issue2) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 2) }
- let!(:issue3) { create(:issue, project: issue_project, labels: [label, label2], relative_position: nil) }
- let!(:issue4) { create(:issue, project: issue_project, labels: [label], relative_position: 9) }
- let!(:issue5) { create(:issue, project: issue_project, labels: [label2], relative_position: 432) }
- let!(:issue6) { create(:issue, project: issue_project, labels: [label, label2], relative_position: nil) }
+ let_it_be(:board) { create(:board, resource_parent: board_parent) }
+ let_it_be(:label_list) { create(:list, board: board, label: label, position: 10) }
+ let_it_be(:issue1) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 9) }
+ let_it_be(:issue2) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 2) }
+ let_it_be(:issue3) { create(:issue, project: issue_project, labels: [label, label2], relative_position: nil) }
+ let_it_be(:issue4) { create(:issue, project: issue_project, labels: [label], relative_position: 9) }
+ let_it_be(:issue5) { create(:issue, project: issue_project, labels: [label2], relative_position: 432) }
+ let_it_be(:issue6) { create(:issue, project: issue_project, labels: [label, label2], relative_position: nil) }
+ let_it_be(:issue7) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 5, confidential: true) }
context 'when the user does not have access to the board' do
it 'returns nil' do
@@ -90,23 +92,33 @@ RSpec.describe 'get board lists' do
expect(issue_id).not_to include(issue6.id)
expect(issue3.relative_position).to be_nil
end
+
+ context 'when filtering by confidential' do
+ let(:confidential) { true }
+
+ it 'returns matching issue' do
+ expect(issue_titles).to match_array([issue7.title])
+ expect(issue_relative_positions).not_to include(nil)
+ end
+ end
end
end
describe 'for a project' do
- let(:board_parent) { project }
- let(:label) { project_label }
- let(:label2) { project_label2 }
- let(:issue_project) { project }
+ let_it_be(:board_parent) { project }
+ let_it_be(:label) { project_label }
+ let_it_be(:label2) { project_label2 }
+ let_it_be(:issue_project) { project }
it_behaves_like 'group and project board list issues query'
end
describe 'for a group' do
- let(:board_parent) { group }
- let(:label) { group_label }
- let(:label2) { group_label2 }
- let(:issue_project) { create(:project, :private, group: group) }
+ let_it_be(:board_parent) { group }
+ let_it_be(:label) { group_label }
+ let_it_be(:label2) { group_label2 }
+
+ let_it_be(:issue_project) { create(:project, :private, group: group) }
before do
allow(board_parent).to receive(:multiple_issue_boards_available?).and_return(false)
diff --git a/spec/requests/api/graphql/boards/board_list_query_spec.rb b/spec/requests/api/graphql/boards/board_list_query_spec.rb
index dec7ca715f2..f01f7e87f10 100644
--- a/spec/requests/api/graphql/boards/board_list_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_list_query_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe 'Querying a Board list' do
let_it_be(:list) { create(:list, board: board, label: label) }
let_it_be(:issue1) { create(:issue, project: project, labels: [label]) }
let_it_be(:issue2) { create(:issue, project: project, labels: [label], assignees: [current_user]) }
+ let_it_be(:issue3) { create(:issue, project: project, labels: [label], confidential: true) }
let(:filters) { {} }
let(:query) do
@@ -37,19 +38,33 @@ RSpec.describe 'Querying a Board list' do
it { is_expected.to include({ 'issuesCount' => 2, 'title' => list.title }) }
- context 'with matching issue filters' do
- let(:filters) { { assigneeUsername: current_user.username } }
+ describe 'issue filters' do
+ context 'with matching assignee username issue filters' do
+ let(:filters) { { assigneeUsername: current_user.username } }
- it 'filters issues metadata' do
- is_expected.to include({ 'issuesCount' => 1, 'title' => list.title })
+ it 'filters issues metadata' do
+ is_expected.to include({ 'issuesCount' => 1, 'title' => list.title })
+ end
end
- end
- context 'with unmatching issue filters' do
- let(:filters) { { assigneeUsername: 'foo' } }
+ context 'with unmatching assignee username issue filters' do
+ let(:filters) { { assigneeUsername: 'foo' } }
+
+ it 'filters issues metadata' do
+ is_expected.to include({ 'issuesCount' => 0, 'title' => list.title })
+ end
+ end
+
+ context 'when filtering by confidential' do
+ let(:filters) { { confidential: true } }
+
+ before_all do
+ project.add_developer(current_user)
+ end
- it 'filters issues metadata' do
- is_expected.to include({ 'issuesCount' => 0, 'title' => list.title })
+ it 'filters issues metadata' do
+ is_expected.to include({ 'issuesCount' => 1, 'title' => list.title })
+ end
end
end
end
diff --git a/spec/requests/api/graphql/boards/board_lists_query_spec.rb b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
index ace8c59e82d..e8fb9daa43b 100644
--- a/spec/requests/api/graphql/boards/board_lists_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
@@ -109,9 +109,15 @@ RSpec.describe 'get board lists' do
it 'returns the correct list with issue count for matching issue filters' do
label_list = create(:list, board: board, label: label, position: 10)
create(:issue, project: project, labels: [label, label2])
+ create(:issue, project: project, labels: [label, label2], confidential: true)
create(:issue, project: project, labels: [label])
- post_graphql(query(id: global_id_of(label_list), issueFilters: { labelName: label2.title }), current_user: user)
+ post_graphql(
+ query(
+ id: global_id_of(label_list),
+ issueFilters: { labelName: label2.title, confidential: false }
+ ), current_user: user
+ )
aggregate_failures do
list_node = lists_data[0]['node']
diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb
index b2f4801a083..3a1df3525ef 100644
--- a/spec/requests/api/graphql/ci/jobs_spec.rb
+++ b/spec/requests/api/graphql/ci/jobs_spec.rb
@@ -14,8 +14,8 @@ RSpec.describe 'Query.project.pipeline' do
describe '.stages.groups.jobs' do
let(:pipeline) do
pipeline = create(:ci_pipeline, project: project, user: user)
- stage = create(:ci_stage_entity, project: project, pipeline: pipeline, name: 'first')
- create(:ci_build, stage_id: stage.id, pipeline: pipeline, name: 'my test job')
+ stage = create(:ci_stage_entity, project: project, pipeline: pipeline, name: 'first', position: 1)
+ create(:ci_build, stage_id: stage.id, pipeline: pipeline, name: 'my test job', scheduling_type: :stage)
pipeline
end
@@ -44,13 +44,23 @@ RSpec.describe 'Query.project.pipeline' do
name
jobs {
nodes {
- detailedStatus {
- id
- }
name
needs {
nodes { #{all_graphql_fields_for('CiBuildNeed')} }
}
+ previousStageJobsOrNeeds {
+ nodes {
+ ... on CiBuildNeed {
+ #{all_graphql_fields_for('CiBuildNeed')}
+ }
+ ... on CiJob {
+ #{all_graphql_fields_for('CiJob')}
+ }
+ }
+ }
+ detailedStatus {
+ id
+ }
pipeline {
id
}
@@ -62,58 +72,61 @@ RSpec.describe 'Query.project.pipeline' do
FIELDS
end
- context 'when there are build needs' do
- before do
- pipeline.statuses.each do |build|
- create_list(:ci_build_need, 2, build: build)
- end
- end
-
- it 'reports the build needs' do
- post_graphql(query, current_user: user)
-
- expect(jobs_graphql_data).to contain_exactly a_hash_including(
- 'needs' => a_hash_including(
- 'nodes' => contain_exactly(
- a_hash_including('name' => String),
- a_hash_including('name' => String)
- )
- )
- )
- end
- end
-
it 'returns the jobs of a pipeline stage' do
post_graphql(query, current_user: user)
expect(jobs_graphql_data).to contain_exactly(a_hash_including('name' => 'my test job'))
end
- describe 'performance' do
+ context 'when there is more than one stage and job needs' do
before do
build_stage = create(:ci_stage_entity, position: 2, name: 'build', project: project, pipeline: pipeline)
test_stage = create(:ci_stage_entity, position: 3, name: 'test', project: project, pipeline: pipeline)
- create(:commit_status, pipeline: pipeline, stage_id: build_stage.id, name: 'docker 1 2')
- create(:commit_status, pipeline: pipeline, stage_id: build_stage.id, name: 'docker 2 2')
- create(:commit_status, pipeline: pipeline, stage_id: test_stage.id, name: 'rspec 1 2')
- create(:commit_status, pipeline: pipeline, stage_id: test_stage.id, name: 'rspec 2 2')
- end
- it 'can find the first stage' do
- post_graphql(query, current_user: user, variables: first_n.with(1))
+ create(:ci_build, pipeline: pipeline, name: 'docker 1 2', scheduling_type: :stage, stage: build_stage, stage_idx: build_stage.position)
+ create(:ci_build, pipeline: pipeline, name: 'docker 2 2', stage: build_stage, stage_idx: build_stage.position, scheduling_type: :dag)
+ create(:ci_build, pipeline: pipeline, name: 'rspec 1 2', scheduling_type: :stage, stage: test_stage, stage_idx: test_stage.position)
+ test_job = create(:ci_build, pipeline: pipeline, name: 'rspec 2 2', scheduling_type: :dag, stage: test_stage, stage_idx: test_stage.position)
- expect(jobs_graphql_data).to contain_exactly(a_hash_including('name' => 'my test job'))
+ create(:ci_build_need, build: test_job, name: 'my test job')
end
- it 'reports the build needs and previous stages with no duplicates', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/346433' do
+ it 'reports the build needs and execution requirements', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/347290' do
post_graphql(query, current_user: user)
expect(jobs_graphql_data).to contain_exactly(
- a_hash_including('name' => 'my test job'),
- a_hash_including('name' => 'docker 1 2'),
- a_hash_including('name' => 'docker 2 2'),
- a_hash_including('name' => 'rspec 1 2'),
- a_hash_including('name' => 'rspec 2 2')
+ a_hash_including(
+ 'name' => 'my test job',
+ 'needs' => { 'nodes' => [] },
+ 'previousStageJobsOrNeeds' => { 'nodes' => [] }
+ ),
+ a_hash_including(
+ 'name' => 'docker 1 2',
+ 'needs' => { 'nodes' => [] },
+ 'previousStageJobsOrNeeds' => { 'nodes' => [
+ a_hash_including( 'name' => 'my test job' )
+ ] }
+ ),
+ a_hash_including(
+ 'name' => 'docker 2 2',
+ 'needs' => { 'nodes' => [] },
+ 'previousStageJobsOrNeeds' => { 'nodes' => [] }
+ ),
+ a_hash_including(
+ 'name' => 'rspec 1 2',
+ 'needs' => { 'nodes' => [] },
+ 'previousStageJobsOrNeeds' => { 'nodes' => [
+ a_hash_including('name' => 'docker 1 2'),
+ a_hash_including('name' => 'docker 2 2')
+ ] }
+ ),
+ a_hash_including(
+ 'name' => 'rspec 2 2',
+ 'needs' => { 'nodes' => [a_hash_including('name' => 'my test job')] },
+ 'previousStageJobsOrNeeds' => { 'nodes' => [
+ a_hash_including('name' => 'my test job' )
+ ] }
+ )
)
end
diff --git a/spec/requests/api/graphql/ci/pipelines_spec.rb b/spec/requests/api/graphql/ci/pipelines_spec.rb
index 1f47f678898..95ddd0250e7 100644
--- a/spec/requests/api/graphql/ci/pipelines_spec.rb
+++ b/spec/requests/api/graphql/ci/pipelines_spec.rb
@@ -79,12 +79,13 @@ RSpec.describe 'Query.project(fullPath).pipelines' do
create(:ci_build, pipeline: pipeline, stage_id: other_stage.id, name: 'linux: [baz]')
end
- it 'is null if the user is a guest' do
+ it 'is present if the user has guest access' do
project.add_guest(user)
- post_graphql(query, current_user: user, variables: first_n.with(1))
+ post_graphql(query, current_user: user)
- expect(graphql_data_at(:project, :pipelines, :nodes)).to contain_exactly a_hash_including('stages' => be_nil)
+ expect(graphql_data_at(:project, :pipelines, :nodes, :stages, :nodes, :name))
+ .to contain_exactly(eq(stage.name), eq(other_stage.name))
end
it 'is present if the user has reporter access' do
@@ -113,12 +114,13 @@ RSpec.describe 'Query.project(fullPath).pipelines' do
wrap_fields(query_graphql_path(query_path, :name))
end
- it 'is empty if the user is a guest' do
+ it 'is present if the user has guest access' do
project.add_guest(user)
post_graphql(query, current_user: user)
- expect(graphql_data_at(:project, :pipelines, :nodes, :stages, :nodes, :groups)).to be_empty
+ expect(graphql_data_at(:project, :pipelines, :nodes, :stages, :nodes, :groups, :nodes, :name))
+ .to contain_exactly('linux', 'linux')
end
it 'is present if the user has reporter access' do
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index ab53ff654e9..98d3a3b1c51 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -63,7 +63,7 @@ RSpec.describe 'Query.runner(id)' do
'revision' => runner.revision,
'locked' => false,
'active' => runner.active,
- 'status' => runner.status.to_s.upcase,
+ 'status' => runner.status('14.5').to_s.upcase,
'maximumTimeout' => runner.maximum_timeout,
'accessLevel' => runner.access_level.to_s.upcase,
'runUntagged' => runner.run_untagged,
@@ -221,6 +221,54 @@ RSpec.describe 'Query.runner(id)' do
end
end
+ describe 'for runner with status' do
+ let_it_be(:stale_runner) { create(:ci_runner, description: 'Stale runner 1', created_at: 3.months.ago) }
+ let_it_be(:never_contacted_instance_runner) { create(:ci_runner, description: 'Missing runner 1', created_at: 1.month.ago, contacted_at: nil) }
+
+ let(:status_fragment) do
+ %(
+ status
+ legacyStatusWithExplicitVersion: status(legacyMode: "14.5")
+ newStatus: status(legacyMode: null)
+ )
+ end
+
+ let(:query) do
+ %(
+ query {
+ staleRunner: runner(id: "#{stale_runner.to_global_id}") { #{status_fragment} }
+ pausedRunner: runner(id: "#{inactive_instance_runner.to_global_id}") { #{status_fragment} }
+ neverContactedInstanceRunner: runner(id: "#{never_contacted_instance_runner.to_global_id}") { #{status_fragment} }
+ }
+ )
+ end
+
+ it 'retrieves status fields with expected values' do
+ post_graphql(query, current_user: user)
+
+ stale_runner_data = graphql_data_at(:stale_runner)
+ expect(stale_runner_data).to match a_hash_including(
+ 'status' => 'NOT_CONNECTED',
+ 'legacyStatusWithExplicitVersion' => 'NOT_CONNECTED',
+ 'newStatus' => 'STALE'
+ )
+
+ paused_runner_data = graphql_data_at(:paused_runner)
+ expect(paused_runner_data).to match a_hash_including(
+ 'status' => 'PAUSED',
+ 'legacyStatusWithExplicitVersion' => 'PAUSED',
+ 'newStatus' => 'OFFLINE'
+ )
+
+ never_contacted_instance_runner_data = graphql_data_at(:never_contacted_instance_runner)
+ expect(never_contacted_instance_runner_data).to match a_hash_including(
+ 'status' => 'NOT_CONNECTED',
+ 'legacyStatusWithExplicitVersion' => 'NOT_CONNECTED',
+ 'newStatus' => 'NEVER_CONTACTED'
+ )
+ end
+ end
+
describe 'for multiple runners' do
let_it_be(:project1) { create(:project, :test_repo) }
let_it_be(:project2) { create(:project, :test_repo) }
diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb
index 51a07e60e15..267dd1b5e6f 100644
--- a/spec/requests/api/graphql/ci/runners_spec.rb
+++ b/spec/requests/api/graphql/ci/runners_spec.rb
@@ -62,6 +62,15 @@ RSpec.describe 'Query.runners' do
it_behaves_like 'a working graphql query returning expected runner'
end
+
+ context 'runner_type is PROJECT_TYPE and status is NEVER_CONTACTED' do
+ let(:runner_type) { 'PROJECT_TYPE' }
+ let(:status) { 'NEVER_CONTACTED' }
+
+ let!(:expected_runner) { project_runner }
+
+ it_behaves_like 'a working graphql query returning expected runner'
+ end
end
describe 'pagination' do
diff --git a/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb b/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
index d93afcc0f33..802ab847b3d 100644
--- a/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
+++ b/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
@@ -30,6 +30,14 @@ RSpec.describe 'container repository details' do
subject { post_graphql(query, current_user: user, variables: variables) }
+ shared_examples 'returning an invalid value error' do
+ it 'returns an error' do
+ subject
+
+ expect(graphql_errors.first.dig('message')).to match(/invalid value/)
+ end
+ end
+
it_behaves_like 'a working graphql query' do
before do
subject
@@ -138,6 +146,80 @@ RSpec.describe 'container repository details' do
end
end
+ context 'sorting the tags' do
+ let(:sort) { 'NAME_DESC' }
+ let(:tags_response) { container_repository_details_response.dig('tags', 'edges') }
+ let(:variables) do
+ { id: container_repository_global_id, n: sort }
+ end
+
+ let(:query) do
+ <<~GQL
+ query($id: ID!, $n: ContainerRepositoryTagSort) {
+ containerRepository(id: $id) {
+ tags(sort: $n) {
+ edges {
+ node {
+ #{all_graphql_fields_for('ContainerRepositoryTag')}
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ it 'sorts the tags', :aggregate_failures do
+ subject
+
+ expect(tags_response.first.dig('node', 'name')).to eq('tag5')
+ expect(tags_response.last.dig('node', 'name')).to eq('latest')
+ end
+
+ context 'invalid sort' do
+ let(:sort) { 'FOO_ASC' }
+
+ it_behaves_like 'returning an invalid value error'
+ end
+ end
+
+ context 'filtering by name' do
+ let(:name) { 'l' }
+ let(:tags_response) { container_repository_details_response.dig('tags', 'edges') }
+ let(:variables) do
+ { id: container_repository_global_id, n: name }
+ end
+
+ let(:query) do
+ <<~GQL
+ query($id: ID!, $n: String) {
+ containerRepository(id: $id) {
+ tags(name: $n) {
+ edges {
+ node {
+ #{all_graphql_fields_for('ContainerRepositoryTag')}
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ it 'sorts the tags', :aggregate_failures do
+ subject
+
+ expect(tags_response.size).to eq(1)
+ expect(tags_response.first.dig('node', 'name')).to eq('latest')
+ end
+
+ context 'invalid filter' do
+ let(:name) { 1 }
+
+ it_behaves_like 'returning an invalid value error'
+ end
+ end
+
context 'with tags with a manifest containing nil fields' do
let(:tags_response) { container_repository_details_response.dig('tags', 'nodes') }
let(:errors) { container_repository_details_response.dig('errors') }
diff --git a/spec/requests/api/graphql/current_user/todos_query_spec.rb b/spec/requests/api/graphql/current_user/todos_query_spec.rb
index 981b10a7467..5a45f0db518 100644
--- a/spec/requests/api/graphql/current_user/todos_query_spec.rb
+++ b/spec/requests/api/graphql/current_user/todos_query_spec.rb
@@ -69,7 +69,7 @@ RSpec.describe 'Query current user todos' do
QUERY
end
- it 'avoids N+1 queries', :request_store do
+ it 'avoids N+1 queries', :request_store, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338671' do
control = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) }
project2 = create(:project)
diff --git a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
index 1dffb86b344..1f43f113e65 100644
--- a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
@@ -53,7 +53,7 @@ RSpec.describe "deleting designs" do
context 'the designs list contains filenames we cannot find' do
it_behaves_like 'a failed request' do
- let(:designs) { %w/foo bar baz/.map { |fn| instance_double('file', filename: fn) } }
+ let(:designs) { %w/foo bar baz/.map { |fn| double('file', filename: fn) } }
let(:the_error) { a_string_matching %r/filenames were not found/ }
end
end
diff --git a/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb b/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb
index 3da702c55d7..2da69509ad6 100644
--- a/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe 'Setting issues crm contacts' do
let(:issue) { create(:issue, project: project) }
let(:operation_mode) { Types::MutationOperationModeEnum.default_mode }
- let(:crm_contact_ids) { [global_id_of(contacts[1]), global_id_of(contacts[2])] }
+ let(:contact_ids) { [global_id_of(contacts[1]), global_id_of(contacts[2])] }
let(:does_not_exist_or_no_permission) { "The resource that you are attempting to access does not exist or you don't have permission to perform this action" }
let(:mutation) do
@@ -20,7 +20,7 @@ RSpec.describe 'Setting issues crm contacts' do
project_path: issue.project.full_path,
iid: issue.iid.to_s,
operation_mode: operation_mode,
- crm_contact_ids: crm_contact_ids
+ contact_ids: contact_ids
}
graphql_mutation(:issue_set_crm_contacts, variables,
@@ -83,7 +83,7 @@ RSpec.describe 'Setting issues crm contacts' do
end
context 'append' do
- let(:crm_contact_ids) { [global_id_of(contacts[3])] }
+ let(:contact_ids) { [global_id_of(contacts[3])] }
let(:operation_mode) { Types::MutationOperationModeEnum.enum[:append] }
it 'updates the issue with correct contacts' do
@@ -95,7 +95,7 @@ RSpec.describe 'Setting issues crm contacts' do
end
context 'remove' do
- let(:crm_contact_ids) { [global_id_of(contacts[0])] }
+ let(:contact_ids) { [global_id_of(contacts[0])] }
let(:operation_mode) { Types::MutationOperationModeEnum.enum[:remove] }
it 'updates the issue with correct contacts' do
@@ -107,7 +107,7 @@ RSpec.describe 'Setting issues crm contacts' do
end
context 'when the contact does not exist' do
- let(:crm_contact_ids) { ["gid://gitlab/CustomerRelations::Contact/#{non_existing_record_id}"] }
+ let(:contact_ids) { ["gid://gitlab/CustomerRelations::Contact/#{non_existing_record_id}"] }
it 'returns expected error' do
post_graphql_mutation(mutation, current_user: user)
@@ -120,7 +120,7 @@ RSpec.describe 'Setting issues crm contacts' do
context 'when the contact belongs to a different group' do
let(:group2) { create(:group) }
let(:contact) { create(:contact, group: group2) }
- let(:crm_contact_ids) { [global_id_of(contact)] }
+ let(:contact_ids) { [global_id_of(contact)] }
before do
group2.add_reporter(user)
@@ -137,7 +137,7 @@ RSpec.describe 'Setting issues crm contacts' do
context 'when attempting to add more than 6' do
let(:operation_mode) { Types::MutationOperationModeEnum.enum[:append] }
let(:gid) { global_id_of(contacts[0]) }
- let(:crm_contact_ids) { [gid, gid, gid, gid, gid, gid, gid] }
+ let(:contact_ids) { [gid, gid, gid, gid, gid, gid, gid] }
it 'returns expected error' do
post_graphql_mutation(mutation, current_user: user)
@@ -149,7 +149,7 @@ RSpec.describe 'Setting issues crm contacts' do
context 'when trying to remove non-existent contact' do
let(:operation_mode) { Types::MutationOperationModeEnum.enum[:remove] }
- let(:crm_contact_ids) { ["gid://gitlab/CustomerRelations::Contact/#{non_existing_record_id}"] }
+ let(:contact_ids) { ["gid://gitlab/CustomerRelations::Contact/#{non_existing_record_id}"] }
it 'raises expected error' do
post_graphql_mutation(mutation, current_user: user)
diff --git a/spec/requests/api/graphql/mutations/user_callouts/create_spec.rb b/spec/requests/api/graphql/mutations/user_callouts/create_spec.rb
index 716983f01d2..28a46583d2a 100644
--- a/spec/requests/api/graphql/mutations/user_callouts/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/user_callouts/create_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe 'Create a user callout' do
let_it_be(:current_user) { create(:user) }
- let(:feature_name) { ::UserCallout.feature_names.each_key.first }
+ let(:feature_name) { ::Users::Callout.feature_names.each_key.first }
let(:input) do
{
diff --git a/spec/requests/api/graphql/packages/package_spec.rb b/spec/requests/api/graphql/packages/package_spec.rb
index 83ea9ff4dc8..a9019a7611a 100644
--- a/spec/requests/api/graphql/packages/package_spec.rb
+++ b/spec/requests/api/graphql/packages/package_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe 'package details' do
include GraphqlHelpers
- let_it_be(:project) { create(:project) }
+ let_it_be_with_reload(:project) { create(:project) }
let_it_be(:composer_package) { create(:composer_package, project: project) }
let_it_be(:composer_json) { { name: 'name', type: 'type', license: 'license', version: 1 } }
let_it_be(:composer_metadatum) do
@@ -68,7 +68,7 @@ RSpec.describe 'package details' do
subject
expect(graphql_data_at(:package, :versions, :nodes, :version)).to be_present
- expect(graphql_data_at(:package, :versions, :nodes, :versions, :nodes)).to be_empty
+ expect(graphql_data_at(:package, :versions, :nodes, :versions, :nodes)).to eq [nil, nil]
end
end
end
@@ -96,4 +96,87 @@ RSpec.describe 'package details' do
expect(graphql_data_at(:b)).to be(nil)
end
end
+
+ context 'with unauthorized user' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it 'returns no packages' do
+ subject
+
+ expect(graphql_data_at(:package)).to be_nil
+ end
+ end
+
+ context 'pipelines field', :aggregate_failures do
+ let(:pipelines) { create_list(:ci_pipeline, 6, project: project) }
+ let(:pipeline_gids) { pipelines.sort_by(&:id).map(&:to_gid).map(&:to_s).reverse }
+
+ before do
+ composer_package.pipelines = pipelines
+ composer_package.save!
+ end
+
+ def run_query(args)
+ pipelines_nodes = <<~QUERY
+ nodes {
+ id
+ }
+ pageInfo {
+ startCursor
+ endCursor
+ }
+ QUERY
+
+ query = graphql_query_for(:package, { id: package_global_id }, query_graphql_field("pipelines", args, pipelines_nodes))
+ post_graphql(query, current_user: user)
+ end
+
+ it 'loads the second page with pagination first correctly' do
+ run_query(first: 2)
+ pipeline_ids = graphql_data.dig('package', 'pipelines', 'nodes').pluck('id')
+
+ expect(pipeline_ids).to eq(pipeline_gids[0..1])
+
+ cursor = graphql_data.dig('package', 'pipelines', 'pageInfo', 'endCursor')
+
+ run_query(first: 2, after: cursor)
+
+ pipeline_ids = graphql_data.dig('package', 'pipelines', 'nodes').pluck('id')
+
+ expect(pipeline_ids).to eq(pipeline_gids[2..3])
+ end
+
+ it 'loads the second page with pagination last correctly' do
+ run_query(last: 2)
+ pipeline_ids = graphql_data.dig('package', 'pipelines', 'nodes').pluck('id')
+
+ expect(pipeline_ids).to eq(pipeline_gids[4..5])
+
+ cursor = graphql_data.dig('package', 'pipelines', 'pageInfo', 'startCursor')
+
+ run_query(last: 2, before: cursor)
+
+ pipeline_ids = graphql_data.dig('package', 'pipelines', 'nodes').pluck('id')
+
+ expect(pipeline_ids).to eq(pipeline_gids[2..3])
+ end
+
+ context 'with unauthorized user' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it 'returns no packages' do
+ run_query(first: 2)
+
+ expect(graphql_data_at(:package)).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/project/cluster_agents_spec.rb b/spec/requests/api/graphql/project/cluster_agents_spec.rb
index dc7254dd552..585126f3849 100644
--- a/spec/requests/api/graphql/project/cluster_agents_spec.rb
+++ b/spec/requests/api/graphql/project/cluster_agents_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe 'Project.cluster_agents' do
let_it_be(:project) { create(:project, :public) }
let_it_be(:current_user) { create(:user, maintainer_projects: [project]) }
- let_it_be(:agents) { create_list(:cluster_agent, 5, project: project) }
+ let_it_be(:agents) { create_list(:cluster_agent, 3, project: project) }
let(:first) { var('Int') }
let(:cluster_agents_fields) { nil }
@@ -105,4 +105,37 @@ RSpec.describe 'Project.cluster_agents' do
})
end
end
+
+ context 'selecting activity events' do
+ let_it_be(:token) { create(:cluster_agent_token, agent: agents.first) }
+ let_it_be(:event) { create(:agent_activity_event, agent: agents.first, agent_token: token, user: current_user) }
+
+ let(:cluster_agents_fields) { [:id, query_nodes(:activity_events, of: 'ClusterAgentActivityEvent', max_depth: 2)] }
+
+ it 'retrieves activity event details' do
+ post_graphql(query, current_user: current_user)
+
+ response = graphql_data_at(:project, :cluster_agents, :nodes, :activity_events, :nodes).first
+
+ expect(response).to include({
+ 'kind' => event.kind,
+ 'level' => event.level,
+ 'recordedAt' => event.recorded_at.iso8601,
+ 'agentToken' => hash_including('name' => token.name),
+ 'user' => hash_including('name' => current_user.name)
+ })
+ end
+
+ it 'preloads associations to prevent N+1 queries' do
+ user = create(:user)
+ token = create(:cluster_agent_token, agent: agents.second)
+ create(:agent_activity_event, agent: agents.second, agent_token: token, user: user)
+
+ post_graphql(query, current_user: current_user)
+
+ expect do
+ post_graphql(query, current_user: current_user)
+ end.to issue_same_number_of_queries_as { post_graphql(query, current_user: current_user, variables: [first.with(1)]) }
+ end
+ end
end
diff --git a/spec/requests/api/graphql/project/jobs_spec.rb b/spec/requests/api/graphql/project/jobs_spec.rb
new file mode 100644
index 00000000000..1a823ede9ac
--- /dev/null
+++ b/spec/requests/api/graphql/project/jobs_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe 'Query.project.jobs' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:user) { create(:user) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, project: project, user: user)
+ end
+
+ let(:query) do
+ <<~QUERY
+ {
+ project(fullPath: "#{project.full_path}") {
+ jobs {
+ nodes {
+ name
+ previousStageJobsAndNeeds {
+ nodes {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ QUERY
+ end
+
+ it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do
+ build_stage = create(:ci_stage_entity, position: 1, name: 'build', project: project, pipeline: pipeline)
+ test_stage = create(:ci_stage_entity, position: 2, name: 'test', project: project, pipeline: pipeline)
+ create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 1 2', stage: build_stage)
+ create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 2 2', stage: build_stage)
+ create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 1 2', stage: test_stage)
+ test_job = create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 2 2', stage: test_stage)
+ create(:ci_build_need, build: test_job, name: 'docker 1 2')
+
+ 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', stage: test_stage, stage_idx: test_stage.position, pipeline: pipeline)
+ test_b_job = create(:ci_build, name: 'test-b', stage: test_stage, stage_idx: test_stage.position, pipeline: pipeline)
+ create(:ci_build_need, build: test_b_job, name: 'docker 2 2')
+
+ expect do
+ post_graphql(query, current_user: user)
+ end.not_to exceed_all_query_limit(control)
+ end
+end
diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb
index d46ef313563..73e02e2a4b1 100644
--- a/spec/requests/api/graphql/project/pipeline_spec.rb
+++ b/spec/requests/api/graphql/project/pipeline_spec.rb
@@ -273,6 +273,48 @@ RSpec.describe 'getting pipeline information nested in a project' do
end
end
+ context 'N+1 queries on pipeline jobs' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ let(:fields) do
+ <<~FIELDS
+ jobs {
+ nodes {
+ previousStageJobsAndNeeds {
+ nodes {
+ name
+ }
+ }
+ }
+ }
+ FIELDS
+ end
+
+ it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do
+ build_stage = create(:ci_stage_entity, position: 1, name: 'build', project: project, pipeline: pipeline)
+ test_stage = create(:ci_stage_entity, position: 2, name: 'test', project: project, pipeline: pipeline)
+ create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 1 2', stage: build_stage)
+ create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 2 2', stage: build_stage)
+ create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 1 2', stage: test_stage)
+ test_job = create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 2 2', stage: test_stage)
+ create(:ci_build_need, build: test_job, name: 'docker 1 2')
+
+ post_graphql(query, current_user: current_user)
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ post_graphql(query, current_user: current_user)
+ end
+
+ create(:ci_build, name: 'test-a', stage: test_stage, stage_idx: test_stage.position, pipeline: pipeline)
+ test_b_job = create(:ci_build, name: 'test-b', stage: test_stage, stage_idx: test_stage.position, pipeline: pipeline)
+ create(:ci_build_need, build: test_b_job, name: 'docker 2 2')
+
+ expect do
+ post_graphql(query, current_user: current_user)
+ end.not_to exceed_all_query_limit(control)
+ end
+ end
+
context 'N+1 queries on stages jobs' do
let(:depth) { 5 }
let(:fields) do
diff --git a/spec/requests/api/graphql/project_query_spec.rb b/spec/requests/api/graphql/project_query_spec.rb
index e44a7efb354..310a8e9fa33 100644
--- a/spec/requests/api/graphql/project_query_spec.rb
+++ b/spec/requests/api/graphql/project_query_spec.rb
@@ -143,6 +143,40 @@ RSpec.describe 'getting project information' do
end
end
+ context 'when the user has guest access' do
+ context 'when the project has public pipelines' do
+ before do
+ pipeline = create(:ci_pipeline, project: project)
+ create(:ci_build, project: project, pipeline: pipeline, name: 'a test job')
+ project.add_guest(current_user)
+ end
+
+ it 'shows all jobs' do
+ query = <<~GQL
+ query {
+ project(fullPath: "#{project.full_path}") {
+ jobs {
+ nodes {
+ name
+ stage {
+ name
+ }
+ }
+ }
+ }
+ }
+ GQL
+
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data_at(:project, :jobs, :nodes)).to contain_exactly({
+ 'name' => 'a test job',
+ 'stage' => { 'name' => 'test' }
+ })
+ end
+ end
+ end
+
context 'when the user does not have access to the project' do
it 'returns an empty field' do
post_graphql(query, current_user: current_user)
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 75f5a974d22..d226bb07c73 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -174,18 +174,6 @@ RSpec.describe API::Groups do
'Remaining records can be retrieved using keyset pagination.'
)
end
-
- context 'when the feature flag `keyset_pagination_for_groups_api` is disabled' do
- before do
- stub_feature_flags(keyset_pagination_for_groups_api: false)
- end
-
- it 'returns successful response' do
- get api('/groups'), params: { page: 3000, per_page: 25 }
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
end
context 'on making requests below the allowed offset pagination threshold' do
@@ -247,24 +235,6 @@ RSpec.describe API::Groups do
expect(records.size).to eq(1)
expect(records.first['id']).to eq(group_2.id)
end
-
- context 'when the feature flag `keyset_pagination_for_groups_api` is disabled' do
- before do
- stub_feature_flags(keyset_pagination_for_groups_api: false)
- end
-
- it 'ignores the keyset pagination params and performs offset pagination' do
- get api('/groups'), params: { pagination: 'keyset', per_page: 1 }
-
- expect(response).to have_gitlab_http_status(:ok)
- records = json_response
- expect(records.size).to eq(1)
- expect(records.first['id']).to eq(group_1.id)
-
- params_for_next_page = params_for_next_page(response)
- expect(params_for_next_page).not_to include('cursor')
- end
- end
end
context 'on making requests with unsupported ordering structure' do
@@ -1973,6 +1943,116 @@ RSpec.describe API::Groups do
end
end
+ describe 'POST /groups/:id/transfer' do
+ let_it_be(:user) { create(:user) }
+ let_it_be_with_reload(:new_parent_group) { create(:group, :private) }
+ let_it_be_with_reload(:group) { create(:group, :nested, :private) }
+
+ before do
+ new_parent_group.add_owner(user)
+ group.add_owner(user)
+ end
+
+ def make_request(user)
+ post api("/groups/#{group.id}/transfer", user), params: params
+ end
+
+ context 'when promoting a subgroup to a root group' do
+ shared_examples_for 'promotes the subgroup to a root group' do
+ it 'returns success' do
+ make_request(user)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['parent_id']).to be_nil
+ end
+ end
+
+ context 'when no group_id is specified' do
+ let(:params) {}
+
+ it_behaves_like 'promotes the subgroup to a root group'
+ end
+
+ context 'when group_id is specified as blank' do
+ let(:params) { { group_id: '' } }
+
+ it_behaves_like 'promotes the subgroup to a root group'
+ end
+
+ context 'when the group is already a root group' do
+ let(:group) { create(:group) }
+ let(:params) { { group_id: '' } }
+
+ it 'returns error' do
+ make_request(user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq('Transfer failed: Group is already a root group.')
+ end
+ end
+ end
+
+ context 'when transferring a subgroup to a different group' do
+ let(:params) { { group_id: new_parent_group.id } }
+
+ context 'when the user does not have admin rights to the group being transferred' do
+ it 'forbids the operation' do
+ developer_user = create(:user)
+ group.add_developer(developer_user)
+
+ make_request(developer_user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when the user does not have access to the new parent group' do
+ it 'fails with 404' do
+ user_without_access_to_new_parent_group = create(:user)
+ group.add_owner(user_without_access_to_new_parent_group)
+
+ make_request(user_without_access_to_new_parent_group)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when the ID of a non-existent group is mentioned as the new parent group' do
+ let(:params) { { group_id: non_existing_record_id } }
+
+ it 'fails with 404' do
+ make_request(user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when the transfer fails due to an error' do
+ before do
+ expect_next_instance_of(::Groups::TransferService) do |service|
+ expect(service).to receive(:proceed_to_transfer).and_raise(Gitlab::UpdatePathError, 'namespace directory cannot be moved')
+ end
+ end
+
+ it 'returns error' do
+ make_request(user)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq('Transfer failed: namespace directory cannot be moved')
+ end
+ end
+
+ context 'when the transfer succceds' do
+ it 'returns success' do
+ make_request(user)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['parent_id']).to eq(new_parent_group.id)
+ end
+ end
+ end
+ end
+
it_behaves_like 'custom attributes endpoints', 'groups' do
let(:attributable) { group1 }
let(:other_attributable) { group2 }
diff --git a/spec/requests/api/import_github_spec.rb b/spec/requests/api/import_github_spec.rb
index d5fed330401..f0c4fcc4f29 100644
--- a/spec/requests/api/import_github_spec.rb
+++ b/spec/requests/api/import_github_spec.rb
@@ -11,12 +11,12 @@ RSpec.describe API::ImportGithub do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:provider_username) { user.username }
- let(:provider_user) { OpenStruct.new(login: provider_username) }
+ let(:provider_user) { double('provider', login: provider_username) }
let(:provider_repo) do
- OpenStruct.new(
+ double('provider',
name: 'vim',
full_name: "#{provider_username}/vim",
- owner: OpenStruct.new(login: provider_username)
+ owner: double('provider', login: provider_username)
)
end
diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb
index cba4256adc5..702e6ef0a2a 100644
--- a/spec/requests/api/invitations_spec.rb
+++ b/spec/requests/api/invitations_spec.rb
@@ -152,25 +152,7 @@ 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 tasks_to_be_done and tasks_project_id in the params' do
- before do
- stub_experiments(invite_members_for_task: true)
- end
-
let(:project_id) { source_type == 'project' ? source.id : create(:project, namespace: source).id }
context 'when there is 1 invitation' do
diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb
index 07fa1d40f7b..9948e13e9ae 100644
--- a/spec/requests/api/issues/get_project_issues_spec.rb
+++ b/spec/requests/api/issues/get_project_issues_spec.rb
@@ -873,7 +873,7 @@ RSpec.describe API::Issues do
end
it 'returns 404 if the issue is confidential' do
- post api("/projects/#{project.id}/issues/#{confidential_issue.iid}/participants", non_member)
+ get api("/projects/#{project.id}/issues/#{confidential_issue.iid}/participants", non_member)
expect(response).to have_gitlab_http_status(:not_found)
end
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 4b6868f42bc..db9d72245b3 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -589,6 +589,15 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:forbidden)
end
+ it 'returns 403 if reporter promotes label' do
+ reporter = create(:user)
+ project.add_reporter(reporter)
+
+ put api("/projects/#{project.id}/labels/promote", reporter), params: { name: label1.name }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
it 'returns 404 if label does not exist' do
put api("/projects/#{project.id}/labels/promote", user), params: { name: 'unknown' }
@@ -601,6 +610,13 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('name is missing')
end
+
+ it 'returns 400 if project does not have a group' do
+ project = create(:project, creator_id: user.id, namespace: user.namespace)
+ put api("/projects/#{project.id}/labels/promote", user), params: { name: label1.name }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
end
describe "POST /projects/:id/labels/:label_id/subscribe" do
diff --git a/spec/requests/api/markdown_golden_master_spec.rb b/spec/requests/api/markdown_golden_master_spec.rb
new file mode 100644
index 00000000000..4fa946de342
--- /dev/null
+++ b/spec/requests/api/markdown_golden_master_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# See spec/fixtures/markdown/markdown_golden_master_examples.yml for documentation on how this spec works.
+RSpec.describe API::Markdown, 'Golden Master' do
+ markdown_yml_file_path = File.expand_path('../../fixtures/markdown/markdown_golden_master_examples.yml', __dir__)
+ include_context 'API::Markdown Golden Master shared context', markdown_yml_file_path
+end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 7f4345faabb..02061bb8ab6 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -387,38 +387,7 @@ RSpec.describe API::Members do
end
end
- context 'with areas_of_focus considerations', :snowplow 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 'with tasks_to_be_done and tasks_project_id in the params' do
- before do
- stub_experiments(invite_members_for_task: true)
- end
-
let(:project_id) { source_type == 'project' ? source.id : create(:project, namespace: source).id }
context 'when there is 1 user to add' do
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index 097d374640c..3ed08afd57d 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -47,7 +47,7 @@ RSpec.describe API::ProjectImport do
it 'executes a limited number of queries' do
control_count = ActiveRecord::QueryRecorder.new { subject }.count
- expect(control_count).to be <= 101
+ expect(control_count).to be <= 104
end
it 'schedules an import using a namespace' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index cc546cbcda1..79dbbd20d83 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1160,6 +1160,15 @@ RSpec.describe API::Projects do
expect(response).to have_gitlab_http_status(:forbidden)
end
+ it 'allows creating a project without an import_url when git import source is disabled', :aggregate_failures do
+ stub_application_setting(import_sources: nil)
+ project_params = { path: 'path-project-Foo' }
+
+ expect { post api('/projects', user), params: project_params }.to change { Project.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
it 'disallows creating a project with an import_url that is not reachable', :aggregate_failures do
url = 'http://example.com'
endpoint_url = "#{url}/info/refs?service=git-upload-pack"
@@ -1504,6 +1513,20 @@ RSpec.describe API::Projects do
expect(json_response.map { |project| project['id'] }).to contain_exactly(private_project1.id)
end
+ context 'and using an admin to search', :enable_admin_mode, :aggregate_errors do
+ it 'returns users projects when authenticated as admin' do
+ private_project1 = create(:project, :private, name: 'private_project1', creator_id: user4.id, namespace: user4.namespace)
+
+ # min_access_level does not make any difference when admins search for a user's projects
+ get api("/users/#{user4.id}/projects/", admin), params: { min_access_level: 30 }
+
+ 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 contain_exactly(project4.id, private_project1.id, public_project.id)
+ end
+ end
+
context 'and using the programming language filter' do
include_context 'with language detection'
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index f3146480be2..21a8622e08d 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -731,6 +731,71 @@ RSpec.describe API::Repositories do
end
end
+ describe 'GET /projects/:id/repository/changelog' do
+ it 'generates the changelog for a version' do
+ spy = instance_spy(Repositories::ChangelogService)
+ release_notes = 'Release notes'
+
+ allow(Repositories::ChangelogService)
+ .to receive(:new)
+ .with(
+ project,
+ user,
+ version: '1.0.0',
+ from: 'foo',
+ to: 'bar',
+ date: DateTime.new(2020, 1, 1),
+ trailer: 'Foo'
+ )
+ .and_return(spy)
+
+ expect(spy).to receive(:execute).with(commit_to_changelog: false).and_return(release_notes)
+
+ get(
+ api("/projects/#{project.id}/repository/changelog", user),
+ params: {
+ version: '1.0.0',
+ from: 'foo',
+ to: 'bar',
+ date: '2020-01-01',
+ trailer: 'Foo'
+ }
+ )
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['notes']).to eq(release_notes)
+ end
+
+ it 'supports leaving out the from and to attribute' do
+ spy = instance_spy(Repositories::ChangelogService)
+
+ allow(Repositories::ChangelogService)
+ .to receive(:new)
+ .with(
+ project,
+ user,
+ version: '1.0.0',
+ date: DateTime.new(2020, 1, 1),
+ trailer: 'Foo'
+ )
+ .and_return(spy)
+
+ expect(spy).to receive(:execute).with(commit_to_changelog: false)
+
+ get(
+ api("/projects/#{project.id}/repository/changelog", user),
+ params: {
+ version: '1.0.0',
+ date: '2020-01-01',
+ trailer: 'Foo'
+ }
+ )
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['notes']).to be_present
+ end
+ end
+
describe 'POST /projects/:id/repository/changelog' do
it 'generates the changelog for a version' do
spy = instance_spy(Repositories::ChangelogService)
@@ -751,7 +816,7 @@ RSpec.describe API::Repositories do
)
.and_return(spy)
- allow(spy).to receive(:execute)
+ allow(spy).to receive(:execute).with(commit_to_changelog: true)
post(
api("/projects/#{project.id}/repository/changelog", user),
@@ -787,7 +852,7 @@ RSpec.describe API::Repositories do
)
.and_return(spy)
- expect(spy).to receive(:execute)
+ expect(spy).to receive(:execute).with(commit_to_changelog: true)
post(
api("/projects/#{project.id}/repository/changelog", user),
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index 8012892a571..b75fe11b06d 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -122,6 +122,23 @@ RSpec.describe API::Search do
end
end
+ context 'when DB timeouts occur from global searches', :aggregate_errors do
+ %w(
+ issues
+ merge_requests
+ milestones
+ projects
+ snippet_titles
+ users
+ ).each do |scope|
+ it "returns a 408 error if search with scope: #{scope} times out" do
+ allow(SearchService).to receive(:new).and_raise ActiveRecord::QueryCanceled
+ get api(endpoint, user), params: { scope: scope, search: 'awesome' }
+ expect(response).to have_gitlab_http_status(:request_timeout)
+ end
+ end
+ end
+
context 'when scope is not supported' do
it 'returns 400 error' do
get api(endpoint, user), params: { scope: 'unsupported', search: 'awesome' }
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 641c6a2cd91..7e940d52a41 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -523,15 +523,6 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do
end
end
- context "missing spam_check_api_key value when spam_check_endpoint_enabled is true" do
- it "returns a blank parameter error message" do
- put api("/application/settings", admin), params: { spam_check_endpoint_enabled: true, spam_check_endpoint_url: "https://example.com/spam_check" }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['error']).to eq('spam_check_api_key is missing')
- end
- end
-
context "overly long spam_check_api_key" do
it "fails to update the settings with too long spam_check_api_key" do
put api("/application/settings", admin), params: { spam_check_api_key: "0123456789" * 500 }
diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb
index 5d2635126e8..24f38b04348 100644
--- a/spec/requests/api/terraform/state_spec.rb
+++ b/spec/requests/api/terraform/state_spec.rb
@@ -152,6 +152,16 @@ RSpec.describe API::Terraform::State do
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Json.parse(response.body)).to be_empty
end
+
+ context 'when serial already exists' do
+ let(:params) { { 'instance': 'example-instance', 'serial': state.latest_version.version } }
+
+ it 'returns unprocessable entity' do
+ request
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
end
context 'without body' do
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index c6b4f50afae..0944bfb6ba6 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -380,7 +380,7 @@ RSpec.describe API::Todos do
end
end
- describe 'POST :id/issuable_type/:issueable_id/todo' do
+ describe 'POST :id/issuable_type/:issuable_id/todo' do
context 'for an issue' do
let_it_be(:issuable) do
create(:issue, :confidential, project: project_1)
diff --git a/spec/requests/api/topics_spec.rb b/spec/requests/api/topics_spec.rb
index a5746a4022e..70eee8a1af9 100644
--- a/spec/requests/api/topics_spec.rb
+++ b/spec/requests/api/topics_spec.rb
@@ -5,15 +5,15 @@ require 'spec_helper'
RSpec.describe API::Topics do
include WorkhorseHelpers
- let_it_be(:topic_1) { create(:topic, name: 'Git', total_projects_count: 1) }
+ let_it_be(:file) { fixture_file_upload('spec/fixtures/dk.png') }
+
+ let_it_be(:topic_1) { create(:topic, name: 'Git', total_projects_count: 1, avatar: file) }
let_it_be(:topic_2) { create(:topic, name: 'GitLab', total_projects_count: 2) }
let_it_be(:topic_3) { create(:topic, name: 'other-topic', total_projects_count: 3) }
let_it_be(:admin) { create(:user, :admin) }
let_it_be(:user) { create(:user) }
- let(:file) { fixture_file_upload('spec/fixtures/dk.png') }
-
describe 'GET /topics', :aggregate_failures do
it 'returns topics ordered by total_projects_count' do
get api('/topics')
@@ -184,6 +184,14 @@ RSpec.describe API::Topics do
expect(json_response['avatar_url']).to end_with('dk.png')
end
+ it 'keeps avatar when updating other fields' do
+ put api("/topics/#{topic_1.id}", admin), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['name']).to eq('my-topic')
+ expect(topic_1.reload.avatar_url).not_to be_nil
+ end
+
it 'returns 404 for non existing id' do
put api("/topics/#{non_existing_record_id}", admin), params: { name: 'my-topic' }
@@ -196,6 +204,32 @@ RSpec.describe API::Topics do
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eql('id is invalid')
end
+
+ context 'with blank avatar' do
+ it 'removes avatar' do
+ put api("/topics/#{topic_1.id}", admin), params: { avatar: '' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['avatar_url']).to be_nil
+ expect(topic_3.reload.avatar_url).to be_nil
+ end
+
+ it 'removes avatar besides other changes' do
+ put api("/topics/#{topic_1.id}", admin), params: { name: 'new-topic-name', avatar: '' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['name']).to eq('new-topic-name')
+ expect(json_response['avatar_url']).to be_nil
+ expect(topic_1.reload.avatar_url).to be_nil
+ end
+
+ it 'does not remove avatar in case of other errors' do
+ put api("/topics/#{topic_1.id}", admin), params: { name: topic_2.name, avatar: '' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(topic_1.reload.avatar_url).not_to be_nil
+ end
+ end
end
context 'as normal user' do
diff --git a/spec/requests/api/v3/github_spec.rb b/spec/requests/api/v3/github_spec.rb
index 6d8ae226ce4..838948132dd 100644
--- a/spec/requests/api/v3/github_spec.rb
+++ b/spec/requests/api/v3/github_spec.rb
@@ -567,18 +567,6 @@ RSpec.describe API::V3::Github do
expect(response_diff_files(response)).to be_blank
end
- it 'does not handle the error when feature flag is disabled', :aggregate_failures do
- stub_feature_flags(api_v3_commits_skip_diff_files: false)
-
- allow(Gitlab::GitalyClient).to receive(:call)
- .with(*commit_diff_args)
- .and_raise(GRPC::DeadlineExceeded)
-
- call_api
-
- expect(response).to have_gitlab_http_status(:error)
- end
-
it 'only calls Gitaly once for all attempts within a period of time', :aggregate_failures do
expect(Gitlab::GitalyClient).to receive(:call)
.with(*commit_diff_args)